Compare commits
717
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b7bed247 | ||
|
|
8bd11f7532 | ||
|
|
a7831db516 | ||
|
|
ca142d4b98 | ||
|
|
230dc73bc1 | ||
|
|
f7d066fb32 | ||
|
|
0f9ab08e2d | ||
|
|
53c407350f | ||
|
|
4001fd3515 | ||
|
|
be36341681 | ||
|
|
fec5bc42dc | ||
|
|
27fd5ff860 | ||
|
|
a5eae7aa0a | ||
|
|
4cb9efadae | ||
|
|
2c129789ff | ||
|
|
8c98c27a5e | ||
|
|
283363d5cf | ||
|
|
435fe73a0f | ||
|
|
04de5c3b30 | ||
|
|
78fedf86aa | ||
|
|
cc4da43b3c | ||
|
|
e97647cd18 | ||
|
|
b9cd0bc492 | ||
|
|
06fd6f26b1 | ||
|
|
dec17874ab | ||
|
|
224348ff4d | ||
|
|
4d7fc66110 | ||
|
|
9ebf6e58f0 | ||
|
|
071192e586 | ||
|
|
f26c8afc20 | ||
|
|
0ea81e0e96 | ||
|
|
1de7bc38c8 | ||
|
|
5c766b7c8c | ||
|
|
294a30b82b | ||
|
|
f6cb279eea | ||
|
|
aef65c2f1e | ||
|
|
6772904668 | ||
|
|
fdac1264ac | ||
|
|
3acb50433a | ||
|
|
a6d455f59c | ||
|
|
0bf9465cba | ||
|
|
aad0eafa79 | ||
|
|
5744cc0472 | ||
|
|
47bb7409be | ||
|
|
383f21ed49 | ||
|
|
1b9c63e947 | ||
|
|
edb7030102 | ||
|
|
4eba1f451b | ||
|
|
dce214b7f5 | ||
|
|
a0147f94af | ||
|
|
a70316b512 | ||
|
|
26faa9981f | ||
|
|
a4f9d43037 | ||
|
|
ea3b790f1d | ||
|
|
9a14624b69 | ||
|
|
d02f00bce7 | ||
|
|
480a857635 | ||
|
|
4fa5891697 | ||
|
|
f522c2114d | ||
|
|
5c98c86d70 | ||
|
|
b75c04f038 | ||
|
|
023a355cdb | ||
|
|
b03f5193b8 | ||
|
|
80863372dc | ||
|
|
01fdde3701 | ||
|
|
ef76908e0d | ||
|
|
eed5be9244 | ||
|
|
12c9c1efcf | ||
|
|
7247b92fd6 | ||
|
|
ce0287dd2d | ||
|
|
067de23181 | ||
|
|
0d04a16560 | ||
|
|
7c533965bf | ||
|
|
f99c0c9e29 | ||
|
|
66d37b8f30 | ||
|
|
852a0d1163 | ||
|
|
cbbf170718 | ||
|
|
3c357ee1f1 | ||
|
|
43612bf1be | ||
|
|
5f35cd2e32 | ||
|
|
048545b266 | ||
|
|
7792e96670 | ||
|
|
34660dd256 | ||
|
|
84faadbe38 | ||
|
|
11d257516a | ||
|
|
99d01964ef | ||
|
|
da6c3ab163 | ||
|
|
1995bd1247 | ||
|
|
63ccbe512b | ||
|
|
1a6a72dc02 | ||
|
|
45ee24759b | ||
|
|
8326a3b57d | ||
|
|
e81a8d62a5 | ||
|
|
41dd30fe43 | ||
|
|
c7850092fa | ||
|
|
9dfde6fca1 | ||
|
|
a8da15caff | ||
|
|
e1a9a0f2d2 | ||
|
|
de676868c6 | ||
|
|
da9b672747 | ||
|
|
14f908926e | ||
|
|
a412c0760b | ||
|
|
ff30ac166e | ||
|
|
a45b31fdaf | ||
|
|
5235c00630 | ||
|
|
6141d7b5d1 | ||
|
|
2132bb711d | ||
|
|
f74f9b19be | ||
|
|
ac294e7b86 | ||
|
|
3c40a6e6c8 | ||
|
|
77379f62ce | ||
|
|
15d73fe09c | ||
|
|
b9f7ff6054 | ||
|
|
3d4691c544 | ||
|
|
f9bdd3b1d1 | ||
|
|
6fc3ccda4b | ||
|
|
658d33892c | ||
|
|
efcc065f8f | ||
|
|
f9aa43bf09 | ||
|
|
5266949a59 | ||
|
|
3631a151a3 | ||
|
|
216bc8ad8b | ||
|
|
e98e675673 | ||
|
|
c94a65f2d8 | ||
|
|
6738de67e3 | ||
|
|
01ecd2426a | ||
|
|
7540c9aa6b | ||
|
|
9fc2bd73fb | ||
|
|
09c9dc49f7 | ||
|
|
29b498c59d | ||
|
|
1f368771f6 | ||
|
|
9d82c2dee8 | ||
|
|
162f0520a3 | ||
|
|
3b383de72e | ||
|
|
c8c6f4344d | ||
|
|
220d2fbe69 | ||
|
|
e58be12323 | ||
|
|
5f6d71da21 | ||
|
|
fa839454f6 | ||
|
|
da260f0835 | ||
|
|
9de6b0fb15 | ||
|
|
5b39ffcfd0 | ||
|
|
1ce07690a4 | ||
|
|
1b37bb8d29 | ||
|
|
57b261840b | ||
|
|
70bfb72c78 | ||
|
|
ff669cc720 | ||
|
|
491e606472 | ||
|
|
a7e339d7df | ||
|
|
d40854a073 | ||
|
|
637df7d374 | ||
|
|
7e9bb2dab4 | ||
|
|
20e3e1a3cd | ||
|
|
41b2345ba4 | ||
|
|
c80b5eedd2 | ||
|
|
52dc32cb07 | ||
|
|
2904b60c1e | ||
|
|
3b309fa61e | ||
|
|
534fd88be4 | ||
|
|
92358ddfd7 | ||
|
|
0363e6f5f1 | ||
|
|
a50c26ecea | ||
|
|
88c44c095e | ||
|
|
04955c3112 | ||
|
|
eba38e5ed9 | ||
|
|
b69d526139 | ||
|
|
653d2a84d9 | ||
|
|
777bf7d281 | ||
|
|
f8986a5541 | ||
|
|
1c29c4bc56 | ||
|
|
24a0fc7272 | ||
|
|
931c0878c1 | ||
|
|
0dbff50da4 | ||
|
|
e8a2134d94 | ||
|
|
96404d8acf | ||
|
|
091551e053 | ||
|
|
3f0f6483c8 | ||
|
|
4fff5cdfab | ||
|
|
ae6407fb71 | ||
|
|
d4a14a7765 | ||
|
|
1fa531268b | ||
|
|
beac2c0170 | ||
|
|
ba7e8a2dfb | ||
|
|
b85f659f1a | ||
|
|
fffad9f578 | ||
|
|
5f761c16cd | ||
|
|
69fdbc7cb3 | ||
|
|
fbb60b952c | ||
|
|
a56c9b6262 | ||
|
|
ab03e427f4 | ||
|
|
af25c1424f | ||
|
|
456a1eae45 | ||
|
|
18ecf1c47f | ||
|
|
d60302b2bd | ||
|
|
6e65fe2246 | ||
|
|
fc7e52c404 | ||
|
|
98d7be07fd | ||
|
|
7843f56522 | ||
|
|
8e767e10fb | ||
|
|
e0f23683b0 | ||
|
|
9adfad1108 | ||
|
|
24df2be69a | ||
|
|
2188dfaffe | ||
|
|
19a1d9342e | ||
|
|
18a5617fa4 | ||
|
|
39b4149687 | ||
|
|
88e0ca6f2d | ||
|
|
d01e1121f8 | ||
|
|
0d153c98e0 | ||
|
|
92709ec311 | ||
|
|
2f5268d97a | ||
|
|
0a10272f3d | ||
|
|
ae6d3ddecc | ||
|
|
8834af8bd0 | ||
|
|
1bfbeda9ec | ||
|
|
23be8beda9 | ||
|
|
c518e59b9b | ||
|
|
7f7eefdb44 | ||
|
|
f053779abe | ||
|
|
07aa46a02d | ||
|
|
9c6a6d2f00 | ||
|
|
0afac8d52e | ||
|
|
65dcc83bf1 | ||
|
|
4c7b86af2c | ||
|
|
db726d734e | ||
|
|
d8dade2563 | ||
|
|
17f8ec2fbb | ||
|
|
a2f2e5b98d | ||
|
|
19f969b2db | ||
|
|
b13dc2bfc4 | ||
|
|
7ebdfb3278 | ||
|
|
d71bd5d0fa | ||
|
|
28c38dc2b9 | ||
|
|
01d8328758 | ||
|
|
ea4cb84ce1 | ||
|
|
7098d550f3 | ||
|
|
e89d0ce830 | ||
|
|
644a9484ab | ||
|
|
a6ffb0a7a1 | ||
|
|
4020ff8b70 | ||
|
|
b054da9141 | ||
|
|
95e47c2776 | ||
|
|
79d8ec7a52 | ||
|
|
9a90bec951 | ||
|
|
82c483fccc | ||
|
|
df569ad63a | ||
|
|
9e737e7c4c | ||
|
|
855149b25b | ||
|
|
2b13097956 | ||
|
|
2d8ff36d03 | ||
|
|
00c436dd15 | ||
|
|
12a98c2183 | ||
|
|
d16d23e2a2 | ||
|
|
0d19a01ea7 | ||
|
|
294df2d72e | ||
|
|
be5ab83015 | ||
|
|
47583173db | ||
|
|
f9bb4a6d78 | ||
|
|
61b2a273f2 | ||
|
|
54f6d4bb5d | ||
|
|
4e121e37cd | ||
|
|
8392ec5c2d | ||
|
|
7b03a6831b | ||
|
|
1869e6b0a2 | ||
|
|
5b1d068de3 | ||
|
|
8d5fcb8236 | ||
|
|
c764f46577 | ||
|
|
53f119fb48 | ||
|
|
29692693fa | ||
|
|
401f5dcb8b | ||
|
|
a1faef315f | ||
|
|
aef9bf8453 | ||
|
|
b42864542a | ||
|
|
fe138a638c | ||
|
|
17c9525ebd | ||
|
|
cb0efaf9a6 | ||
|
|
6af2aecb73 | ||
|
|
363da8fbab | ||
|
|
c4ad757265 | ||
|
|
168e41fd69 | ||
|
|
1ff3066bab | ||
|
|
69771a4d8e | ||
|
|
044b913a9e | ||
|
|
28cd959de4 | ||
|
|
93f8ef4a2f | ||
|
|
87f86772f6 | ||
|
|
7092d2afa9 | ||
|
|
4e2bf72219 | ||
|
|
627ef84453 | ||
|
|
34f5ee62d2 | ||
|
|
4f19bdf6fb | ||
|
|
84e9f8a192 | ||
|
|
d65ed10931 | ||
|
|
e65c0ea27f | ||
|
|
044cd2dfdd | ||
|
|
c75f1072ea | ||
|
|
369c875358 | ||
|
|
3ffb0f8d8e | ||
|
|
9ff6368e82 | ||
|
|
87f2901f55 | ||
|
|
a2da2a3e74 | ||
|
|
ccfac82fd1 | ||
|
|
b6ea3a740c | ||
|
|
f29a8f0ebe | ||
|
|
362c8074d1 | ||
|
|
ab39ee3290 | ||
|
|
9efa510ad5 | ||
|
|
f426b4e5fa | ||
|
|
1c9488c2ae | ||
|
|
f871c9f968 | ||
|
|
e1609f5dbb | ||
|
|
e8fce4ca46 | ||
|
|
ed9a41147a | ||
|
|
95d2c22471 | ||
|
|
fa8c046682 | ||
|
|
fe3b86d65f | ||
|
|
720cff7271 | ||
|
|
128236e7a0 | ||
|
|
2eb897699f | ||
|
|
4ffcadf4e9 | ||
|
|
04f4d715bd | ||
|
|
fb10a9359b | ||
|
|
d0beb962d8 | ||
|
|
d0c7373d38 | ||
|
|
03813cce78 | ||
|
|
c96d5d6c6d | ||
|
|
f103680251 | ||
|
|
c096c4af1c | ||
|
|
63153faa94 | ||
|
|
82e0d85700 | ||
|
|
834370bf67 | ||
|
|
5b4cbc035c | ||
|
|
1963baefb3 | ||
|
|
2f8e07d923 | ||
|
|
acdef3a98d | ||
|
|
9d406b6f02 | ||
|
|
6cf7372ad4 | ||
|
|
e67326b6c7 | ||
|
|
6ec5ac27a7 | ||
|
|
e9c927d448 | ||
|
|
8e87bdc148 | ||
|
|
0fe2118105 | ||
|
|
01b6441a48 | ||
|
|
173d6d2cd4 | ||
|
|
ba91206c6b | ||
|
|
23356b2c88 | ||
|
|
441296f37a | ||
|
|
e1764b4892 | ||
|
|
eb061c7603 | ||
|
|
c1d331da40 | ||
|
|
a062015501 | ||
|
|
2bf174b57c | ||
|
|
712f2cc60f | ||
|
|
fe5275b470 | ||
|
|
fdaff7b780 | ||
|
|
93985958bc | ||
|
|
5b54cfdad1 | ||
|
|
ac217628a1 | ||
|
|
927ff51842 | ||
|
|
7ee9271a3b | ||
|
|
85b1a9f317 | ||
|
|
5216099fe4 | ||
|
|
7e9ed3abf3 | ||
|
|
a8c596c270 | ||
|
|
da5c034928 | ||
|
|
d50a593d68 | ||
|
|
6818f61ce0 | ||
|
|
18639c7392 | ||
|
|
315cd7ec24 | ||
|
|
8d772ad40c | ||
|
|
84bb85d037 | ||
|
|
e1819e0a0d | ||
|
|
d9a0e19b20 | ||
|
|
2d5f6ac949 | ||
|
|
fd04333fa5 | ||
|
|
feebb8cb20 | ||
|
|
867229003b | ||
|
|
c5bdcbf0c5 | ||
|
|
efec941fa7 | ||
|
|
528a7ee41c | ||
|
|
9d57887357 | ||
|
|
2b731e1141 | ||
|
|
d0a7565088 | ||
|
|
d60f14c56c | ||
|
|
2103f4479b | ||
|
|
a1c77688ab | ||
|
|
dca4bc973e | ||
|
|
201e998418 | ||
|
|
597d097982 | ||
|
|
a1efa45250 | ||
|
|
c0409d137a | ||
|
|
0bcfff8e11 | ||
|
|
9e3b63a6db | ||
|
|
cab8c547f1 | ||
|
|
d0597bd2e5 | ||
|
|
21fa15d96d | ||
|
|
1ac0f4e311 | ||
|
|
4d729a009a | ||
|
|
c3de071fc8 | ||
|
|
b057329e65 | ||
|
|
99d9721ff3 | ||
|
|
c58a00b3a7 | ||
|
|
7c476bfb4a | ||
|
|
7dc0707008 | ||
|
|
269d226a8d | ||
|
|
b6556a4bcf | ||
|
|
0e1a742b10 | ||
|
|
cfb6666711 | ||
|
|
51a9919c56 | ||
|
|
3e7befcb0b | ||
|
|
e95b7f8ea0 | ||
|
|
7f094b8278 | ||
|
|
26f4b1c2a5 | ||
|
|
60e99f3875 | ||
|
|
e6c8bb15d1 | ||
|
|
414490fa5a | ||
|
|
ccc8a4c492 | ||
|
|
350d05b2c2 | ||
|
|
51c6dcbe46 | ||
|
|
9381cde1b4 | ||
|
|
d004b90e49 | ||
|
|
1012514fdc | ||
|
|
0cdd66f945 | ||
|
|
108a56fbc7 | ||
|
|
785cd229b3 | ||
|
|
e5d199263b | ||
|
|
8999f8e9d0 | ||
|
|
6280fc3a3e | ||
|
|
b4abb98aaa | ||
|
|
1c6fe9611c | ||
|
|
f65acb98c7 | ||
|
|
51b43dfb10 | ||
|
|
040ad22e69 | ||
|
|
e164a66362 | ||
|
|
caa7a9265c | ||
|
|
0b90251098 | ||
|
|
a3d9975e4b | ||
|
|
cbafc7556a | ||
|
|
6d0506402d | ||
|
|
e504dac534 | ||
|
|
e41dbeef4a | ||
|
|
05b16d63bb | ||
|
|
529cba366b | ||
|
|
298e68c71f | ||
|
|
e9ae4afc12 | ||
|
|
ec6be19b11 | ||
|
|
ae1b5ee957 | ||
|
|
9ca4757357 | ||
|
|
073160ca46 | ||
|
|
a1cbcfb23f | ||
|
|
4976caa124 | ||
|
|
6c1e759e26 | ||
|
|
d0760629e5 | ||
|
|
27ec6f8500 | ||
|
|
c098518cc8 | ||
|
|
f1fe609b3e | ||
|
|
8e9116469a | ||
|
|
28c22c65cb | ||
|
|
fbd7981b7b | ||
|
|
b4a07c1de6 | ||
|
|
30b29a58a4 | ||
|
|
b08147e531 | ||
|
|
7dfb6f0b7b | ||
|
|
95ae1dd6ed | ||
|
|
de62fe5852 | ||
|
|
bddf6ce95a | ||
|
|
636b2be7ac | ||
|
|
a6c9b96be3 | ||
|
|
ca1fbf7867 | ||
|
|
aec610a6f4 | ||
|
|
d758964ca9 | ||
|
|
c232b1fb81 | ||
|
|
024627f658 | ||
|
|
c74546a16f | ||
|
|
87827ebb82 | ||
|
|
bfd5fd2c0b | ||
|
|
9f9cb58f54 | ||
|
|
9bcd0ad197 | ||
|
|
27c9c6f11f | ||
|
|
893d83d23b | ||
|
|
5df2eca59e | ||
|
|
b157283202 | ||
|
|
f215cb9207 | ||
|
|
c46669a17e | ||
|
|
e27fd02269 | ||
|
|
63eeb96ebd | ||
|
|
da83f29f47 | ||
|
|
000a35d5cf | ||
|
|
b58d789e83 | ||
|
|
e65d0f8f2b | ||
|
|
cdfed7593e | ||
|
|
6e90fa443e | ||
|
|
8f9a6d4f7b | ||
|
|
b229f5c4df | ||
|
|
ebf115303a | ||
|
|
3d97fe016c | ||
|
|
b33ac325c0 | ||
|
|
d0d71b5548 | ||
|
|
c814bf2e0f | ||
|
|
2ba88971cf | ||
|
|
38915bbe01 | ||
|
|
78ef41e7bd | ||
|
|
7bbd4a86b4 | ||
|
|
43eac215d2 | ||
|
|
40661df80f | ||
|
|
45cc27cf24 | ||
|
|
a8103cf0b1 | ||
|
|
bce14ad12e | ||
|
|
55ac9f2d8e | ||
|
|
3de6895e32 | ||
|
|
2fa15a1c8d | ||
|
|
471fd8791d | ||
|
|
6611b94e3d | ||
|
|
fc6a30070f | ||
|
|
c9abb1c5f9 | ||
|
|
b3484cdcfc | ||
|
|
76b317ed3a | ||
|
|
205992f15c | ||
|
|
e73a1a7bcf | ||
|
|
64cdada15a | ||
|
|
4f388e8452 | ||
|
|
2fd96e1e4e | ||
|
|
7f2f37d9e6 | ||
|
|
2de011e070 | ||
|
|
d61bed820f | ||
|
|
3cf28e840d | ||
|
|
c57930a759 | ||
|
|
e7f090e333 | ||
|
|
dea6b197c5 | ||
|
|
1fb7943efd | ||
|
|
e01bf97a17 | ||
|
|
b6b657917e | ||
|
|
aaad9daa69 | ||
|
|
ec71485755 | ||
|
|
885099eebb | ||
|
|
d676466578 | ||
|
|
da828c147a | ||
|
|
35b92fde78 | ||
|
|
60369fddee | ||
|
|
12489d580c | ||
|
|
dd55153cf3 | ||
|
|
27d31d5490 | ||
|
|
8a85a2fd54 | ||
|
|
49ecfb6cdf | ||
|
|
25d9f8df61 | ||
|
|
af6695161c | ||
|
|
ce3fe6aa3b | ||
|
|
de7358a2c5 | ||
|
|
8d98d54bd2 | ||
|
|
dbc047bf56 | ||
|
|
5ff1298e21 | ||
|
|
c1cf0149b6 | ||
|
|
5e3b836404 | ||
|
|
cad2693361 | ||
|
|
e99094ab94 | ||
|
|
1ca5a3a133 | ||
|
|
b926db338d | ||
|
|
d61e18de41 | ||
|
|
8b923e6bff | ||
|
|
26b6dcf4f6 | ||
|
|
5688a2efa1 | ||
|
|
7e33e9a15f | ||
|
|
674f88f9ab | ||
|
|
b81c05c36e | ||
|
|
160f384e4f | ||
|
|
e1713bf59a | ||
|
|
cb1b055b80 | ||
|
|
c70b275187 | ||
|
|
b165eb9438 | ||
|
|
7d77102db0 | ||
|
|
d1e2059dab | ||
|
|
304d17b561 | ||
|
|
331e570792 | ||
|
|
749249466f | ||
|
|
c4b98b2a03 | ||
|
|
6b8b6b08e3 | ||
|
|
f91a5a7c75 | ||
|
|
aac8494ca3 | ||
|
|
f0c05d1edb | ||
|
|
b525f1059b | ||
|
|
69f95f551d | ||
|
|
d4ae727823 | ||
|
|
304ce84cac | ||
|
|
e0a70a468b | ||
|
|
571c0a3e68 | ||
|
|
b0a9fa14da | ||
|
|
b3a9970ac7 | ||
|
|
5499c3546b | ||
|
|
c3d8367b33 | ||
|
|
8f75606167 | ||
|
|
901c92a924 | ||
|
|
bdc5ae4fe6 | ||
|
|
aced03a287 | ||
|
|
da1c7f30a9 | ||
|
|
273d608d9f | ||
|
|
cac55dc254 | ||
|
|
01d4f2781e | ||
|
|
48fa0a5a48 | ||
|
|
f47744a8b6 | ||
|
|
6cd965b3cd | ||
|
|
4f6b9f0ca5 | ||
|
|
a838b6b3d3 | ||
|
|
6c6b2c5cc1 | ||
|
|
fb7aa0915f | ||
|
|
74b69857a1 | ||
|
|
0e8a698025 | ||
|
|
01d2ea0855 | ||
|
|
e7ac664aff | ||
|
|
457ac2eabd | ||
|
|
216e0e5102 | ||
|
|
f91f4879fc | ||
|
|
c8e6eb5edf | ||
|
|
a81334b1af | ||
|
|
546a18a20e | ||
|
|
a9745b63cb | ||
|
|
021fca5e6d | ||
|
|
fa2d199b16 | ||
|
|
cdfddff72e | ||
|
|
939d000327 | ||
|
|
e51e6961e3 | ||
|
|
0168a545c5 | ||
|
|
0ae7fc2e57 | ||
|
|
da823becbe | ||
|
|
3b1278b3bc | ||
|
|
4710fd18c6 | ||
|
|
34384b6c9c | ||
|
|
2d48b85c0e | ||
|
|
01a0a3a4d9 | ||
|
|
276cbef0c4 | ||
|
|
5b2cead5e3 | ||
|
|
9845b78e5b | ||
|
|
20157fcafe | ||
|
|
27063a492b | ||
|
|
f6bd4246b0 | ||
|
|
33d25c5074 | ||
|
|
bf5ef24ea3 | ||
|
|
979bf3f19c | ||
|
|
c5644e4a5d | ||
|
|
43d5347258 | ||
|
|
b5347d0294 | ||
|
|
4315bb6307 | ||
|
|
e8c154c3a0 | ||
|
|
666c9271dc | ||
|
|
d829fd6f9b | ||
|
|
10df1c7be5 | ||
|
|
8420c6f236 | ||
|
|
9a2d398070 | ||
|
|
476557deeb | ||
|
|
3d4e361425 | ||
|
|
0cd0cfd778 | ||
|
|
c033592509 | ||
|
|
221165c712 | ||
|
|
ed67d1f84f | ||
|
|
08b968fd80 | ||
|
|
b01f867345 | ||
|
|
92fa66f4e7 | ||
|
|
6bc1f7cc10 | ||
|
|
402ac3bfda | ||
|
|
ae4c90f990 | ||
|
|
4d4524cda3 | ||
|
|
5accd21320 | ||
|
|
3a3501d9f4 | ||
|
|
8402a7d773 | ||
|
|
5d9a654767 | ||
|
|
1a389be090 | ||
|
|
c2b162a9e8 | ||
|
|
3122e8be84 | ||
|
|
36517018d3 | ||
|
|
b8ad9da784 | ||
|
|
eaa98758b9 | ||
|
|
b9a42eaf90 | ||
|
|
6256fc8e3b | ||
|
|
012d276dcc | ||
|
|
5a9a5d8d1d | ||
|
|
2731c796f0 | ||
|
|
7470927a07 | ||
|
|
6c395cc206 | ||
|
|
594a0871dd | ||
|
|
7021d0cf5a | ||
|
|
4037bc1fb0 | ||
|
|
121b582ebc | ||
|
|
fa3d8aaeec | ||
|
|
633c587a1e | ||
|
|
6f2bacf6c4 | ||
|
|
f5624e7d9f | ||
|
|
b861b3ae36 | ||
|
|
0ac621e03f | ||
|
|
f312e7cd0e | ||
|
|
2b9e6b256b | ||
|
|
1747485cb3 | ||
|
|
0c450cad25 | ||
|
|
2e235286b1 | ||
|
|
a35c7ed6d0 | ||
|
|
e65f897a1f | ||
|
|
7fd89de7cb | ||
|
|
f4fdbde558 | ||
|
|
7d022710b3 | ||
|
|
f89b8599c6 | ||
|
|
eec73b8af0 | ||
|
|
1aa887dd28 | ||
|
|
aa4c2fe7ef | ||
|
|
292232dad2 | ||
|
|
a9830f5110 | ||
|
|
02a18c9aa8 | ||
|
|
606956da97 | ||
|
|
d088f17cd2 | ||
|
|
dd03229ea8 | ||
|
|
781392b121 | ||
|
|
c2f4bb2d48 | ||
|
|
ea0c62355c | ||
|
|
8f8393fa93 | ||
|
|
dd36b7557a | ||
|
|
d348256090 | ||
|
|
2ea3368b76 | ||
|
|
3c2d5af436 | ||
|
|
bcad1e363a | ||
|
|
edbb53bb78 |
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: hq-design-writer
|
||||
description: Writes one set of amendments to hq's to-be designs, glossary and playbooks from accepted decisions and the operator's answers to design questions (hq playbook 02, amending a design and answering a design question; playbook 04 step 4a), in the shape a builder reads whole - a main file and one milestone part per milestone, numbered design lines that leave the builder no choice - in a worktree on a branch of its own, ready for the review by architectural domain. Writes only in its worktree; never pushes.
|
||||
tools: Read, Grep, Glob, Bash, Write, Edit, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
|
||||
model: opus
|
||||
---
|
||||
|
||||
You amend hq's designs for the lead who started you. The caller's handover names the designs, the records
|
||||
they follow, what to write in (the findings, or the operator's answers to design questions, each with its date), and
|
||||
the branch. You work in a git worktree of hq under
|
||||
`.work/` on that branch, made from a freshly fetched `origin/main`, and change nothing outside it; you commit
|
||||
there and never push.
|
||||
|
||||
**First, what the mesh already knows.** Ask the knowledge base for every record the handover names
|
||||
(`knowledge-base.get` with `reasoning`, and `links`), and `knowledge-base.ask` for the area in plain words: a
|
||||
record you did not expect may already decide a point. Read each design whole before changing it, hq's glossary
|
||||
(`00-META/glossary.md`) and playbook 02's steps for amending a to-be design. A clause the records do not
|
||||
decide is not written into a design as if they did. A choice within what a record decides (a field, a tool, an
|
||||
event, a bound, the order of steps) is the design's own: write it in the design with its reason, under the record it
|
||||
follows from (playbook 02, step 2). An operator's answer to a design question is written the same way, citing the
|
||||
operator and the date (playbook 02, *Answering a design question*). Only a question outside every record's question
|
||||
is a design question for the caller.
|
||||
**It reads the mesh and writes nothing there.** It calls only verbs that read (`knowledge-base.get`, `ask`, `links`,
|
||||
`holds`; `issue-tracker.list`, `get`); never `knowledge-base.reserve` or `propose`, nor any `issue-tracker` verb that
|
||||
writes (`open`, `move`, `note`, `link`). What a record or an issue must carry goes to the caller in its report.
|
||||
|
||||
**Then write, by the rules of the record.** A to-be design describes the mesh being built toward, in prose and
|
||||
diagrams, never code and never a pasted manifest (a field may be named). **Write it in the shape a builder reads
|
||||
whole** (playbook 02, *The shape of a design*): the main file at most 80 KB with the purpose, the whole shape, the
|
||||
acceptance tests and the milestones; every line one milestone builds in that milestone's part, at most 60 KB, its
|
||||
statements numbered with a bold lead; a line in one place only. **Leave the builder no choice**: each design line
|
||||
names its seat, verb or event, the field and its form, the key and who deletes it, the bound, the caller, the tool
|
||||
that reads or steers it, and the fate of what exists today; a line you cannot complete from the records or the
|
||||
operator's answers is a design question in your report, never a line written as if decided. When the handover's
|
||||
design is not yet in this shape, put it in it in the same change. Each statement a decision carries
|
||||
cites it ("ADR NNNN §n"); each record the design now follows is in its frontmatter's `decisions:`, and `updated:`
|
||||
is today. Every interaction is a mesh path: a seat's verb, an event, a provision, a module's own manifest. Speak
|
||||
of the seat first, the module second. A rule the design states says how it is checked. hq is public: role
|
||||
names, never node names, addresses or paths. A new word goes into the glossary with its definition and its
|
||||
*Not:* line in the same change; a retired word is never used.
|
||||
|
||||
**Then check.** Run `go test ./...` at the worktree's root (the records, index, cycle and words tests) and fix
|
||||
what it refuses. Commit in the imperative, about why.
|
||||
|
||||
Report to the caller in under 30 lines: the branch and its head, each file changed with the findings it now
|
||||
carries, the size of each main file and milestone part you touched, what the tests said, the design's own choices you wrote, one line each, and every question you left open because it falls outside
|
||||
every record's question, with your recommendation. Glossary words only; a guess is said as one.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: hq-record-writer
|
||||
description: Proposes one decision record or research effort to the knowledge base (seat knowledge-base, hq playbooks 01 and 02) from a draft the lead names - reserves its number when it has none, proposes it whole, reads the knowledge base's own review, revises and proposes again until it approves or asks the operator, at most four versions. Writes nothing in hq or in any repository.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
|
||||
model: opus
|
||||
---
|
||||
|
||||
You put one record into the knowledge base for the lead who started you. The handover names the draft (a file the
|
||||
design writer wrote, outside hq), the record's number when it has one, whether it needs the operator, and its homes
|
||||
(the hq designs that cite it).
|
||||
|
||||
**First, what holds.** Read the draft whole, then every record it links (`knowledge-base.get` with `reasoning`,
|
||||
`knowledge-base.links`), and the clauses it changes as they stand now (`holds`). A clause the draft says it changes
|
||||
must exist and still be in force; a fact the draft rests on that you can read on the mesh, read it. When a link or a
|
||||
fact is wrong, or a clause names a part, event or verb the designs retire, stop and say so to the lead: never
|
||||
propose a clause you know to be false.
|
||||
|
||||
**Then propose it whole.** `knowledge-base.reserve` a number only when the draft has none. `knowledge-base.propose`
|
||||
takes the title, topic, date, deciders (the operator with the date and the words of each answer, then the lead and
|
||||
the writer who wrote it down), the clauses in order, each opening with a bold lead, the sections (Context,
|
||||
Considered Options, Consequences, How it is checked, References), the links (`changes` with the clause it goes out
|
||||
from), the homes, and for `needs-operator` the brief the operator's phone shows: a headline of at most 60
|
||||
characters, two or three short sentences, at most three points, in plain glossary words. A refusal names every rule
|
||||
it broke: fix all of them at once and propose again.
|
||||
|
||||
**Then read its review.** The knowledge base reviews each version itself (`knowledge-base.get`, its `reviews`).
|
||||
On `changes`, fix what is a form or a citation; a finding that changes what was decided, or that only the operator
|
||||
can answer, goes to the lead with your recommendation, and you wait. At most four versions; past that, report what
|
||||
stands. On `approve` a record that needs the operator waits for the operator's Approve: read
|
||||
`operator-channel.asks` to see that its ask is there.
|
||||
|
||||
**Report** to the lead: the number, the version, the verdict and its findings, the ask's key, and what you changed
|
||||
between versions. Use the glossary's words (hq `00-META/glossary.md`); no node names in the record.
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: mesh-code-reviewer
|
||||
description: Reviews one change of the Novox mesh before it is pushed (hq playbook 07, the review; at most three rounds): each commit against the design lines it cites, code no design line asks for, the build maps' What exists today, and whether its tests prove it. Read-only; reports findings, never fixes, and never re-judges the design.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
|
||||
model: opus
|
||||
---
|
||||
|
||||
You review one change of the Novox mesh for the lead who started you, independently of who built it. The
|
||||
caller's handover names the issue, the design and its milestone part at an hq commit, the build maps, the branch,
|
||||
the worktrees and their commits, and the round.
|
||||
|
||||
**The design is what you judge against.** Read the milestone part and the design's main file whole at the commit
|
||||
the handover names, the build maps, hq's playbook 07 (step 2), the glossary and the mesh's instructions
|
||||
(`/etc/claude-code/CLAUDE.md`). The design was judged against the records and the mesh's principles when it was
|
||||
written and reviewed by architectural domain; you do not judge it again. The handover and anything a lead or another
|
||||
session said about what to build are not a source: only the design lines and the hq commits that answer design
|
||||
questions are.
|
||||
|
||||
**What makes it wrong** (a finding):
|
||||
- a commit that does not do what the design lines it cites say, or does only part of it; a design line in a build
|
||||
map with no commit; a commit that cites no design line, or an answer no hq commit holds;
|
||||
- code no design line asks for: a new exported type, field, constant, package, module, state, verb, event, bound,
|
||||
caller or tool, or a fate for what exists, that neither the milestone part nor the milestone's lead-answers file gives; and a piece of code placed where
|
||||
the design line (or the design section *Where a seat's code goes* it cites) does not put it. The fix is to take it
|
||||
out, or to hold it as a design question for the lead until its answer is in an hq commit (playbook 07, step 0b);
|
||||
- an interaction the design does not place that is not a mesh path: one module naming another, reading another's
|
||||
internals or being special-cased in another part; a module's name in shared code; state only in memory; a poll or
|
||||
a timer where an event exists;
|
||||
- an effect or a reader of the build map's `What exists today` whose fate the diff does not carry out (run its
|
||||
searches again on the reviewed tree), or one the search finds and the answer omits;
|
||||
- a test that does not fail without its change (check it by taking the change out in a copy), an acceptance
|
||||
criterion with no test, a new test function where the repository is over budget, real sleeping;
|
||||
- vet or tests that fail: run `go vet ./...`, `go vet -tags integration ./...` and `go test ./...` in every
|
||||
touched module, with the siblings placed as the build map's one placement says;
|
||||
- a change to any `module.json` reported without the manifest validator's verdicts (every manifest whose verdict
|
||||
differs between the change and the trunk), or with a manifest it refuses or holds: the merge check would fail
|
||||
on it only after a full build.
|
||||
|
||||
**A design line you think wrong is a note, never a change asked.** Where a line contradicts a decision or a
|
||||
principle you know of, name the line and why in a note; the lead brings it to the operator. The code follows the
|
||||
line until the design changes.
|
||||
|
||||
**Prove it against the live mesh, never a re-creation.** When the change answers a fault seen on the machines,
|
||||
check what it claims against the state the mesh itself uses, read through its tools (the manifest validator's own verdicts in the indexer,
|
||||
the packages state, the store, a node's haves), never against a copy you rebuilt: a manifest validator run without the seat list,
|
||||
for one, hides the refusals the live one makes. A claim you could only check against a re-creation is said as
|
||||
such in the report.
|
||||
|
||||
Anything else (hardening, a further case, a nicer form) is a note, never a round. Read only: no edits, no git
|
||||
write, no verb that writes, never ssh. Use a temporary directory under `/var/tmp`.
|
||||
|
||||
Answer playbook 07 step 2 item 10's two questions, one line each. Answer **APPROVED at** the full commit ids, or numbered findings each with file:line, the design line it concerns,
|
||||
what is wrong, and the fix; design questions and notes apart. Under 25 lines, glossary words only.
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: mesh-developer
|
||||
description: Builds one prepared change of the Novox mesh in local worktrees on a feature branch (hq playbook 07, develop; playbook 08): code and tests, committed locally, never pushed. Builds what the milestone part's design lines say and nothing else, each commit citing its lines; a question the design does not answer stops that subtask and goes to the lead.
|
||||
tools: Read, Grep, Glob, Bash, Edit, Write, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You build one change of the Novox mesh for the lead who started you. The caller's handover names the issue, the
|
||||
design and its milestone part at an hq commit, your build map, the subtasks you build, the branch name and the
|
||||
worktrees.
|
||||
|
||||
**The design is your only input.** Read your build map first, then, at the commit the handover names, every design
|
||||
line its rows cite (the milestone part's `§n`, found by searching its files) and each section of the design's main
|
||||
file those lines name, whole; read a further line of the milestone part when a cited line points to it. Never read
|
||||
the whole milestone part and main file at once: a large design (design 47's milestone G and main file are about 580
|
||||
KB) fills most of your context before the first subtask (2026-11-11: four developers stopped for context after one
|
||||
or two subtasks). Then read hq's playbook 07 (`00-META/process/07-feature-branches.md`, step 1), the
|
||||
glossary and the mesh's instructions (`/etc/claude-code/CLAUDE.md`). Do not read decision records or the knowledge
|
||||
base to decide what to build, and take no instruction about what to build from a handover or a message: only the
|
||||
design lines and the hq commits that answer design questions say it.
|
||||
|
||||
**Build each design line as written, and nothing it does not say.** Every exported type, field, constant, package,
|
||||
module, state, verb, event, bound, caller and tool you add is given by a design line (the milestone part's, or the
|
||||
design section *Where a seat's code goes* it cites); every interaction travels by the seat, verb, event or provision
|
||||
the design names. Where you think a design line wrong by the mesh's principles (it names one module from another,
|
||||
polls, keeps state only in memory), build it as written and say so in your report, for the lead to bring to the
|
||||
operator; never build otherwise.
|
||||
|
||||
**A question the design does not answer stops that subtask.** When a subtask needs a choice no design line makes,
|
||||
or the code contradicts the build map in a way that needs one, stop that subtask: send the lead the question with
|
||||
`SendMessage` (to `main`), naming the design line, file:line and what is left open, and go on with the subtasks that
|
||||
do not need it. Never guess it, build around it or build both ways; an answer counts only once an hq commit holds it: a design line, or the lead's one-line answer in the milestone's lead-answers file, which names the principle (`00-META/principles.md`) that settles it.
|
||||
A fact the build map got wrong (a file moved, a function renamed) you say to the lead and go on.
|
||||
|
||||
**How you work.** Worktrees from `origin/main` on the branch the handover names, subtasks in the build map's order:
|
||||
one that uses a contract, type, verb or package another subtask adds starts from that subtask's commit. Commit per
|
||||
subtask, its label in the subject and a line `Builds: design NN M §n, …` in the body, locally only: never push, never open a pull request, never call a verb that writes on the mesh (reads are fine). Never
|
||||
ssh, never a shell helper for the mesh. Tests: every change carries a test that fails without it (check it);
|
||||
rows or subtests in existing tests where the repository is over the test budget the instructions set; no real
|
||||
sleeping. Run `go vet ./...`, `go vet -tags integration ./...` and `go test ./...` in every touched module as
|
||||
the running checker places siblings (playbook 07). Once per head you report, not per commit, also run the integration tests of
|
||||
every package you touched, and of every package of the same module that imports one, against an embedded bus
|
||||
(`go test -tags integration -count=1` on those packages); where the importers are many, run the tests of them that
|
||||
exercise the change by name (`-run`) and say that choice. Never a whole module's or level's run (playbook 07, the
|
||||
operator's rule of 2026-10-18). Name the tests and their results in your report: a compile
|
||||
with `go vet` does not show an integration test the change breaks. In mesh-sdk run the whole untagged `./...` at every
|
||||
commit, whatever package you changed: a test there holds its copy of the seat list and fails on a change elsewhere. Use a temporary directory under `/var/tmp` (TMPDIR). Place siblings as the build map's one placement says (`go.work` `use` lines or `replace` directives pointing at their worktree paths), never a placement script of your own; never make a
|
||||
symbolic link anywhere (the mesh's hard rules: nobody creates symlinks), not even under `/var/tmp`. Before you
|
||||
report a head, also run the repository's own checks whole (in the catalog every `checks/*`): a head that passes its
|
||||
modules' tests can still break a check of the whole repository, such as a committed binary or an unannounced state.
|
||||
When the change touches any `module.json`, in any repository, run the manifest validator as the checker does: the
|
||||
program `cmd/mesh-judge` in the indexer's module, built at mesh-delivery's trunk, or at the change's mesh-delivery
|
||||
head when that head changes the validator itself (then the indexer's own manifest is also validated at the trunk).
|
||||
Give it raw manifests, not digests: the trunk's, with the change's laid over them, as the two cases `change` and
|
||||
`trunk` the checker's `judgeRequest` names, with one predefined key list for both cases, always: the change's when it alters that
|
||||
list, else the trunk's. Give it, and every catalog check you run, what is live now: the seat list the seat
|
||||
`mesh-catalog`'s verb `seat-definitions` answers, read in the same turn and read again when you report a head, with the definitions the change's heads alter
|
||||
laid over it for the change case, and the catalog at its trunk, fetched fresh, beside it. Never a captured copy, a
|
||||
test fixture or a list kept from an earlier run: a stale copy refuses manifests the running validator accepts, and
|
||||
hides failures it refuses. The merge check fails a changed manifest the validator refuses or holds,
|
||||
and also an unchanged one in which the change causes a
|
||||
problem the trunk case lacks; it says so only after a full build, and no module's unit tests run the validator.
|
||||
Report every manifest whose verdict differs between the two cases, with your heads. The reviewer asks for those
|
||||
verdicts, and a head reported without them is not reviewed.
|
||||
Start no agents of your own: the lead decides who works on what.
|
||||
|
||||
**Start a subtask only with room to finish it.** Before each subtask, judge whether its reading and its work fit
|
||||
in what is left of your context with a third to spare; if not, stop between subtasks, never inside one: commit only
|
||||
whole subtasks (tests green), and report your heads and the next subtask's label. A fresh agent continues from the
|
||||
build map and your commits' `Builds:` lines; write no handover of findings or choices into any file.
|
||||
|
||||
**Say a commit others wait on as it lands.** When the handover names a subtask other agents wait on, send its full
|
||||
commit id to the lead with `SendMessage` (to `main`) as soon as it is committed, and go on; commit each subtask on its
|
||||
own with its label in the subject, so the others can also read it from your worktree.
|
||||
|
||||
Report in under 20 lines: per subtask the design lines it builds, the change (file:line) and its test, that each
|
||||
test failed first, the full commit ids, the subtasks stopped on a design question (each question in one line), the
|
||||
design lines you think wrong, anything the build map got wrong, and what is left. Glossary words only.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: mesh-preparer
|
||||
description: Prepares one milestone or small fix of the Novox mesh before any code is written (hq playbook 07, the prepare step; playbook 08), from the milestone's part of its design and the design's main file, read whole, then the code each design line lands in - one build map per repository (where each design line lands in code, what exists there today and what relies on it, the subtasks and their order), the list of design questions the design does not answer, and the list of places where a design line and the code disagree. Decides nothing; writes only its build maps and the milestone file's two lists.
|
||||
tools: Read, Grep, Glob, Bash, Write, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
|
||||
model: opus
|
||||
---
|
||||
|
||||
You prepare one change of the Novox mesh for the lead who started you. The caller's handover names the issue, the
|
||||
design and its milestone part at an hq commit, and where your files go (playbook 07, step 0). You write the build
|
||||
maps and the milestone file's sections `Design questions` and `Design meets code`, nothing else; you call no verb that writes and make no git
|
||||
write.
|
||||
|
||||
**The design is your only input for what is built.** Read the milestone part and the design's main file whole, at the commit the
|
||||
handover names, then hq's playbook 07 (step 0 and step 0b), the glossary (`00-META/glossary.md`) and the mesh's
|
||||
instructions (`/etc/claude-code/CLAUDE.md`). Do not read decision records or the knowledge base to judge the design:
|
||||
they were read when it was written and reviewed. Read the issues the handover names (`issue-tracker.get`) for their
|
||||
done-when. Code shows what exists, never what is to be built; but it says whether a line can be built as written, so
|
||||
you read it too (next paragraph).
|
||||
|
||||
**Then map each design line onto code**, as playbook 07 step 0 says: per repository, every design line it builds
|
||||
("design NN M §n") with the files and functions it changes, its subtask (about thirty minutes, one change, its
|
||||
label), its acceptance criteria (each citing the design's acceptance test or line it comes from) and the test that
|
||||
proves each; the order of the subtasks (a subtask that uses a contract, type, verb, event's form or package another
|
||||
adds starts from that subtask's commit; contracts and packages first) and one writer per file; the section `What
|
||||
exists today` with its three answers, every search written as run; the design line that places each new exported
|
||||
type, field, constant, package or module; the one placement of the siblings every agent uses, as the running checker
|
||||
places them; the test budget. Compare each seat contract the milestone adds or changes (its fields, subjects,
|
||||
callers and kinds, as on main) with the design lines that name that seat, field by field: every difference is a
|
||||
design question. Code: read the repositories at `origin/main` (`git fetch`, `git show`, `git grep`),
|
||||
never a working tree; the live mesh through its tools.
|
||||
|
||||
**Then read the code each design line lands in, and list where they disagree** (playbook 07 step 0, items 3 and 10;
|
||||
the operator, 2026-11-11). For each design line, read at `origin/main` the files and functions it changes, the
|
||||
contracts, types and packages it uses and the rules the code holds there (a guard, a check, a test that refuses), and
|
||||
ask: can the code take the form the line names; is every part the line assumes built (a verb, a field, a package, an
|
||||
event, a module) there on main; does the line break a rule the code holds. Each no is an entry under `Design meets
|
||||
code`: the design line, the code (file:line at the commit read, each repository's commit named once at the top),
|
||||
which of the three it is, and your recommendation, said as one. The lead answers these against the principles before
|
||||
develop starts, as it does the design questions; you answer none.
|
||||
|
||||
**Everything the design does not say is a design question, never your answer.** A reader or an effect with no fate,
|
||||
a field's form, a key and who deletes it, a bound, a caller, an owner, a tool for a new state, an interaction whose
|
||||
seat, verb, event or provision the design does not name, a live state that contradicts a design line: list
|
||||
each under `Design questions` with the design line (or the missing one), what it leaves to its builder, where you met
|
||||
it (file:line or the tool's answer) and your recommendation, said as one. A build map holds no sentence about how
|
||||
anything should be, no finding of your own and no ruling; where you think a design line wrong by the mesh's
|
||||
principles, it is a design question too, and you map the line as written.
|
||||
|
||||
Report to the caller in under 30 lines: the build maps' paths, the count of design lines mapped per repository and
|
||||
any line not mapped, the subtasks' order, the design questions and the entries of `Design meets code`, one line each with your
|
||||
recommendation. Glossary
|
||||
words only; a guess is said as one.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: mesh-watcher
|
||||
description: Follows one thing the lead waits on in the Novox mesh (a pull request's check, a merge, a delivery, a decision's Approve, an answer on Telegram) and reports when it lands, fails, or stands still. Read-only; never acts.
|
||||
tools: mcp__mesh__mesh_call, mcp__mesh__mesh_wait, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You watch one thing for the lead who started you. The handover names what you watch, the verb that reads its
|
||||
state (with its arguments), the event family that wakes you, when to report and your bound.
|
||||
|
||||
**First, the owner's event.** Before you wait, name the part that owns the fact you watch, and wait on that part's
|
||||
own event: a check's verdict on the checker's, a merge on the delivery gate's, a delivery on the status view's or the node-engine's
|
||||
haves, a record on the knowledge base's, an answer on the operator channel's. Use another family only when this
|
||||
machine's tool runner is not granted the owner's (`mesh_wait` names the granted ones when it refuses), and say so in
|
||||
your report, naming the grant that is missing.
|
||||
|
||||
**What a machine runs is read from the machine.** For a delivery, read `<node>/node-engine.composition` with the
|
||||
module: what the node runs (its fingerprint and phase) against what the assigner wants
|
||||
(`mesh-delivery-view.assigner_want` with the node and the module). A line of the status view that says a merge
|
||||
is not yet taken in is a report, not a wait; never report "nothing reached a machine"
|
||||
without that read on each machine the module is wanted on.
|
||||
|
||||
Loop: wait with `mesh_wait` on the event family (its answer only wakes you), then read the state from the verb
|
||||
the handover names; never report a state you did not read in that round.
|
||||
|
||||
**A report along the way does not end you.** Your final message ends you, and then nothing watches. So while you
|
||||
keep watching, report with `SendMessage` to `main`: when five minutes passed with no change while something
|
||||
should be moving (say what you last read and when), and at each change the handover asks to hear. Then go on
|
||||
waiting. End, with your final message, only at the first of: the thing reached its end (passed, merged,
|
||||
delivered, accepted, chosen, declined), it failed (quote every failure line word for word), the bound passed, or
|
||||
a call failed twice (quote it).
|
||||
|
||||
You only read: no verb that writes, never ssh, never a shell helper. Report in a few lines, with times in UTC as
|
||||
the tools give them; the lead says them to the operator in local time.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: research-reader
|
||||
description: Carries one reading task of a research effort's step 5 (hq playbook 01, Do the research) - reads the code on main, counts from the manifests, or tries a program's behaviour in process, and reports facts with their repository, commit and function. Read-only on the mesh and every working copy.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You carry one reading task of a research effort of the Novox mesh (hq playbook 01, step 5, *Do the research*).
|
||||
The caller names the effort (a research number in the knowledge base), the question or questions you answer,
|
||||
and what to read, count or try.
|
||||
|
||||
Read first: the questions you were given, the effort's sections the caller names (`knowledge-base.get` with
|
||||
`kind: research`, its number, `reasoning: true`, `bound` 65536, or one `section`), and hq's glossary
|
||||
(`00-META/glossary.md`). Then the code.
|
||||
|
||||
How you read:
|
||||
|
||||
- **Code on main only, never a working copy's checkout.** Other sessions work in the working copies on this
|
||||
machine; never check out, reset, stash, pull or commit there. Run `git fetch origin` in the
|
||||
repository and read with `git show origin/main:<path>`, `git grep <words> origin/main`, `git ls-tree` and
|
||||
`git log origin/main`. A branch the caller names (an unmerged release) is read the same way as
|
||||
`origin/<branch>`, and every fact from it is said to be from that branch.
|
||||
- **Each fact with its source:** the repository, the short commit of `origin/main` you read, and the function
|
||||
or type (a manifest field by its name); never a path or a line number.
|
||||
- **Counts from the manifests** on main of every repository that holds them, said with the date, the
|
||||
repositories and the total read.
|
||||
- **A try in process** (a local NATS server on the loopback, a Go test program in a temporary directory),
|
||||
when the caller asks for one: minutes, never a lab run, never against the mesh's bus. Say what you ran and
|
||||
what it printed.
|
||||
- Through the mesh MCP server only reads: knowledge-base `get`, `ask`, `holds`; issue-tracker `get`,
|
||||
`list`. Call no verb that writes.
|
||||
|
||||
Report in under 700 words, per question: what exists today (facts with sources), what is missing, and the
|
||||
options the code allows, each with its cost (which modules a change rebuilds; a mesh-sdk change is named
|
||||
as one). Separate fact from guess: a guess is said as one, with how to check it. Write plain words in the
|
||||
glossary's words. Say what you read and what you could not.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: review-health-and-events
|
||||
description: Reviews a research effort's settled direction or a design against the architectural domains health and events and logs, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
|
||||
model: opus
|
||||
---
|
||||
|
||||
You review one subject of the Novox mesh against two architectural domains: **health** and **events and logs**. The caller
|
||||
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
|
||||
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
|
||||
domains (hq playbook 01, *Review by architectural domain*).
|
||||
|
||||
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
|
||||
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
|
||||
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
|
||||
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
|
||||
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
|
||||
|
||||
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
|
||||
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
|
||||
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
|
||||
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
|
||||
write.
|
||||
|
||||
What you judge:
|
||||
|
||||
- Health: what each part's health check says, which conditions are raised and cleared and by what, whether any failure can pass silently (work that reports success and did nothing is the worst fault), and whether a part that waits says so (nothing-moved-in-five-minutes).
|
||||
- Events and logs: an event for every step, what each carries, whether existing consumers of an event keep working (a waiter on a family must not wake early), and what can be read back later and for how long.
|
||||
|
||||
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
|
||||
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
|
||||
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
|
||||
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
|
||||
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
|
||||
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
|
||||
a decision says so and goes to the operator through the lead.
|
||||
|
||||
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
|
||||
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
|
||||
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
|
||||
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
|
||||
words, with no analogies. Say what you read; a guess is said as one.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: review-running-and-testing
|
||||
description: Reviews a research effort's settled direction or a design against the architectural domains running qualities and testing, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
|
||||
model: opus
|
||||
---
|
||||
|
||||
You review one subject of the Novox mesh against two architectural domains: **running qualities** and **testing**. The caller
|
||||
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
|
||||
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
|
||||
domains (hq playbook 01, *Review by architectural domain*).
|
||||
|
||||
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
|
||||
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
|
||||
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
|
||||
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
|
||||
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
|
||||
|
||||
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
|
||||
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
|
||||
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
|
||||
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
|
||||
write.
|
||||
|
||||
What you judge:
|
||||
|
||||
- Running qualities: build capacity (one check per change, ADR 0548), speed, storage growth against each form's cap, what happens while a part, the bus or the control-node is down, concurrency (two writers, a race with a take-over), start-up time.
|
||||
- Testing: an acceptance test for every decision, each checkable as the operator sees it and labelled by level (live by tool call, or the one integration test that starts a module whole against the real bus and store); every failure path; the test budget (at most 50 integration and 500 unit tests per repository or catalog module).
|
||||
|
||||
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
|
||||
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
|
||||
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
|
||||
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
|
||||
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
|
||||
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
|
||||
a decision says so and goes to the operator through the lead.
|
||||
|
||||
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
|
||||
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
|
||||
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
|
||||
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
|
||||
words, with no analogies. Say what you read; a guess is said as one.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: review-seams-and-tools
|
||||
description: Reviews a research effort's settled direction or a design against the architectural domains seams and tools, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
|
||||
model: opus
|
||||
---
|
||||
|
||||
You review one subject of the Novox mesh against two architectural domains: **seams** and **tools**. The caller
|
||||
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
|
||||
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
|
||||
domains (hq playbook 01, *Review by architectural domain*).
|
||||
|
||||
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
|
||||
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
|
||||
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
|
||||
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
|
||||
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
|
||||
|
||||
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
|
||||
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
|
||||
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
|
||||
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
|
||||
write.
|
||||
|
||||
What you judge:
|
||||
|
||||
- Seams: where each piece of state lives on the bus (key-value bucket, stream, object store) and which module owns it; which seat, verb or event joins two parts; any link that is not NATS-native (a poll, a timer, HTTP between modules, a shared file, state only in memory); whether a form the manifest lacks is named with its cost (a mesh-sdk change rebuilds every module).
|
||||
- Tools: for every new piece of state a tool that reads it, and for every new behaviour a tool that steers it (do by hand, ask again, drop, repair, re-check), as the instruction tools-are-how-a-module-is-spoken-to says.
|
||||
|
||||
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
|
||||
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
|
||||
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
|
||||
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
|
||||
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
|
||||
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
|
||||
a decision says so and goes to the operator through the lead.
|
||||
|
||||
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
|
||||
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
|
||||
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
|
||||
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
|
||||
words, with no analogies. Say what you read; a guess is said as one.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: review-security-and-upgrade
|
||||
description: Reviews a research effort's settled direction or a design against the architectural domains security and upgrade, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
|
||||
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
|
||||
model: opus
|
||||
---
|
||||
|
||||
You review one subject of the Novox mesh against two architectural domains: **security** and **upgrade**. The caller
|
||||
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
|
||||
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
|
||||
domains (hq playbook 01, *Review by architectural domain*).
|
||||
|
||||
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
|
||||
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
|
||||
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
|
||||
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
|
||||
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
|
||||
|
||||
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
|
||||
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
|
||||
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
|
||||
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
|
||||
write.
|
||||
|
||||
What you judge:
|
||||
|
||||
- Security: who may call each new verb, how its grants are scoped (ADR 0363's grant form), which credentials go or remain, what can be replaced or forged (a digest, an author, a tag), what enters from outside the mesh, and every act that removes data.
|
||||
- Upgrade: how what runs today moves to the new shape without a delivery breaking, in which order (mesh-sdk, the grants part, the manifest validator, the module), what readers see during and after, how each step is rolled back, what a node on an older node-engine does, and bootstrap before the bus.
|
||||
|
||||
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
|
||||
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
|
||||
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
|
||||
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
|
||||
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
|
||||
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
|
||||
a decision says so and goes to the operator through the lead.
|
||||
|
||||
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
|
||||
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
|
||||
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
|
||||
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
|
||||
words, with no analogies. Say what you read; a guess is said as one.
|
||||
@@ -26,7 +26,10 @@ and says they disagree.
|
||||
1. Propose the decision record in the knowledge base (`hq-graduate`, steps 2 and 3). If it reverses an
|
||||
earlier one, it `supersedes` it, or `changes` the clauses it replaces — the earlier record's text is
|
||||
never edited.
|
||||
2. Edit the design, add `"ADR NNNN"` to its `decisions:`, and set `updated:` to today.
|
||||
2. Edit the design, add `"ADR NNNN"` to its `decisions:`, and set `updated:` to today, keeping it in the shape of
|
||||
playbook 02's *The shape of a design* (a main file and one milestone part per milestone, numbered design lines).
|
||||
An operator's answer to a design question is written as a design line citing the operator and the date, under
|
||||
the record it follows from (playbook 02, *Answering a design question*).
|
||||
3. If this came from an issue, give the issue the document in the issue tracker: `amended-design` on its
|
||||
`issue-tracker.move` to `resolved`, or `issue-tracker.link` with kind `design` before then. Never in the
|
||||
report's frontmatter (ADR 0275).
|
||||
|
||||
@@ -27,12 +27,14 @@ written into this repository.
|
||||
status, reviews and the operator's ask.
|
||||
4. **Write the design** under `03-DESIGN/01-to-be/` with `layer: to-be`, `status: designed`,
|
||||
empty `code:`, today's `updated:`, and `decisions:` listing `"ADR NNNN"`.
|
||||
5. **Close the effort** — `knowledge-base.propose` the effort again, whole, with `status: graduated` and
|
||||
`became` links to the record (`"NNNN"`) and the design (its path).
|
||||
5. **Close the effort** — `knowledge-base.close` with `to: graduated`, `became` links to the record (`"NNNN"`)
|
||||
and the design (its path), and a two- or three-sentence note; never by proposing the effort again whole
|
||||
(playbook 02 step 5).
|
||||
|
||||
## Amending an existing design
|
||||
|
||||
A design changes only through a decision. Propose the record first; if it reverses an earlier one,
|
||||
A design changes only through a decision; a choice within what a decision decides (a field, a tool, an event, a
|
||||
bound) is the design's own, written in it with its reason. Propose the record first; if it reverses an earlier one,
|
||||
it `supersedes` it (or `changes` the clauses it replaces) — **never edit the earlier record's text**;
|
||||
a correction of fact is a progressive insight (`propose` the accepted record with `insight`). Then edit
|
||||
the design, add `"ADR NNNN"` to its `decisions:`, and set `updated:`.
|
||||
|
||||
@@ -19,9 +19,12 @@ Hands a settled design to a code repository. **Authoritative playbook:**
|
||||
3. **Read the as-is counterpart.** What is being replaced must be written down in
|
||||
`03-DESIGN/00-as-is/` before it is changed. Building against an undocumented as-is is how
|
||||
shipped behaviour gets lost.
|
||||
4. **Flip the status** to `in-progress`, `updated:` today.
|
||||
5. Build in the code repository. HQ never carries implementation.
|
||||
6. On completion, run `hq-graduate`'s "when something ships" section.
|
||||
4. **Give each milestone its part** (playbook 04 step 4a, playbook 02 *The shape of a design*): every line it
|
||||
builds in `NN-milestones/<milestone>.md`, numbered, at most 60 KB; the main file at most 80 KB.
|
||||
5. **Flip the status** to `in-progress`, `updated:` today.
|
||||
6. Build in the code repository, from the milestone part alone (playbook 07): no code before every design question
|
||||
its prepare finds is answered by the operator and written into the design. HQ never carries implementation.
|
||||
7. On completion, run `hq-graduate`'s "when something ships" section.
|
||||
|
||||
## Non-negotiable
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ All through the mesh MCP server (`mesh_describe` gives each verb's arguments).
|
||||
number is spent; never pick one by hand.
|
||||
3. **Propose the effort.** `mesh_call knowledge-base.propose` with `kind: research`, the reserved `number`,
|
||||
`title`, `status: active`, `date` (today), `sections` (the documents in order, each `name` and `text`;
|
||||
the first `00-overview.md`: **what** is investigated, **why**, and **what it touches**), and `links`
|
||||
each named by a plain title, never as a file; the first, **Overview**: **what** is investigated, **why**, and **what it touches**), and `links`
|
||||
of kind `touches` for every record, effort, issue or design it touches (`cites` for one it only names).
|
||||
4. **Do the research** in further documents of the same effort. Each change is `propose` again with the
|
||||
effort whole and `why`. A refusal names every rule broken: fix them all and propose again. On review
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
__pycache__/
|
||||
.work/
|
||||
|
||||
@@ -9,11 +9,12 @@ in memory. The Python files and `merge-check.sh` are gone (issue 1296).
|
||||
records_test.go structure: links, no record written here, citations in the form "ADR NNNN"
|
||||
index_test.go the reading order's topics are written, and no list of records is stored
|
||||
cycle_test.go the development cycle: a design's layer, status, decisions and code; an issue report's frontmatter
|
||||
words_test.go the glossary's retired words are not used, no daemon's line is written, and no word is defined twice
|
||||
words_test.go the glossary's retired words are not used, no daemon's line is written, no word is defined twice,
|
||||
and the glossary names no seat's holder
|
||||
```
|
||||
|
||||
A failing test fails `go test`, so it can be a gate rather than a report. With `MESH_CATALOG_DIR` set, the words
|
||||
test also compares the catalogue's copies of the retired words and the daemons' lines with the glossary.
|
||||
test also compares the catalog's copies of the retired words and the daemons' lines with the glossary.
|
||||
|
||||
**Why this exists.** Until now nothing in this repository was verified by anything but reading,
|
||||
which is how a superseded decision stayed live in the constitution for days and in
|
||||
@@ -31,6 +32,7 @@ indistinguishable from one that cannot.
|
||||
| `decisions-form` | every entry of a design's `decisions:` is "ADR NNNN", four digits | written with issue 426; it failed on an entry `ADR 290` before it passed |
|
||||
| `mention-form` | every "ADR N" in the text has four digits, and every "research N" three; a code span is an example and is not read | written with issue 426; it failed on `ADR 12` and `research 45` before it passed |
|
||||
| *(index test)* | the reading order's six topics are written in `02-DECISIONS/README.md`, none twice, and the README stores no list of records (ADR 0248). The knowledge base reads these lines and refuses a record whose topic is not one of them | a stored list made every two open decisions conflict ([issue 297](../../04-ISSUES/297-two-open-decisions-always-conflict/00-report.md)); it failed on the README of `main` before the list was removed |
|
||||
| `holders` | the glossary names no module as a seat's holder: no seat "held by" or "claimed by" a named module, no named module "holder of" a seat, no module that "holds the seat"; the holder is read from the mesh's assignments (the operator, 2026-11-09) | **twenty statements** in the glossary, from the registry seats' "held today by the forge module" to the knowledge base's holder |
|
||||
| `status-vs-code` | a to-be document naming specific code is not still `designed` | **ten documents**, several with a *What was built* section, describing lab-proven code |
|
||||
|
||||
## Where the rules on records went
|
||||
@@ -99,7 +101,7 @@ quotations, struck-through text, link targets, comments and frontmatter are take
|
||||
`03-DESIGN/`, `AGENTS.md`, `README.md`, and issues opened from 2026-10-07. Decision records and research
|
||||
efforts live in the knowledge base, which holds a proposed record to the same words (the table above). 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. With `MESH_CATALOG_DIR` set it compares the catalogue's copy
|
||||
[`words-allowed.md`](words-allowed.md) names a document that could not be reworded at once, with a date. With `MESH_CATALOG_DIR` set it compares the catalog's copy
|
||||
of the tools' retired words (`retired-words`) with the glossary.
|
||||
|
||||
It also fails on a **daemon's line** (ADR 0272,
|
||||
@@ -107,7 +109,7 @@ its insight of 2026-10-09): the operator types one command, `nox`, and a documen
|
||||
controller's or the node-engine's binary followed by one of its commands. The lines are the check's own list
|
||||
(`daemonLines` in the words test); a command that is a line only with an argument after it is matched as a line is written. Looked
|
||||
for in the same documents, from 2026-10-09 for issues, in the document's own words — code blocks and
|
||||
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalogue's copy
|
||||
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalog's copy
|
||||
(`retired-lines`) with the list.
|
||||
|
||||
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 —
|
||||
|
||||
@@ -19,4 +19,10 @@ which keeps a record's words as it was imported.
|
||||
| `04-ISSUES/353-the-controller-asked-before-the-bus-held-its-grant/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
|
||||
| `04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
|
||||
| `04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
|
||||
| `04-ISSUES/364-the-gate-refuses-a-module-that-uses-a-seat-another-module-declares/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
|
||||
| `04-ISSUES/364-the-gate-refuses-a-module-that-uses-a-seat-another-module-declares/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the words of its day, "merge gate" (issue 1303) and "judge" (the glossary's manifest validator retired the noun); it is not rewritten |
|
||||
| `04-ISSUES/286-a-merge-check-passed-on-an-agents-machine-and-failed-on-the-build-seat/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the composition check's program built from a change; the glossary's manifest validator retired the noun); it is not rewritten |
|
||||
| `04-ISSUES/306-a-check-on-the-control-node-ran-past-its-timeout/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the composition check's program built from a change; the glossary's manifest validator retired the noun); it is not rewritten |
|
||||
| `04-ISSUES/310-a-pull-request-ran-its-own-copy-of-its-check/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the composition check's program built from a change; the glossary's manifest validator retired the noun); it is not rewritten |
|
||||
| `04-ISSUES/343-the-controllers-terminal-has-no-command/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (a part of the controller's pull request, named so then; the glossary's manifest validator retired the noun); it is not rewritten |
|
||||
| `04-ISSUES/361-the-control-nodes-agent-account-is-never-judged/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the self-check's look at what an account can reach; the glossary's manifest validator retired the noun); it is not rewritten |
|
||||
| `04-ISSUES/299-an-optional-verb-makes-a-holder-look-silent/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "seat set" (the seats compiled into the controller then; the glossary's seat list retired it); it is not rewritten |
|
||||
|
||||
+116
-10
@@ -35,7 +35,11 @@ import (
|
||||
// struck-through text, link targets, HTML comments and frontmatter are taken out. A word's plural is the word.
|
||||
// unique no head word heads two entries, and none is also struck through on a *Not:* line.
|
||||
// lines no daemon's line in a checked document (ADR 0272, its insight of 2026-10-09): the operator types `nox`.
|
||||
// copy with MESH_CATALOG_DIR set to a checkout of the catalogue, its copies of the retired words and the
|
||||
// holders the glossary says no seat's holder: no seat "held by" or "claimed by" a named module, no named module
|
||||
// "holder of" a seat, no module that "holds the seat" (the operator, 2026-11-09). Who holds a seat is read
|
||||
// from the mesh's assignments, and the glossary's head says where.
|
||||
// domains no retired domain heads a section of the glossary (ADR 0641 §5: Placement).
|
||||
// copy with MESH_CATALOG_DIR set to a checkout of the catalog, its copies of the retired words and the
|
||||
// daemons' lines equal this glossary's and this list (skipped, with its reason, when unset).
|
||||
//
|
||||
// Checked documents: 00-META/, 03-DESIGN/ (both layers), AGENTS.md and README.md, always; an issue opened on or after
|
||||
@@ -52,7 +56,7 @@ const (
|
||||
linesFrom = "2026-10-09"
|
||||
)
|
||||
|
||||
var wordsAlways = []string{"00-META/", "03-DESIGN/", "AGENTS.md", "README.md"}
|
||||
var wordsAlways = []string{"00-META/", "03-DESIGN/", ".claude/", "AGENTS.md", "README.md"}
|
||||
|
||||
// daemonLines are the daemons' lines (ADR 0272): each daemon's commands. "alone" is a command that is a line by
|
||||
// itself; "with an argument" one that is a line only when written as one ("the mesh-controller module" is prose;
|
||||
@@ -396,7 +400,7 @@ func checkUnique(retired []retiredWord, heads []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// linesList is the daemons' lines as the catalogue's copy lists them: "<daemon> <command>", with " …" when an
|
||||
// linesList is the daemons' lines as the catalog's copy lists them: "<daemon> <command>", with " …" when an
|
||||
// argument must follow.
|
||||
func linesList() []string {
|
||||
var out []string
|
||||
@@ -479,26 +483,26 @@ func readList(file string) ([]string, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// checkCopy compares the catalogue's copies of the retired words and the daemons' lines with this glossary's. A
|
||||
// checkCopy compares the catalog's copies of the retired words and the daemons' lines with this glossary's. A
|
||||
// difference names what the copy should list, so regenerating it is copying that from the failure.
|
||||
func checkCopy(catalogue string, retired []retiredWord) []string {
|
||||
var out []string
|
||||
copyWords, err := readList(path.Join(catalogue, "retired-words"))
|
||||
if err != nil {
|
||||
return []string{"the catalogue's copy of the retired words cannot be read: " + err.Error()}
|
||||
return []string{"the catalog's copy of the retired words cannot be read: " + err.Error()}
|
||||
}
|
||||
sort.Slice(copyWords, func(i, j int) bool { return strings.ToLower(copyWords[i]) < strings.ToLower(copyWords[j]) })
|
||||
want := toolsList(retired)
|
||||
if strings.Join(copyWords, "\n") != strings.Join(want, "\n") {
|
||||
out = append(out, "the catalogue's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
|
||||
out = append(out, "the catalog's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
|
||||
}
|
||||
copyLines, err := readList(path.Join(catalogue, "retired-lines"))
|
||||
if err != nil {
|
||||
return append(out, "the catalogue's copy of the daemons' lines cannot be read: "+err.Error())
|
||||
return append(out, "the catalog's copy of the daemons' lines cannot be read: "+err.Error())
|
||||
}
|
||||
sort.Strings(copyLines)
|
||||
if strings.Join(copyLines, "\n") != strings.Join(linesList(), "\n") {
|
||||
out = append(out, "the catalogue's retired-lines differs from this check's list: it should list, one per line:\n"+strings.Join(linesList(), "\n"))
|
||||
out = append(out, "the catalog's retired-lines differs from this check's list: it should list, one per line:\n"+strings.Join(linesList(), "\n"))
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -532,7 +536,7 @@ func TestWordsNoDaemonLineWritten(t *testing.T) { expectNone(t, checkLines(rootF
|
||||
func TestWordsCatalogueCopyAgrees(t *testing.T) {
|
||||
catalogue := os.Getenv("MESH_CATALOG_DIR")
|
||||
if catalogue == "" {
|
||||
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalogue's copy of the list was not read")
|
||||
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalog's copy of the list was not read")
|
||||
}
|
||||
retired, _, _ := parseGlossary(readFile(rootFS(), glossaryFile))
|
||||
expectNone(t, checkCopy(catalogue, retired))
|
||||
@@ -691,7 +695,7 @@ func TestAllowanceHoldsAWordBackUntilItsDateAndShrinks(t *testing.T) {
|
||||
expectProblem(t, checkRetired(mapOf(tree("an overlay\n", "")), retired, nil, "2026-10-23"), `AGENTS.md:1: "overlay" is retired`)
|
||||
}
|
||||
|
||||
// H5d: the daemons' lines and the catalogue comparison.
|
||||
// H5d: the daemons' lines and the catalog comparison.
|
||||
|
||||
func TestLinesCatchesADaemonsLineButNotProse(t *testing.T) {
|
||||
fsys := mapOf(map[string]string{
|
||||
@@ -754,3 +758,105 @@ func mapOf(files map[string]string) fs.FS {
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// holders: the glossary defines a word and never says which module holds or claims a seat. Who holds a seat is a fact
|
||||
// of the mesh's assignments, read with one call (the operator, 2026-11-09: "it shouldn't be in the glossary"), and a
|
||||
// copy here drifts. Refused, across line breaks: a seat "held by" or "claimed by" anything but a generic holder or a
|
||||
// seat ("held once for the mesh by `x`", "held today by the forge module"); a named module "holder of" a seat ("the
|
||||
// module `x`, holder of"); a module that "holds the seat" ("it holds the seat", "the `x` module holds it"); and "its
|
||||
// holder, the x". A word for the role ("the module that holds the seat `x`") names no module and passes.
|
||||
|
||||
var (
|
||||
heldByRx = regexp.MustCompile("(?i)\\b(held|claimed)(\\s+once)?(\\s+for\\s+the\\s+mesh)?(\\s+today)?\\s+by\\s+")
|
||||
// What may follow "held by": a holder in general, a seat, or the knowledge base (a seat's word) holding a record.
|
||||
heldByGenericRx = regexp.MustCompile("^(a\\s+module\\b|some\\s+module\\b|its\\s+holder\\b|the\\s+holder\\b|the\\s+identity\\s+seat\\b|the\\s+knowledge\\s+base\\b|one\\s+author\\b|the\\s+`[^`]+`\\s+seat\\b|the\\s+(node\\s+)?seat\\s+`)")
|
||||
holderNamedRx = []*regexp.Regexp{
|
||||
regexp.MustCompile("(?i)(\\bmodule\\s+`?[a-z][a-z0-9-]*`?|`[a-z][a-z0-9-]*`)\\s*,\\s+(the\\s+)?holder\\s+of\\b"),
|
||||
regexp.MustCompile("(?i)\\b(it|which)\\s+holds\\s+(the\\s+)?(node\\s+)?seats?\\b"),
|
||||
regexp.MustCompile("(?i)\\bmodule\\s+holds\\s+(it|the\\s+(node\\s+)?seat)\\b"),
|
||||
regexp.MustCompile("(?i)\\bholder\\s*,\\s+the\\s+[a-z`]"),
|
||||
}
|
||||
)
|
||||
|
||||
func checkHolders(text string) []string {
|
||||
var out []string
|
||||
say := func(i int, m string) {
|
||||
out = append(out, fmt.Sprintf("%s:%d: %q names a seat's holder; the glossary says where it is read instead", glossaryFile, lineOf(text, i), strings.Join(strings.Fields(m), " ")))
|
||||
}
|
||||
for _, loc := range heldByRx.FindAllStringIndex(text, -1) {
|
||||
rest := text[loc[1]:]
|
||||
if heldByGenericRx.MatchString(rest) {
|
||||
continue
|
||||
}
|
||||
end := loc[1] + min(len(rest), 30)
|
||||
say(loc[0], text[loc[0]:end])
|
||||
}
|
||||
for _, rx := range holderNamedRx {
|
||||
for _, loc := range rx.FindAllStringIndex(text, -1) {
|
||||
say(loc[0], text[loc[0]:loc[1]])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestWordsGlossaryNamesNoSeatHolder(t *testing.T) {
|
||||
expectNone(t, checkHolders(readFile(rootFS(), glossaryFile)))
|
||||
}
|
||||
|
||||
func TestHoldersRefusesANamedHolderAndPassesARole(t *testing.T) {
|
||||
for _, bad := range []string{
|
||||
"- **x** — the seat `x`, held once for the mesh by the module `mesh-x`.",
|
||||
"- **x** — the seat `x`, held\n by `mesh-x`.",
|
||||
"- **x** — `go-package-registry` (held today by the forge module).",
|
||||
"- **x** — the seat `x`, claimed by mesh-x.",
|
||||
"- **x** — the module mesh-x, holder of the seat `x`.",
|
||||
"- **x** — the module `mesh-x`, holder of `x`.",
|
||||
"- **mesh-x** — the module that does x: it holds the seat `mesh-x` once.",
|
||||
"- **bus** — The `nats` module holds it.",
|
||||
"- **x** — written by its holder, the assigner.",
|
||||
} {
|
||||
if len(checkHolders(bad)) == 0 {
|
||||
t.Errorf("not refused: %q", bad)
|
||||
}
|
||||
}
|
||||
for _, good := range []string{
|
||||
"- **seat** — a named role, held by a module assignment.",
|
||||
"- **router** — the module that holds the `operator-channel` seat.",
|
||||
"- **bus** — Held by the `mesh-broker` seat.",
|
||||
"- **x** — the seat `x`; its holder is read with `mesh-module-assigner.assignments`.",
|
||||
"- **depends on a seat** — a seat held on its node by some module.",
|
||||
} {
|
||||
if got := checkHolders(good); len(got) != 0 {
|
||||
t.Errorf("refused a role: %v", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// domains: a retired domain is no heading of the glossary. ADR 0641 §1 retired Placement into Core (module
|
||||
// assignment) and Delivery (the wants), and §5 has this test refuse it as a domain heading.
|
||||
|
||||
var retiredDomains = []string{"Placement"}
|
||||
|
||||
func checkRetiredDomains(text string) []string {
|
||||
var out []string
|
||||
for _, d := range retiredDomains {
|
||||
rx := regexp.MustCompile(`(?m)^##\s+` + regexp.QuoteMeta(d) + `\b`)
|
||||
for _, loc := range rx.FindAllStringIndex(text, -1) {
|
||||
out = append(out, fmt.Sprintf("%s:%d: %q is a retired domain (ADR 0641 §1) and heads no section", glossaryFile, lineOf(text, loc[0]), d))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestWordsGlossaryHasNoRetiredDomainHeading(t *testing.T) {
|
||||
expectNone(t, checkRetiredDomains(readFile(rootFS(), glossaryFile)))
|
||||
}
|
||||
|
||||
func TestRetiredDomainsRefusesTheHeadingAndPassesProse(t *testing.T) {
|
||||
if len(checkRetiredDomains("## Placement — what runs on which node\n")) == 0 {
|
||||
t.Error("a Placement heading was not refused")
|
||||
}
|
||||
if got := checkRetiredDomains("## Core\n\nwhich was the Placement domain until ADR 0641\n"); len(got) != 0 {
|
||||
t.Errorf("prose refused: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
+603
-116
File diff suppressed because it is too large
Load Diff
@@ -196,7 +196,7 @@ Three levels, by how much real system a test uses (ADR 0391):
|
||||
| Level | Uses | Budget | Runs |
|
||||
|---|---|---|---|
|
||||
| **Unit** | one piece of logic alone: no database, network, bus, running service or other process | well under a second per test | locally, the tests of what changed, while working; and in the forge's checks |
|
||||
| **Integration** | the real thing beside it: a database, the bus, a catalogue tree | the repository's whole suite in seconds, never over sixty | only in the forge's checks, once per push |
|
||||
| **Integration** | the real thing beside it: a database, the bus, a catalog tree | the repository's whole suite in seconds, never over sixty | only in the forge's checks, once per push |
|
||||
| **End to end** | the whole mesh, or a lab mesh, from the outside | — | only when the operator asks |
|
||||
|
||||
- **Test each behaviour at the lowest level that can prove it.** A defect found higher up gets a test at the
|
||||
@@ -209,7 +209,7 @@ Three levels, by how much real system a test uses (ADR 0391):
|
||||
|
||||
### Tests within a time budget — how it was earned
|
||||
|
||||
On 2026-10-16 a feature's fixing agents ran the catalogue's whole merge check (about ten minutes), a
|
||||
On 2026-10-16 a feature's fixing agents ran the catalog's whole merge check (about ten minutes), a
|
||||
throwaway database and a locally built gate during local development. Each round cost tens of minutes and
|
||||
caught neither real failure; the forge's checks found both.
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
status: canonical
|
||||
updated: 2026-11-11
|
||||
---
|
||||
|
||||
# The principles
|
||||
|
||||
A short, living list. Every question met while building is tested against these principles and answered by the
|
||||
lead in one line; only a question no principle settles goes to the operator or to a decision record. A detail that
|
||||
a principle settles is not written down as a design line or a record. The list changes when the operator says so:
|
||||
add, sharpen or drop a principle in a pull request to this file.
|
||||
|
||||
1. **NATS-native.** Every interaction between modules is an event, or a call over the bus where an answer is
|
||||
needed. A module keeps its data in the bus's own storage, and reads another module's data only by asking it,
|
||||
through its verbs or its tools.
|
||||
2. **The mesh owns the truth.** State lives in the mesh and is written onto the nodes from there. Nothing is changed
|
||||
by hand on a node.
|
||||
3. **Failure is loud.** Nothing reports success when it did nothing, and nothing fails silently. When in doubt, fail.
|
||||
4. **A module is steered by its tools.** Everything a module holds can be read, everything it does can be started
|
||||
by hand, and everything that goes wrong can be recovered, all through its tools.
|
||||
5. **Everything ships the same way.** The mesh's own parts go through the same build, check and delivery as any app.
|
||||
6. **Nothing is exposed publicly.** No data leaves the mesh unasked: no secret in a record, a log or a public
|
||||
repository, no service reachable from outside unless it is meant to be. Inside the mesh, every agent and module
|
||||
is trusted; who may do what is not a question to ask.
|
||||
7. **The simplest thing that works.** Prefer what already exists. No new mechanism unless it is needed, and fewer
|
||||
parts over more.
|
||||
8. **One word per thing**, from the [glossary](glossary.md).
|
||||
|
||||
How it is checked: the one code review of a milestone checks the code against these principles and the design's
|
||||
shape; an answer to a develop question names the principle that settles it.
|
||||
@@ -46,6 +46,18 @@ to-be document's frontmatter goes to `implemented`, and both stand — one descr
|
||||
the other recording what was intended. Deleting the intention loses the reasoning, which is
|
||||
the expensive half.
|
||||
|
||||
## A design is built as it is written
|
||||
|
||||
Research, decisions and the review by architectural domain are where the mesh's choices are made; building makes
|
||||
none (the operator, 2026-11-10). A design is written to be built without a choice of the builder's own, in a main
|
||||
file and one part per milestone that an agent reads whole (playbook [02](02-graduation.md), *The shape of a
|
||||
design*). The prepare, develop and the code review of playbook [07](07-feature-branches.md) read only that design:
|
||||
the prepare says where each design line lands in code and lists what the design does not answer, the operator
|
||||
answers each such design question and the answer is written into the design, and only then does develop start. Every
|
||||
commit cites the design lines it builds, and the review judges each change against its line. How it is checked:
|
||||
playbook 07's *How it is checked* (the build maps, the commits' `Builds:` lines, the design questions' answering hq
|
||||
commits).
|
||||
|
||||
## The playbooks
|
||||
|
||||
A drawn overview of them, the cycle, a feature's steps, the review loop and an issue's life, is
|
||||
@@ -59,7 +71,7 @@ A drawn overview of them, the cycle, a feature's steps, the review loop and an i
|
||||
| [04](04-build-handoff.md) | Work breakdown and handoff | A design is ready to be built: milestones usable on their own, issues per repository, each milestone one feature step |
|
||||
| [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces |
|
||||
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
|
||||
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery |
|
||||
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: a build map from the milestone's part of its design, the design complete before any code, local development, one review against the design lines, one check, one delivery |
|
||||
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one check, one delivery |
|
||||
| [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest |
|
||||
| [10](10-visual-design.md) | Visual design | A page the operator will use is about to be designed or rebuilt: mockups with fixed data, the operator picks, the pick is the reference |
|
||||
|
||||
@@ -39,20 +39,33 @@ An idea arrives in one of two forms, and the first step differs:
|
||||
- **Explain each question with a concrete picture** in the operator's words: what they would see, do or
|
||||
lose under each option. No internal names (a record number, a verb, a word the glossary lacks) without
|
||||
what it is beside it.
|
||||
- **Write the requirements from the answers, before the options.** Each one line, numbered, saying what
|
||||
the result must hold and how it is checked (for example "R5. A change across repositories is
|
||||
delivered whole or not at all; check: a change over three repositories"). The requirements shape the
|
||||
effort: every option is weighed against them, and the operator corrects them before any option is
|
||||
worked out (the operator, 2026-11-09: "coming up with a list of requirements can also be an important
|
||||
part, it actually shapes the research").
|
||||
3. **Take a number.** `knowledge-base.reserve` with `kind: research` and `for`: the effort's title. The number
|
||||
is spent whether or not the effort is ever proposed; one never proposed is withdrawn after a week. Never
|
||||
pick one by hand.
|
||||
4. **Propose it.** `knowledge-base.propose` with `kind: research`, the `number`, a one-line `title`,
|
||||
`status: active`, `date` (the day it began), and `sections`, each a `name` and its markdown `text`:
|
||||
- `00-overview.md`: the idea in the operator's words, what exists, the operator's answers (dated), and the
|
||||
`status: active`, `date` (the day it began), and `sections`, each a `name` and its markdown `text`. A
|
||||
section is a part of the record, never a file: its `name` is a plain title, a reference to another section
|
||||
names that title ("the Direction's test 8"), and code evidence names the repository, the commit and the
|
||||
function, never a path or a line (the operator, 2026-11-02: "don't use file references or local files
|
||||
anymore"; the knowledge base is to be shown as a web page, where a file name links to nothing). Once
|
||||
proposed, the record is the only copy: it is changed by `knowledge-base.get` and `propose`, never kept
|
||||
in a local draft. The first sections:
|
||||
- **Overview**: the idea in the operator's words, what exists, the operator's answers (dated), and the
|
||||
words the effort uses that the glossary lacks, each defined in one line;
|
||||
- `01-questions.md`: the questions to answer, the first ones first;
|
||||
- `02-method.md`: what will be tried and measured, and on what.
|
||||
- **Requirements**: the numbered requirements of step 2, each with its check;
|
||||
- **Questions**: the questions to answer, the first ones first;
|
||||
- **Method**: what will be tried and measured, and on what.
|
||||
|
||||
Name what it touches as `links` of kind `touches` (`"0276"`, `"research 045"`, `"issue 365"`, or a
|
||||
design's path), and what it names for another reason as `cites`.
|
||||
5. **Do the research.** Try, measure and read, smallest question first. Each finding goes into
|
||||
`04-findings.md`: what was tried, what it showed (with numbers), what it means. Each change of the effort
|
||||
the section **Findings**: what was tried, what it showed (with numbers), what it means. Each change of the effort
|
||||
is `knowledge-base.propose` again, whole, with `why`; every version is kept, and `knowledge-base.history`
|
||||
lists them and gives the difference between two. Until issue 844 is delivered, the knowledge base
|
||||
takes a new version of an accepted, active effort only as a progressive insight: give `insight`, saying
|
||||
@@ -69,20 +82,52 @@ An idea arrives in one of two forms, and the first step differs:
|
||||
server, a protocol) is tried in process, on the machine's loopback, in minutes. Lab runs (about 12 to
|
||||
16 minutes fresh) are for the mesh's own code; before that code exists, a lab run tests nothing an
|
||||
in-process test does not, and its lab scenario is written as the build's acceptance test instead.
|
||||
6. **Place it in the domains.** Before the direction, write `03-domains.md`: the domain or domains the idea
|
||||
6. **Place it in the domains.** Before the direction, write the section **Domains**: the domain or domains the idea
|
||||
belongs to (the glossary's sections, ADR 0244); the modules and seats it lands in, each new, changed or
|
||||
retired; what it does to each domain's words, each word added (with its domain and a one-line meaning),
|
||||
changed or retired, and none on a *Not:* line or already meaning something else (the glossary's homonyms);
|
||||
and whether a domain grows or shrinks, a concept moving between domains included. The direction names its
|
||||
decisions in those words. Asked when the idea is set up, too: an option that lands a concept in the wrong
|
||||
domain is offered with that cost named, like one that breaks a rule.
|
||||
7. **Settle the direction with the operator.** When the questions are answered, write `05-direction.md`:
|
||||
the proposed decisions in a few lines each, the options rejected and why, and the build's acceptance
|
||||
tests: what the build must prove before it is done, carried by its milestones (playbook
|
||||
7. **Settle the direction with the operator.** When the questions are answered, write the section **Direction**:
|
||||
the question the effort answers, the options weighed and the one proposed (a second only for a second
|
||||
question of its own; playbook [02](02-graduation.md), step 2), the design choices that follow from it in a
|
||||
line each, and the build's acceptance tests: what the build must prove before it is done, carried by its milestones (playbook
|
||||
[04](04-build-handoff.md)). Nothing else is left open: each question closes by a test, by a decision the
|
||||
effort records with its reason (a small design limit is the lead's), or by the operator. Ask the
|
||||
operator to confirm or change the direction, one point at a time, then propose it once. Then the effort
|
||||
graduates through playbook [02](02-graduation.md).
|
||||
operator to confirm or change the direction, one point at a time. Then review it by architectural domain (below) and
|
||||
propose it once. Then the effort graduates through playbook [02](02-graduation.md).
|
||||
|
||||
## Review by architectural domain
|
||||
|
||||
A direction the operator settled can still be wrong where nobody looked. Before it is proposed, and again
|
||||
before a design is merged (playbook [02](02-graduation.md)), it is reviewed against each **architectural domain**
|
||||
(the operator, 2026-11-02), by reviewers on the larger model, about two architectural domains each, side by side:
|
||||
the four reviewer agents in `.claude/agents/` (`review-security-and-upgrade`, `review-seams-and-tools`,
|
||||
`review-running-and-testing`, `review-health-and-events`), each given the subject by number or path:
|
||||
|
||||
- **security**: who may call each new verb, how its grants are scoped, which credentials go or remain, what
|
||||
can be replaced or forged, and what enters from outside the mesh;
|
||||
- **seams**: where each piece of state lives on the bus, which seat, verb or event joins two parts, and any
|
||||
link that is not NATS-native. Each piece of state is kept in the bus storage form that fits it (a key-value
|
||||
bucket, a stream, an object store); a form the manifest lacks is added to mesh-sdk, and the review names that change
|
||||
and its cost (a mesh-sdk change rebuilds every module) to the operator, never folds it in silently (the
|
||||
operator, 2026-11-02);
|
||||
- **running qualities**: build capacity, speed, storage growth, and what happens while a part, the bus or the
|
||||
control-node is down;
|
||||
- **health**: what each part's health check says, which conditions are raised and cleared, and whether any
|
||||
failure can pass silently;
|
||||
- **events and logs**: what each step says on the bus, and what can be read back later;
|
||||
- **testing**: an acceptance test for every decision, each one checkable and within the test budget, and the
|
||||
failure paths;
|
||||
- **upgrade**: how what runs today moves to the new shape without a delivery breaking, in which order, and
|
||||
how it is rolled back;
|
||||
- **tools**: which tool reads each new piece of state and which one steers each new behaviour.
|
||||
|
||||
A reviewer judges only whether the settled direction is complete and sound in its architectural domains; it never reopens
|
||||
a decision. Each finding is a sentence for the direction or a new acceptance test; a finding that changes a
|
||||
decision goes to the operator. The direction ends with a section *Reviewed by architectural domain*: per architectural domain, what was
|
||||
found and how it was answered, or that nothing was found.
|
||||
|
||||
## Bounds and models
|
||||
|
||||
@@ -90,9 +135,10 @@ An idea arrives in one of two forms, and the first step differs:
|
||||
|---|---|---|
|
||||
| reading and the setup questions (1, 2) | 30 minutes | the session's own, larger |
|
||||
| proposing (3, 4) | 15 minutes | larger |
|
||||
| each experiment or reading task (5) | 30 minutes | smaller, one agent at a time unless the operator allows more |
|
||||
| each experiment or reading task (5) | 30 minutes | smaller, one agent at a time unless the operator allows more: the agent `research-reader` |
|
||||
| placing it in the domains (6) | 15 minutes | larger |
|
||||
| the direction (7) | 30 minutes | larger |
|
||||
| the review by architectural domain | 20 minutes | larger, about two architectural domains per reviewer |
|
||||
|
||||
A step past its bound is said to the operator at once, with what is being done about it. The lead reports
|
||||
as playbook 07 does: every ten minutes and at once when something is found, stops or needs the operator,
|
||||
@@ -125,7 +171,8 @@ for the operator's Approve on a channel that proves who answered.
|
||||
## Closing
|
||||
|
||||
An effort never just stops. It closes through playbook [02](02-graduation.md) as `graduated` or
|
||||
`abandoned`, always with a `became` link to what it turned into. Nothing is deleted — what was rejected,
|
||||
`abandoned`: a graduated effort with a `became` link to what it turned into, an abandoned one with a `cites` link to
|
||||
what replaced it (playbook 02 step 5). Nothing is deleted — what was rejected,
|
||||
and why, is the more expensive half to rediscover.
|
||||
|
||||
## How it is checked
|
||||
@@ -134,7 +181,8 @@ The rules on an effort are the knowledge base's: `propose` refuses one that brea
|
||||
record (the module's table test holds one case per rule), and the rules that judge the whole record set run
|
||||
after every acceptance and raise a condition when one fails. The habits of steps 2, 5, 6 and 7 (options checked
|
||||
against the rules, one version per settled set, the cheapest level, nothing left open) are the lead's, read by
|
||||
the reviewer; of the pre-check, `propose` refuses a link to nothing and a retired word, and the review returns
|
||||
the reviewer; the review by architectural domain is read in the direction's *Reviewed by architectural domain* section (nothing refuses a direction
|
||||
without it yet: the lead and the reviewer hold it); of the pre-check, `propose` refuses a link to nothing and a retired word, and the review returns
|
||||
a record named but not linked, an untracked deferral and a figure not as measured. That this repository holds
|
||||
no effort is the records test's `no-records` check, on every pull request. An active effort with no new version
|
||||
for seven days is the smell of research left behind: its lead closes or continues it.
|
||||
|
||||
@@ -13,7 +13,7 @@ clauses in force for a topic or a record).
|
||||
|
||||
## Graduating research
|
||||
|
||||
The effort's `05-direction.md`, confirmed by the operator (playbook [01](01-research.md), step 7), is what
|
||||
The effort's Direction, confirmed by the operator (playbook [01](01-research.md), step 7), is what
|
||||
graduates. The lead writes the records and the design on the larger model; it is one sitting, bounded at
|
||||
about an hour, and said to the operator if it runs past.
|
||||
|
||||
@@ -21,8 +21,17 @@ about an hour, and said to the operator if it runs past.
|
||||
[`mission.md`](../mission.md), [`context.md`](../context.md) and
|
||||
[`effect.md`](../effect.md). If it conflicts, either the effort is wrong or GENESIS is —
|
||||
say which, in writing, before proceeding.
|
||||
2. **Record the decisions** (below, *Writing a decision record*): one record per choice that could be
|
||||
reversed on its own, not one record for the whole effort.
|
||||
2. **Record the decisions** (below, *Writing a decision record*): one record per question the effort
|
||||
answers, holding that question, the options weighed and the one chosen. Most efforts answer one question
|
||||
and graduate into one record; a second record is written only for a second question of its own, such as
|
||||
how the change can be checked or delivered at all (the operator, 2026-11-06: an effort graduated into
|
||||
eleven records where it answered two questions). The choices made while working out the design that
|
||||
follows (a field, a tool, an event, a bound, the order of steps) are the design's: written in the design
|
||||
with their reasons, under the record they follow from, never as records of their own. A clause of an
|
||||
earlier record the decision changes is a `changes` link from the record, with one line saying in what.
|
||||
How it is checked: the lead names, in the graduation's report to the operator, the question each record
|
||||
answers; an effort graduating into more than two records is said to the operator with why, before the
|
||||
records are proposed.
|
||||
- **Write them together, not one after another.** Reserve every number first, then propose each record
|
||||
as soon as it is written, without waiting for the review of the one before; the reviews run side by
|
||||
side, and the operator's asks for the records that need them arrive together. A record that
|
||||
@@ -49,21 +58,39 @@ about an hour, and said to the operator if it runs past.
|
||||
---
|
||||
```
|
||||
|
||||
The design lists the direction's acceptance tests: what the build proves before it is done.
|
||||
The design lists the direction's acceptance tests: what the build proves before it is done. Before it is
|
||||
merged, the design is reviewed by architectural domain as playbook [01](01-research.md#review-by-architectural-domain) says, and
|
||||
ends with its own *Reviewed by architectural domain* section; a design amended without an effort is reviewed the same way.
|
||||
|
||||
The words of the effort's `03-domains.md` go into the glossary in the same change, each in its domain: a
|
||||
The words of the effort's Domains go into the glossary in the same change, each in its domain: a
|
||||
word added gets its entry, a word whose meaning changed gets its entry rewritten, and a retired word moves to
|
||||
its replacement's *Not:* line. A word that already means something else in another domain is named as a
|
||||
homonym with the qualified form each domain uses.
|
||||
|
||||
4. **Hand it to the work breakdown.** The design goes on through playbook [04](04-build-handoff.md):
|
||||
milestones, issues, and each milestone built as one feature step (playbook [07](07-feature-branches.md)).
|
||||
5. **Close the effort.** `knowledge-base.propose` the effort again, whole, with `status: graduated` and a
|
||||
`became` link to the decision record (`"NNNN"`) and to the design document (its path). An effort that
|
||||
stops without a decision is proposed with `status: abandoned` and a `became` link to what replaced it.
|
||||
5. **Close the effort.** `knowledge-base.close` the effort with `to: graduated`, a `became` link to each
|
||||
decision record (`"NNNN"`) and the design document (its path), and a note of two or three sentences (what it
|
||||
became, what carries it). The verb keeps the served text unchanged and adds a section *Graduated* with the note;
|
||||
the knowledge base's review judges only the closing. Never close by proposing the effort again whole:
|
||||
that is judged as a fresh proposal, so the approved reasoning is re-judged against the decisions that replace it
|
||||
(on 2026-11-10 an effort closed that way met 5, then 11 findings on unchanged text; `close` was approved at once).
|
||||
An effort that stops without a decision is closed `to: abandoned` with a `cites` link to what replaced it: the
|
||||
knowledge base refuses `became` from a research effort to another research effort (its link-fits rule, design
|
||||
55 § 2), so a replacing effort is cited, and `became` stays for the decision and the design an effort turned
|
||||
into.
|
||||
|
||||
## Writing a decision record
|
||||
|
||||
**Before a number: what holds, and the mesh path, reviewed.** Ask the knowledge base what is already decided in
|
||||
the record's area (`knowledge-base.ask`, `holds`), and name in the draft, for every interaction between modules the
|
||||
record decides, the seat and verb, event, provision, state on the bus or manifest field that carries it. Then
|
||||
`review-seams-and-tools` reviews the draft before it is proposed: a clause that has one module name another, read its
|
||||
internals or be special-cased in another part, or puts a module's name into shared code, is a finding (the operator,
|
||||
2026-11-03).
|
||||
How it is checked: the record's References name the seams review's verdict; a record proposed without it is a breach
|
||||
the lead names.
|
||||
|
||||
1. **Take a number.** `knowledge-base.reserve` with `kind: decision` and `for`: the record's title. The
|
||||
number comes from the issue tracker's counter and is spent whether or not the record is accepted; one
|
||||
never proposed is withdrawn after a week. Never pick a number by hand.
|
||||
@@ -123,13 +150,89 @@ by a progressive insight.
|
||||
record `changes` the older one's clause, so a reader who arrives at the older record by a citation sees
|
||||
what changed it since.
|
||||
|
||||
## The shape of a design
|
||||
|
||||
A design is what its builders build from, and nothing else is (playbook [07](07-feature-branches.md)): decision
|
||||
records and the mesh's principles are read when a design is written, amended or reviewed, and never again by the
|
||||
prepare, the developers or the code review. So a design is written to be built without a choice of the builder's
|
||||
own, and in pieces an agent can read whole. On 2026-11-10 one design of more than 400 KB was built in one
|
||||
milestone: its agents read it by searching for pieces, filled what they did not find themselves, and an audit of
|
||||
that day's 40 or so choices by the leads found seven against the records or the design and 29 questions the design
|
||||
never answered, all found only in develop.
|
||||
|
||||
- **A main file and one milestone part per milestone.** The design's main file
|
||||
(`03-DESIGN/01-to-be/NN-<slug>.md`) holds its frontmatter, its purpose, the whole shape (its parts, their seats,
|
||||
events, provisions and state, drawn and in short prose), its acceptance tests and its *Milestones* section. Each
|
||||
milestone not yet delivered has its **milestone part**, `03-DESIGN/01-to-be/NN-milestones/<milestone>.md`, which
|
||||
holds every line that milestone builds and opens with frontmatter naming `layer: to-be`, the design, the
|
||||
milestone and the `decisions:` its lines follow. A line lives in one place: a statement one milestone builds moves
|
||||
from the main file into that part, never copied.
|
||||
- **Bounds.** The main file at most 80 KB, a milestone part at most 60 KB. An agent reads both whole in under
|
||||
40,000 tokens, under a fifth of what it can hold, and has the rest for the code. A milestone whose part would
|
||||
pass 60 KB is two milestones (playbook [04](04-build-handoff.md)), each usable on its own.
|
||||
- **Numbered design lines.** A milestone part's statements are numbered, each one thing to build with a bold
|
||||
lead, like a decision's clauses, and cited as "design NN M §n"; a subtask, a commit and a review finding cite
|
||||
them so. Each line names what its builder needs: the seat, verb or event, the field and its form, the key and
|
||||
who deletes it, the bound, the caller, the tool that reads or steers it, and the fate of each thing that exists
|
||||
today and changes. A line a builder would have to complete is a design question left open.
|
||||
- **A delivered milestone's part stays.** It records what was intended, as a to-be design does once it ships
|
||||
(the overview, *The two design layers*); no agent reads it again, so it costs no builder anything, and what runs is
|
||||
written in the as-is design. The history of how a milestone was cut or why it waited is the issue tracker's and the
|
||||
records', not the design's.
|
||||
- **Design 47's next amendment is written in this shape**: the main file keeps the purpose, the whole shape, the
|
||||
acceptance tests and the milestones, within 80 KB; every milestone gets its part with every line it builds or
|
||||
built, moved from the main file, the one about to be built first; and the narrative of past milestones and their
|
||||
prepare steps leaves the design for the issues it names. Every other design is put in this shape
|
||||
at its next amendment, and a new design is written in it.
|
||||
|
||||
How it is checked: the reviewer of a design's pull request reads `wc -c` of the main file and of each milestone
|
||||
part against the bounds, and that every statement of a milestone part is numbered; a check among hq's Go tests is to
|
||||
be built (the cycle test reads only a design's main file today).
|
||||
|
||||
## Answering a design question
|
||||
|
||||
A design question (playbook [07](07-feature-branches.md), step 0) is first answered by the lead, in one line in the
|
||||
milestone's lead-answers file, tested against the principles (`00-META/principles.md`) and naming the principle
|
||||
that settles it, with no design batch and no review round (playbook 07, step 0, *Who answers a design question*; the
|
||||
operator, 2026-11-11). The preparer, a developer and a reviewer answer none. Only a question no principle settles
|
||||
comes to this section: the operator answers it when its answer changes what the operator sees or how they work,
|
||||
removes data or needs the operator's terminal; every other one `hq-design-writer` answers as a design line of the
|
||||
milestone part with its reason, under the record it follows from, read in item 3's round, and told to the operator in
|
||||
one line. The lead asks the operator's as the instruction on asking says, one question per ask, with what the
|
||||
milestone part says, what it leaves open, where it was met (file:line) and a recommendation. Then:
|
||||
|
||||
1. **Within a record's question** (a field, a bound, an owner, an order of steps, inside what a record decides), the
|
||||
answer is the design's own choice: `hq-design-writer` writes it as a design line of the milestone part, citing
|
||||
the operator's answer and its date and the record it follows from.
|
||||
2. **Outside every record's question**, or **against a record** (the design contradicts a decision in force), the
|
||||
answer is a decision: written first as *Writing a decision record* or *Changing a decision* above says, then as
|
||||
design lines.
|
||||
3. **The answers are reviewed together.** Before develop starts, the four reviewers by architectural domain read the
|
||||
milestone part as amended, in one round (playbook [01](01-research.md#review-by-architectural-domain)); answers
|
||||
met during develop are read by them in one round before the code review.
|
||||
4. **They land on the milestone's branch.** The amendment is committed on hq's branch of the milestone
|
||||
(`feat/<slug>`), so its pull request joins the milestone's delivery group, and the builders read the milestone
|
||||
part at that branch's head.
|
||||
|
||||
How it is checked: every design question in the milestone file names the hq commit that answers it; a lead's answer
|
||||
that names no principle, or a design line added during a milestone with no operator's answer or record behind it,
|
||||
other than the writer's answer to one that changes nothing the operator sees or how they work, removes no data and
|
||||
needs no terminal, is a finding of the review by architectural domain.
|
||||
|
||||
## Amending an existing design
|
||||
|
||||
A design changes only through a decision.
|
||||
A design changes only through a decision. A choice within what a decision decides (a field, a tool, an event,
|
||||
a bound) is the design's own, written in it with its reason; it needs no record of its own. It is made when the
|
||||
design is written or amended, never while it is built: a choice the design leaves to its builder is a design
|
||||
question (above).
|
||||
|
||||
1. Write the decision record (above). If it reverses an earlier one, it `supersedes` it.
|
||||
2. Edit the to-be design document, add the record to its `decisions:` as `"ADR NNNN"`, and set `updated:`
|
||||
to today.
|
||||
to today, keeping the design in the shape above (a line one milestone builds goes into that milestone's part).
|
||||
A set of amendments across several designs (a milestone's findings, a graduation's) is written by the
|
||||
agent `hq-design-writer` (`.claude/agents/`), started with a handover naming the designs, the records and the
|
||||
branch. Review it by architectural domain before it is merged (playbook [01](01-research.md#review-by-architectural-domain)), and
|
||||
rewrite its *Reviewed by architectural domain* section.
|
||||
3. If the amendment came from an issue, give the issue the document path in the issue tracker:
|
||||
`amended-design` on its `issue-tracker.move` to `resolved`, or `issue-tracker.link` with kind
|
||||
`design` before then — never in the report's frontmatter
|
||||
@@ -164,7 +267,8 @@ record (its module's table test holds one case per rule), and the four rules tha
|
||||
changes, an accepted record extending a superseded one) run after every acceptance and raise a condition
|
||||
when one fails. In this repository, the records test checks that a design's `decisions:` entries and every
|
||||
"ADR NNNN" in the text are well formed, and that no record is written here; the index test checks the reading
|
||||
order's six topics, which the knowledge base reads. The three habits of graduating step 2 (side by side, clauses
|
||||
order's six topics, which the knowledge base reads. A design's *Reviewed by architectural domain* section is read by the
|
||||
reviewer of its pull request; nothing refuses a design without it yet. The three habits of graduating step 2 (side by side, clauses
|
||||
from the direction, the pre-check) are the lead's, read by the reviewer; `propose` refuses a link to nothing,
|
||||
a deferral no issue tracks and a retired word, and the review returns a record named but not linked.
|
||||
The rounds are read in `knowledge-base.get` (its reviews, one per version): a question to the operator about a
|
||||
|
||||
@@ -91,7 +91,7 @@ import read, which every imported issue names (`imported.commit` in `issue-track
|
||||
ADR 0281).
|
||||
4. Resolve. `issue-tracker.move` to `resolved`, which the tracker takes only from `fixing`, with `fixed-by`, and, if the root cause was a design gap,
|
||||
run playbook [02](02-graduation.md) and give `amended-design`. **A core issue** — its `located-in`
|
||||
names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build
|
||||
names the controller, the node-engine, the tool runner, the SDK, or the catalog'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);
|
||||
|
||||
@@ -41,6 +41,11 @@ A design becomes **milestones**, and each milestone becomes **issues**:
|
||||
first. A milestone is named by what it lets someone do; how it ships is shaped by the modules its changes move
|
||||
and the nodes that run them (ADR 0401 §6), read from the indexer's fingerprints through the status view's
|
||||
`indexer_modules` and `indexer_candidates`, and from the nodes' haves (`node_haves`).
|
||||
4a. **Give each milestone its part** (playbook [02](02-graduation.md), *The shape of a design*): every line the
|
||||
milestone builds moves from the main file into `NN-milestones/<milestone>.md`, as numbered design lines, each
|
||||
naming what its builder needs. A milestone whose part would pass 60 KB is split here into two milestones, each
|
||||
usable on its own; this is the one place a milestone is ever cut, for what one agent can read, before any work
|
||||
starts. The main file stays within 80 KB. Written by `hq-design-writer` in the same change as the milestones.
|
||||
5. **Ask the operator to confirm the milestones**, in a short table: number, outcome, the acceptance tests it
|
||||
proves, repositories, the parts each repository builds. Change what they change.
|
||||
6. **Open the issues.** For each confirmed milestone, `issue-tracker.open` one issue per repository part, its
|
||||
@@ -50,7 +55,9 @@ A design becomes **milestones**, and each milestone becomes **issues**:
|
||||
7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook
|
||||
[08](08-small-fixes.md) (one hq pull request).
|
||||
8. **Hand each milestone to playbook 07, one at a time.** The milestone is one release of playbook 07; its issues
|
||||
are the release's issues; the next milestone starts when the previous one is delivered. A design waits
|
||||
are the release's issues; the next milestone starts when the previous one is delivered. Playbook 07 builds from
|
||||
the milestone part alone and starts its develop step only once that part answers every design question its
|
||||
prepare finds (playbook 07, *Design complete*); a question is never answered by building around it. A design waits
|
||||
while another design's milestones are being built: one design is built at a time (playbook
|
||||
[07](07-feature-branches.md)), so this step starts only when the design before it is delivered.
|
||||
9. **On completion of the last milestone**, run the "when something ships" section of playbook
|
||||
@@ -72,6 +79,8 @@ Read by the lead and the reviewer today; a check among hq's Go tests is tracked
|
||||
|
||||
- A design in `status: in-progress` has a *Milestones* section, each milestone with its outcome and its
|
||||
issues; an in-progress design without one is the smell of building without a breakdown.
|
||||
- Each milestone of an in-progress design has its part under `NN-milestones/`, within 60 KB, its lines numbered
|
||||
(`ls -l 03-DESIGN/01-to-be/NN-milestones/`); a milestone handed to playbook 07 without one is step 4a skipped.
|
||||
- Every issue a milestone names exists in the tracker (`issue-tracker.get`), and an issue in `fixing` names
|
||||
its milestone in its report.
|
||||
- A milestone whose issues are all `resolved` is delivered (the status view said `delivered` for its source
|
||||
|
||||
@@ -33,11 +33,22 @@ Three questions, answered from the machine:
|
||||
`package-manager`, `seat`. A capability is detected and refused against; it is not something a
|
||||
module can install.
|
||||
|
||||
3. **Write the resources in the order they must happen.** They are applied in the order written
|
||||
3. **Choose each part's form, from the table in [design 20](../../03-DESIGN/01-to-be/20-writing-a-module.md) *What a
|
||||
module is built of*.** Tools are a `bundle` the tool runner loads (`loads`, and every tool in `tools`); a service of
|
||||
the module's own code is a `process` resource; a scheduled job says `schedule`, a one-time step `run-once`, a
|
||||
migration of the module's own state `prepares`; a seat's implementation is a claim and the verbs it serves; an image
|
||||
is an `image` or `upstream` artifact run by a `container`; a program of the operating system is a `package` and its
|
||||
`service` resources. A bundle or a package is in Go or TypeScript, the languages the builder has build modules for.
|
||||
|
||||
4. **Ask for kept data in `requires`, by template** (`state`, `events`, `work-queue`, `log`, `objects`, `ledger`), each
|
||||
entry with its `name`, `storage`, `template` and `options`. The old sections `state`, `logs`, `streams` and
|
||||
`objects` are for the parts the bus module needs before it can answer, and for no new entry anywhere else.
|
||||
|
||||
5. **Write the resources in the order they must happen.** They are applied in the order written
|
||||
and orphans are removed in reverse, so a `network` is written before the containers that join
|
||||
it and removed after them.
|
||||
|
||||
4. **Put every secret in a file, never in `env`.** A declaration travels over the broker in plain
|
||||
6. **Put every secret in a file, never in `env`.** A declaration travels over the broker in plain
|
||||
text: a password in `env` is a password the broker sees. **The mesh delivers parts; a module
|
||||
that needs them combined combines them.**
|
||||
|
||||
@@ -67,21 +78,64 @@ Three questions, answered from the machine:
|
||||
Add `restart-on` naming the env file, or the container keeps the credential it started with
|
||||
through every rotation.
|
||||
|
||||
5. **Pin every image by digest.** A tag moves. The manifest in a repository names artifacts; the
|
||||
manifest the mesh holds names digests, and they are not the same document.
|
||||
7. **Pin every image by digest.** A tag moves. An `upstream` artifact's `from`, and a container's `image` written
|
||||
directly, name `@sha256:`: the builder refuses an upstream reference with a tag alone. The manifest in a repository
|
||||
names artifacts; the manifest the mesh holds names digests, and they are not the same document.
|
||||
|
||||
6. **Decide generate or accept.** A new module's credential is generated. **An adopted one keeps
|
||||
8. **Decide generate or accept.** A new module's credential is generated. **An adopted one keeps
|
||||
the credential it already has** — `secret accept` — because minting a new password for a
|
||||
database that already exists locks the application out of its own data.
|
||||
|
||||
7. **Add a provisioner only if the software cannot read a file.** A proxy that watches a
|
||||
9. **Add a provisioner only if the software cannot read a file.** A proxy that watches a
|
||||
directory needs nothing. PostgreSQL needs `CREATE ROLE`, an object store needs a bucket and a
|
||||
policy, an identity provider needs a realm and a client — those need a small program beside
|
||||
them. It reads what the mesh granted and reconciles; it does not decide anything.
|
||||
|
||||
8. **Prove it in the lab, against the real software.** Not that a container started — that the
|
||||
thing works: the credential authenticates, a wrong one is refused, the containers reach each
|
||||
other, the data survives a restart.
|
||||
10. **Prove it in the lab, against the real software.** Not that a container started — that the
|
||||
thing works: the credential authenticates, a wrong one is refused, the containers reach each
|
||||
other, the data survives a restart.
|
||||
|
||||
## Where the parts of the mesh read a manifest differently
|
||||
|
||||
*Written 2026-11-12 from reading the manifest validator, the builder, the node-engine's vocabulary and the tool runner
|
||||
on their main branches.* The manifest validator accepting a manifest does not yet mean every other part takes it.
|
||||
Until each of these is closed in the part named, write the form in the right column:
|
||||
|
||||
| the manifest validator accepts | but | so write |
|
||||
|---|---|---|
|
||||
| a `bundle` or a `package` in any language word | the builder builds only Go and TypeScript bundles and TypeScript packages, and refuses the rest by name at the build | a bundle in Go or TypeScript |
|
||||
| an `upstream` artifact whose `from` names a tag alone | the builder refuses an upstream reference with no `@sha256:` | the reference with its digest |
|
||||
| a `log` asked for in `requires` by template | the tool runner's log calls reach only a log of the old `logs` section; a template log is reached only by its stream's read and fill calls, so a module cannot append to it through the runtime | a template log only where the module's own bus account publishes it; a log its tools append to through the runtime waits for the tool runner's fix |
|
||||
| any `type` and any field on a resource | the node-engine refuses an unknown type and a field the type does not use, when the module is composed for a machine: in the change's composition step only for the machines the change touches, and for a module assigned nowhere never | each resource's fields as the node-engine's vocabulary has them (mesh-sdk's declaration package) |
|
||||
| `schedule` and `run-once` on a `process` unjudged | the node-engine refuses a process with both, or a scheduled one with `restart-on`, `replaces` or `witness`, or an invalid cron | a process in one mode |
|
||||
|
||||
How it is checked: each row is closed by a change in the part it names, which deletes the row in the same pull
|
||||
request; a reviewer of a manifest reads these rows.
|
||||
|
||||
## A module that calls or holds a seat, or shares code
|
||||
|
||||
*Added 2026-11-04, widened 2026-11-05* (ADR 0625, ADR 0627, ADR 0634, ADR 0635). Where each piece of shared code goes
|
||||
is [design 20](../../03-DESIGN/01-to-be/20-writing-a-module.md) *Where a seat's code goes*: shared code lives with its
|
||||
owner. In short:
|
||||
|
||||
- **Import the seat's contract and, where it has one, its client package**, from the seat's domain's contracts module
|
||||
(`mesh-<domain>-contracts`, or `mesh-contracts` for the Core domain and a domain with none), never the holder module
|
||||
and never a copy of the seat's types in your own code. The list of seats your module needs is read from the bus,
|
||||
never compiled in.
|
||||
- **Logic you would write over a seat that another caller would write too** belongs in that seat's client package,
|
||||
through a change of the contracts module; it never goes into mesh-sdk, and never into the holder.
|
||||
- **Logic and state layouts only your module owns**, where it holds no seat for them, are your module's own package;
|
||||
another repository's module imports it only by a published version, so your manifest declares it as a package
|
||||
artifact.
|
||||
- **Logic your domain's modules share, owned by no one module and carried by no seat**, is your domain's SDK's
|
||||
(`mesh-<domain>-sdk`); only what modules of more than one domain use goes into mesh-sdk.
|
||||
- **Holding a seat** means implementing its contract in your module. A seat you define yourself gets a contracts
|
||||
module of its own (it may share your repository); a role no other module could ever take over is your module's
|
||||
own tools and events, not a seat.
|
||||
|
||||
The review of the pull request checks the home of every new exported name; the checks design 26 lists refuse a stray
|
||||
file in a contracts module, a seat named in mesh-sdk, an import of a contracts module from mesh-sdk, and a client
|
||||
reaching another seat's client.
|
||||
|
||||
## What the first port actually cost
|
||||
|
||||
@@ -99,8 +153,9 @@ time and the scaffolding was not.**
|
||||
|
||||
## Rules
|
||||
|
||||
- **Read the node-engine's log before theorising.** A declaration that was sent and not applied says so
|
||||
there and nowhere else.
|
||||
- **Read the check's verdict, then the node-engine's log, before theorising.** The change's check names every
|
||||
problem the manifest validator and the composition step found; a declaration that was sent and not applied says
|
||||
so in the node-engine's journal (`<node>/node-service-manager.journal`) 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.
|
||||
- **A data directory is never removed by the mesh** (ADR 0030),
|
||||
|
||||
@@ -24,7 +24,7 @@ A small fix (one repository, one issue, about an hour of work) follows
|
||||
- **Review that keeps hardening.** One fix went through seven review rounds in five and a half hours, each
|
||||
asking for more hardening than its issue asked (issue 704, 2026-10-14). A round is for what makes the
|
||||
change wrong; everything else is an issue of its own.
|
||||
- **The first check after the whole branch is written.** A feature's catalogue branch, pushed once after
|
||||
- **The first check after the whole branch is written.** A feature's catalog branch, pushed once after
|
||||
hours of local work, met its first check on 2026-10-16 and failed on 29 problems, TypeScript errors among
|
||||
them, that a unit suite run continuously while working finds at once.
|
||||
- **A green pull request nobody marks.** An approved, green pull request that was not marked ready waited
|
||||
@@ -44,19 +44,38 @@ A small fix (one repository, one issue, about an hour of work) follows
|
||||
on the bus; two reviews read the code, no test ran it under a node's real permissions, and once it was live every
|
||||
node's agent was refused that read and stopped composing (issue 1126). How-we-build's rule "A bus permission is
|
||||
proven under the real permission" (ADR 0560) is what the review now asks for.
|
||||
- **A change built on what was remembered, not on what runs.** In the days up to 2026-11-02 a release took the
|
||||
controller's jobs apart from the records and the survey alone. Jobs nobody had named surfaced only after the
|
||||
delivery, a moved module's wiring and a changed failure label's readers were never read, and every check found one
|
||||
more fault. Each layer had checked the layer before it; none had read the running code. Step 0 item 3a is the answer.
|
||||
- **Implementation that decided.** On 2026-11-10 one milestone was prepared and developed from a design of more
|
||||
than 400 KB, by agents told to start from the knowledge base and to report what was not mesh-shaped. The prepare
|
||||
wrote its own findings and a file of about 40 rulings by its two leads, a third layer of truth between the design
|
||||
and the code; a developer refused an event the design names; and an audit found seven rulings against the records
|
||||
or the design (among them a store kept twice and a copy of another part's data) and 29 questions the design never
|
||||
answered, all found in develop. The design is now the only input to the prepare and to develop, the prepare's only
|
||||
output is where each design line lands and what the design does not answer, and develop starts only when it is all
|
||||
answered (step 0b, *Design complete*). Since 2026-11-11 the lead answers each such question in one line against the
|
||||
principles, and only one no principle settles goes further (step 0, *Who answers a design question*).
|
||||
- **A design that met the code only in develop.** On 2026-11-11, in the develop step of milestone 47/G, the lead
|
||||
answered over 100 questions, most of them a design line meeting the code: a line naming a form the code cannot
|
||||
take, or assuming a part already built that was not. The prepare had read the design alone. It now reads the code
|
||||
each design line lands in as well, and lists every place where the two disagree before any developer starts
|
||||
(step 0, items 3 and 10; the operator, 2026-11-11).
|
||||
- **A module proven only after it reached the machines.** The mail modules were delivered with unit tests
|
||||
only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
|
||||
|
||||
## The shape
|
||||
|
||||
One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**,
|
||||
opened **once**, after the review, and **one check** (ADR 0548). It runs in five steps, and its time is bounded. A milestone has no phases
|
||||
opened **once**, after the review, and **one check** (ADR 0548). It runs in five steps, with the design completed between the first two (step 0b), and its time is bounded. A milestone has no phases
|
||||
(the operator, 2026-10-17): these are its steps, and a report names the step it is in:
|
||||
|
||||
| Step | What happens | Bound |
|
||||
|---|---|---|
|
||||
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
|
||||
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit, the integration tests compiled; nothing is pushed | the milestone's issues, 30 minutes each |
|
||||
| 0. Prepare | from the milestone part and the design's main file, read whole, then the code each design line lands in: where each line lands, what exists there today and what relies on it, as one build map per repository; every design question, the design's silences, listed; and every place where a design line and the code disagree, listed | 15 minutes, longer when what exists is large: code never starts on an incomplete answer |
|
||||
| 0b. Design complete | every design question and every place where design and code disagree is answered: by the lead in one line against the principles, or, where no principle settles it, by the operator or `hq-design-writer` (step 0); each answer is written down (the lead's in the milestone's lead-answers file, the others into the milestone part, playbook 02) and its build map rows added | until the list is empty: develop never starts before |
|
||||
| 1. Develop | one fixing agent per repository builds every design line of its build map locally, each commit citing the lines it builds, the whole unit suite before every commit, the integration tests compiled, and once per head it reports those of the packages it touched and their importers run against an embedded bus (below); nothing is pushed | the milestone's issues, 30 minutes each |
|
||||
| 2. Review | one reviewer reads all of it, the local commits, judging the integration tests by reading them against the code; reports go both ways; at most 3 rounds | 15 minutes |
|
||||
| 3. Check | push the reviewed heads once and open the pull requests together; the checker's one check runs the integration tests; no further review unless the check fails | one check run |
|
||||
| 4. Deliver | merge; the source revision moves, each module is built once and wanted on its nodes, its canary first | until the view says the source revision `delivered` |
|
||||
@@ -93,10 +112,58 @@ small fix ([playbook 08](08-small-fixes.md)) while the milestone waits on a chec
|
||||
rest are fixed right after the milestone's delivery, before the next milestone starts, together on one branch where
|
||||
they share a repository. Either way it stays out of the running milestone's branch and pull request, unless it blocks
|
||||
the running milestone, so the scope above holds. Only an issue in another design's milestones (one design is built at
|
||||
a time), or one that needs a decision or the operator first, waits, and the report says which and why. Each report's line "Opened along the way" says for every
|
||||
a time), or one that needs a decision or the operator first, waits, and the report says which and why. Each detailed report's line "Opened along the way" says for every
|
||||
issue whether it is being fixed in between, after the delivery, or waits, and on what.
|
||||
|
||||
## Step 0 — Prepare (the lead, larger model)
|
||||
## Step 0 — Prepare (mesh-preparer, larger model; the lead runs the process)
|
||||
|
||||
**Agents work from hq's definitions, never from a prompt written by hand.** Every agent the lead starts is one of
|
||||
`.claude/agents/`: `mesh-preparer` for this step, `mesh-developer` for step 1, `mesh-code-reviewer` for step 2,
|
||||
`mesh-watcher` for every wait, and the four reviewers by architectural domain and `research-reader` of playbook 01.
|
||||
The lead writes only the handover: what the agent works on (issue, the design and its milestone part at an hq commit,
|
||||
branch, worktrees, commits) and where it reports; a handover carries no choice about what is built, and one that
|
||||
does is the lead answering a design question outside its lead-answers file (step 0b). A subagent runs in the session that started it, from hq: its way of working comes from its definition and hq's playbooks; what it builds comes from the milestone part and the milestone's lead-answers file and nothing else; one given a coding task works in a worktree under hq's `.work/` and also reads the conventions of the repository it changes, and where those contradict hq, hq wins and the agent says so. How it works, what it reads first and what it judges come from its definition, and so does its model: a
|
||||
`mesh-developer` runs on the smaller model, and the lead starts one on the larger model only for work whose parts meet
|
||||
at an interface several of the milestone's subtasks build at once (the design names it whole; nothing is designed in
|
||||
develop), written with its reason in the milestone file; a lesson about how an agent
|
||||
works goes into its definition by an hq pull request (the operator, 2026-11-03: "please don't write prompts by hand,
|
||||
only the handover prompt"). How it is checked: the lead's report names each agent by its definition; an agent started
|
||||
from a prompt written by hand is a breach the lead names at the next report.
|
||||
|
||||
**The design is the only input; the implementation decides nothing** (the operator, 2026-11-10: implementation
|
||||
follows the decided design and makes no decisions, least of all architectural ones). The milestone's part of its
|
||||
design (playbook [02](02-graduation.md), *The shape of a design*) and the design's main file are what the preparer,
|
||||
the developers and the code reviewer read whole, and build or judge against. Decision records, the mesh's principles
|
||||
and the knowledge base were read when that design was written and reviewed by architectural domain; they are not read
|
||||
again to judge the design while it is built, because a builder who re-judges it builds a second design. The
|
||||
principles answer only what the design leaves open (step 0b), never a line it states. Where each
|
||||
interaction travels (the seat, verb, event, provision, state on the bus, manifest field) is a design line; one the
|
||||
design does not name is a design question, never the preparer's finding with its own alternative. The mesh paths
|
||||
are held where they are decided: the reviewers by architectural domain judge the milestone part before it is built
|
||||
(playbook [02](02-graduation.md), *Answering a design question*).
|
||||
- **The prepare's only output** is one build map per repository (item 4), the list of design questions and the list
|
||||
of places where a design line and the code disagree (item 10). It writes no findings of its own, no recommendations into the build map and no rulings, and no file of
|
||||
the milestone holds a statement of how something should be.
|
||||
- **Who answers a design question, and where design and code disagree** (the operator, 2026-11-11, about 21:48
|
||||
CET). The lead tests each design question and each entry of `Design meets code` (item 10) against the principles
|
||||
(`00-META/principles.md`) and answers it in one line in the milestone's lead-answers file, naming the principle that
|
||||
settles it, without a design batch or a review round. Only a question no principle settles goes further: to the
|
||||
operator when its answer changes what the operator sees or how they work, removes data or needs the operator's
|
||||
terminal; otherwise to `hq-design-writer`, who answers it as a design line from the design and its records, with
|
||||
its reason, read by the four reviewers by architectural domain in one round, and the lead tells the operator each
|
||||
such answer in one line. When unsure whether a question changes what the operator sees or how they work, it goes to
|
||||
the operator. The preparer, a developer and a reviewer answer none, nor does a lead of another session.
|
||||
- **One milestone has one lead.** A session that held part of the work hands its branches and heads to the lead and
|
||||
stops; between sessions travel heads, commits, design questions and the hq commits that answer them, never an
|
||||
instruction on how to build.
|
||||
|
||||
How it is checked: every subtask in a build map and every commit of the milestone cites the design lines it builds
|
||||
("design NN M §n"), and the review finds code that cites none (step 2, item 2a); the milestone's files hold no
|
||||
rulings (`grep -n -i 'ruling' .work/<slug>/*.md` finds none); an answer of the lead's that names no principle, a
|
||||
question answered other than as *Who answers a design question* says, or one sent on that no principle left open, is
|
||||
a breach the lead names at the next report; the milestone file's `Design meets code` names, for each repository a design line lands in, the `origin/main`
|
||||
commit the prepare read, and a developer's question that is a design line meeting code the prepare read is a miss
|
||||
of the prepare the lead names at the next report.
|
||||
|
||||
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches (but see the
|
||||
exception for a new manifest word in phase 3: a release that must merge first has a name of its own). The
|
||||
@@ -108,25 +175,70 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
`main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks. **A repository's own
|
||||
clone stays on `main`** (the operator, 2026-10-17): no work is done in it and no branch is checked out
|
||||
there, so a session that opens in it reads what holds now; fetch and fast-forward it before reading.
|
||||
3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through
|
||||
the knowledge base, or by asking the operator. **Read what is decided before designing** (`knowledge-base.ask`):
|
||||
a question about delivery, checks or builds is most likely answered in design 47 and its decisions already (the operator,
|
||||
2026-10-26: "that is simply our delivery plan and milestones, we've already researched it"); propose new research
|
||||
only for what no record decides. A fixing agent that meets an undecided design question
|
||||
stops, and its run is spent again.
|
||||
4. **Write one context file per repository**, `.work/<slug>/<repo>.context.md`. It is what a fixing agent
|
||||
reads instead of rediscovering the code, and it is reused by every agent and every round:
|
||||
- the issues it builds, in order, each with: the files and functions it changes, the decisions it builds
|
||||
with (quoted, with where each was decided), and its acceptance criteria (item 5), each with the test that
|
||||
proves it;
|
||||
- the repository's map for this feature: the packages and files involved, with one line each on what they
|
||||
hold, and the patterns to copy (file:line);
|
||||
- what not to touch, and what lands beside it in other repositories;
|
||||
3. **Read the milestone part and the design's main file whole, then the code each design line lands in**, before any
|
||||
code is written (the operator, 2026-11-11: the prepare reads the code as well as the design). The design is the
|
||||
only thing the preparer reads to know what is to be built; the code, read at `origin/main` in each repository a
|
||||
line lands in, says whether the line can be built as written. For each design line the preparer reads the files
|
||||
and functions it changes, the contracts, types and packages it uses, and the rules the code holds there (a guard,
|
||||
a check, a test that refuses), and asks three things: can the code take the form the line names; is every part the
|
||||
line assumes built (a verb, a field, a package, an event, a module) there on main; does the line break a rule the
|
||||
code holds. Each no goes onto the list `Design meets code` (item 10). Every question the code or the live mesh
|
||||
raises that the milestone part does not answer goes onto the list of design questions (item 10). Neither is ever
|
||||
answered by the preparer. A fixing agent that meets either stops that part, and its run is spent again: the
|
||||
prepare is where both are found.
|
||||
3a. **Read what exists today, and write it down before any code** (the operator, 2026-11-02: "if i ask to implement
|
||||
feature A, you'd better first check what exists today with regards to feature A"). Research, decisions, the survey
|
||||
and the review each check the layer before them; this is the step that checks the running code. Each repository's
|
||||
build map (item 4) opens with a section `What exists today` that answers three questions, in writing:
|
||||
1. **What exists today for this feature**, read from the code (file:line) and from the live mesh through the mesh
|
||||
MCP server's tools (the seats, the modules on each machine, their state on the bus, their tools' answers), never
|
||||
from memory and never from a record alone. Where the code or the mesh contradicts the milestone part, the answer
|
||||
says so and the contradiction is a design question (item 10).
|
||||
2. **Everything the change touches, replaces or removes, and what relies on each today.** For a part that is removed
|
||||
or moved (a module, a seat, a job of a module, a file, a value's form, a label others read), every effect it has
|
||||
is listed, found mechanically from its code and not recalled: each bus write (bucket key, stream subject, event),
|
||||
each file it writes, each credential or grant it issues or rotates, each plan or record it makes, each process or
|
||||
container it starts, each verb, tool or request it answers, and each timer it runs. Then, for each effect, every
|
||||
reader that relies on it (a search across every repository and the live bus, file:line or address). The search is
|
||||
written down as run (the pattern and where), so the review can run it again.
|
||||
3. **For each item of the second answer, what happens to it after the change** (kept as it is, moved to a named seat,
|
||||
module or verb, replaced by a named part, or dropped and why nothing needs it), **and the test that proves it**: an
|
||||
acceptance criterion (item 5) at its level, or, for a dropped effect, the search showing no reader is left.
|
||||
The fate is the milestone part's: the answer cites the design line that gives it, and an effect or a reader no
|
||||
design line gives a fate is a design question. For every new exported type, field, constant, package or module,
|
||||
the answer names the design line that places it (the milestone part's, or the design section *Where a seat's
|
||||
code goes*): where new code goes is always the design's, or a lead's answer naming its principle (step 0b), never
|
||||
a choice nobody wrote down.
|
||||
**A seat's contract is a design line's form too.** For each seat contract the milestone adds or changes, the
|
||||
answer compares what the contract on main says (its fields, subjects, callers and kinds) with the design lines
|
||||
that name that seat, field by field; every difference is a design question (item 10), answered before develop
|
||||
(2026-11-10: a seat contract and an event's subject that differed from the design surfaced only in develop).
|
||||
Code starts only when the three answers are complete for every repository: no fixing agent starts on a build map
|
||||
without them, and an effect found later is the prepare step missed, written into the answer and its issue before the
|
||||
fix. In the days up to 2026-11-02 the controller was taken apart without this answer: jobs nobody had named (making
|
||||
a provision's credentials for a new consumer, planning a module's next build) surfaced only after the delivery; a
|
||||
module was moved without reading how its pieces were wired; a failure label was changed without reading what relied
|
||||
on it; and each check found one more fault the preparation should have known.
|
||||
4. **Write one build map per repository**, `.work/<slug>/<repo>.map.md`. It is what a fixing agent reads beside the
|
||||
milestone part instead of rediscovering the code, and it is reused by every agent and every round. It says where,
|
||||
never how: each entry is a design line's citation and a place in code, and a sentence in it that says how
|
||||
something should be is a design question put in the wrong place.
|
||||
- first, the section `What exists today` with item 3a's three answers;
|
||||
- one row per design line the repository builds: the line ("design NN M §n"), the files and functions it
|
||||
changes, the subtask that builds it (about 30 minutes, one change, its label), its acceptance criteria (item 5)
|
||||
and the test that proves each;
|
||||
- the order of the subtasks: **a subtask that uses what another adds** (a seat's contract, a type, a verb, an
|
||||
event's form, a package) **starts from that subtask's commit, never beside it**, so the contracts and packages
|
||||
come first and their users after; and **one writer per file**: each file the milestone changes has one subtask's
|
||||
agent, named here;
|
||||
- the patterns to copy (file:line), what not to touch, and what lands beside it in other repositories;
|
||||
- the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside
|
||||
the home (`TMPDIR`), and the unit tests to run (never a database server: tests that need one are
|
||||
integration tests, which the pull request's one check runs).
|
||||
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with
|
||||
file:line, and the lead writes the file from its answers.
|
||||
the home (`TMPDIR`), the unit tests to run (never a database server: tests that need one are integration tests,
|
||||
which the pull request's one check runs), and **the one placement of the siblings** every agent of the
|
||||
milestone uses (its `go.work` `use` lines or `replace` directives), as the running checker places them; no agent
|
||||
writes a placement of its own.
|
||||
When the reading is large, a read-only agent on the smaller model answers the preparer's exact questions with
|
||||
file:line, and the preparer writes the map from its answers.
|
||||
5. **Write the findings into the issues** (the operator, 2026-10-17: the better the content of the issues, the
|
||||
better work the agents do, and the less review is needed). Before any agent starts, each issue carries, with
|
||||
`issue-tracker.note`: every file and test that touches the change, found by search and not from memory
|
||||
@@ -135,12 +247,14 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
(2026-10-19: the router's sync with the controller ended every condition the controller did not list, and the
|
||||
survey, which followed only the new condition's own path, never named it); how the change is checked and delivered end to end (which test proves its done-when, which
|
||||
modules its merge moves, read from the indexer's candidates of its group, and to which machines); and for a
|
||||
group, its merge order. The context file
|
||||
group, its merge order. The build map
|
||||
points to these notes; an issue reread by another session or a later agent then holds what was found.
|
||||
When the reading is large, it starts with a read-only survey agent on the smaller model.
|
||||
**Each issue carries its acceptance criteria, written by the lead** (the operator, 2026-10-18: the larger
|
||||
model writes them, and they guide every test). Each criterion is one sentence, given, when, then, about what
|
||||
the module or the operator observes, never about how the code does it, and names the level of the test that
|
||||
**Each issue carries its acceptance criteria, written on the larger model from the milestone part** (the operator,
|
||||
2026-10-18: the larger model writes them, and they guide every test). Each criterion cites the design's
|
||||
acceptance test or design line it comes from; a case the issue's done-when needs that no design line covers is a
|
||||
design question, never a criterion of the writer's own. Each criterion is one sentence, given, when, then, about
|
||||
what the module or the operator observes, never about how the code does it, and names the level of the test that
|
||||
proves it: unit, integration (a bus, a store or a process started) or end to end (none exist yet, so such a
|
||||
criterion is proven live after the delivery and said so). Together they cover the issue's done-when and the
|
||||
cases its life passes through: on 2026-10-18 mesh-delivery's group life (a group of three, a member's own
|
||||
@@ -157,7 +271,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
the SDK's users (they pin an SDK commit, and a pin to a commit no branch holds is a download that fails later,
|
||||
issue 1148); every program that parses a manifest strictly (search `ParseManifest` across every repository, with
|
||||
the SDK commit each pins), which must take an SDK that knows a new manifest word (2026-10-21: design 60's
|
||||
catalogue milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
|
||||
catalog milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
|
||||
word); the SDK's list of a defined seat's verbs, which must name a verb a module adds (2026-10-21: node-files' two
|
||||
new verbs were refused on the claim); the node-engine's decoder for a new key on the wire; the route by which a
|
||||
repository's first module registers; and a part that needs something the bus or a manifest cannot yet give, which
|
||||
@@ -182,14 +296,67 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
- <the one check that runs it: the group's pull requests, one check each, none before the review>
|
||||
- what the check does not cover: <the parts of the group that meet only live>
|
||||
```
|
||||
A key or stream with no one who deletes it and no time to live is a leak and is settled before work starts.
|
||||
Each line cites the design line that gives its deleter or its time to live. A key or stream with no one who deletes
|
||||
it and no time to live, or with a lifetime no design line gives, is a design question, answered before work starts;
|
||||
the lead writes no lifetime of its own.
|
||||
9. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
|
||||
10. **List the design questions** in the milestone file, under `Design questions`: each with the design line it
|
||||
concerns (or the section where a line is missing), what the line leaves to its builder, where the prepare met it
|
||||
(file:line or the tool's answer), and the preparer's recommendation, said as a recommendation. A design that
|
||||
contradicts a decision in force, a reader or an effect with no fate, a lifetime, a caller, a bound, a field's
|
||||
form, a tool for a new state: each is a question.
|
||||
Then, under `Design meets code`, every place item 3 found where a design line and the code disagree, each with
|
||||
the design line, the code (file:line at the `origin/main` commit read), which of the three it is (a form the code
|
||||
cannot take, a part the line assumes built that is not, a rule in code the line would break), and the preparer's
|
||||
recommendation, said as a recommendation. The two lists are the prepare's last output and step 0b's input.
|
||||
|
||||
## Step 0b — Design complete
|
||||
|
||||
Develop starts only when the milestone's design questions and the entries of `Design meets code` are all answered,
|
||||
each in an hq commit (the operator, 2026-11-10: a question the design does not answer stops that part, and its
|
||||
answer is written down before the code; 2026-11-11: the lead answers it against the principles).
|
||||
|
||||
1. **The lead answers each question against the principles**, in one line in the milestone's lead-answers file
|
||||
(beside the milestone part, cited as "design NN M A-n"), naming the principle that settles it (step 0, *Who answers
|
||||
a design question*). **Before an answer names a field, a key, a bucket, a subject or a function, the lead reads it
|
||||
where it lives** (the code at the join head, or the seat definition) and checks the answer against what is there,
|
||||
not against the question's words (2026-11-12: three answers that day named a structure from memory and needed a
|
||||
correction once a developer read the code). One no principle settles the lead sends on: to the operator, one per ask, as playbook
|
||||
[02](02-graduation.md), *Answering a design question*, says, each ask added to its wait list, when its answer
|
||||
changes what the operator sees or how they work, removes data or needs the operator's terminal; every other one to
|
||||
`hq-design-writer` in its handover. A question the writer finds is the operator's it hands back.
|
||||
2. **An answer the lead does not give is written into the milestone part** by `hq-design-writer` on hq's branch of
|
||||
the milestone, as a design line (the operator's citing the operator and the date, the writer's own giving its
|
||||
reason) (or, outside every record's question, after its decision record), and the four reviewers by architectural
|
||||
domain read the amended part in one round.
|
||||
3. **The preparer adds the new lines to the build maps** (item 4) and the question's entry names the hq commit that
|
||||
answers it.
|
||||
4. **The design is complete** when every entry of both lists names its answering commit. Then step 1 starts, from the milestone
|
||||
part at that commit.
|
||||
|
||||
**A question met later stops only its part.** A developer or the reviewer who meets a question the milestone part
|
||||
does not answer stops the subtasks that need the answer, says the question to the lead with the design line and
|
||||
file:line, and goes on with the subtasks that do not; the lead adds it to the list and answers it, or sends it on,
|
||||
as item 1 says, and the stopped subtasks resume from the hq commit that answers it. Nobody builds around it, guesses it or builds both ways.
|
||||
**A decision accepted while the milestone is built** that changes its part stops the subtasks of the lines it changes
|
||||
until the part is amended (playbook [02](02-graduation.md)) and their build map rows are redone; code written before
|
||||
the design was complete joins the milestone only where each of its commits builds a design line of the complete part,
|
||||
and the review reads it as new code.
|
||||
|
||||
How it is checked: the milestone file's `Design questions` and `Design meets code` all name an answering hq commit
|
||||
older than the first commit of step 1 (`git log` of each repository's branch against hq's); a commit with no design line, or one built on
|
||||
an answer no hq commit holds, is a finding of the review (step 2, item 2a).
|
||||
|
||||
## Step 1 — Develop (one fixing agent per repository, smaller model)
|
||||
|
||||
- **One agent per repository, kept for the whole feature.** It reads its context file once and builds the
|
||||
issues one after another, committing each locally with a message naming the issue ("Keep the assignments
|
||||
on the bus (issue 652)"). It is not replaced per issue: the lead continues the same agent (by its id) with
|
||||
- **One agent per repository, kept for the whole feature.** It reads its build map whole, and of the design the lines
|
||||
its rows cite and the main-file sections those lines name (never the whole milestone part at once: on 2026-11-11
|
||||
four developers ran out of context reading design 47's 580 KB before their second subtask), and builds the subtasks one after another in the build map's order, committing each
|
||||
locally with a message naming the issue and, in its body, the design lines it builds ("Keep the assignments on
|
||||
the bus (issue 652)", then "Builds: design 47 G §12, §13"). It builds what each line says, and nothing a line does
|
||||
not say: no type, field, state, verb, event, bound, caller or tool of its own. Where it thinks a design line wrong
|
||||
by the mesh's principles, it still builds it as written and says so in its report, for the lead to bring to the
|
||||
operator; it never builds otherwise. It is not replaced per issue: the lead continues the same agent (by its id) with
|
||||
the next instruction, so its context is paid for once.
|
||||
- **Unit tests only, each well under a second** (how-we-build §5, ADR 0391). **The whole unit suite runs before
|
||||
every commit** (the operator, 2026-10-17), not only the tests of what changed: it takes under a second, and a
|
||||
@@ -201,30 +368,54 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
acceptance criteria**: one test or more per criterion at the level it names, each test's comment citing
|
||||
the criterion it proves by its label (`// 1118/AC2`), and no criterion left without its test; an integration criterion is a test
|
||||
that compiles and is run by the pull request's one check. The integration tests (those that start a store, a bus
|
||||
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check, nowhere
|
||||
else.
|
||||
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check. **Once per head it
|
||||
reports, not per commit, a developer also runs the integration tests of every package the change touches, and of
|
||||
every package of the same module that imports one, against an embedded bus** (`go test -tags integration -count=1`
|
||||
on those packages). Where the importers are many, it runs instead the tests of them that exercise the change
|
||||
(`-run '<names>'`) and names that choice in its report. It is never a whole module's or level's run, so it stays
|
||||
within the operator's rule of 2026-10-18 (step 3). On 2026-11-10 a kept call broke an integration test of a fix group whose
|
||||
developers had run only the untagged suite, and the review's first round found it, not the developers. Before the
|
||||
group's push the tests that exercise the change have run so at the reviewed heads (step 3), nothing else. **In mesh-sdk the whole
|
||||
untagged `./...` runs at every commit, whatever package the change is in**: a test there holds its copy of the seat
|
||||
list and fails on a change elsewhere. How it is checked: the developer's report names those packages and their
|
||||
results, and the reviewer refuses a head reported without them.
|
||||
- **Nothing is pushed and no pull request is opened in this step, and there is no try** (ADR 0548; the operator,
|
||||
2026-10-26: "every release should only run 1 check or 1 try"). When every issue is committed, the lead brings each
|
||||
feature branch up to the current trunk (fetch by name, then merge) and the review reads the local commits. Never call
|
||||
`mesh-controller.try` or `mesh-delivery-checker.checker_try`; one is run only when the operator asks for it. A reviewer
|
||||
`mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`; one is run only when the operator asks for it. A reviewer
|
||||
or a session that wants the code on the forge may push a branch that has no pull request: it runs nothing. Never land
|
||||
on a red check: read the result, then land in a separate step.
|
||||
- **The fixing agent's brief** (the lead gives it with the context file): name your issue in every step and
|
||||
- **The fixing agent's brief** (the lead gives it with the build map): name your issue in every step and
|
||||
message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh;
|
||||
never push, open, merge or close a pull request; never call a try; never write to a store's database by hand, a schema change
|
||||
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
|
||||
move another issue; **leave the integration tests to the check** (no store or bus started by hand): compile them
|
||||
with `go vet -tags integration ./...`. A single integration test may be run locally against an embedded bus when
|
||||
it is the fastest way to find a cause, said in the review report and the pull request. After a failed check it
|
||||
is a numbered migration and the consolidated schema; Go only unless the milestone part says otherwise; never
|
||||
move another issue; **leave a module's whole integration level to the check** (no store or bus started by hand):
|
||||
compile it with `go vet -tags integration ./...`, and once per head you report run the touched packages' and their importers' integration
|
||||
tests against an embedded bus, or by name where the importers are many (above). **Judge a manifest and run the catalog's checks against what is live
|
||||
now:** the seat list the seat `mesh-catalog`'s verb `seat-definitions` answers, read in the same turn and read again
|
||||
when you report a head, with the
|
||||
definitions the change's heads alter laid over it for the change's case (ADR 0636 §4), and the catalog at its trunk,
|
||||
fetched fresh, beside it; never a captured copy, a test fixture or a list kept from an earlier run (2026-11-10: a
|
||||
stale seat list made eight manifests refused locally that the running validator accepts, and stale copies hid two
|
||||
failures). **Start a subtask only with room to finish it**: a subtask is sized so that its reading and its work
|
||||
fit well inside a third of an agent's context; an agent with less than that left commits what is whole (tests
|
||||
green) and stops between subtasks, never inside one (2026-11-10: developers that stopped at their bound inside a
|
||||
subtask left half-built work for the next to read again). Its last report names its heads and the next subtask's
|
||||
label; a fresh agent continues from the build map and the commits' design lines, and no file of the milestone
|
||||
carries a handover of findings or choices. A single integration test may be run locally against an embedded bus when
|
||||
it is the fastest way to find a cause, said in the review report and the pull request; before the group's push the
|
||||
tests that exercise the change are run this way, by name (step 3). After a failed check it
|
||||
usually pays to run that repository's whole integration level locally (`go test -tags integration -count=1 ./...` in each
|
||||
module), by the lead or by the fixing agent on the lead's word, before the one more push: a verdict names only the first failed
|
||||
test (issue 1921), and on 2026-10-23 R4g's faults surfaced one per run until a local run listed them all in a minute (a
|
||||
guideline, the operator). Report back in under 150 words: commits, tests added and that they failed first, the
|
||||
local results, anything you stopped on.
|
||||
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead
|
||||
settles it, updates the context file, and continues the agent.
|
||||
- **Two repositories that depend on each other** keep their order in the context files: the reader before the
|
||||
writer, the controller's new manifest word before the catalogue manifest that uses it.
|
||||
- **An agent that finds the code differs from its build map stops that subtask and reports** (file:line). A fact the
|
||||
map got wrong (a file moved, a function named otherwise) the preparer corrects in the map, and the agent goes on;
|
||||
anything that needs a choice is a design question (step 0b, *A question met later*), never settled by the lead.
|
||||
- **Two repositories that depend on each other** keep their order in the build maps: the reader before the
|
||||
writer, the contract before its users, the new manifest word before the catalog manifest that uses it. Two agents
|
||||
never build against an interface neither has committed: the user's subtask starts from the commit that adds it.
|
||||
- **Several agents in one repository** (only for a large repository with independent areas): each works on a
|
||||
local branch of its own from the feature branch's head and lands by fast-forward under a lock
|
||||
(`flock <feature worktree>.lock git -C <feature worktree> merge --ff-only <its branch>`), after its unit
|
||||
@@ -241,15 +432,22 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
repository is judged on it until it moves, so the operator's Approve will merge nothing. Say it then, not at the
|
||||
merge (issue 1726: on 2026-10-24 the gate dropped two merges it heard before it held its seat, and a
|
||||
reviewed, approved pull request in each of the two repositories was refused at its Approve).
|
||||
2. **One reviewer reads every repository's local commits** together, against the context files and the
|
||||
decisions. **It is one agent, kept for the whole release** like the fixing agents: the lead continues it by
|
||||
2. **One reviewer reads every repository's local commits** together, against the milestone part, the design's
|
||||
main file and the build maps. **It is one agent, kept for the whole release** like the fixing agents: the lead continues it by
|
||||
its id for every later round, for the extra cycle after a failed check (step 3) and for its review record, so it
|
||||
reads the decisions, the issues, the context files and the commits once (issue 1042). A fresh reviewer per
|
||||
reads the milestone part, the issues, the build maps and the commits once (issue 1042). A fresh reviewer per
|
||||
round or per mark pays for all of that again. Its report is per repository: each finding as file:line, the failure it causes, and the fix.
|
||||
It says which round it is in ("round 1 of at most 3").
|
||||
2a. **Every change is judged against its design line.** Each commit cites the design lines it builds; the reviewer
|
||||
reads each line against the diff that cites it: the code does what the line says, all of it, and nothing it does
|
||||
not say. Code no design line asks for (a new exported type, field, state, verb, event, bound, caller or tool, a
|
||||
fate for what exists) is a finding: a choice the design does not make, taken out or held until the operator's
|
||||
answer is in the design (step 0b). A design line in a build map with no commit, or a commit citing an answer no hq
|
||||
commit holds, is a finding. The reviewer does not re-judge the design: a line it believes wrong by a decision or a
|
||||
principle is a note to the lead for the operator, never a change asked of the code.
|
||||
3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix
|
||||
with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
|
||||
4. **Every new integration test is read against the code it drives.** Fixing agents only compile it, and no run precedes the review (step 1), so the reviewer judges it by reading; the check that follows shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. A review that finds a fault only by reading is the cheaper run. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
|
||||
4. **Every new integration test is read against the code it drives.** Fixing agents run only the touched packages' integration tests and their importers' (step 1), and no whole module's run precedes the review, so the reviewer judges each by reading too: a passing local run and the check that follows show whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. A review that finds a fault only by reading is the cheaper run. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
|
||||
5. **The reviewer reads the answers and the new commits**, and approves or reports again (step 7 says what a round
|
||||
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
|
||||
6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
|
||||
@@ -263,10 +461,19 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
work-queue retention; three review rounds passed it, and only its try showed that every seat's work stream has
|
||||
that retention too, so dead asks across the mesh would no longer have been delivered again (issue 1178, fixed
|
||||
before its pull request by a mark only a declared module work queue carries).
|
||||
**A test fixture writes state in the form its real writer writes it** (for example a resolved config value with its
|
||||
source, as the controller stores it); a fixture that agrees with the mistake proves the mistake. How it is checked:
|
||||
the review asks it of every new fixture, and says which writer it compared with.
|
||||
6a. **The reviewer reads the milestone file's three answers** (step 0, item 8) against the change: it lists every bucket
|
||||
key and stream the diff creates or writes (search the diff for the bucket and stream names) and finds each in the
|
||||
lifetime answer with a deleter and event, or a time to live; it reads the switch-over answer and checks it was done as
|
||||
written or that the operator's choice is recorded; it reads the integration-level answer against the check the pull requests will ask. A missing answer, or a key or stream the diff creates that the answer omits, is a change asked.
|
||||
6b. **The reviewer holds the change to each build map's `What exists today`** (step 0, item 3a). It refuses to
|
||||
review a repository whose build map lacks the section or leaves one of its three answers open: that is the
|
||||
prepare step undone, sent back to the lead, and not a round. For every part the diff removes, moves or changes the
|
||||
form of, it runs the recorded search again on the reviewed tree and finds each effect and each reader in the second
|
||||
answer; for each, it reads in the diff that the third answer's fate happened and that its test exists. An effect or a
|
||||
reader the search finds and the answer omits, or a fate the diff does not carry out, makes the change wrong.
|
||||
7. **A finding outside the issues' done-when and their acceptance criteria is never a change asked, in any round.** More hardening, a
|
||||
further case, a nicer form: the reviewer opens it with `issue-tracker.open` and names the number in its
|
||||
report, and the round does not wait for it. What a round asks is only what makes the change wrong against
|
||||
@@ -285,6 +492,18 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
its shape, fixes every hit in one change and lists each file and line in the report. How it is checked: a
|
||||
report to the second round naming a kind and one place only, followed by a third round on the same kind, is
|
||||
this rule skipped.
|
||||
10. **The reviewer asks two questions of every change and every design, and answers both in its report** (the
|
||||
operator, 2026-10-28: "design everything NATS-native"; "repositories are fully mesh agnostic"). The first:
|
||||
*where does each piece of state live, and how does each seam (an integration, data, a part talking to
|
||||
another) travel?* Anything that is not bus state, a stream or a seat's verb on the bus is a finding. The second:
|
||||
*does a repository hold anything about this mesh's setup, such as node names, node-specific placement or
|
||||
settings?* That is a finding: repositories are mesh-agnostic. On 2026-10-28 a placement design kept placement in
|
||||
repository files with node names, because the accepted record it followed said so and nobody checked it against
|
||||
these two principles. A finding in code the design does not place makes the change wrong (item 2a); where the
|
||||
design line itself puts the state or the seam there, the finding is the design's: the reviewer names the line and
|
||||
the record behind it, the lead brings it to the operator as a design question, and the code waits for the answer
|
||||
in the design. How it is checked: the review report has a line for each question with its answer, and the lead reads both before the
|
||||
push; a report without them is not an approval.
|
||||
|
||||
## Step 3 — Check (no review)
|
||||
|
||||
@@ -295,11 +514,24 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
processing power"). The integration tests run once, in the checker's merge check on the merge with the trunk's head, at the unit
|
||||
and the integration level, through the build module of the repository's language and by that language's convention
|
||||
(ADR 0445, ADR 0529); the check waits on the language's check stream until a build node has a free slot
|
||||
(ADR 0525 §1). **No try:** never call `mesh-controller.try` or `mesh-delivery-checker.checker_try`, except when the
|
||||
operator asks for one. While developing, no `go test -tags integration` run of a whole level, and no store or bus started
|
||||
(ADR 0525 §1). **No try:** never call `mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`, except when the
|
||||
operator asks for one. While developing, no `go test -tags integration` run of a whole module or level, and no store or bus started
|
||||
by hand on the machine (the operator, 2026-10-18).
|
||||
**A group is one check.** Before the push, every member pins the group's own mesh-sdk head exactly (the checker refuses an
|
||||
older commit of the same branch), then all members are pushed and opened together, so the group's check runs once. A head the review replaces
|
||||
**Before a group's single push, the integration tests that exercise the change have run locally against an
|
||||
embedded bus**, at the reviewed heads: the touched packages' and their importers' tests that each developer ran for
|
||||
the head it reported (step 1), or those picked by name (`go test -tags integration -count=1 -run '<names>'`) where
|
||||
the importers are many; never a whole module's or level's integration run, and not only a compile with `go vet`; a
|
||||
change in how a node composes or applies always has such a test (2026-10-28: a compiled-only group failed in the one
|
||||
check on what a local run shows). How it is checked: the pull request's description names those tests and their
|
||||
results.
|
||||
**While a group is being checked, nothing else is merged or pushed into a repository that is one of its members:**
|
||||
a moved main asks the group's check again. How it is checked: the lead's wait list names the group's members, and
|
||||
`mesh-delivery.show` shows one check per head.
|
||||
**A group is one check.** Before the push, every member pins the group's own package heads (mesh-sdk, and any other
|
||||
package a member publishes) so it builds with `GOWORK=off`; the check itself builds against the group's heads whatever a
|
||||
member's go.mod names (ADR 0573 §2, §4), so an older pin is not refused there, but the trunk builds after the merge read
|
||||
the go.mod as written. Two members that import each other are pinned in order, each to the other's commit before its own
|
||||
pin. Then all members are pushed and opened together, so the group's check runs once. A head the review replaces
|
||||
counts for nothing against a running check; say so, and ask no other until the new head is reviewed.
|
||||
**Before a session trusts an agent's "pushed", it reads the forge branch's head** (`git ls-remote origin refs/heads/<branch>`) and compares it with the commit reviewed; an agent may commit on a detached HEAD and report a push that never happened (2026-10-26: the first check of a reviewed fix failed "not our ref"). The pull request is opened only on that read head. Pull requests in two or
|
||||
more repositories that share the branch name are one delivery group (ADR 0239 §2).
|
||||
@@ -326,21 +558,33 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
ask named them): mesh-delivery takes a pull request's issues from the words `issue <n>` and `issues <n>` in its
|
||||
description (the plural yields its first number only), so write `issue 1608, issue 1646`, and name any other
|
||||
issue in words, never by `issue <n>` or `issues <n>`.
|
||||
**A catalogue pull request that removes a module, or a subject a module names, is also judged by the
|
||||
controller's unit tests against the catalogue's branch**, because those tests read the catalogue and no check
|
||||
of the catalogue runs them: on 2026-10-20 mesh-catalog #381 removed the old delivery module, and every
|
||||
controller check failed afterwards on a test of the catalogue's subjects until issue 1406 removed the name
|
||||
mesh-issues still listened to. A controller's check does not find it, since a check reads the catalogue's main
|
||||
(also in a delivery group that holds the catalogue's pull request); the lead runs mesh-controller's unit
|
||||
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalogue's branch, as mesh-controller's
|
||||
**A catalog pull request that removes a module, or a subject a module names, is also judged by the
|
||||
controller's unit tests against the catalog's branch**, because those tests read the catalog and no check
|
||||
of the catalog runs them: on 2026-10-20 mesh-catalog #381 removed the old delivery module, and every
|
||||
controller check failed afterwards on a test of the catalog's subjects until issue 1406 removed the name
|
||||
mesh-issues still listened to. A controller's check does not find it, since a check reads the catalog's main
|
||||
(also in a delivery group that holds the catalog's pull request); the lead runs mesh-controller's unit
|
||||
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalog's branch, as mesh-controller's
|
||||
`internal/beside` says.
|
||||
2. **The checker's merge check runs the integration tests once, as the gate** (its one status, `mesh/check`). No
|
||||
second review: step 2 reviewed this tree.
|
||||
**Before the push, every member's suites run as the running checker runs them**: each sibling repository placed
|
||||
beside it by main's beside rules, the node-engine at the commit the machines run, and the running manifest validator
|
||||
over every manifest the change touches, given the live seat list and the catalog's fresh trunk (step 1's brief). On 2026-11-02 release sdk1 failed four checks one fault at a time (test
|
||||
globals left set, a sibling placed otherwise, the checker's manifest archive, a composition step the old checker could not
|
||||
build), each of which a run as the checker places things would have shown before the first push. How it is
|
||||
checked: the pull request's description names the placement, the node-engine commit and the manifest validator run used
|
||||
locally; a check that fails on a placement the local run did not use is named by the lead at the next report.
|
||||
2. **The checker's merge check runs the integration tests once, as the gate** (its one status, `mesh/check`).
|
||||
**A merge with no verdict is unchecked, never passed.** Until the pipeline runner replaces the checker, the
|
||||
checker drops a check still running when its pull request merges (met twice on 2026-11-10): the lead reads the
|
||||
check's verdict (`mesh-delivery.show`) after every merge, and a merged change whose check has none is reported to
|
||||
the operator as merged unchecked, with what its local runs covered, never as passed. How it is checked: a report
|
||||
that says a merged change passed is read against its verdict in `mesh-delivery.show`. No second review: step 2
|
||||
reviewed this tree.
|
||||
3. **A check that fails** goes back to the same reviewer, continued by its id, with the failure, for **one more cycle** of step 2 with
|
||||
the same fixing agents; then push **once** again (the one more push of ADR 0548). The check is **read, never re-asked to see if it passes**: a
|
||||
check asked again without a new, reviewed head spends the build capacity for nothing. **The failure is read from the check's whole log**, never
|
||||
reasoned from its summary: `mesh-delivery.show` names the check's build id, and `mesh-controller.builds` with
|
||||
`log` set to that id prints everything the builder said. A check's summary picks only some failing lines;
|
||||
reasoned from its summary: `mesh-delivery-checker.checker_pull` names every failure the verdict holds, one line each, and the full report
|
||||
of the check. A check's summary picks only some failing lines;
|
||||
on 2026-10-17 it showed a store test's `FAIL` without its cause, and a fix was first guessed from the code.
|
||||
**A failure the check hides or loses** (a verdict naming only the first failed test, a log not kept) is a fault of the
|
||||
checking tool: it is fixed there, the same turn (instruction fix-a-bug-met-twice), not asked around.
|
||||
@@ -352,6 +596,13 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
which tests run at which level is the build tag's (ADR 0445 §2), and hq's checks are Go tests like any repository's
|
||||
(ADR 0527). The prepare step looks for this: a change to what the checker's build module selects (its recipe, a build
|
||||
tag convention) is cut ahead of the milestone.
|
||||
**The same holds for every part that judges a change**: the checker, the indexer and its manifest validator, the grants part and
|
||||
the build modules. A release that changes one of them is judged by the version that runs, so whatever the running
|
||||
version cannot judge is delivered first as a change of its own, or left out and added back once the new part runs.
|
||||
On 2026-11-02 release sdk1 delivered the build module's relative replaces first, and took the checker's new
|
||||
composition call out of the release, adding it back in the next pull request once the new node-engines answered it. How it is checked: the prepare step's
|
||||
milestone file lists every judging part the release changes and how each is delivered first or left out; the review
|
||||
refuses a group that holds one without that line.
|
||||
4. **The milestone's acceptance is proven by its check.** A milestone that adds or changes a module
|
||||
carries an integration test that starts the module whole, its process against the real bus and store, and
|
||||
shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
|
||||
@@ -379,16 +630,31 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
moved to include it): one Approve, one delivery. The old Approve ask closes itself on the new head and the new
|
||||
head is asked once its checks pass. How it is checked: a follow-up that sits in a second pull request while the
|
||||
first still waits shows in `gitea.gitea_list_pull_requests` as two open pull requests of one lead.
|
||||
6. **A member pins mesh-sdk only at a commit that is a pull request's head**, merged or open (2026-10-28: the check
|
||||
refused a pin to a merge commit on main as "on no pull request", though its tree equalled the head's). How it is
|
||||
checked: the check's pin step; a refusal "on no pull request" is this rule skipped.
|
||||
7. **Two groups that pin the same module's mesh-sdk conflict in `go.mod` and `go.sum`.** The group that merges
|
||||
second merges main in and re-pins once, to a pull request head that holds both changes, after the first group
|
||||
is merged and not before (a re-pin before makes the first group conflict). How it is checked: the check's merge
|
||||
step; a group that conflicts after the first one merged is this rule skipped.
|
||||
8. **A group that adds a seat, a verb or a manifest field in mesh-sdk and uses it in a module's manifest in the
|
||||
same group cannot pass its check** until the running assigner pins a mesh-sdk that defines it, because the
|
||||
check composes with the running assigner's mesh-sdk (issue 1585). The assigner's pin ships first, as its own
|
||||
release. How it is checked: the check's composition step; a composition refusing the new word in such a group
|
||||
is this rule skipped.
|
||||
9. **A new head on an open pull request needs its `Reviewed-by` line at that head before the Approve is asked,
|
||||
even for a pin-only commit.** How it is checked: the Approve's ask names the review record;
|
||||
an ask whose line names another commit is this rule skipped.
|
||||
|
||||
**One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads
|
||||
**One exception: a catalog change that uses a new manifest word.** The catalog's merge check reads
|
||||
manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first;
|
||||
then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way.
|
||||
then `mesh-delivery.recheck` the catalog's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way.
|
||||
**A new key the node-engine checks is such a word too** (issue 1051): the controller judges every machine with the
|
||||
node-engine's own validator, vendored at the commit `go.mod`'s `replace` pins, so a key added to the node-engine is
|
||||
refused by the gate until the controller's pin moves to the delivered node-engine (`go mod vendor`); the feature is
|
||||
then three releases, the node-engine's, the controller's, the catalogue's. **Every release that must merge before
|
||||
the catalogue's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
|
||||
feature's name it joins the catalogue's delivery group, and a group merges only whole, so neither merges.
|
||||
then three releases, the node-engine's, the controller's, the catalog's. **Every release that must merge before
|
||||
the catalog's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
|
||||
feature's name it joins the catalog's delivery group, and a group merges only whole, so neither merges.
|
||||
The exception ends when milestone 3 of design 47 is delivered (issue 977, ADR 0402, ADR 0403): the group's composed
|
||||
check, judged by the group's controller, is then each member's gate, and the feature is one group.
|
||||
|
||||
@@ -406,7 +672,10 @@ the source revision equals its fingerprint and every have its want). After each
|
||||
the view's wants page or its tools: `assigner_wants` and `assigner_want` (a want not yet at the newest fingerprint
|
||||
names, in `newest` and `rule`, the rule that holds it back, and its words say why), and `node_haves` (the node
|
||||
agent's haves: fingerprint, composition digest, phase and health). The delivery is done when the view says
|
||||
`delivered` for its source revision; then the lead resolves the feature's issues with `fixed-by` naming its pull
|
||||
`delivered` for its source revision; **"on the machines" from the delivery tools is not yet "delivered"**: the lead
|
||||
also reads what the nodes run (the view's haves per node) and the indexer's record of the module (its newest
|
||||
fingerprint) before saying a change is delivered. How it is checked: the report names each node's build.
|
||||
Then the lead resolves the feature's issues with `fixed-by` naming its pull
|
||||
requests, and removes what is left: no `feat/<slug>` branch and no `.work/<slug>` worktree survive.
|
||||
|
||||
**What a merge moves is read before the operator is told.** A change to mesh-sdk rebuilds every module that takes
|
||||
@@ -466,8 +735,9 @@ Nothing here is a work-around for a delivery that can run normally: while the bu
|
||||
|
||||
| Who | Model | Why |
|
||||
|---|---|---|
|
||||
| The lead (step 0 decisions, the context files, following and reporting) | larger | judgement |
|
||||
| Read-only questions about the code for the context files | smaller | reading, answered with file:line |
|
||||
| The lead (who works on what, asking the operator the design questions, following and reporting; it answers no design question) | larger | judgement of the process |
|
||||
| The preparer (the build maps and the design questions, from the milestone part alone) | larger | reading the design and the code together |
|
||||
| Read-only questions about the code for the build maps | smaller | reading, answered with file:line |
|
||||
| Fixing agents (step 1, and the fixing in steps 2 and 3) | smaller | building to a written context |
|
||||
| The reviewer (step 2, the extra cycle in step 3 and the review record; one agent, continued by its id) | larger | judgement, once per round |
|
||||
| Decision records | larger | judgement |
|
||||
@@ -511,61 +781,51 @@ Approve, a Retry or a Release waits for them, it reads `operator-channel.asks` a
|
||||
on 2026-10-20 an ask was named to the operator that the router did not hold. A merge the gate passed but whose
|
||||
ask is not on the router is the fault of the Approve's wait in the table above.
|
||||
|
||||
**Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery moves (the view says modules `moving`), and at once when something lands, stops or
|
||||
needs the operator,** the lead reports, in one of the two layouts below (the operator, 2026-10-16: an overview
|
||||
of the issues, often). Times are local and absolute, from the clock (`date`), never guessed; every number has its
|
||||
unit and its bound or baseline; an issue is named by its number and a few words, never by its number alone.
|
||||
**The cadence is the instruction section "report-cadence"** (the operator, 2026-10-28; it replaces the full layout every
|
||||
five minutes of "how-work-is-done"). Times are local and absolute, from the clock (`date`), never guessed; every number
|
||||
has its unit and its bound or baseline; an issue is named by its number and a few words, never by its number alone.
|
||||
|
||||
**The full layout** (in the session) is the whole design as a tree (the operator, 2026-10-17: always the full
|
||||
overview): every milestone of the design with its state, and under the running milestone its repositories and
|
||||
their issues, each issue under the one it waits for, marked ✓ landed or delivered, ▶ fixing, ⏸ waiting, ✗ stopped:
|
||||
- **Every five minutes** (ten until 2026-10-26, five since), every two minutes while a delivery moves (the view says
|
||||
modules `moving`), and at once when something lands, stops or needs the operator, the lead sends the **short
|
||||
report**: the tree exactly as `knowledge-base.progress` with `of: "design <nn>"` and `detail: minimal` answers it,
|
||||
unedited, with the verb's read-at time, then at most three lines: what changed, what needs the operator, what is next.
|
||||
Once the verb takes `since`, the short report asks `since: 5m`.
|
||||
- **About every thirty minutes**, and whenever something stops, the lead sends the **detailed report**: the same verb
|
||||
with `detail: detailed` (or `since: 60m`), then the full layout below.
|
||||
- **Every report ends by scheduling the next** (a timer or a watcher set ahead), so that agents' incoming reports
|
||||
never replace it.
|
||||
- **An answer to the operator between reports is short and has no tree.**
|
||||
|
||||
**The tree is the verb's answer and is never drawn or corrected by hand.** `knowledge-base.progress` answers the
|
||||
design's milestones, issues, pull requests and held groups in one call, each with when it last moved (issue 1947).
|
||||
What it cannot read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it
|
||||
does not correct the tree by hand.
|
||||
|
||||
**The full layout** (the detailed report) is the verb's detailed tree followed by these lines:
|
||||
|
||||
```
|
||||
Feature <slug> · milestone <n> of <m> · step <name>, <minutes used> of <bound> min
|
||||
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
|
||||
|
||||
Design <nn> · <its title>
|
||||
├── ✓ milestone 1 <its outcome, a few words> delivered 12:58
|
||||
├── ▶ milestone 2 <its outcome, a few words>
|
||||
│ ├── mesh-controller (agent: fixing, 2 of 3 landed)
|
||||
│ │ ├── ✓ 652 assignments on the bus landed 14:02
|
||||
│ │ ├── ▶ 736 placement words leave the layers fixing since 13:51, 23 of 30 min
|
||||
│ │ │ └── ⏸ 751 config keys named (mesh-catalog) waits for 736
|
||||
│ │ └── ✓ 654 a file names its template landed 13:20
|
||||
│ └── mesh-catalog (agent: waiting)
|
||||
│ └── ⏸ 751 (above, under 736)
|
||||
└── ⏸ milestone 3 <its outcome, a few words> <its issues>
|
||||
|
||||
Opened along the way: <each issue opened during the feature, its number and a few words, and when it is fixed: in between, after the delivery, or waits on what> — or "none"
|
||||
Past its bound: <the wait, how long, what is being done> — or "none"
|
||||
Needs you: <one line per question, with my recommendation> — or "nothing"
|
||||
Allowance: week <n> % (<+d> since the last report), session <n> %
|
||||
Worth it: <what in the last stretch served the goal, and what did not>
|
||||
Next: <the next actions up to the delivery, in order>
|
||||
```
|
||||
|
||||
**The tree is read, not assembled.** `knowledge-base.progress` with `of: "design <nn>"` answers the tree's
|
||||
milestones, issues, pull requests and held groups in one call, each with when it last moved: `detail: minimal` for the
|
||||
five-minute report, `detailed` about every thirty minutes and whenever something stops (issue 1947). What it cannot
|
||||
read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it does not
|
||||
correct the tree by hand. How it is checked: a report's tree that disagrees with the verb's answer of the same minute
|
||||
is a tree assembled by hand.
|
||||
How it is checked: the operator sees a short report at least every five minutes and a detailed one at least every
|
||||
thirty; a report's tree that differs from the verb's answer of the same minute is a tree assembled by hand, a report
|
||||
without the verb's read-at time is the same, and a report that does not end by scheduling the next is a breach the
|
||||
lead reports.
|
||||
|
||||
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
|
||||
|
||||
```
|
||||
<slug> milestone <n>/<m> · <step> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %
|
||||
```
|
||||
|
||||
A report says what changed since the last one first. In the session every report is the full layout, the whole
|
||||
tree of the milestone's issues included, also when only time passed: the operator reads the state from it without
|
||||
asking. The short layout is for a channel.
|
||||
On a channel such as Telegram the same short report is sent, the tree as a code block.
|
||||
|
||||
## The context of the lead's own session
|
||||
|
||||
At the end of each step, and whenever the session's context grows large or spread over unrelated work, the
|
||||
lead prepares a `/clear` as the mesh's instruction "prepare before clear" says: a save point with the
|
||||
feature's step, its branches and heads, the context files' paths, the agents' ids, the wait list and the
|
||||
next action; then the prompt the operator pastes. The context files make the feature resumable after a clear.
|
||||
feature's step, its branches and heads, the build maps' paths, the design questions still open, the agents' ids,
|
||||
the wait list and the next action; then the prompt the operator pastes. The milestone part, the build maps and the
|
||||
commits' design lines make the feature resumable after a clear.
|
||||
|
||||
## The design's state is written back
|
||||
|
||||
@@ -600,7 +860,19 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
|
||||
- A feature's issues show their commits on the feature branch (`git log --grep 'issue <n>'`), and no pull
|
||||
request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in
|
||||
step 1, is the failure this playbook prevents.
|
||||
- A context file exists per touched repository under `.work/<slug>/` while the feature runs.
|
||||
- A build map exists per touched repository under `.work/<slug>/` while the feature runs, and every row cites a
|
||||
design line of the milestone part.
|
||||
- Every commit of the milestone names in its body the design lines it builds (`git log --format=%B | grep 'Builds:'`),
|
||||
and the milestone file's `Design questions` each name the hq commit that answers it, older than step 1's first
|
||||
commit.
|
||||
- The milestone's part is within 60 KB and the design's main file within 80 KB (`wc -c`); a milestone developed from
|
||||
a larger part is playbook 04's step 4a skipped.
|
||||
- Every build map opens with `What exists today` holding step 0 item 3a's three answers, each search written as run
|
||||
(`grep -n -A3 'What exists today' .work/<slug>/*.map.md`), and its time (the file's commit or the lead's note) is
|
||||
older than the repository's first commit of the feature; the review report says it ran the searches again (step 2,
|
||||
item 6b). A fixing agent started on a file without the section, or an effect of a removed or moved part met only
|
||||
after the delivery, is the prepare step skipped, named in the next detailed report and written into the playbook's
|
||||
lessons here.
|
||||
- No issue of a running milestone is sized over an hour in its prepare note; an issue whose fixing agent passes
|
||||
twice its bound was not split.
|
||||
- Every issue of a running milestone has a note from the prepare step naming its files and tests (file:line), how
|
||||
@@ -645,7 +917,7 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
|
||||
- **One release, one check** (ADR 0548): `gitea.gitea_get_pull_request` shows each pull request created after its
|
||||
review record's time, and `mesh-delivery.show` shows one check per head and, for a group, one head per member; a pull
|
||||
request that existed before its review approved it, a second head with no failed check before it, or a check of a
|
||||
head asked more than once, is this rule skipped. A call of `mesh-controller.try` or `checker_try` that the operator
|
||||
head asked more than once, is this rule skipped. A call of `mesh-controller.try`, `mesh-delivery-checker.try` or `checker_try` that the operator
|
||||
did not ask for shows in the checker's asks and is the same breach.
|
||||
- A milestone that adds or changes a module whose pull request has no integration test that starts the module
|
||||
whole is the failure of the check step's fourth item.
|
||||
|
||||
@@ -33,12 +33,31 @@ processing power"). Everything before the push is local and fast, and no try is
|
||||
## Steps
|
||||
|
||||
1. **Read the issue and the code it names** (the session's own model, or one read-only agent on the smaller
|
||||
model answering exact questions with file:line). If a design question is open, it is settled before any
|
||||
code is written: on the issue, through the knowledge base (`knowledge-base.ask` first: what is decided is read before anything is designed), or by the operator. A fix that turns out to
|
||||
need a second repository, or more than about an hour, stops here and becomes a feature; one that needs a
|
||||
decision stays a small fix. **The session's own model (the larger) then writes the issue's acceptance
|
||||
model answering exact questions with file:line). **Read what exists in the fix's area today, briefly, before any
|
||||
code** (playbook [07](07-feature-branches.md) step 0 item 3a, at a small fix's size): what the code and the live mesh
|
||||
(through the mesh MCP server's tools) do there now, and for each value, label, file or job the fix changes or removes,
|
||||
every reader that relies on it, found by a search across the repositories (written down as run) and not from memory,
|
||||
with what happens to each after the fix. It is a few lines in the issue's note, under `What exists today`, and the
|
||||
review runs the search again; a reader it finds that the note omits makes the fix wrong. **A fix builds what a
|
||||
design says, and decides nothing** (the operator, 2026-11-10; playbook [07](07-feature-branches.md), step 0): its
|
||||
input is the issue and the design that covers the area (the design whose `code:` names the repository, or the
|
||||
issue's design link), read in the parts the fix touches and in the design section that places code, *Where a
|
||||
seat's code goes*. The note names, for everything the fix adds or changes (an exported type, field, constant,
|
||||
package or module, a state, a verb, an event, a bound, a caller), the design line that gives it. A fix that only
|
||||
makes the code do what its issue and its design already say needs nothing more. Anything no design line gives is
|
||||
a **design question**, answered as playbook [07](07-feature-branches.md), step 0, *Who answers a design question*,
|
||||
says, before any code builds it: the session's lead answers it in one line against the principles
|
||||
(`00-META/principles.md`), written in the issue's note, naming the principle that settles it; one no principle
|
||||
settles goes to the operator, one question with its recommendation, when its answer changes what the operator sees
|
||||
or how they work, removes data or needs their terminal, and otherwise to `hq-design-writer` as a design line
|
||||
(playbook [02](02-graduation.md), *Answering a design question*), read by the four reviewers by architectural
|
||||
domain in one round and told to the operator in one line. A handover to the developer quotes the design lines and never
|
||||
decides a placement itself (2026-11-04: a class placed in mesh-sdk by a lead's handover, against that section,
|
||||
passed review and its check). A fix that turns out to need a second repository, or more than about an hour,
|
||||
stops here and becomes a feature; one that needs a design question answered stays a small fix. **The session's own model (the larger) then writes the issue's acceptance
|
||||
criteria** as playbook [07](07-feature-branches.md) step 0 item 5 says: each one sentence, given, when, then,
|
||||
naming the level of the test that proves it, as an `issue-tracker.note` headed `Acceptance criteria`. A
|
||||
naming the level of the test that proves it and the design line or issue done-when it comes from, as an
|
||||
`issue-tracker.note` headed `Acceptance criteria`. A
|
||||
playbook or design edit has none: its check is `go test ./...` at hq's root, which runs the four checks (ADR 0527).
|
||||
2. **Move the issue to `fixing`** with `by` as who@node.
|
||||
3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`, never in
|
||||
@@ -51,19 +70,27 @@ processing power"). Everything before the push is local and fast, and no try is
|
||||
caught only by the review); read `git show --stat` before pushing.
|
||||
The tests are written from the acceptance criteria, at least one per criterion at its level, each citing the
|
||||
criterion it proves by its label (`<issue>/AC<n>`, playbook 07 step 0 item 5). Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
|
||||
`go test ./...` at the root). The integration tests are compiled (`go vet -tags integration ./...`) and not run:
|
||||
nothing is pushed, no pull request is opened and no try (`mesh-controller.try`, `mesh-delivery-checker.checker_try`) is called, unless the operator asks for
|
||||
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause.
|
||||
`go test ./...` at the root). The integration tests are compiled whole (`go vet -tags integration ./...`), and once
|
||||
per head reported those of the packages the fix touches and of their importers in the module run against an
|
||||
embedded bus, or the tests of them that exercise the fix by name where the importers are many, named in the report;
|
||||
never a whole module's or level's run (playbook 07 step 1). Nothing is pushed, no pull request is opened and no try (`mesh-controller.try`, `mesh-delivery-checker.try`, `mesh-delivery-checker.checker_try`) is called, unless the operator asks for
|
||||
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause;
|
||||
before the push, the integration tests that exercise the change have run that way at the reviewed head
|
||||
(playbook 07 step 3), and the pull request names them and their results. A test fixture writes state in the form its real
|
||||
writer writes it; the review asks it.
|
||||
4. **One review of the local commit** (a reviewer that did not write it, larger
|
||||
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it reads each acceptance criterion
|
||||
against its test, as playbook 07 step 2 item 6; a finding outside the issue's done-when and its acceptance
|
||||
criteria is opened as an issue of its own, never asked as a change; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says). The reviewer's findings go back to the same agent that wrote the fix, continued by
|
||||
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it judges each change against the design line its note names, as playbook 07 step 2 item 2a, and code no
|
||||
design line gives is a finding; it reads each acceptance criterion
|
||||
against its test, as playbook 07 step 2 item 6; it runs the `What exists today` search again, as playbook 07 step 2 item 6b; a finding outside the issue's done-when and its acceptance
|
||||
criteria is opened as an issue of its own, never asked as a change; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says; it answers playbook 07 step 2 item 10's two questions, on state and seams and on mesh-agnostic repositories, in its report). The reviewer's findings go back to the same agent that wrote the fix, continued by
|
||||
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
|
||||
operator's Approve of its merge is its review, and the review record says so.
|
||||
5. **Push the reviewed head once and open the pull request, only after the review approves.** The trunk is what runs and nothing holds merged work back
|
||||
(ADR 0401), so a fix waits for no other change's delivery. No pull request, draft or not, exists before the review
|
||||
approves the head, and no fix round is pushed to an open pull request: every push to an open pull request asks a new
|
||||
check. The checker's merge check runs the unit and the integration level once, as the gate (playbook [07](07-feature-branches.md) step 3); the pushed head is read from the forge first, as playbook 07 step 3 item 1 says.
|
||||
check. The checker's merge check runs the unit and the integration level once, as the gate (playbook [07](07-feature-branches.md) step 3); the pushed head is read from the forge first, as playbook 07 step 3 item 1 says. A merge whose check has no verdict
|
||||
(the checker drops a check still running at the merge) is reported to the operator as merged unchecked, never as
|
||||
passed (playbook 07 step 3 item 2).
|
||||
A failed check is read from its whole log (never re-asked to see if it passes), then the repository's whole integration level
|
||||
is run locally (`go test -tags integration -count=1 ./...` in each module) so every failure shows at once, where a verdict
|
||||
names only the first (issue 1921); the failures go back to the same agent, fixed in one change, the reviewer reads only the change
|
||||
@@ -87,7 +114,8 @@ processing power"). Everything before the push is local and fast, and no try is
|
||||
7. **Follow its delivery to done**: a watcher waits on the status view's events (`mesh-delivery-view.moving`,
|
||||
`.settled`, `.stuck` and `.delivered`) and reads the view's `assigner_wants` and `node_haves` after each wake, as
|
||||
playbook [07](07-feature-branches.md) step 4 says; the delivery is done when the view says `delivered` for its
|
||||
source revision. Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
|
||||
source revision, and the haves per node and the indexer's record of the module name the build (playbook 07 step 4;
|
||||
"on the machines" alone is not "delivered"). Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
|
||||
`located-in` and `fixed-by` the same hour.
|
||||
|
||||
A fix that stops before its delivery moves its issue back to `open` with a note saying how far it got
|
||||
@@ -137,11 +165,16 @@ reading itself and uses one subagent only for the review.
|
||||
- A pull request merged by hand in a temporary mode names the verdict of its one check in its description
|
||||
on the forge (`gitea.gitea_get_pull_request`); one without is the step skipped.
|
||||
- One fix, one check (ADR 0548): a pull request that existed before its review approved the head, or a head whose check was
|
||||
asked more than once with no new reviewed head, is steps 4 and 5 skipped; so is a `mesh-controller.try` the operator did not ask for.
|
||||
asked more than once with no new reviewed head, is steps 4 and 5 skipped; so is a `mesh-controller.try` or `mesh-delivery-checker.try` the operator did not ask for.
|
||||
- Its issue moves to `resolved` within the hour the view says its source revision `delivered`.
|
||||
- A pull request with no review record at its head (the Approve's ask says so first), a fix to a module's
|
||||
running pushed with no integration test of the module whole, or a review report
|
||||
asking a change outside the issue's done-when and acceptance criteria, is the failure of steps 4 to 6.
|
||||
- A fixed code issue's note names the design line for each thing the fix adds, and its commit names the same lines
|
||||
(`Builds:`); a fix adding what no design line gives, with no answered design question behind it, is step 1 skipped.
|
||||
- A fixed code issue shows a note with `What exists today` (`issue-tracker.get`) older than its first commit, its
|
||||
search written as run; one without is step 1 skipped, and a reader of a changed value met only after the delivery
|
||||
is that note left short.
|
||||
- A fixed code issue shows a note headed `Acceptance criteria` (`issue-tracker.get`) older than its first
|
||||
commit, and each criterion's label is cited by a test in the pushed tree
|
||||
(`git grep -n '<issue>/AC<n>'`); one without is step 1 skipped.
|
||||
|
||||
@@ -198,7 +198,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<!-- step columns -->
|
||||
<g class="mute">
|
||||
<text x="190" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">0 · Prepare</text>
|
||||
<text x="190" y="34" text-anchor="middle" class="mute">15 min</text>
|
||||
<text x="190" y="34" text-anchor="middle" class="mute">15 min · then design complete</text>
|
||||
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop</text>
|
||||
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · local only</text>
|
||||
<text x="630" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">2 · Review</text>
|
||||
@@ -221,8 +221,8 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
</g>
|
||||
<!-- lead -->
|
||||
<rect x="110" y="56" width="160" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="190" y="78" text-anchor="middle" class="t">Survey · criteria</text>
|
||||
<text x="190" y="96" text-anchor="middle" class="mute">context file per repo</text>
|
||||
<text x="190" y="78" text-anchor="middle" class="t">Build map · questions</text>
|
||||
<text x="190" y="96" text-anchor="middle" class="mute">from the milestone part alone</text>
|
||||
<rect x="340" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="410" y="78" text-anchor="middle" class="t">Up to trunk</text>
|
||||
<text x="410" y="96" text-anchor="middle" class="mute">each branch, local</text>
|
||||
@@ -234,15 +234,15 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<text x="1000" y="96" text-anchor="middle" class="mute">resolve the issues</text>
|
||||
<!-- fixing -->
|
||||
<rect x="300" y="128" width="220" height="52" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
|
||||
<text x="410" y="150" text-anchor="middle" class="t">Build each issue, commit</text>
|
||||
<text x="410" y="168" text-anchor="middle" class="mute">unit suite before every commit</text>
|
||||
<text x="410" y="150" text-anchor="middle" class="t">Build each design line, commit</text>
|
||||
<text x="410" y="168" text-anchor="middle" class="mute">Builds: line · unit suite each commit</text>
|
||||
<rect x="560" y="128" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
|
||||
<text x="630" y="150" text-anchor="middle" class="t">Fix findings</text>
|
||||
<text x="630" y="168" text-anchor="middle" class="mute">the same agents</text>
|
||||
<!-- reviewer -->
|
||||
<rect x="560" y="200" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="630" y="222" text-anchor="middle" class="t">Read all repos</text>
|
||||
<text x="630" y="240" text-anchor="middle" class="mute">reads tests · every criterion</text>
|
||||
<text x="630" y="240" text-anchor="middle" class="mute">each change against its line</text>
|
||||
<!-- mesh -->
|
||||
<rect x="340" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="410" y="294" text-anchor="middle" class="t">no try</text>
|
||||
@@ -254,11 +254,17 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<text x="1000" y="294" text-anchor="middle" class="t">Merge · wants</text>
|
||||
<text x="1000" y="312" text-anchor="middle" class="mute">backups first, canary first</text>
|
||||
<!-- operator -->
|
||||
<rect x="110" y="344" width="160" height="52" rx="8" fill="var(--card)" stroke="var(--op)" stroke-width="2.5"/>
|
||||
<text x="190" y="366" text-anchor="middle" class="t">Answers each question</text>
|
||||
<text x="190" y="384" text-anchor="middle" class="mute">written into the design</text>
|
||||
<rect x="860" y="344" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--op)" stroke-width="2.5"/>
|
||||
<text x="930" y="366" text-anchor="middle" class="t">Approve</text>
|
||||
<text x="930" y="384" text-anchor="middle" class="mute">once, for the group</text>
|
||||
<!-- edges -->
|
||||
<path class="edge" d="M190,108 C190,140 260,154 298,154" marker-end="url(#ar2)"/>
|
||||
<path class="edge dash" d="M180,108 V342" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M270,370 C290,370 280,170 298,166" marker-end="url(#ar2)"/>
|
||||
<text x="186" y="232" class="mute">questions</text>
|
||||
<text x="276" y="262" class="mute">answered</text>
|
||||
<path class="edge" d="M410,128 V110" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
|
||||
<path class="edge dash" d="M610,182 V198" marker-end="url(#ar2)"/>
|
||||
@@ -275,10 +281,14 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<div class="box">
|
||||
<h3>Step 0 · what the lead writes before any agent starts</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<li>Every open question settled: on the issue, in the knowledge base, or by the operator.</li>
|
||||
<li><span class="tag new">new</span>The milestone part of the design is the only input: the prepare maps each design line onto code (a build map per repository, each row citing its line) and lists every design question, what the design does not answer. It writes no findings or rulings of its own.</li>
|
||||
<li><span class="tag new">new</span>The prepare reads the design whole, then the code each design line lands in, and lists under Design meets code every place where they disagree: a form the code cannot take, a part the line assumes built that is not, a rule in code the line would break. The lead answers each in one line against the principles before develop starts.</li>
|
||||
<li><span class="tag new">new</span>Design complete: the lead answers each question in one line against the principles, naming the principle; only one no principle settles goes to the operator (when it changes what they see or how they work, removes data or needs their terminal) or else to the design writer as a design line, read by the reviewers by architectural domain. Develop starts only when every answer is in an hq commit.</li>
|
||||
<li><span class="tag new">new</span>What exists today, read from the code and the live mesh, never from memory: every effect of a part removed or moved (bus writes, files, credentials, plans, processes, calls answered), every reader relying on each, what happens to each after the change and its test. It opens each build map; no code before it is complete, and the review refuses work without it.</li>
|
||||
<li><span class="tag new">new</span>Subtasks ordered so a user starts from the commit that adds its contract; one writer per file; one placement of the siblings for every agent.</li>
|
||||
<li>Into each issue: every file and test it touches (file:line), how it is checked and delivered, a group's merge order.</li>
|
||||
<li><span class="tag new">new</span>Every other reader and writer of each value the change touches, each with a criterion.</li>
|
||||
<li>Acceptance criteria: given / when / then, each naming its test level.</li>
|
||||
<li>Acceptance criteria: given / when / then, each naming its test level and the design line it comes from.</li>
|
||||
<li>Every part the change reaches is an issue of the milestone; their order is the assigner's rules', never a cut.</li>
|
||||
<li>Fixtures copied from what the other side really writes, never from memory.</li>
|
||||
<li>An issue sized over an hour is split before work starts.</li>
|
||||
@@ -287,19 +297,22 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<div class="box">
|
||||
<h3>Step 1 · the fixing agent's brief</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<li>One commit per issue, naming it; tests cite their criterion (<code>// 1217/AC3</code>).</li>
|
||||
<li><span class="tag new">new</span>Build each design line as written and nothing it does not say; every commit names its lines (<code>Builds: design NN M §n</code>).</li>
|
||||
<li><span class="tag new">new</span>A question the design does not answer stops that subtask and goes to the lead for the operator; never guessed or built around.</li>
|
||||
<li>One commit per subtask, naming its issue; tests cite their criterion (<code>// 1217/AC3</code>).</li>
|
||||
<li><span class="tag new">new</span>A test fails first on what the code does. A compile error is no failure: take out the change's effect and see an assertion fail.</li>
|
||||
<li>The whole unit suite before every commit; each test well under a second.</li>
|
||||
<li>Integration tests are compiled only: never run on the machine.</li>
|
||||
<li><span class="tag new">new</span>Integration tests compiled whole; once per head reported, those of the touched packages and their importers run against an embedded bus (by name where the importers are many); never a whole module's run.</li>
|
||||
<li>Never push, open or merge; never move an issue; never ssh.</li>
|
||||
<li>Code that differs from the context file: stop and report, file:line.</li>
|
||||
<li>Code that differs from the build map: stop that subtask and report, file:line.</li>
|
||||
<li><span class="tag new">new</span>Start a subtask only with room to finish it; stop between subtasks, never inside one.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ TESTS ============ -->
|
||||
<h2 id="tests">When tests run</h2>
|
||||
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) are compiled there and never run: the checker's one merge check, asked by the one push after the review, runs them as the gate. There is no try (ADR 0548).</p>
|
||||
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) are compiled there whole, and once per head reported only those of the touched packages and their importers run there, against an embedded bus, never a whole module's or level's: the checker's one merge check, asked by the one push after the review, runs them as the gate. There is no try (ADR 0548).</p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 170" role="img" aria-label="Test levels over a feature's timeline">
|
||||
<defs><marker id="ar3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
|
||||
@@ -311,10 +324,10 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<circle cx="160" cy="44" r="6"/><circle cx="220" cy="44" r="6"/><circle cx="290" cy="44" r="6"/><circle cx="350" cy="44" r="6"/><circle cx="420" cy="44" r="6"/><circle cx="600" cy="44" r="6"/><circle cx="680" cy="44" r="6"/>
|
||||
</g>
|
||||
<text x="830" y="42" class="mute">whole suite before every commit</text><text x="830" y="56" class="mute">each test under a second</text>
|
||||
<text x="10" y="104" class="t">Integration</text><text x="10" y="120" class="mute">never on the machine</text>
|
||||
<text x="10" y="104" class="t">Integration</text><text x="10" y="120" class="mute">touched packages only</text>
|
||||
<g>
|
||||
<rect x="120" y="96" width="340" height="16" rx="8" fill="var(--mute)" opacity=".18"/>
|
||||
<text x="290" y="108" text-anchor="middle" class="mute">compiled only: go vet -tags integration</text>
|
||||
<text x="290" y="108" text-anchor="middle" class="mute">compiled whole; touched packages run</text>
|
||||
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">push once</text>
|
||||
<rect x="920" y="90" width="120" height="28" rx="6" fill="var(--mesh)"/><text x="980" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">merge check</text>
|
||||
</g>
|
||||
@@ -353,8 +366,10 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<h3 style="margin-top:12px">What every round reads</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<li>Every criterion against its test: a test at its level that would fail without the change.</li>
|
||||
<li>Every new integration test traced through the code by reading it: no run exists before the check.</li>
|
||||
<li>Every new integration test traced through the code by reading it: a local run of the touched packages shows only that it passed.</li>
|
||||
<li>That each integration test enters by the path the live trigger takes.</li>
|
||||
<li>Where each piece of state lives and how each seam travels: anything but bus state, a stream or a seat's verb is a finding, even if a record put it there.</li>
|
||||
<li>That no repository holds this mesh's setup (node names, placement, settings): repositories are mesh-agnostic.</li>
|
||||
</ul>
|
||||
<div class="note" style="margin-top:10px">Why reviews took more than one round (R3b, 2026-10-19): a test that "failed" only by not compiling, a reader of the changed record the survey missed, and bus tests judged by reasoning before they ran. ADR 0548 and playbook 07 step 2 item 4 change the last: bus tests are judged by reading them against the code, and the one check runs them after the review.</div>
|
||||
</div>
|
||||
@@ -413,15 +428,15 @@ const P = [
|
||||
{n:"01",name:"Research",kind:"idea",c:"--idea",
|
||||
trig:"An idea worth investigating before it is committed to design. A symptom is not research.",
|
||||
who:"The session that heard the idea leads.",
|
||||
steps:["Read what is known: ask, get, the tracker's list.","Ask 3 to 5 setup questions, one at a time, each with a recommendation.","Check every option against the mission and how-we-build first.","Reserve a number (kind research), never pick one by hand.","Propose the effort: overview, questions, method, with links.","Research; findings in 04-findings, one version per settled set.","Try at the cheapest level that answers the question.","Write 03-domains: modules, seats, glossary words added or retired.","Write 05-direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","If the review asks changes: propose again, at most three times."],
|
||||
steps:["Read what is known: ask, get, the tracker's list.","Ask 3 to 5 setup questions, one at a time, each with a recommendation.","Check every option against the mission and how-we-build first.","Write the numbered requirements from the answers, each with its check, before any option.","Reserve a number (kind research), never pick one by hand.","Propose the effort: Overview, Requirements, Questions, Method, sections named by title, no file references.","Research; findings in the section Findings, one version per settled set.","Try at the cheapest level that answers the question.","Write the Domains: modules, seats, glossary words added or retired.","Write the Direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","Review the direction by architectural domain: security, seams, running qualities, health, events and logs, testing, upgrade, tools.","If the review asks changes: propose again, at most three times."],
|
||||
prod:"A research effort in the knowledge base, status active, every version kept. Nothing in hq.",
|
||||
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · report every 10 min",
|
||||
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · review by architectural domain 20 min · report every 10 min",
|
||||
prev:["Research that measured nothing","A recommendation the operator must argue back","Efforts copied into hq"],
|
||||
check:"propose refuses rule breaks; the knowledge base reviews; the records test on every PR."},
|
||||
{n:"02",name:"Graduation & design change",kind:"idea",c:"--dec",
|
||||
trig:"A research effort concludes, or an existing design must change.",
|
||||
who:"The lead, on the larger model; the decision is recorded before the design moves.",
|
||||
steps:["Check the conclusion against mission, context and effect.","Reserve a number per decision.","Propose one record per choice that can be reversed on its own.","Pre-check, then propose siblings together.","On changes: revise whole; after three rounds fix findings yourself.","A finding that would change what was decided: ask the operator.","needs-operator: wait for the operator's Approve.","Amend the design, or create a to-be design with status designed.","Add the decisions, updated:, and the effort's glossary words.","Hand the design to the breakdown (04).","Close the effort: graduated with became links, or abandoned.","When it ships: write the as-is, set the to-be implemented."],
|
||||
steps:["Check the conclusion against mission, context and effect.","Reserve a number per question the effort answers.","Propose one record per question: its options and the one chosen; design choices go in the design.","Pre-check, then propose siblings together.","On changes: revise whole; after three rounds fix findings yourself.","A finding that would change what was decided: ask the operator.","needs-operator: wait for the operator's Approve.","Amend the design, or create a to-be design with status designed: a main file and a part per milestone, leaving the builder no choice. (new)","A design question: the operator answers, written as a design line. (new)","Add the decisions, updated:, and the effort's glossary words.","Review the design by architectural domain before it is merged.","Hand the design to the breakdown (04).","Close the effort: graduated with became links, or abandoned.","When it ships: write the as-is, set the to-be implemented."],
|
||||
prod:"Decision records; a to-be design with frontmatter; glossary entries; later the as-is.",
|
||||
next:"04",bounds:"One sitting of about an hour",
|
||||
prev:["A decision rewritten silently","A deferral nothing tracks","A design amended without a decision"],
|
||||
@@ -437,7 +452,7 @@ const P = [
|
||||
{n:"04",name:"Work breakdown & handoff",kind:"idea",c:"--dec",
|
||||
trig:"A to-be design is settled and its building is about to start.",
|
||||
who:"The lead who will run the feature, on the larger model.",
|
||||
steps:["Confirm status designed; each decision traces to a record.","An open question remains: back to research.","Set code: to the owning repositories.","Read the as-is; write it first if it does not say what is replaced.","Write the Milestones: outcome, acceptance tests, repositories.","Cut milestones by functionality, never by an order between parts; fold any not usable alone.","Ask the operator to confirm the milestones.","Open one issue per repository part, linked depends-on.","Write the issue numbers into the design; flip to in-progress.","Hand each milestone to 07, one at a time, one design at a time."],
|
||||
steps:["Confirm status designed; each decision traces to a record.","An open question remains: back to research.","Set code: to the owning repositories.","Read the as-is; write it first if it does not say what is replaced.","Write the Milestones: outcome, acceptance tests, repositories.","Cut milestones by functionality, never by an order between parts; fold any not usable alone.","Give each milestone its part: numbered design lines, at most 60 KB; the main file at most 80 KB. (new)","Ask the operator to confirm the milestones.","Open one issue per repository part, linked depends-on.","Write the issue numbers into the design; flip to in-progress.","Hand each milestone to 07, one at a time, one design at a time."],
|
||||
prod:"Milestones in the design; issues per repository part; the design in-progress.",
|
||||
next:"07",bounds:"One sitting · one design built at a time · a milestone is one release",
|
||||
prev:["Building without a breakdown","Building against an undocumented as-is","Deferrals with no owning issue"],
|
||||
@@ -453,7 +468,7 @@ const P = [
|
||||
{n:"06",name:"Writing a module",kind:"side",c:"--side",
|
||||
trig:"Something that runs today must run on the mesh.",
|
||||
who:"Whoever ports or writes it.",
|
||||
steps:["Read what runs: compose file, environment, where data sits.","Name what it provides and requires.","Declare capabilities for machine facts.","Write resources in the order they must happen.","Every secret in a file, never in env.","Pin every image by digest.","New: generate the credential; adopted: accept it.","Prove it against the real software: auth, refusal, restart."],
|
||||
steps:["Read what runs: compose file, environment, where data sits.","Name what it provides and requires.","Declare capabilities for machine facts.","Choose each part's form (design 20).","Kept data in requires, by template.","Write resources in the order they must happen.","Every secret in a file, never in env.","Pin every image by digest, upstream too.","New: generate the credential; adopted: accept it.","Prove it against the real software: auth, refusal, restart."],
|
||||
prod:"A module declaration, optionally a provisioner, and its proof.",
|
||||
next:"07",bounds:"no time bound",
|
||||
prev:["A service running with no password","A new password locking out adopted data","A test that cannot fail"],
|
||||
@@ -461,15 +476,15 @@ const P = [
|
||||
{n:"07",name:"Features",kind:"build",c:"--build",
|
||||
trig:"Work that changes code across issues or repositories, too large for a small fix.",
|
||||
who:"A lead (larger model), one fixing agent per repository (smaller), one reviewer (larger).",
|
||||
steps:["Prepare: name the branch, make the worktrees, settle every question.","Write one context file per repository.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it, reading the integration tests against the code; at most 3 rounds.","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
|
||||
steps:["Prepare: name the branch, make the worktrees, read the milestone part and the design's main file whole, then the code each design line lands in. (new)","List every place where a design line and the code disagree, beside the design questions. (new)","Every agent is one of hq's definitions (mesh-preparer, mesh-developer, mesh-code-reviewer, mesh-watcher); the lead writes only the handover. (new)","The prepare's only output: a build map per repository, each row a design line and where it lands, and the design questions. No rulings. (new)","Design complete: the lead answers every question against the principles; one no principle settles goes to the operator or the design writer; then develop. (new)","Read what exists today from the code and the live mesh: every effect of what is removed or moved, every reader of each, what happens to each and its test. No code before it is complete; the review refuses work without it. (new)","The build map opens with what exists today; subtasks ordered contract first, one writer per file.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per subtask, each citing its design lines; a question stops only its subtask. (new)","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it against the design lines, code no line asks for a finding, reading the integration tests against the code; at most 3 rounds; the report answers the state-and-seams and mesh-agnostic questions. (new)","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
|
||||
prod:"One pull request per repository, one source revision delivered, resolved issues, no branch or worktree left.",
|
||||
next:"02 (when it ships)",bounds:"Prepare 15 min · 30 min per issue · review 15 min, ≤ 3 rounds · one check, pushed once",
|
||||
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
|
||||
prev:["Pull requests as scratchpads","Context bought again and again","A change built on what was remembered, not on what runs","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
|
||||
check:"The checker's one status, mesh/check; the review record; the tracker's move."},
|
||||
{n:"08",name:"Small fixes",kind:"build",c:"--build",
|
||||
trig:"One issue in one repository, about an hour of work.",
|
||||
who:"The session, with one reviewer on the larger model.",
|
||||
steps:["Read the issue and the code; settle any open question.","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
|
||||
steps:["Read the issue, the design lines it builds and the code; a design question is answered by the lead against the principles (one no principle settles by the operator or the design writer) before code. (new)","Read what exists in the fix's area today, briefly: every reader of what the fix changes. (new)","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
|
||||
prod:"One pull request, one delivery, a resolved issue.",
|
||||
next:"—",bounds:"About an hour · one review, one push, one check",
|
||||
prev:["A fix reviewed after its pull request opened","A long part run twice","A fix waiting on someone else's delivery","Ten SDK merges and eight controller merges in a day, one per fix"],
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ and a forge address is an operational detail (see [`README`](../README.md)).
|
||||
|
||||
| Repository | Owns |
|
||||
|---|---|
|
||||
| `hal` | The monorepo — the node runtime, the module catalogue, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
|
||||
| `hal` | The monorepo — the node runtime, the module catalog, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
|
||||
| `hq` | This repository, under the company organisation — mission, research, design, decisions, issue diagnosis. Company-scoped (ADR 0019); the mesh is its first product. The source of truth for *why*. Carries no implementation. |
|
||||
| *(one per application)* | Every standalone application, site or side-project gets its own repository, with `module.yml` at the root. Registered with the mesh as a build source; built and deployed by the same pipeline as anything in the monorepo. |
|
||||
| `migration` | **Private.** The record of one installation replacing the predecessor mesh with this one: the runbook, a dated log of every step and what it cost, the per-service data procedures, the readiness checks, and the scripts. Private because it is the opposite of this repository in every way that matters — it names machines, addresses, ports and paths, because a procedure that cannot be followed is not one. Where hq asks *what did we decide and why*, that repository answers *what happened on the machines, in what order, and what to do next*. Its `HANDOFF.md` is where somebody picking the work up starts. |
|
||||
@@ -48,7 +48,7 @@ Named by role, because the layout is itself part of the as-is design — see
|
||||
|
||||
| Area | Holds |
|
||||
|---|---|
|
||||
| Module catalogue | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
|
||||
| Module catalog | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
|
||||
| Node runtime | The daemon and interactive runtime that every node runs. |
|
||||
| Bootstrap scripts | First-node initialisation, joining an existing mesh, and node rescue. |
|
||||
| Shared library | The SDK every module builds against. |
|
||||
|
||||
@@ -11,7 +11,7 @@ decisions:
|
||||
|
||||
# The mesh as it stands
|
||||
|
||||
A set of machines, each running the same runtime, each loading only the parts of the catalogue
|
||||
A set of machines, each running the same runtime, each loading only the parts of the catalog
|
||||
it has been assigned. They hold no shared filesystem and make no direct connections to one
|
||||
another. What makes them a mesh is a database that knows what should run where, and a message
|
||||
broker that carries everything between them.
|
||||
|
||||
@@ -105,9 +105,9 @@ the resource rather than on the node that built the module.
|
||||
## A documented rule with no enforcement
|
||||
|
||||
Every module exposing capabilities is documented as required to declare the mesh's core runtime
|
||||
as a dependency. **Zero of the catalogue's modules do.**
|
||||
as a dependency. **Zero of the catalog's modules do.**
|
||||
|
||||
This is recorded here rather than quietly corrected, because it is the clearest instance of the
|
||||
rule this repository states about itself: a rule whose enforcement does not exist is
|
||||
indistinguishable from a wrong one, and costs more, because people believe it. Whether the rule
|
||||
or the catalogue is wrong has not been decided.
|
||||
or the catalog is wrong has not been decided.
|
||||
|
||||
@@ -24,7 +24,7 @@ is what makes a node a participant — it is reachable whether or not anyone is
|
||||
protocol. Its capability surface is larger, because it includes stand-ins for every capability
|
||||
discovered on peers.
|
||||
|
||||
The two are the same code with the same catalogue. A capability is written once and is
|
||||
The two are the same code with the same catalog. A capability is written once and is
|
||||
available to both.
|
||||
|
||||
## Anatomy naming, and what it obscures
|
||||
@@ -52,7 +52,7 @@ For each assigned module the runtime, at startup:
|
||||
4. For a service module, ensures it is installed and running.
|
||||
|
||||
Installation is idempotent and does the unglamorous work: ensure the runtime directory,
|
||||
reconcile the link from the catalogue's definition into it, generate the environment, run any
|
||||
reconcile the link from the catalog's definition into it, generate the environment, run any
|
||||
outstanding local migrations, create data directories with the right ownership, then start the
|
||||
service under supervision.
|
||||
|
||||
@@ -85,7 +85,7 @@ import is one-time and not persisted.
|
||||
Three bootstrap scripts, and which one runs depends on the situation:
|
||||
|
||||
- **First node.** Nothing exists yet, so the script stands up the database the rest of the
|
||||
mesh reads from, publishes the catalogue, and starts the mesh. This resolves the
|
||||
mesh reads from, publishes the catalog, and starts the mesh. This resolves the
|
||||
circularity of a mesh whose source of truth is itself a provisioned module.
|
||||
- **Joining.** The node registers, takes its assignment from the database, and syncs.
|
||||
- **Rescue.** A node that cannot reach the mesh is brought back far enough to.
|
||||
|
||||
@@ -9,9 +9,9 @@ decisions:
|
||||
- "ADR 0009"
|
||||
---
|
||||
|
||||
# The catalogue, and what its shape says
|
||||
# The catalog, and what its shape says
|
||||
|
||||
The catalogue holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
|
||||
The catalog holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
|
||||
ninety-one run *on* the mesh rather than being *of* it
|
||||
(ADR 0001).
|
||||
|
||||
@@ -30,7 +30,7 @@ By namespace, and the namespace records origin rather than purpose:
|
||||
|
||||
## What the flat level actually contains
|
||||
|
||||
Grouped by what they are *for* — a grouping the catalogue itself does not express:
|
||||
Grouped by what they are *for* — a grouping the catalog itself does not express:
|
||||
|
||||
| Purpose | Roughly |
|
||||
|---|---|
|
||||
@@ -51,7 +51,7 @@ connectivity is made four times.
|
||||
|
||||
## What the shape records
|
||||
|
||||
**The catalogue's shape records what was installed, not what anything is for.** One module is
|
||||
**The catalog's shape records what was installed, not what anything is for.** One module is
|
||||
the unit of one piece of software, because that is the only granularity the module system
|
||||
offers.
|
||||
|
||||
@@ -64,7 +64,7 @@ deliberately does not yet settle the domain list.
|
||||
|
||||
## Where the shape came from
|
||||
|
||||
The catalogue's shape is not arbitrary and it is not a series of mistakes. **This repository
|
||||
The catalog's shape is not arbitrary and it is not a series of mistakes. **This repository
|
||||
began as a dotfiles repository**, and most of what looks inexplicable is inherited from that
|
||||
directly.
|
||||
|
||||
@@ -88,7 +88,7 @@ The generalisation from *place files on my machines* to *manage a mesh of nodes*
|
||||
move and it worked. What it did not do is revisit the assumptions underneath, because they were
|
||||
never stated as assumptions — they were just how the thing already worked.
|
||||
|
||||
**This is the most useful single fact for anyone changing the catalogue**, and it is why the
|
||||
**This is the most useful single fact for anyone changing the catalog**, and it is why the
|
||||
linking principle in particular reads as a deliberate architectural choice when it is an
|
||||
inheritance. See ADR 0012, whose case
|
||||
this strengthens: the argument for links was never made *for a mesh*.
|
||||
@@ -109,10 +109,10 @@ module out of the repository safe.
|
||||
|
||||
**Placement is already decided.** A standalone application belongs in its own repository
|
||||
(ADR 0015), and reviewers reject
|
||||
it in the monorepo. The catalogue's flat level is not a dumping ground by policy; it is one by
|
||||
it in the monorepo. The catalog's flat level is not a dumping ground by policy; it is one by
|
||||
history.
|
||||
|
||||
## Known inconsistencies in the catalogue itself
|
||||
## Known inconsistencies in the catalog itself
|
||||
|
||||
Recorded because a reader will meet them:
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ decisions:
|
||||
|
||||
# The seats, as they run
|
||||
|
||||
Written from the controller's code and the catalogue's manifests on their main branches. This is
|
||||
Written from the controller's code and the catalog's manifests on their main branches. This is
|
||||
the second piece of the new shape to exist, after [the lab](11-the-lab.md), and the first that the
|
||||
predecessor has no equivalent of: there, any well-formed name became a seat by being claimed.
|
||||
|
||||
@@ -24,7 +24,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
|
||||
one ecosystem and the git service. The remaining nine — the controller and catalog seats, and 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.
|
||||
|
||||
@@ -21,7 +21,7 @@ decisions:
|
||||
**The `mesh-console` container no longer exists.** Read 2026-10-17 from `mesh-tools` (package
|
||||
`node-tools`), `mesh-host` and `mesh-controller` on their forges' main, and from the live mesh
|
||||
through `mesh_overview`, `mesh_machine`, `mesh_runtimes` and `mesh_describe`. Checked: the
|
||||
catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
|
||||
catalog (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
|
||||
in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a
|
||||
module of that name. How the tool runner reaches a node, its count of modules and tools and the
|
||||
controller's verbs were read again on 2026-10-26, after the delivery of release 6 (ADR 0539).
|
||||
@@ -120,11 +120,11 @@ to-be document still describes it as assigned to "whichever machines an operator
|
||||
actually runs is the opposite — every node the mesh holds carries one, because it is core
|
||||
infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's
|
||||
tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb
|
||||
for it: removing it from a node is not an operation the controller or the catalogue exposes.
|
||||
for it: removing it from a node is not an operation the controller or the catalog exposes.
|
||||
|
||||
## What this repository's prior text got wrong
|
||||
|
||||
The document this replaces described `mesh-console` as a catalogue module served by a container and
|
||||
The document this replaces described `mesh-console` as a catalog module served by a container and
|
||||
assigned only to workstations. That module and its container are gone; the manifest is not — it
|
||||
moved to `node-tools/module.json`, still declares `invokes: ["*"]`, and gained `waits` and the
|
||||
`mcp-endpoint` provision, described above. What also moved is which language builds it: the
|
||||
|
||||
@@ -18,7 +18,7 @@ machine and its licence manager; on the day this was written, three buckets exis
|
||||
## What runs
|
||||
|
||||
- **The controller** creates a key-value bucket `<module>_<name>` for every declared state, from the
|
||||
catalogue — on every start and, since the first module that declared state found it missing, on every
|
||||
catalog — on every start and, since the first module that declared state found it missing, on every
|
||||
push before the memberships that name it. A bucket nothing declares any more is reported and kept.
|
||||
Every bucket carries the mesh's caps: 256 KiB a value, 64 MiB a bucket.
|
||||
- **The grants**: the machine's runtime is granted, for each bucket a module it carries owns, writing
|
||||
@@ -46,7 +46,7 @@ machine and its licence manager; on the day this was written, three buckets exis
|
||||
|
||||
## How it is checked
|
||||
|
||||
The controller's catalogue and broker tests (names, grants, memberships, a bucket asserted in place
|
||||
The controller's catalog and broker tests (names, grants, memberships, a bucket asserted in place
|
||||
against a real server); the runtime's tests over a real bus (current values without deletions, refusals,
|
||||
the TypeScript SDK through the runtime); `module check` names a read whose owner on the shelf keeps no
|
||||
such state.
|
||||
|
||||
@@ -15,7 +15,7 @@ decisions:
|
||||
**A node record names at most two logins: the operator account, always, and an agent account, on one node of
|
||||
four. Where a node names no agent account, agents run as the operator account. Nothing ties an identity to a
|
||||
login, and a module that needs one declares it itself.** Read on 2026-10-17 from the controller's and the
|
||||
catalogue's code on main (both local clones were a few commits behind the forge, so each claim below was
|
||||
catalog's code on main (both local clones were a few commits behind the forge, so each claim below was
|
||||
also checked against a live read) and from the live node records. The change planned in ADR 0411 to 0414
|
||||
replaces this document when it is built.
|
||||
|
||||
@@ -113,4 +113,4 @@ Not here: see [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md).
|
||||
tools for its status.
|
||||
- `knowledge-base.get` with `kind` decision and number 266, 409 or 410; `issue-tracker.get` with 989 or 991.
|
||||
- The code: the controller's node-record and agent-account files, and the `claude-code` module's manifest and
|
||||
guard in the catalogue. Their unit tests hold the node record's refusals and the guard's account rule.
|
||||
guard in the catalog. Their unit tests hold the node record's refusals and the guard's account rule.
|
||||
|
||||
@@ -18,7 +18,7 @@ Where the two disagree, the implementation wins and the disagreement is stated.
|
||||
| [`07-knowledge.md`](07-knowledge.md) | The mesh keeps no store: what it knows is what modules answer, and the record is read by one |
|
||||
| [`08-agents-and-work.md`](08-agents-and-work.md) | Agents as employees, tasks, workflows, and the meeting model |
|
||||
| [`09-interfaces-and-observability.md`](09-interfaces-and-observability.md) | How the mesh is reached and watched — tools, board, proxy, health, thoughts |
|
||||
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalogue's shape, and what its shape says |
|
||||
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalog's shape, and what its shape says |
|
||||
| [`11-the-lab.md`](11-the-lab.md) | The lab — the first piece of the new shape that exists, and what it does not yet do |
|
||||
| [`12-the-seats.md`](12-the-seats.md) | The seats the mesh defines, who holds one, and where a seat changes resolution |
|
||||
| [`13-the-console.md`](13-the-console.md) | The mesh's tools on the machine a person sits at, served by a module the mesh assigned there |
|
||||
|
||||
@@ -431,17 +431,17 @@ It is the same rule the node-engine follows about a service that does not exist
|
||||
(ADR 0004) — absence must be distinguishable
|
||||
from a failure to answer — applied to coverage instead of to a machine.
|
||||
|
||||
## A bed reads the catalogue it proves
|
||||
## A bed reads the catalog it proves
|
||||
|
||||
*Written 2026-09-21, from resolving [04-ISSUES/073](../../04-ISSUES/073-beds-carry-copies-of-catalogue-manifests/00-report.md);
|
||||
decided in ADR 0089.*
|
||||
|
||||
The rule above — the run rebuilds what it tests — was held for binaries and images and not for
|
||||
manifests. Beds built the manifests they install inline, as literals copied from the catalogue
|
||||
when each bed was written; the copies did not move when the catalogue did, and a catalogue change
|
||||
manifests. Beds built the manifests they install inline, as literals copied from the catalog
|
||||
when each bed was written; the copies did not move when the catalog did, and a catalog change
|
||||
was proven by no bed at all, while every bed stayed green against its copy.
|
||||
|
||||
**A bed that installs a catalogue module reads that module's manifest from the catalogue the run
|
||||
**A bed that installs a catalog module reads that module's manifest from the catalog the run
|
||||
was pointed at.** It rewrites what the lab must — a build artifact becomes the image the machine
|
||||
holds, an image is pinned, a host port is remapped where one machine carries colliding modules,
|
||||
an address may point at a stand-in the bed raises — and nothing else.
|
||||
@@ -451,15 +451,15 @@ server, a secret in the environment, a requirement edge cut so no second provide
|
||||
that is a mesh test. It does not get a name of its own if it runs the module's runtime — a
|
||||
module's name is its tool namespace and its broker scope
|
||||
(ADR 0093) —
|
||||
so it reads the catalogue and installs what the module requires; a name of its own is for a
|
||||
so it reads the catalog and installs what the module requires; a name of its own is for a
|
||||
fixture that runs no real runtime.
|
||||
|
||||
**The receipt names the catalogue's commit** with the others', so a run taken before a manifest
|
||||
**The receipt names the catalog's commit** with the others', so a run taken before a manifest
|
||||
changed says so — the same rule as for the binaries, for the same reason.
|
||||
|
||||
*How it is checked:* a unit test in the lab refuses an inline manifest literal that names a
|
||||
catalogue module unless the bed is declared, with its reason, in the test's own list, and refuses
|
||||
a declaration for a copy that is gone; the receipt test asserts the catalogue is claimed whenever
|
||||
catalog module unless the bed is declared, with its reason, in the test's own list, and refuses
|
||||
a declaration for a copy that is gone; the receipt test asserts the catalog is claimed whenever
|
||||
the run is pointed at one.
|
||||
|
||||
## Consequences
|
||||
|
||||
@@ -31,7 +31,7 @@ offered one driver, everything worked, and snapshots took **seventy-six times lo
|
||||
needed to. Nothing failed. Nothing warned. A lab in that state runs correctly and is simply too
|
||||
slow to use — and the inner loop it exists to provide quietly does not exist.
|
||||
|
||||
The second is the more dangerous shape and it is a variant the mesh has not catalogued before.
|
||||
The second is the more dangerous shape and it is a variant the mesh has not cataloged before.
|
||||
Its usual failure is *reported success and did nothing*. This is **reported success and did it
|
||||
seventy-six times slower**, which no error surface catches because nothing is wrong.
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ raises the bootstrap forge under the forge module's container name, with the mod
|
||||
its data directory, so the module holds it by the found rule; the network is the one difference a take
|
||||
has left to say. *How it is checked:* a host test joins a kept network and refuses one it cannot; a
|
||||
host test keeps a left-out module's record and hold and removes an absent module's; a bootstrap test
|
||||
holds the installer's constants to the module's manifest where the catalogue is checked out beside it.
|
||||
holds the installer's constants to the module's manifest where the catalog is checked out beside it.
|
||||
|
||||
**What filters the machine, and the found firewall kept retired** — revision, 2026-10-02
|
||||
(ADR 0168). The node-engine
|
||||
|
||||
@@ -326,7 +326,7 @@ shape moves — it names a thing and the node-engine fetches it — and the cont
|
||||
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 node-engine, the controller, the catalogue, the builder, the vault — are
|
||||
The mesh's own components — the node-engine, the controller, the catalog, the builder, the vault — are
|
||||
delivered as binaries into directories named for their versions, by the mechanism
|
||||
ADR 0141 describes. Third-party
|
||||
software stays a container. The split is not about isolation; it is about who built the thing.
|
||||
|
||||
@@ -425,8 +425,8 @@ time.*
|
||||
A network manager rewrites `/etc/resolv.conf` on every connectivity change unless it is told not to
|
||||
(ADR 0117), so the module holding
|
||||
`node-uplink` — the one telling it — writes the file itself, and there is one owner for it, the one
|
||||
whose program would otherwise overwrite it. Each of the catalogue's managers' modules
|
||||
(NetworkManager, systemd-networkd; dhcpcd's left the catalogue with
|
||||
whose program would otherwise overwrite it. Each of the catalog's managers' modules
|
||||
(NetworkManager, systemd-networkd; dhcpcd's left the catalog with
|
||||
ADR 0226, held by no machine) renders the same template from the resolver's holders and
|
||||
requires the mesh's resolver, so a machine is refused when nothing in the mesh resolves rather than
|
||||
given a file listing nothing. The managers' own mechanisms were weighed and not used: NetworkManager's
|
||||
@@ -440,7 +440,7 @@ node declaring one path are refused. The module that wrote the file before, its
|
||||
retire. The file changes owner in one apply on each machine: the node-engine hands a whole file to the
|
||||
resource declaring its path now rather than removing it first, so a machine is never without it.
|
||||
*Checked by the controller's tests that the uplink modules carry one identical template and that
|
||||
nothing else in the catalogue writes the path, a resolution test refusing a second writer, and the
|
||||
nothing else in the catalog writes the path, a resolution test refusing a second writer, and the
|
||||
node-engine's handover test, in which the file is present at every step of the apply.*
|
||||
|
||||
**A machine's names are one seat's** (ADR 0223).
|
||||
@@ -643,7 +643,7 @@ to stop working.*
|
||||
### The name is a label, not a domain
|
||||
|
||||
*2026-09-09, from running the whole mesh in the lab.* A route contribution carried its public name
|
||||
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalogue at a
|
||||
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalog at a
|
||||
different domain — a lab standing in for production, or a second operator's mesh — meant rewriting
|
||||
that name on every routed module. The mesh was holding a **map of names to services**, which is the
|
||||
one thing it must not: a public name is two facts owned by two different places, and neither is the
|
||||
@@ -976,7 +976,7 @@ run-once step before it starts. The step names the binding it reads and the prox
|
||||
step, so when the authority moves the root is fetched again and the proxy is recreated with it
|
||||
(ADR 0099).
|
||||
*How it is checked:* the route-forwarding bed installs the authority, the proxy and a consumer
|
||||
from the catalogue and asserts the routed name is served.
|
||||
from the catalog and asserts the routed name is served.
|
||||
|
||||
**And a machine trusts that authority because a module put its root in its trust store**
|
||||
(ADR 0147). The proxy's fetch
|
||||
|
||||
@@ -57,10 +57,10 @@ produced from there. A module whose path is the repository root is the ordinary
|
||||
not a separate shape.
|
||||
|
||||
*Corrected 2026-09-12. This section previously said the manifest was at the root, full stop, which
|
||||
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalogue
|
||||
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalog
|
||||
holds sixty-seven modules in their own directories, the system being replaced has always built a
|
||||
module from a repository and a path, and the root-only reading left every existing module
|
||||
unbuildable — pointed at the catalogue the builder finds no manifest, pointed at a module's source
|
||||
unbuildable — pointed at the catalog the builder finds no manifest, pointed at a module's source
|
||||
it finds no manifest either.*
|
||||
|
||||
## A manifest is checked where it is written
|
||||
@@ -84,7 +84,7 @@ read without a store (ADR 0122
|
||||
keeps the store authoritative, so a claim on a mesh seat is judged fully only at registration, and the
|
||||
check says that too).
|
||||
|
||||
*How it is checked:* the controller's test runs the check over the catalogue checkout beside it and
|
||||
*How it is checked:* the controller's test runs the check over the catalog checkout beside it and
|
||||
over a manifest with a known fault, and asserts the first passes and the second names the fault; the
|
||||
test that used to be the only check, `TestEveryCatalogueManifestParses`, now stands beside a command
|
||||
anybody can run.
|
||||
@@ -218,7 +218,7 @@ for, arrived at by an implementation detail.
|
||||
until it is told otherwise; the mesh changing its mind is not a machine acting on it, and
|
||||
collapsing the two is how a mesh comes to report success for something that has not happened.
|
||||
|
||||
*Checked end to end: a commit, a build, a catalogue entry, and a machine that ends up running what
|
||||
*Checked end to end: a commit, a build, a catalog entry, and a machine that ends up running what
|
||||
the source says — with both halves that make the answer trustworthy. It is still running the old
|
||||
one until it is pushed, and it stops being reported as behind once it has caught up, because a
|
||||
status that says "behind" for ever is one nobody reads.*
|
||||
@@ -398,7 +398,7 @@ that distinction once already.*
|
||||
## The four the loop cannot build
|
||||
|
||||
*2026-09-12. Written down because it keeps being rediscovered as if it were new, once per
|
||||
component. It is one rule; the instances are listed below. Revised the same day, when the catalogue
|
||||
component. It is one rule; the instances are listed below. Revised the same day, when the catalog
|
||||
gained an owner and turned out to be a fourth — which is the argument for stating the rule rather
|
||||
than the list.*
|
||||
|
||||
@@ -412,23 +412,23 @@ have a route and one does not:
|
||||
| The controller | It is what installs modules. Nothing can install it before it runs. | **Carried inside the installer** and published once there is a registry (ADR 0067) |
|
||||
| The registry | It *is* where artifacts are delivered from. A store cannot be delivered through itself. | **Pulled from the public internet** — its image is an ordinary public one, never built ([`04-ISSUES/029`](../../04-ISSUES/029-the-artifact-store-cannot-be-delivered-by-the-artifact-store/00-report.md)) |
|
||||
| The builder | It is what builds. Nothing builds it before it runs. | Built at genesis by the init builder (ADR 0070) |
|
||||
| The catalogue | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder (ADR 0070) |
|
||||
| The catalog | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder (ADR 0070) |
|
||||
|
||||
**How they arrive is settled and not yet built.** The installer carries an init builder, which
|
||||
clones the source and builds the controller, the catalogue and the builder before a mesh exists
|
||||
clones the source and builds the controller, the catalog and the builder before a mesh exists
|
||||
to install anything. Two things about that are open and named in
|
||||
ADR 0070: where the init builder
|
||||
clones from, given the forge normally runs on the mesh it would be rebuilding, and what it
|
||||
publishes into, given the registry is installed later in the order today.
|
||||
|
||||
**There is a fourth, and the list was closed too early.** The catalogue owns the module graph
|
||||
**There is a fourth, and the list was closed too early.** The catalog owns the module graph
|
||||
(ADR 0070), so it cannot be
|
||||
installed by something that needs it in order to install anything — the same shape as the other
|
||||
three. This section previously said the list was closed at three, which was written before the
|
||||
catalogue had an owner and is corrected here rather than left to be reasoned from.
|
||||
catalog had an owner and is corrected here rather than left to be reasoned from.
|
||||
|
||||
**And the answer for all four is now one mechanism, not four special cases.** Genesis carries an
|
||||
*init builder* and builds the core modules on the machine — controller, catalogue and builder —
|
||||
*init builder* and builds the core modules on the machine — controller, catalog and builder —
|
||||
rather than carrying a finished image of any of them. So the question is no longer "how does this
|
||||
one get here first?" asked once per component; it is answered once, by the thing that is carried
|
||||
being a builder rather than a result.
|
||||
|
||||
@@ -102,7 +102,7 @@ anything on the machine that can talk to the runtime. So a secret reaches a proc
|
||||
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
|
||||
secrets are exposed that way. **Checked** at composition: the catalog engine refuses a
|
||||
`${secret:…}` in a container's `env` outright, and a secret-carrying file named in `env-file`
|
||||
unless the container carries the declaration.
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ the one the mesh runs for its own: it offers a place to push, and claims that ro
|
||||
machine.
|
||||
|
||||
**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
|
||||
refused one correctly — but the controller accepted it into the catalog, 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 node-engine's log. It is now refused where it is written.
|
||||
|
||||
@@ -132,7 +132,7 @@ adopted as modules, and the machine's service is still reachable.
|
||||
|
||||
Genesis ends with a mesh of one that runs, and that is not the same as a mesh that works. What it
|
||||
has is a controller, a store, a queue and a registry. What it cannot yet do is **produce
|
||||
anything** — and almost every module in the catalogue is waiting to be produced, because a manifest
|
||||
anything** — and almost every module in the catalog is waiting to be produced, because a manifest
|
||||
names what its artifacts are and nothing has made them.
|
||||
|
||||
So installing continues:
|
||||
@@ -172,7 +172,7 @@ need no registry. The gitea module's own server is an upstream image, so buildin
|
||||
|
||||
**The store genesis raises is the `postgres` module's server, so adopting it keeps it** (ADR 0338, the
|
||||
store's twin of the bus's ADR 0333). The bundle raises the store under the name, on the image, in the data
|
||||
directory and with the superuser password file that the catalogue's `postgres` module declares, and every step
|
||||
directory and with the superuser password file that the catalog's `postgres` module declares, and every step
|
||||
of the bundle and of the installer reaches it by that name. At the store step the module declares the
|
||||
container the bundle raised, so the store is adopted where it runs and is not restarted. The module does not
|
||||
change, so no running mesh's store moves. *How it is checked:* a test reads the module's manifest and holds
|
||||
@@ -190,7 +190,7 @@ something it made rather than by a placeholder.
|
||||
mesh running; building it from its own repository and path is what makes it upgradeable. The first
|
||||
time that happens is the moment the mesh stops depending on the installer for anything.
|
||||
|
||||
**And then the catalogue.** Every module with source of its own is built the same way. Until this
|
||||
**And then the catalog.** Every module with source of its own is built the same way. Until this
|
||||
has happened a mesh can install only what is public or carried, which is the foundation and little
|
||||
else.
|
||||
|
||||
@@ -233,7 +233,7 @@ was brought up the supported way, so the rule that it must be is, today, unenfor
|
||||
|
||||
**Nothing asks for the core modules yet.** Installing ends with a builder that works — a raised
|
||||
machine will build the shared base and a module on top of it when asked — and nothing asks. The
|
||||
paragraphs above describing the catalogue being built are a thing somebody now types, rather than a
|
||||
paragraphs above describing the catalog being built are a thing somebody now types, rather than a
|
||||
thing that cannot happen.
|
||||
|
||||
**A module's declaration still has to be copied onto the machine by hand.** The installer reads the
|
||||
@@ -242,7 +242,7 @@ longer among them: it lives in the controller's own repository
|
||||
(ADR 0069), the build the
|
||||
installer runs reports it with the artifact resolved, and genesis registers that
|
||||
([issue 072](../../04-ISSUES/072-the-controllers-manifest-exists-twice/00-report.md)). The
|
||||
builder's manifest belongs in the same repository and is still read from the catalogue; that is
|
||||
builder's manifest belongs in the same repository and is still read from the catalog; that is
|
||||
the last of this.
|
||||
|
||||
**A machine has no account for a registry that asks for one.** The mesh grants a consumer a
|
||||
@@ -275,7 +275,7 @@ issue tracker holds it as issue 602.
|
||||
| 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. |
|
||||
| The builder can arrive on a fresh mesh | The installer carries it and installs it as its last step, and the genesis bed raises a machine by running the installer. A bed that raises one any other way fails its own acceptance check. |
|
||||
| The builder is assigned before the package registry exists | The build-agent manifest's own test holds the requirement optional and the genesis document declared and issued outside the mesh; the controller's resolution test over the real catalogue assigns the builder with no provider, lists the requirement unbound, and binds it once gitea is offered. The builder's tests refuse a package, an `.npmrc` image and an `@novox/` TypeScript build with neither binding nor genesis document, before anything is built, and build a Go artifact; they read the genesis document only without a binding. The installer's test seals the document at the packages step and gives the builder no `serves` setting and no `npm-password`. **Not yet proved live:** a genesis bed from bare passing the builder, packages, sdk and base steps. |
|
||||
| The builder is assigned before the package registry exists | The build-agent manifest's own test holds the requirement optional and the genesis document declared and issued outside the mesh; the controller's resolution test over the real catalog assigns the builder with no provider, lists the requirement unbound, and binds it once gitea is offered. The builder's tests refuse a package, an `.npmrc` image and an `@novox/` TypeScript build with neither binding nor genesis document, before anything is built, and build a Go artifact; they read the genesis document only without a binding. The installer's test seals the document at the packages step and gives the builder no `serves` setting and no `npm-password`. **Not yet proved live:** a genesis bed from bare passing the builder, packages, sdk and base steps. |
|
||||
| The controller a mesh runs is one it built | The genesis bed asserts the running controller is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried. |
|
||||
| A core module is built rather than only carried | The controller is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes. |
|
||||
| Installing produced a mesh that can produce | A module with source of its own is asked for and comes back pinned to a digest this mesh's registry assigned, not to a placeholder. **Done by hand on a raised machine, not yet by a bed** — it built the shared base and then a module naming that base. Nothing automated asserts it, which makes this the weakest check on this page. |
|
||||
|
||||
@@ -43,7 +43,7 @@ Turning a module's source into artifacts the mesh can pin, publish and deliver
|
||||
truthfully what was produced and what it was produced against.
|
||||
|
||||
Everything else is somebody else's: *what* to build is the controller's, *what a build means* is
|
||||
the catalogue's (ADR 0072), *where an artifact runs* is the
|
||||
the catalog's (ADR 0072), *where an artifact runs* is the
|
||||
controller's again. The builder's whole responsibility is the middle.
|
||||
|
||||
## The language
|
||||
@@ -83,7 +83,7 @@ output into a second stage; and sets an environment variable naming the compiled
|
||||
module repeats it. Miss any step and the failure arrives somewhere else — as a crash loop, a
|
||||
placeholder digest, a module that builds and does nothing.
|
||||
|
||||
The evidence that this is too hard is in the catalogue: **most modules are not converted**, and the
|
||||
The evidence that this is too hard is in the catalog: **most modules are not converted**, and the
|
||||
two converted during one session were each wrong twice before they were right, against a working
|
||||
example sitting open in the next window.
|
||||
|
||||
@@ -163,8 +163,8 @@ makes the drift worse while hiding it.
|
||||
| A module with its own code needs no Dockerfile | A module declaring only a language builds, and its artifact is pinned to a digest the mesh's registry assigned. |
|
||||
| An archive is produced and delivered | A module declaring an archive is built, and the machine assigned it has the unpacked files — verified on the machine, not in the build's own output. |
|
||||
| A Dockerfile still works | A module that declares one builds exactly as before, because software that needs a particular base has not stopped existing. |
|
||||
| Nothing is announced that was not made | A build made to fail announces nothing, and the catalogue's graph is unchanged afterwards. |
|
||||
| A build keeps what it was told | A catalogue started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. |
|
||||
| Nothing is announced that was not made | A build made to fail announces nothing, and the catalog's graph is unchanged afterwards. |
|
||||
| A build keeps what it was told | A catalog started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. |
|
||||
| The toolchain is the mesh's, not the author's | Changing the toolchain makes every module built against it stale, and each names what moved. |
|
||||
|
||||
## The whole surface, in one place
|
||||
@@ -207,7 +207,7 @@ declared in one of three ways, for the three things a path can be: the module's
|
||||
or file resource, or where a secret, grant or contribution lands), the operator's (an `accesses`
|
||||
entry), or the machine's (a facility a declared capability grants — `container-runtime` grants its
|
||||
socket). *How it is checked:* the parser refuses an undeclared mount naming the path and the three
|
||||
remedies, and a test parses every manifest in the catalogue beside the checkout.
|
||||
remedies, and a test parses every manifest in the catalog beside the checkout.
|
||||
|
||||
### What it builds
|
||||
|
||||
@@ -247,7 +247,7 @@ counts as a copy and what as a base.
|
||||
| `directory` | a directory with a mode and an owner, **placed by the mesh** under the node's root: `place: "."` is the assignment's own root, `place: "mesh"` the mesh's directory for the module, a pathless one sits beneath the root by its id; a stated path is the placement for data that must stay where it is, and may itself sit beneath a placed one (`${dir:<id>}/…`). Everything else names it as `${dir:<id>}` ([issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md), [174](../../04-ISSUES/174-the-meshs-own-files-for-a-module-are-placed-by-the-definition/00-report.md)) | ✅ |
|
||||
| `file` | literal content, with `${bound:…}`, `${secret:…}`, `${dir:…}`, `${port:…}`, `${machine:…}` and `${setting:…}` filled in — the last an operator's value from the assignment's settings, refused by name when unset (ADR 0155) unless the manifest gives a preference a default (ADR 0262) | ✅ |
|
||||
| `user` | a login | ✅ |
|
||||
| `access` | a pre-existing path it may use and must not own, **named by id** and placed by the assignment (`accesses: {<id>: <path>}` on its settings); mounts say `${access:<id>}`; a path in the definition is the default an assignment replaces, tolerated while the catalogue converts ([issue 153](../../04-ISSUES/153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)) | ✅ |
|
||||
| `access` | a pre-existing path it may use and must not own, **named by id** and placed by the assignment (`accesses: {<id>: <path>}` on its settings); mounts say `${access:<id>}`; a path in the definition is the default an assignment replaces, tolerated while the catalog converts ([issue 153](../../04-ISSUES/153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)) | ✅ |
|
||||
| `archive` | files fetched by digest and unpacked | ✅ |
|
||||
| `package` | a package that must be present | ✅ |
|
||||
| `network` | a named container network | ✅ |
|
||||
@@ -292,7 +292,7 @@ This is the bus's shared-work pattern, not a build-specific one: any module decl
|
||||
`accepts` has its work shared by its holders the same way. Building is the first use.
|
||||
|
||||
*Built and proven live 2026-10-03.* `build-agent` holds `node-build-agent` on all four machines; the first
|
||||
build taken by a workstation's agent was a catalogue module at 09:35 UTC; the one-holder `builder` is
|
||||
build taken by a workstation's agent was a catalog module at 09:35 UTC; the one-holder `builder` is
|
||||
retired. The switch found five gaps, each an issue: a worker whose type changed stranded its holder
|
||||
([206](../../04-ISSUES/206-a-seats-worker-changing-type-strands-the-holder-and-the-build-that-would-fix-it/00-report.md)),
|
||||
a re-made worker replayed the stream's history ([207](../../04-ISSUES/207-a-re-made-worker-replayed-every-ask-the-stream-kept/00-report.md)),
|
||||
@@ -316,7 +316,7 @@ controller's seat verb through the mesh MCP server — reads it back afterwards.
|
||||
id beside it, and `build` says the id it asked with. The mesh keeps no second copy: the stream is 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
|
||||
the daemon following the role's event, so a build nobody waited for still reaches the catalog
|
||||
([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
|
||||
builder needs nothing more for it.
|
||||
|
||||
@@ -383,7 +383,7 @@ than retried for ever. Live: the store's size before and after the first nightly
|
||||
*2026-09-29 —
|
||||
ADR 0142.*
|
||||
|
||||
The toolchain list was typescript and python, and only typescript had a base module in the catalogue.
|
||||
The toolchain list was typescript and python, and only typescript had a base module in the catalog.
|
||||
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.
|
||||
|
||||
@@ -3,27 +3,64 @@ layer: to-be
|
||||
status: proposed
|
||||
code:
|
||||
- mesh-catalog modules/showcase
|
||||
- mesh-controller internal/builder
|
||||
- mesh-sdk src
|
||||
updated: 2026-10-02
|
||||
- mesh-delivery modules/mesh-delivery-indexer/judge (the manifest validator)
|
||||
- mesh-delivery modules/mesh-delivery-builder
|
||||
- mesh-sdk go/catalogue (the manifest's shapes)
|
||||
- mesh-sdk go/declaration (the node-engine's resource vocabulary)
|
||||
- mesh-contracts
|
||||
updated: 2026-11-12
|
||||
decisions:
|
||||
- "ADR 0150"
|
||||
- "ADR 0099"
|
||||
- "ADR 0053"
|
||||
- "ADR 0578"
|
||||
- "ADR 0595"
|
||||
- "ADR 0619"
|
||||
- "ADR 0634"
|
||||
- "ADR 0635"
|
||||
- "ADR 0625"
|
||||
- "ADR 0627"
|
||||
- "ADR 0573"
|
||||
- "ADR 0605"
|
||||
- "ADR 0606"
|
||||
- "ADR 0607"
|
||||
- "ADR 0609"
|
||||
- "ADR 0593"
|
||||
- "ADR 0499"
|
||||
- "ADR 0498"
|
||||
- "ADR 0478"
|
||||
- "ADR 0436"
|
||||
- "ADR 0366"
|
||||
- "ADR 0316"
|
||||
- "ADR 0267"
|
||||
- "ADR 0259"
|
||||
- "ADR 0262"
|
||||
- "ADR 0348"
|
||||
- "ADR 0564"
|
||||
- "ADR 0596"
|
||||
- "ADR 0201"
|
||||
- "ADR 0193"
|
||||
- "ADR 0192"
|
||||
- "ADR 0189"
|
||||
- "ADR 0175"
|
||||
- "ADR 0152"
|
||||
- "ADR 0150"
|
||||
- "ADR 0142"
|
||||
- "ADR 0135"
|
||||
- "ADR 0099"
|
||||
- "ADR 0074"
|
||||
- "ADR 0053"
|
||||
- "ADR 0052"
|
||||
- "ADR 0040"
|
||||
- "ADR 0039"
|
||||
- "ADR 0053"
|
||||
- "ADR 0005"
|
||||
---
|
||||
|
||||
# Writing a module
|
||||
|
||||
A worked guide. One module, four capabilities, four languages, and the packages it publishes.
|
||||
A worked guide: what a module is built of, the manifest form of each part, and the packages it publishes.
|
||||
|
||||
The reference for *what can be said* is [`18-building-a-module`](18-building-a-module.md); the
|
||||
reference for *what the code and the mesh say to each other* is
|
||||
[`19-the-module-protocol`](19-the-module-protocol.md). This is how you actually write one.
|
||||
The reference for *what a manifest may say* is the manifest validator, the rules of the seat `mesh-catalog`'s holder
|
||||
that judge every manifest (ADR 0578 §1), with [design 32](32-what-a-module-declares.md) for what the bus makes of it;
|
||||
the reference for *how a build is modelled* is [design 18](18-building-a-module.md); the reference for *what the code
|
||||
and the mesh say to each other* is [design 19](19-the-module-protocol.md). This is how you actually write one.
|
||||
|
||||
## First: what is in the SDK, exactly
|
||||
|
||||
@@ -47,141 +84,181 @@ Split the way the protocol is split:
|
||||
mesh set. Reading a file is not a thing an SDK needs to teach.
|
||||
- **No API clients.** A Plex client belongs in the Plex module. It changes when Plex changes,
|
||||
which has nothing to do with any other module — *frequent **and** cascading is the disease.*
|
||||
Logic over a *seat* is not such a client: it is the seat's client package, in the seat's contracts
|
||||
module (below), never in the SDK and never in a holder.
|
||||
- **No storage, no HTTP framework, no logging library.** Use the language's.
|
||||
|
||||
If you find yourself wanting to add something to the SDK, the test is ADR 0039's: **does editing it
|
||||
recompile unrelated modules, and does it change often?** Both, and it does not belong.
|
||||
recompile unrelated modules, and does it change often?** Both, and it does not belong. It goes to
|
||||
its owner instead: a seat's contract or client, the module that owns it, or its domain's SDK (*Where a seat's code
|
||||
goes*, below; ADR 0634 §1).
|
||||
|
||||
## A module with four capabilities, in four languages
|
||||
## Where a seat's code goes
|
||||
|
||||
A module is **one piece of software** (ADR 0040) and
|
||||
may still be written in several languages — each artifact names its own, compiles alone and is
|
||||
packed alone.
|
||||
*Added 2026-11-04, widened 2026-11-05* (ADR 0625, ADR 0627, ADR 0634, ADR 0635; [design 26](26-the-seats.md) *Where
|
||||
a seat's code goes* is the whole rule). Shared code lives with its owner (ADR 0634 §1): a module that calls or holds
|
||||
a seat, or shares code with other modules, meets these places, and each piece of code has one, the first that fits:
|
||||
|
||||
```
|
||||
showcase/
|
||||
module.json
|
||||
events/ ← TypeScript: reacts to what the mesh does
|
||||
tools/ ← Go: answers questions
|
||||
provisioner/ ← Rust: grants instances of what it provides
|
||||
ingest/ ← Python: a scheduled job
|
||||
```
|
||||
| the code | lives in | written |
|
||||
|---|---|---|
|
||||
| a seat's types, subjects, the layouts of its states and a typed call per verb or accept | the seat's **contract**: a package of its own per language in its domain's contracts module (`mesh-<domain>-contracts`, or `mesh-contracts` for the Core domain and a domain with none), for a seat defined whole; a seat at the seat list's grain has only its definition, which every reader reads from the bus (ADR 0635 §1, §2) | generated from the seat's definition, never by hand |
|
||||
| logic two or more callers or holders would otherwise each write over that seat (an ask and its awaited answer, publishing a file in parts to a registry) | the seat's **client package**, beside its contract, owned by the contracts module | by hand; it uses only its own seat's contract, mesh-sdk's runtime and its own domain's SDK, never another seat's client, and names no holder, no module's tool and no module's subject |
|
||||
| logic and state layouts one module owns, where it holds no seat for them | that module's **own package**, which other modules import from it: by path in the same repository (ADR 0609 §2), by a published version from another (ADR 0573 §1), so its manifest declares it as a package artifact; its fingerprint counts only that package's files (ADR 0593 §2), so its importers rebuild only when it changes | by hand, in the owner module |
|
||||
| logic and forms no seat carries and no one module owns, used by one domain's modules or seats only (moving a file in parts, the build steps' shared logic) | that domain's **domain SDK**, `mesh-<domain>-sdk`, a package-only module (ADR 0634 §2) | by hand; it imports only mesh-sdk and contract packages, names no module's tool or subject, and holds no seat's type or state layout |
|
||||
| what every module needs whatever the seat (the bus connection, the tools bundle's runtime, state), and a part modules or seats of more than one domain share that names no seat, verb or seat's type | **mesh-sdk** | by hand; it takes no seat-specific type, imports no contracts module, and reads the seat list from the bus |
|
||||
| the seat's implementation | the **holder** module | by hand, against the contract; never the place of the contract or a client |
|
||||
|
||||
### The manifest
|
||||
**So a module imports the contracts and clients of the seats it uses, the packages of the modules whose state it
|
||||
reads, its domain's SDK where it uses it, and mesh-sdk.** Its users rebuild when something they use changes, not when
|
||||
any seat or any domain does. A seat you define yourself, outside the mesh's own contracts, gets a
|
||||
contracts module of its own, which may share your holder module's repository (ADR 0625 §7): a second holder then
|
||||
imports only the contract, never your holder. A contracts module and a domain SDK are modules like any other, found by
|
||||
their manifests in folders of a repository and delivered to no node (ADR 0634 §8). And a role no other module could
|
||||
ever take over is no seat at all: it is your module's own tools and events (ADR 0625 §8).
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "showcase",
|
||||
"build": { "artifacts": [
|
||||
{ "name": "events", "kind": "bundle", "language": "typescript",
|
||||
"entrypoints": ["index.js"] },
|
||||
{ "name": "tools", "kind": "bundle", "language": "go",
|
||||
"entrypoints": ["tools"] },
|
||||
{ "name": "provisioner", "kind": "bundle", "language": "rust",
|
||||
"entrypoints": ["provisioner"] },
|
||||
{ "name": "ingest", "kind": "bundle", "language": "python",
|
||||
"entrypoints": ["ingest.py"] }
|
||||
]},
|
||||
"resources": [
|
||||
{ "id": "events", "type": "process", "name": "showcase-events",
|
||||
"artifact": "events", "run": ["node", "index.js"] },
|
||||
{ "id": "tools", "type": "process", "name": "showcase-tools",
|
||||
"artifact": "tools", "run": ["./tools"] },
|
||||
{ "id": "grants", "type": "process", "name": "showcase-grants",
|
||||
"artifact": "provisioner", "run": ["./provisioner"] },
|
||||
{ "id": "ingest", "type": "process", "name": "showcase-ingest",
|
||||
"artifact": "ingest", "run": ["python", "ingest.py"],
|
||||
"schedule": "0 3 * * *" }
|
||||
]
|
||||
}
|
||||
```
|
||||
*How it is checked:* by the checks design 26 lists: the generators' tests, mesh-sdk's import and package-list tests,
|
||||
the build modules' regenerate-and-compare step, the catalog check of a client and the manifest validator's refusal of a
|
||||
contracts module that claims a seat; the home of each new exported name, a domain SDK's importers included, and a role
|
||||
made a seat or a seat left as one module's tools, by the review of the pull request that adds it (ADR 0634 §10).
|
||||
|
||||
**Four artifacts, four toolchains, four processes, one module.** Nothing here says how any of them
|
||||
is hosted — that is the mesh's, and it is why these are `process` rather than four containers.
|
||||
## Who reads a manifest, and what each one judges
|
||||
|
||||
### Step 1 — say what it is, in each language's own terms
|
||||
*Rewritten 2026-11-12 from the manifest validator's code on mesh-delivery's main (the operator, 2026-11-12: "first
|
||||
look into the indexer's code that reads the manifest and update our module-development documentation accordingly").*
|
||||
|
||||
Each part is an ordinary project in its language, depending on the mesh SDK for that language the
|
||||
way it would depend on anything:
|
||||
A manifest is read by four parts, and each judges only what is its own:
|
||||
|
||||
```
|
||||
events/package.json "@novox/mesh-sdk": "^1.2.0"
|
||||
tools/go.mod require novox.example/mesh-sdk v1.2.0
|
||||
provisioner/Cargo.toml mesh-sdk = "1.2"
|
||||
ingest/pyproject.toml dependencies = ["mesh-sdk~=1.2"]
|
||||
```
|
||||
| part | reads | judges | where a refusal is said |
|
||||
|---|---|---|---|
|
||||
| the **manifest validator**, the rules of the seat `mesh-catalog`'s holder (the indexer) | every manifest it takes in, and every manifest a change touches, at the change's head | the manifest strictly: every field known, at every depth; every rule a manifest is held to alone (a name's form, an artifact's words, a claim on a seat of the seat list, kept data, events, secrets, health, a container's `run-once`, `schedule` and `while-stopped`) and every rule between the manifests of one catalog | the change's check, naming each problem; and the indexer's verdict, which every other part takes (ADR 0578 §1, §2) |
|
||||
| the **builder** and its build modules | `build` | which build module builds each artifact (`kind` and `language`) and whether an upstream reference is pinned by digest (ADR 0478, ADR 0498) | the build's refusal, naming the artifact |
|
||||
| the **node-engine**, through its composer in the change's composition step and on each node | the resources of each module composed for one node | each resource's `type` and fields strictly, by the node-engine's vocabulary (mesh-sdk's declaration package): a field a type does not use is refused, never ignored, and an `action` is refused (ADR 0005) | the composition step of the change, for each machine the change touches; the node's verdict at apply |
|
||||
| every other part (the assigner, the grants part, the tool runner, the provisioner) | the manifest, decoded leniently | nothing: it takes the indexer's verdict (ADR 0578 §3) | — |
|
||||
|
||||
**Resolved from the mesh's own package registries**, which are the ordinary registries for each
|
||||
ecosystem, hosted by the mesh. An author does nothing unusual: `npm install`, `go mod tidy`,
|
||||
`cargo build`, `pip install` all work, and a developer's laptop resolves exactly what a build does.
|
||||
**A field the running manifest validator does not know** makes the indexer set the manifest aside to wait for a newer
|
||||
validator; the change's check, which runs the validator built from the change's head, refuses it (ADR 0578 §1, as
|
||||
changed). So a field is added to the validator in the same change that first writes it.
|
||||
|
||||
### Step 2 — write each part against its capability
|
||||
The manifest validator does not judge a resource's own fields: those are the node-engine's vocabulary, judged where a
|
||||
module is composed for a machine. A module assigned to no machine is therefore judged by the validator alone.
|
||||
|
||||
Each uses only the part of the protocol it needs. The event consumer never learns what a grant is.
|
||||
## What a module is built of
|
||||
|
||||
```
|
||||
events/index.ts on("module.builder.built", …) → the events capability
|
||||
tools/main.go tool("showcase_state", …) → the tools capability
|
||||
provisioner/main.rs grant → credential → the provisioning capability
|
||||
ingest/ingest.py emit("module.showcase.ingested", …) → events, emitting only
|
||||
```
|
||||
A module is **one piece of software** (ADR 0040), and may be built of several parts. Each part is an artifact in
|
||||
`build.artifacts`, named locally, and most are run by a resource in `resources` that names the artifact by its
|
||||
`artifact` field: the resolved manifest names the artifact's digest where the repository's manifest names the
|
||||
artifact (a `source` and a `digest` for a bundle or an archive, an `image` for an image).
|
||||
|
||||
### Step 3 — the mesh does the rest
|
||||
**The artifact kinds** the manifest validator accepts are five, and a builder builds each with one build module:
|
||||
|
||||
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 node-engine writes the units.
|
||||
| kind | its words | built by |
|
||||
|---|---|---|
|
||||
| `bundle` — the module's own code | `language`; for Go also `system` (one of the systems the mesh builds for), `from` (the package it compiles, relative to the module) and `binary`; for TypeScript `entrypoints` and, where it keeps imports, `external`; `loads`, `env` and `bus` for a tools bundle (below); `paths` for a language other than Go | the Go or the Node build module: a bundle in Go or TypeScript (ADR 0478); a bundle in another language is a build module of its own first |
|
||||
| `package` — code other modules compile against | `language` (Go or TypeScript), `series`, `paths` (the files it is built from, ADR 0499), `generated-from` for a contracts module's | its language's build module, which asks the registry seat of its language to publish it (ADR 0605 §3, ADR 0606) |
|
||||
| `image` — a container image built from a recipe | `from` (a Dockerfile), `target`, `compiles` (the Go package a recipe compiles, ADR 0267), `context` (a second repository its build reaches into) | the image build module |
|
||||
| `upstream` — somebody else's image, copied | `from`, a reference pinned by its digest (`@sha256:`) | the image build module (ADR 0498 §4) |
|
||||
| `archive` — files packed as they stand | `from` (a directory of the module), or an upstream release by `url`, `sha256` and `version`, with `file` or `within` (ADR 0316) | the Go build module |
|
||||
|
||||
### What it costs you to use four languages
|
||||
Every artifact may also say `needs` (an edge no other line says), and every kind but `upstream` its `series`. A `bundle` reached by nothing (no
|
||||
`loads`, no `tools`, no resource naming it) is refused by name (issue 216); a resource naming a `package` is refused,
|
||||
since a package is a build input and runs nowhere.
|
||||
|
||||
**Honestly: four sets of dependencies to keep current, and four SDKs that must agree.** The mesh
|
||||
makes it possible, not free. A module in one language is simpler, and the reason to use four is
|
||||
that one of them genuinely suits a part better — not that you can.
|
||||
**The parts, and the manifest form of each:**
|
||||
|
||||
| part | the manifest form | what runs it |
|
||||
|---|---|---|
|
||||
| **tools** | a `bundle` with `loads` naming what the runtime loads (for a Go bundle its `binary`), and the module's `tools` listing every tool it serves; `replaces` for the shell commands a tool replaces | the node's tool runner (ADR 0175); with `runs-as`, a **runtime of its own** under the module's account (below) |
|
||||
| **a long-running service of its own code** | a `process` resource: `artifact` (a bundle), `name` (what the unit is called), `run` (the command, relative to the unpacked bundle), `user`, `env` and `env-file`, `restart-on`, `health`, `yields`, `replaces` (the ids it takes over from) | the node-engine, as a unit that stays up |
|
||||
| **its own bus account for a process** | the process's `bus`: the `emits`, `state`, `queues`, `invokes` and `asks` it is granted, a subset of what the manifest declares; an own secret holding the account, owned by the process's `user` (ADR 0436 §1) | the grants part composes the account from it |
|
||||
| **a scheduled job** | a `process` or `container` resource with `schedule`, a five-field cron; never with `run-once`; a process with no `restart-on`, `replaces` or `witness`; a scheduled container may name `while-stopped` (below) | the node-engine, again on each fire (ADR 0053) |
|
||||
| **a one-time step** (a seed, a first-start step, a validator of a file) | a `process` or `container` resource with `run-once` true: a **run-once step**, which gates every resource after it; `restart-on` makes it run again when what it names changes (ADR 0052, ADR 0099) | the node-engine, to completion |
|
||||
| **a migration of its own state before a new version runs** | `prepares` true, and a `container` or `process` that stays up running an artifact the module built: the mesh runs that program in its preparation mode (ADR 0135); never a run-once step written by hand for it | the node-engine, before the new version starts |
|
||||
| **a seat's implementation** | `claims`, each `{name, scope, serves}` for a seat on the seat list (the verbs it serves; a kinded seat's `kind`); a seat the module defines for itself in `seats`; the verbs answered by its tools bundle or a process | as its tools or its process |
|
||||
| **an image** | an `image` or `upstream` artifact, and a `container` resource naming it by `artifact`; or a container naming an image pinned by digest in `image` directly, for an image the module does not copy; a container stays up unless it says `run-once` or `schedule` | the node-engine, on the container runtime the module's `capabilities` name |
|
||||
| **a host program from the operating system** | a `package` resource (`package`, `absent`) and a `service` resource per unit it ships (`unit`, `state`, `boot`, `scope`, `restart-on`, `reload-on`, `health`); never an `action` | the node's package manager and service manager |
|
||||
| **files** | `file` and `directory` resources (a directory by `place` or `path`), an `archive` resource unpacking an `archive` artifact, template files in the module's `templates` directory | the node-engine and the provisioner client |
|
||||
|
||||
**A runtime of its own.** A module that says `runs-as` names an account it makes (a `user` resource of that name,
|
||||
never root nor the operator's); its tools bundle is then served by a runtime of its own, as that account, on a bus
|
||||
account of its own (ADR 0259 §8), whose readiness is the manifest's `runtime-health` (ADR 0366). Its secrets belong to
|
||||
that account (`secrets-owner`), and its bus account's secret is the own secret `broker`. A tools bundle that holds an
|
||||
account of its own instead says `bus` on its artifact, a Go bundle only, with the own secret `broker-<binary>` in a
|
||||
directory the runtime's account may pass (ADR 0564).
|
||||
|
||||
**What it keeps on the bus.** Kept data is asked for in `requires`, each entry in its long form: `name` (a local name,
|
||||
lower-case letters, digits and hyphens), `storage` (`key-value`, `stream`, `object-store` or `ledger`), `template`
|
||||
(`state`, `events`, `work-queue`, `log`, `objects`, `ledger`) and its `options`, or a bare stream's `configuration`
|
||||
(ADR 0595 §1 to §3). The manifest validator holds each option to its bound. The **old sections** `state`, `logs`,
|
||||
`streams` and `objects` are kept by the parts the bus module needs before it can answer, and by no new entry
|
||||
anywhere else: those parts ask nothing by template, and any other module's use of an old section is refused once no
|
||||
accepted module outside them uses that section any more (ADR 0619 §1, ADR 0595 §11). One local name is in one form.
|
||||
A module reads another module's data by asking its tools or seat verbs, never by its bus state (ADR 0201, as changed
|
||||
by ADR 0596).
|
||||
|
||||
**What it says and hears.** `emits` names its events locally; `consumes` names `<emitter>.<event>`, with `*` for one
|
||||
name and `**` for the rest; `invokes` names each tool `<module>.<tool>` or verb `seat:<seat>.<verb>` it calls, or `*`,
|
||||
and an entry's long form `{verb, fields}` names the fields of the answer it relies on (ADR 0152); `uses` names the
|
||||
seats it sends to; `needs-approval` marks a tool, verb or event that waits for the operator's approval.
|
||||
|
||||
**What someone may change.** Each config value a module reads is declared once under `config` (or its alias
|
||||
`settings`, never both), with its type, its default where it is a preference, and whether it is trusted; a file takes
|
||||
it by `takes-config` or a template file's `${config:<key>}` (ADR 0262, ADR 0348).
|
||||
|
||||
*How it is checked:* the manifest validator's tests (mesh-delivery modules/mesh-delivery-indexer/judge) refuse each
|
||||
form above that a manifest gets wrong, by name; the builder's plan test refuses a kind or language no build module
|
||||
builds; the node-engine's declaration tests refuse a field a resource type does not use; and the showcase module in
|
||||
mesh-catalog declares one part of each kind its language allows and composes on a node.
|
||||
|
||||
## The showcase, worked
|
||||
|
||||
The catalog's `showcase` module is the worked example: **one TypeScript bundle, several entrypoints, every mode**.
|
||||
|
||||
| part | artifact | resource | its words |
|
||||
|---|---|---|---|
|
||||
| the module's code | `code`, a `bundle` in TypeScript, `entrypoints` for the tools, the provisioner, the service, the run-once step and the report | — | — |
|
||||
| a run-once step | `code` | `migrate`, a `process` | `run` the step's entrypoint, `run-once` true, `env-file` |
|
||||
| the service | `code` | `server`, a `process` | `run` the service's entrypoint, `user` the module's account, `env-file`, `restart-on` its settings file |
|
||||
| a scheduled report | `code` | `reporting`, a `process` | `run` the report's entrypoint, `schedule` a five-field cron |
|
||||
| a container from somebody else's image | `helper`, an `upstream` pinned by digest | `tools`, a `container` | `network`, `volumes` mounting its broker secret, `args` |
|
||||
| packed files | `files`, an `archive` from a directory | `packed`, an `archive` | `path`, `unknown` |
|
||||
| its settings file | `templates`, the module's templates directory | `settings`, a `file` | `path`, `mode`, `template` |
|
||||
| a seat it defines for itself | — | — | `seats`, and `claims` of it |
|
||||
|
||||
Nothing in it says how any part is hosted beyond its resource type — that is the mesh's: the builder picks the
|
||||
toolchain from the language, compiles each artifact alone and publishes it, the assigner places the module, and the
|
||||
node-engine writes the units. **A module in several languages is several bundles**, each naming its own language and
|
||||
compiled alone; each language is one more build module the mesh must have, and one more set of dependencies to keep
|
||||
current. The reason to use two is that one genuinely suits a part better, not that you can.
|
||||
|
||||
### Its code, in each language's own terms
|
||||
|
||||
Each part is an ordinary project in its language, depending on the mesh's packages the way it would depend on
|
||||
anything, resolved from the mesh's own package registries, which are the ordinary registries for each ecosystem
|
||||
hosted by the mesh (the registry seats, ADR 0605): `npm install` and `go mod tidy` work, and a developer's laptop
|
||||
resolves what a build does. **A Go module requires a mesh package at the zero version only**: the builder builds
|
||||
against the newest version of the package series the manifest's `packages` accepts (ADR 0573 §2, §4, ADR 0606), so no
|
||||
member of a change names another's version.
|
||||
|
||||
Each part uses only the part of the protocol it needs: the event consumer never learns what a grant is, and a run-once
|
||||
step emits only.
|
||||
|
||||
## Publishing a package is a capability
|
||||
|
||||
A module may publish libraries as well as run code. **The SDK is not special; it is simply the first
|
||||
module that did this**, and its own consumers are the mesh's modules.
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "plex",
|
||||
"build": { "artifacts": [
|
||||
{ "name": "server", "kind": "upstream", "from": "plexinc/pms-docker@sha256:…" },
|
||||
{ "name": "client-ts", "kind": "package", "language": "typescript", "from": "clients/typescript" },
|
||||
{ "name": "client-rust", "kind": "package", "language": "rust", "from": "clients/rust" },
|
||||
{ "name": "client-py", "kind": "package", "language": "python", "from": "clients/python" }
|
||||
]}
|
||||
}
|
||||
```
|
||||
|
||||
A `package` artifact is built and **published to the mesh's registry for that ecosystem**, under the
|
||||
version its own project file declares. Another module then depends on it the ordinary way:
|
||||
|
||||
```
|
||||
"@novox/plex-client": "^2.0.0"
|
||||
```
|
||||
A `package` artifact, in Go or TypeScript, is built from the trunk and **published to the mesh's registry seat for its
|
||||
language** under its next version, `<series>.<n>`, which the builder counts (ADR 0606 §1 to §3); a branch's build is a
|
||||
tagged version, taken only by a build that asks its tag (ADR 0607). Another module then depends on it the ordinary
|
||||
way, accepting a package series by its manifest's `packages`.
|
||||
|
||||
**Why this belongs to modules rather than being a separate thing.** A client for a piece of software
|
||||
changes when that software changes, and the module that owns the software is the only thing that
|
||||
knows. Putting the Plex client anywhere else is the shared-library disease with extra steps.
|
||||
|
||||
### What this leaves open
|
||||
|
||||
- **Which private registry.** That there *is* one is settled —
|
||||
ADR 0014 says each module consumes its dependencies
|
||||
from the private registry, the mesh's own shared library included. Which software serves it is
|
||||
not: the catalogue holds `verdaccio`, and a git host usually serves package registries too.
|
||||
- **And the bootstrap does not have one.** ADR 0014 assumes a registry exists; on a fresh mesh
|
||||
nothing has installed one when the first SDK is built. That is the same pivot as everything else
|
||||
and it has not been designed.
|
||||
- **Who may publish.** A builder pushing a package needs an account on that registry, which is a
|
||||
credential in the bootstrap path and does not exist yet.
|
||||
- **Versions and ranges.** Everything else the mesh delivers is pinned by digest, and a range is
|
||||
resolved at build time from whatever the registry holds. A lock file records what was chosen, and
|
||||
the builder records that as the build edge — but *a mesh that can rebuild a commit and get a
|
||||
different library* is a real change from how everything else here works, and it should be a
|
||||
decision rather than a consequence.
|
||||
What design 20 once left open here is decided: the registries are the registry seats (ADR 0605), who publishes is the
|
||||
build module of the package's language, and a build resolves a series, never a range, recorded as its build edge
|
||||
(ADR 0606, ADR 0573).
|
||||
|
||||
## A file and the thing that reads it are guarded by a gate
|
||||
|
||||
@@ -189,47 +266,59 @@ knows. Putting the Plex client anywhere else is the shared-library disease with
|
||||
|
||||
The apply is not a transaction: every resource is attempted, every failure reported, and only a
|
||||
failed gate stops what follows it (ADR 0053).
|
||||
So a push can half-happen, and the pairing that matters is a configuration file beside the
|
||||
So a send can half-happen, and the pairing that matters is a configuration file beside the
|
||||
service that reads it. A module keeps that pair correct by declaring them in this order: the file;
|
||||
a `run-once` container that validates it; the service, with `restart-on` naming the file. A file
|
||||
the validator refuses reaches the disk and nothing else — the gate fails, the service after it is
|
||||
left as it was, and the machine reports the push failed. The service never serves what the
|
||||
validator refused. That is the grouping, made from what the manifest already has; no primitive
|
||||
a run-once step (a `run-once` container or process) that validates it; the service, with `restart-on` naming the
|
||||
file. A file the validating step refuses reaches the disk and nothing else — the gate fails, the service after it is
|
||||
left as it was, and the machine reports the send failed. The service never serves what the
|
||||
step refused. That is the grouping, made from what the manifest already has; no primitive
|
||||
withholds a file from the disk, and a service that reads its file live rather than at start is the
|
||||
one shape this does not protect, and should not be written.
|
||||
|
||||
*How it is checked:* the lab's coupled-pair spike declares exactly this pair, pushes a refused
|
||||
*How it is checked:* the lab's coupled-pair spike declares exactly this pair, sends a refused
|
||||
file, and asserts the file on disk is the new one, the service serves the old one, and the machine
|
||||
reports the push failed.
|
||||
reports the send failed.
|
||||
|
||||
A `run-once` step may itself name what it reads under `restart-on`; for a step the word means
|
||||
A run-once step may itself name what it reads under `restart-on`; for a run-once step the word means
|
||||
*run again* — a step that fetches a fact from a provider names the binding it reads, and runs
|
||||
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). *How it is
|
||||
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.
|
||||
checked:* the node-engine's unit tests run a run-once step again when its named file changed and not otherwise,
|
||||
and recreate a container naming a run-once step after it ran.
|
||||
|
||||
## A recurring step may hold its own module's containers still
|
||||
## A scheduled run may hold its own module's containers still
|
||||
|
||||
*Written 2026-10-02, from ADR 0189
|
||||
and [issue 108](../../04-ISSUES/108-the-registry-has-no-garbage-collection-once-it-has-two-doors/00-report.md).*
|
||||
|
||||
Some work cannot be done underneath a running service: an artifact store's collector rollouts the
|
||||
storage and requires every writer stopped. A `run-once` step runs *beside* containers and a
|
||||
scheduled one is the same container again, so until this a module had no way to say it — and the
|
||||
storage and requires every writer stopped. A run-once step runs *beside* containers and a
|
||||
scheduled run is the same container again, so until this a module had no way to say it — and the
|
||||
mesh inherited a store that has never collected anything, because the predecessor said it with a
|
||||
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 node-engine stops before the run and starts again after it, in the reverse order, **whatever the step
|
||||
A scheduled container may name `while-stopped`: resource ids of **its own module's** containers, which
|
||||
the node-engine stops before the run and starts again after it, in the reverse order, **whatever the run
|
||||
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
|
||||
only, because a module that could quiesce a neighbour could stop the mesh; scheduled runs only,
|
||||
because at apply the declaration is applied in order and a run-once 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 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.
|
||||
run that **failed**, the reverse order for several containers, and a service left down said
|
||||
loudly. The manifest validator refuses, from the manifest alone, a window with no schedule, one on a
|
||||
run-once container, one naming a container the module does not declare, and one naming itself.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- A manifest written by this design's tables is accepted by the manifest validator, built by the builder and composed
|
||||
by the node-engine for a machine, with no form one of them refuses: checked by the showcase module's check on every
|
||||
change to it.
|
||||
- Every refusal names the module, the part and the field, where its author reads it: the change's check.
|
||||
|
||||
## Milestones
|
||||
|
||||
None of its own. This design is the guide every module's change follows; a change to what a manifest may say is a
|
||||
decision and a change of the manifest validator, the builder or the node-engine first, and this design follows it.
|
||||
|
||||
@@ -65,14 +65,14 @@ and waits — which looks exactly like a builder with no work.
|
||||
|
||||
**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.
|
||||
catalog be missing from a test for weeks without anything complaining.
|
||||
|
||||
| # | step | what happens | why it is here |
|
||||
|---|---|---|---|
|
||||
| 13 | the shared base is built | the toolchain and runtime every module with code of its own stands on | nothing else with code can be built until it exists |
|
||||
| 14 | a store module is built and run | a database **provider**, which the foundation's store is not | the foundation's store is the controller's own memory, and offers nothing to anything |
|
||||
| 15 | the catalogue is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
|
||||
| 16 | the catalogue asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalogue itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
|
||||
| 15 | the catalog is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
|
||||
| 16 | the catalog asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalog itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
|
||||
| 17 | the controller is rebuilt from its own repository | and rolled out through the module path | the moment the mesh stops depending on the installer for anything |
|
||||
| 18 | `mesh-wireguard` is assigned **and the node placed** | a private network | assigning installs the module; placing says where this machine is on it. Both, or the machine has no peers. Assigned by its own name since ADR 0226 retired the `networking` bundle |
|
||||
| 19 | the packet filter is assigned | rules generated from what modules declared | until this, every rule the mesh computes has never been applied to anything |
|
||||
|
||||
@@ -44,7 +44,7 @@ after the phases that change its build path are in — not before.
|
||||
|
||||
**Why here.** The Go controller and the TypeScript SDK disagree about what a grant carries
|
||||
(`consumer` is the module in one, the node in the other). That is exercised by the installer's own
|
||||
provisioning — the catalogue's database — so it belongs before more is built on it.
|
||||
provisioning — the catalog's database — so it belongs before more is built on it.
|
||||
|
||||
- [x] 1.1 the drift was not live — inspection showed the wire agrees (contributions file; envelope
|
||||
required headers). The dead types that disagreed are removed, ADR 0074 and doc 19 corrected
|
||||
@@ -91,7 +91,7 @@ a protocol that agrees, a registry to publish to. Issue 051.
|
||||
the controller's records and the database of each module that asks for one. *(Adopted in place: the module's
|
||||
server names the same container and the same pinned upstream image the foundation runs, so the
|
||||
applier reconciles it rather than raising a second postgres. Proven 22/22 in the one-node lab —
|
||||
the catalogue, a `postgres-database` consumer, gets its database from it. Follow-up: the store
|
||||
the catalog, a `postgres-database` consumer, gets its database from it. Follow-up: the store
|
||||
binds `0.0.0.0` from genesis so a mesh consumer can reach it, but the packet filter is
|
||||
installed later — a brief pre-filter window where `mesh-store` is open before `from: mesh`
|
||||
clamps it; bring the filter up earlier or bind narrower at genesis.)*
|
||||
|
||||
@@ -11,7 +11,7 @@ decisions:
|
||||
|
||||
# 23 — Choosing a provider
|
||||
|
||||
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's internal/catalogue/resolve.go implements the model described here, and designs 26, 27 and 29 rely on it. Not yet checked against this text clause by clause, nor given an as-is: issue 1074.
|
||||
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's `internal/catalogue/resolve.go` implements the model described here, and designs 26, 27 and 29 rely on it. Not yet checked against this text clause by clause, nor given an as-is: issue 1074.
|
||||
|
||||
A provision is named for what the consumer's code is coupled to
|
||||
(ADR 0027):
|
||||
|
||||
@@ -185,7 +185,7 @@ recovery addresses both alike.
|
||||
|
||||
### A provider with one credential
|
||||
|
||||
*Decided 2026-10-01 (ADR 0158); the controller's half built the same day (mesh-controller PR 184): the offer's word, the need carrying the shared secret's name, the vault's one value under one generation stamp, remade for every holder on a later binding or a rotation, the rotate command sending every holder. What remains is each provider's definition saying `credential` and `taken`, with a start that applies the file — the media catalogue's work.*
|
||||
*Decided 2026-10-01 (ADR 0158); the controller's half built the same day (mesh-controller PR 184): the offer's word, the need carrying the shared secret's name, the vault's one value under one generation stamp, remade for every holder on a later binding or a rotation, the rotate command sending every holder. What remains is each provider's definition saying `credential` and `taken`, with a start that applies the file — the media catalog's work.*
|
||||
|
||||
Software that holds one credential — a download client's web password, an indexer's one API key —
|
||||
cannot give each consumer a login, so ADR 0048's form does not fit it and its values were accepted
|
||||
@@ -202,7 +202,7 @@ acceptance. *How it is checked:* the rows of ADR 0158's table; the controller's
|
||||
### A credential the provider's software issued
|
||||
|
||||
*Decided 2026-10-12 (ADR 0305, issue 526), accepted on the operator's approval the same day; built on
|
||||
branches in the controller and the media catalogue.*
|
||||
branches in the controller and the media catalog.*
|
||||
|
||||
Some software is given its one credential rather than told it: a media server signs in to its vendor and
|
||||
keeps the token the vendor issued. The mesh can neither make nor rotate such a value, and a copy typed for
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 25
|
||||
milestone: bindings-first-view
|
||||
updated: 2026-11-11
|
||||
code: [mesh-contracts seats/mesh-broker, mesh-sdk go/seatlist, mesh-catalog modules/nats, mesh-delivery modules/mesh-grants]
|
||||
decisions:
|
||||
- "ADR 0595"
|
||||
- "ADR 0615"
|
||||
- "ADR 0621"
|
||||
- "ADR 0596"
|
||||
---
|
||||
|
||||
# Design 25, milestone bindings-first-view — the grants part asks the bus module for every binding
|
||||
|
||||
**What it builds.** The grants part writes a module's binding (the names on the bus of its bus-state entries) into
|
||||
its membership when it hears the bus module's `created`, rewrites it on `recreated` and removes it on `dropped`
|
||||
(design 25 bus-state §4; ADR 0595 §1, §7). Until now those live events were the binding's only source, so an event
|
||||
the grants part never heard lost the binding for good: on 2026-11-11 a registry holder's membership lacked three of
|
||||
the entries its manifest asks for, although the bus held all three, and about 190 publishes since 2026-11-09 were
|
||||
never said `published` (issue 1966). This milestone gives the grants part a first view of every binding from the
|
||||
owner of that data, the seat `mesh-broker`, at every start and when asked again by hand, so a missed event is
|
||||
recovered with nobody's hand. The live events stay the way a binding changes between starts.
|
||||
|
||||
**Where it comes from.** The operator, 2026-11-11 (about 03:35 CET, in the terminal), chose "Ask the bus module":
|
||||
at every start, and on a re-ask, the grants part takes a first view from the bus module's seat of the entries it
|
||||
holds and composes the bindings from it; the owner answers for its own data. Within that answer, these lines follow
|
||||
ADR 0595 §1 and §7 (the binding is the bus module's to say and the grants part's to write), ADR 0596 §2 (another
|
||||
module's data is asked through its seat, never read from its bus state) and the first-view rules of ADR 0615 §3 to
|
||||
§6, which decide them for the indexer's and the assigner's state and which this milestone applies, as the design's
|
||||
choice, to the bus module's. Each choice below says so. The operator, 2026-11-11 13:30 CET (in the terminal, to lead-47), asked whether
|
||||
the grants part should take a bucket's binding from the bus module's existing `list` answer when the new field
|
||||
`binding` is missing, to break the circle §7 names, answered "Yes, build it": §9.
|
||||
|
||||
**Cited as** "design 25 bindings-first-view §n". It amends design 25 bus-state §4, which says so.
|
||||
|
||||
## The lines
|
||||
|
||||
- §1 **The first view is the seat `mesh-broker`'s `list`, asked with no `module`.** No new verb: `list` already
|
||||
answers every entry the bus module created, from its own state `asked`, by module and local name, and lacks only
|
||||
the binding (§2); a second verb answering the same entries would be a second form of one fact. The grants part
|
||||
declares `seat:mesh-broker.list` in its manifest's `invokes`, and composes its own grant of it as it composes any
|
||||
invoker's. `list` names no class, so from milestone G of design 47 it is a `read` and takes that class's callers,
|
||||
invokers, persons and sessions (design 47 G §391); until then its callers are "any caller whose role grants it",
|
||||
which the grants part's invoke grant meets. The verb stays answered where the bus module's tools answer it today,
|
||||
by its tools bundle in the tool runner of the machine holding the seat, and gains no runtime of its own: ADR 0621
|
||||
§1 gives the indexer's and the assigner's first views a runtime of their own so that the tool runner never asks
|
||||
through itself, and here the asker is the grants part, which composes every account without this answer (§5), so
|
||||
no part waits on itself. Why: the design's choice under the operator's answer and ADR 0596 §2. How it is checked:
|
||||
§8, the whole case.
|
||||
- §2 **Each entry of `list`'s answer carries its binding.** The seat's `Held` gains the field `binding`, of the
|
||||
seat's own form `Binding`, the same value the bus module's `created` and `recreated` carry for that entry in their
|
||||
field `binding`, taken from the entry's plan in the bus module's state `asked`. **`Binding` is reshaped to the form
|
||||
on the wire.** Today the seat's definition says `Binding` is a bus name, a storage type, a template and a stream's
|
||||
subjects, while the bus module sends, and the grants part needs, mesh-sdk's membership binding: `local` (the local
|
||||
name), `storage`, `template`, `stream` and `subject` (a stream's, and a ledger's history's subject), `bucket`,
|
||||
`object-store`, and for a ledger `history`, `state`, `indexes` (each index's bucket by its name), `snapshot` and
|
||||
`types`, which `created` and `recreated` carry and only `dropped` clears (corrected 2026-11-11, develop question D-11 of design 47's milestone G: an earlier wording had the bus module's events leave them empty). The
|
||||
definition's `Binding` takes exactly those fields, with those names and forms, so `created`, `recreated` and
|
||||
`list`'s `binding` carry one form, the generated contract decodes what the bus module sends, and no byte changes
|
||||
on the wire; the fields design 47 G §399 adds come on top of it. Why: the design's choice following this line's
|
||||
own words (one value in the events and the answer), met by the developer on 2026-11-11 and directed by lead-47. It is present on every entry whose objects the bus holds (the
|
||||
entries whose answer carries `settings`) and absent on one it does not hold (waiting to be made, or refused), as a
|
||||
refused create hands no binding (design 25 bus-state §4). `show` and `orphans`, which answer `Held` too, carry it
|
||||
alike. A field G's part adds to `Binding` (design 47 G §399: `objects` and `sent-objects`) reaches `list`'s answer
|
||||
with it, since the form is one. What changes: the seat's definition in mesh-contracts (`seats/mesh-broker`), its
|
||||
generated Go contract, and mesh-sdk's bootstrap copy of the definition in `go/seatlist`, which its test against
|
||||
mesh-contracts holds equal; the bus module's tools fill the field. **The size**: 18 entries in the new form are
|
||||
asked by the manifests on main (counted 2026-11-11), so the answer is a few KiB, an estimate; it stays one reply
|
||||
well under the bus's largest message (1 MiB) while the entries number in the hundreds, and from G a reply over that
|
||||
limit goes as an object by design 47 G §407. Why: the design's choice. How it is checked: unit (mesh-contracts' generated contract), a
|
||||
`created` body as the bus module's keeper writes it, a ledger's among them, decodes into the generated type and
|
||||
encodes back to the same bytes; unit (the bus module's
|
||||
tools), a row where `list`'s `binding` for an entry equals the `binding` its `created` carried, and a row where an
|
||||
entry not held has none; the seat's fixtures.
|
||||
- §3 **It is asked at every start and when asked again by hand, never on a timer.** The grants part's process asks
|
||||
`list` once at each start, as it asks its other first views (design 25 bus-state §5), and again when a person
|
||||
calls `grants_first_view_again` with `state` `bindings`, or with no `state`, which asks every first view (§6).
|
||||
Nothing else asks it: no timer, and no event, since the bus module's events carry the indexer's revision of the
|
||||
verdict they acted on and no revision of the bus module's own, so a gap in them cannot be seen (ADR 0615 §5 asks
|
||||
again on a gap only where one can). **At a start the answer comes first**: the grants part takes no event from its
|
||||
durable consumer of `mesh-broker.created`, `recreated` and `dropped` until the answer is applied, or until the
|
||||
bound of §5 passes. Since the shared stream keeps the events in order and each event sets or removes one entry's
|
||||
whole binding, the events held then, taken after the answer, leave each binding as the last word on it said,
|
||||
whichever came later. Why: the design's choice under ADR 0615 §4 to §6. How it is checked: §8.
|
||||
- §4 **How the answer is applied.** For each entry of the answer that carries a binding, the grants part writes the
|
||||
binding into the module's membership on every machine the module is assigned, kept in its state `memberships` as
|
||||
on `created`, where it holds none, and rewrites it, as on `recreated`, where it holds a different one; where it
|
||||
holds the same, it writes nothing. Each binding written or rewritten is said in its log, naming the module, the
|
||||
local name and the first view as the cause, and recomposes that module's memberships as an event's would. An entry
|
||||
listed without a binding (§2) changes nothing the grants part holds for it, but for an entry the bus holds in an
|
||||
answer of a bus module's build from before this milestone, which carries no `binding` on any entry: §9 composes
|
||||
that binding, and it is then applied by this line as one the answer carried. **A binding the bus no longer
|
||||
lists is kept, never removed by a first view**: the grants part removes a binding only on `dropped` (ADR 0595 §1,
|
||||
§7), so a bus module whose own state `asked` was lost or rebuilt, and answers few entries or none, strips no
|
||||
module of its data. Instead it raises the condition `mesh-grants.binding-unlisted`, one per such binding, naming
|
||||
the module and the local name and the operator's step (read the entry with the seat's `show`; an entry its
|
||||
manifest no longer asks for leaves by the bus module's `drop`, whose `dropped` removes the binding); a later answer
|
||||
that lists the entry, or a `dropped` for it, clears it. **On a re-ask the events keep flowing**: an answer is
|
||||
applied only to entries no `created`, `recreated` or `dropped` named since that ask was sent, so an answer never
|
||||
writes over a newer event. Why: the design's choice under ADR 0595 §7, and ADR 0615 §6's rule that a reader keeps
|
||||
what it holds rather than taking an empty answer as the truth. How it is checked: §8.
|
||||
- §5 **While the bus module does not answer, the grants part composes the rest and says so.** It composes every
|
||||
account and keeps every binding it holds in `memberships`, as it does today; only a binding it never heard waits.
|
||||
Past two minutes without an answer, the bound of its other first views (design 25 bus-state §5), it raises its
|
||||
condition `mesh-grants.first-view-unanswered` naming the seat `mesh-broker` and the verb `list`, as that condition
|
||||
names the owner and the tool of its other first views, and it then takes the events from its durable consumer as
|
||||
they come, so the live path never waits on the first view. It keeps asking, from 10 seconds, doubling, at most
|
||||
every 5 minutes, the backoff the bus module keeps for its own first view (design 25 bus-state §1); the answer
|
||||
clears the condition. This form holds until milestone G of design 47 is delivered; from then the grants part, one
|
||||
of G's observers (design 47 G §387), writes `first-view-unanswered` and `binding-unlisted` (§4) as findings in its
|
||||
state `findings`, said as `finding-written` and `finding-cleared` on its finding stream (design 47 G §383) and
|
||||
stated at its start (§384), naming the same things. Why: the design's choice under ADR 0615 §6. How it is checked:
|
||||
§8; from G, G's own checks of §383 and §387.
|
||||
- §6 **The tools that read and steer it.** `grants_first_views` shows the first view `bindings` beside the others:
|
||||
the seat and verb (`mesh-broker.list`), when it was asked and answered, how many entries the answer listed, how
|
||||
many bindings it wrote and rewrote, how many of those it composed by §9 and the entries §9 left unbound (by module,
|
||||
local name and storage type), the bindings it found unlisted, how many asks and hand acts, and whether its
|
||||
condition is raised, kept in the grants part's own state `user-list` under the key `first-views` as the others are.
|
||||
`grants_first_view_again` takes `bindings` among its `state` values, by a person's act with why, recorded as a hand
|
||||
act; its description names it. From G these two tools are served by mesh-sdk's `go/views` as every part's are
|
||||
(design 47 G §245), with `bindings` among the first views they show. Why: tools-are-how-a-module-is-spoken-to. How
|
||||
it is checked: unit (grants part's tools), `bindings` shown and asked again with its hand act recorded.
|
||||
- §7 **What exists today, and its fate.** The live-event path stays whole (design 25 bus-state §4). The bus module's
|
||||
`list`, `show` and `orphans` keep every field they answer and gain `binding`. The grants part reads no other
|
||||
module's bus state and gains no grant on the shared stream `EVENTS` beyond its durable consumer of the three events:
|
||||
a missed event is recovered by asking its owner, never by reading the stream back by hand. **The order, and what
|
||||
the first wording missed.** This line first said the order between the repositories needs no edge of the module
|
||||
graph, because a grants part meeting an answer without `binding` wrote nothing from it and kept working on the
|
||||
events. It missed that the bus module's own build goes through the registry (corrected 2026-11-11, issue 1966): its
|
||||
image build asks the seat `container-image-registry`'s `mirror` for its base image, which the registry holder
|
||||
serves only with its state `mirrors`, a binding whose `created` the grants part never heard; so the bus module's
|
||||
new build could not be built while the grants part waited for that build's `binding`, a circle. §9 breaks it: the
|
||||
grants part composes the key-value and object-store bindings from the running bus module's answer, so it reaches
|
||||
the machines in a delivery of the grants part (mesh-delivery) alone, with no new build of the bus module (the
|
||||
operator, 2026-11-11 13:30 CET, in the terminal: "Yes, build it"). The order is then the grants part with §9
|
||||
before the bus module's build of this milestone, which mesh-contracts, mesh-sdk and the nats module (mesh-catalog)
|
||||
carry; the edges between the grants part and the bus module otherwise lie on the foundation's cycle, where ADR 0615
|
||||
§2 has the owner's answer and the reader's wait carry the order.
|
||||
- §8 **How it is checked.** **Whole**: a case in the grants part's existing whole-module integration test (mesh-delivery
|
||||
is over its integration budget, so no new test function): a fixture bus module answers `mesh-broker.list` with an
|
||||
entry whose `created` was never said, and after the grants part starts, the module's membership holds that
|
||||
entry's binding; a binding the grants part holds that the answer does not list stays and raises
|
||||
`mesh-grants.binding-unlisted`; with `list` unanswered, the part composes every account, raises
|
||||
`mesh-grants.first-view-unanswered` naming `mesh-broker` and `list` past its bound, and applies a `created` heard
|
||||
meanwhile; an answer that comes after a `dropped` heard since the ask does not write the dropped binding back.
|
||||
**Unit**: the bus module's tools (§2), the grants part's tools (§6), and a table of the grants part's reconcile
|
||||
with one row per case of §4. **Live**: after the delivery, at the grants part's first start, the registry holder's
|
||||
membership names every entry its manifest asks for (`grants_membership`), and `grants_first_views` shows
|
||||
`bindings` answered with no binding unlisted. **§9's cases**: unit rows in the same reconcile table, one per
|
||||
case of §9 (a key-value and an object-store entry composed; a stream and a ledger entry left unbound and logged;
|
||||
an entry without `settings` left unbound; an entry whose `settings` lack the bus object named from its `bus-name`
|
||||
left unbound and logged; an answer carrying `binding` taken as it is, nothing composed). **§9 live**: after the
|
||||
delivery of the grants part alone, before any new build of the bus module, `grants_membership` for the registry
|
||||
holder on the machine it runs on lists `holds`, `windows` and `mirrors` beside the entries it held, and
|
||||
`grants_first_views` shows `bindings` answered with those three composed by §9.
|
||||
- §9 **An answer without `binding` is composed from the bus module's own fields, for the storage types it names
|
||||
whole.** A bus module's build from before this milestone answers `list` with no `binding` on any entry; each entry
|
||||
still carries its `module`, `local-name`, `bus-name`, `storage-type`, `template`, `options`, `fill`, and `settings`
|
||||
keyed by the bus object's name (a key-value bucket's stream as `KV_` and its bus name, an object store's as `OBJ_`
|
||||
and its bus name, a stream by its own name). For an entry of such an answer the grants part composes the binding
|
||||
only when its `settings` hold the bus object its `bus-name` names (so the bus holds it, as §2 has `binding`
|
||||
present only on a held entry), and only for the storage types whose binding the answer names whole: for
|
||||
`key-value`, `local` from `local-name`, `storage` from `storage-type`, `template` from `template` and `bucket` from
|
||||
`bus-name`; for `object-store`, the same with `object-store` from `bus-name`. It composes nothing for a `stream`
|
||||
(the answer does not carry its subjects) or a `ledger` (it does not carry its parts), nor for an entry whose
|
||||
`settings` lack its bus object: those are left as today, unbound until the bus module's `created` or `recreated`,
|
||||
or a later answer that carries `binding`. An answer is taken as one without `binding` only when no entry in it
|
||||
carries the field; an answer where any entry carries it is applied by §4 alone, nothing composed. **It is the bus
|
||||
module's own word**: every field the binding takes is the bus module's answer about what it holds, from its seat,
|
||||
and the grants part adds no name and reads no bus state of its own, so ADR 0595 §1 and §7 (the binding is the bus
|
||||
module's to say and the grants part's to write) and ADR 0596 §2 hold. **How it is said**: once per answer, a log
|
||||
line of the grants part naming the seat `mesh-broker` and the verb `list`, that the answer carries no `binding`,
|
||||
how many bindings it composed and every entry it left unbound by module, local name and storage type; each
|
||||
binding then written is logged by §4 with the cause "first view, composed from `list`'s fields". `grants_first_views`
|
||||
shows the same (§6). **Its fate**: it stays after the bus module's build of this milestone is everywhere, since it
|
||||
acts only on an answer no entry of which carries `binding`, which that build never gives, and covers a bus module
|
||||
held back or set back to an earlier build. Why: the operator's answer of 2026-11-11 13:30 CET (in the terminal,
|
||||
to lead-47, for issue 1966), under ADR 0595 §1 and §7; the fields, the storage types and the log are the design's
|
||||
choice following that answer and the bus module's `list` answer as it stands today (issue 1966, read live
|
||||
2026-11-11). How it is checked: §8, §9's cases and §9 live.
|
||||
@@ -0,0 +1,406 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 25
|
||||
milestone: bus-state
|
||||
updated: 2026-11-11
|
||||
code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog modules/nats]
|
||||
decisions:
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0597"
|
||||
- "ADR 0259"
|
||||
- "ADR 0363"
|
||||
- "ADR 0386"
|
||||
- "ADR 0387"
|
||||
- "ADR 0436"
|
||||
- "ADR 0564"
|
||||
- "ADR 0578"
|
||||
- "ADR 0603"
|
||||
- "ADR 0612"
|
||||
- "ADR 0613"
|
||||
- "ADR 0614"
|
||||
- "ADR 0615"
|
||||
- "ADR 0619"
|
||||
- "ADR 0620"
|
||||
- "ADR 0621"
|
||||
- "ADR 0622"
|
||||
- "ADR 0625"
|
||||
- "ADR 0626"
|
||||
- "ADR 0627"
|
||||
- "ADR 0631"
|
||||
---
|
||||
|
||||
# Design 25, milestone bus-state — what a module keeps on the bus, created by the bus module
|
||||
|
||||
**What it builds.** The lines of [design 25](../25-the-bus-on-nats.md) § 12 that are built under issue 1966: who
|
||||
creates a module's bus state and how the module receives its binding (§ 12.2), the seat `mesh-broker`'s verbs,
|
||||
events and fill (§ 12.3), the ledger (§ 12.4), the calls (§ 12.5) and the order of the build and the move (§ 12.6).
|
||||
The three layers and the templates are the design's main file (§ 12.1), and so is how each rule is checked
|
||||
(§ 12.7). The sections keep their numbers, so "design 25 § 12.2" still names them. *Moved here from the
|
||||
main file on 2026-11-11, when the main file was put in the shape of playbook 02, and numbered; their words are
|
||||
unchanged but for the note on bus-state §4 that design 25 bindings-first-view amends it.*
|
||||
|
||||
**How a line is cited.** Each statement is cited as "design 25 bus-state §n". A bare "§n" inside a statement is a
|
||||
clause of the record that statement cites, as it was before the move; "§ 12.n" is a section of this design.
|
||||
|
||||
### 12.2 Who creates it, and how a module receives it
|
||||
|
||||
- §1 **The trigger is the manifest validator's verdict** (ADR 0595 §4), heard as the indexer's event `manifest-accepted`, which
|
||||
carries the module's entries whole, so the bus module never reads the indexer's state and never polls. The indexer
|
||||
says it for every newly accepted manifest, with its entries empty when it asks for none, and the bus module reads an
|
||||
empty one as "this module asks nothing now", which is what its orphans need. A module whose manifest leaves the
|
||||
trunk (its directory deleted) gets an empty `manifest-accepted` too, so its entries reach `orphans` and raise
|
||||
`orphaned`; a manifest the manifest validator set aside to wait gets none, so its entries stay asked. The indexer's tool
|
||||
`indexer_say_accepted` says `manifest-accepted` again for one module by hand, from the verdict it holds, recorded as a
|
||||
hand act, for a verdict the bus module is thought to have missed. It goes, as every event of the
|
||||
indexer, on the module events stream (ADR 0363) under the indexer's own subjects. A publish of it that fails is
|
||||
published again, and one still failing writes the indexer's finding naming the module, cleared once said (today
|
||||
the indexer only logs it). It hears the verdicts on a durable consumer, so one given while it was down is heard
|
||||
when it is back. **Its first view** at start is the indexer's
|
||||
tool `indexer_bus_state` (every accepted manifest's bus-state entries, by module and digest), asked once; the
|
||||
events keep it current after that, and a stream that keeps a verdict only for its age is never its source. A
|
||||
verdict replayed from its consumer is applied only if newer than what the first view answered for that module, by
|
||||
the indexer's revision alone (a digest has no order), so a replay never takes an entry back. An `indexer_bus_state`
|
||||
that does not answer makes the bus module's health unhealthy, naming the verb, and is asked again on a bounded
|
||||
backoff (from 10 seconds, doubling, at most every 5 minutes); nothing is created or orphaned meanwhile. Beside the
|
||||
backoff, the bus module's tool `nats_first_view_again` asks `indexer_bus_state` at once by hand, recorded as a hand
|
||||
act. It keeps
|
||||
what it last heard per module in its own state `asked` (an old `state` section, its own kept data staying there,
|
||||
ADR 0595 §10), from which `list`, `check` and `orphans` answer; a key goes only on an empty `manifest-accepted` for
|
||||
the module, never because a manifest the manifest validator set aside to wait is missing from the first view. Then it runs `check` over what it holds, creating what
|
||||
is missing. The manifest validator checks every entry once, at the merge check and at take-in (ADR 0578; ADR 0595 §8), and also
|
||||
each ledger type's operations: a set-entry or a remove names `into` and `key`, `fields` and an index's `on` name
|
||||
fields of the schema, and `digest-of` names a text field.
|
||||
- §2 **A module that asks bus state waits for the bus module** (ADR 0614 §5). Each bus-state entry of a manifest is a
|
||||
requirement on the provision `bus-state`, which the bus module's manifest provides; the module graph reads it as any
|
||||
requirement, so the assigner holds a consumer's new build until a build of the bus module that offers `bus-state`
|
||||
runs healthy (ADR 0603 §5). That each entry exists before its user starts is the bus module's own work, above, not
|
||||
an edge.
|
||||
- §3 **The bus module has a process of its own** beside its tool bundle, with a declared health check. Its credential
|
||||
creates streams and buckets under modules' names only, and deletes only through `drop` and `recreate`. Its health
|
||||
turns unhealthy, naming the cause, when an accepted verdict has waited on its durable consumer, unanswered, for
|
||||
more than three minutes, or when its last pass over fill, which runs every minute, is more than three minutes old, so the only guard against a store filling never stops without a sound; another module's entry past the raise mark
|
||||
raises that module's condition and leaves the bus module healthy. **Its process has a process account of its own,
|
||||
granted more than any other process's** (ADR 0626 §1; ADR 0436 §1, §2). The process declares `bus` as any process
|
||||
may, with one key more, `keeps`, which is allowed on this process alone; the grants part composes the account from
|
||||
that declaration as it composes every process account, and the vault mints it as the module's own secret. Beyond
|
||||
what a process's `bus` grants, `keeps` grants the bus's create and update subjects for streams, buckets and object
|
||||
stores under modules' names only (`<module>_*`), the snapshot request for an entry before `recreate` or `drop` with
|
||||
its chunks' acknowledgement subjects and its own inbox, and the delete subjects for those two acts. It is granted
|
||||
no subject on which a tool or a seat's verb is answered (ADR 0436 §3), and no subscription to any restricted
|
||||
stream's own subjects: the snapshot is the one way a restricted stream's data reaches it, kept only in the bus's
|
||||
snapshot store, and the process reads no message of it. No other account holds those subjects, the tool runner's
|
||||
included. **The snapshot account stays as it is** (ADR 0626 §2, § 3 above). **The manifest validator refuses any other bus
|
||||
declaration of the bus module** (ADR 0626 §3): anything it would say or hear on the bus beyond its process's `bus`
|
||||
and the bus state the foundation keeps on the old sections (ADR 0595 §10), `keeps` on any other process or module,
|
||||
and a `bus` of its process that names a subject on which a tool or a verb is answered.
|
||||
- §4 **The binding travels on the bus module's events** (ADR 0595 §1, §7, as their progressive insight of 2026-11-03
|
||||
has it, waiting for the operator's Approve). The grants part, which issues every membership, hears them on a durable
|
||||
consumer: it writes the **binding** (the names on the bus) into the module's membership on `created`, on every
|
||||
machine the module is assigned, rewrites it on `recreated` and removes it on `dropped`, kept in its own state
|
||||
`memberships`, so no part writes another's state and a binding outlives the 7 days its event is kept on the shared stream (§ 12.3).
|
||||
*Amended 2026-11-11 by design 25 bindings-first-view §1 to §5*: the events are no longer the binding's only
|
||||
source; at every start, and when asked again by hand, the grants part also takes a first view of every binding
|
||||
from the seat `mesh-broker`'s `list`, so a missed event loses no binding for good. A module whose binding is missing at start fails loudly, naming the entry; it never starts blind. A `refused`
|
||||
create hands no binding. **A refused or overdue creation is loud**: overdue means no `created` or `refused` within
|
||||
five minutes of an accepted verdict that asks for the entry. The condition is raised by the watchman, which today raises the manifest validator's
|
||||
refusals as findings and gains, through its `consumes`, the accepted verdicts and the seat `mesh-broker`'s `created`
|
||||
and `refused`; never by the bus module, which cannot raise it while it is down; it names the module
|
||||
and the entry; a later `created` clears it, and so does a later accepted verdict of that module that no longer asks
|
||||
for the entry.
|
||||
- §5 **A foundation part's first view of another's state is a tool its owner answers in a runtime of its own** (ADR 0615,
|
||||
ADR 0621). The four
|
||||
foundation parts that read each other (the grants part, the tool runner, the indexer and the assigner) stop
|
||||
reading the indexer's and the assigner's bus state in the milestone of ADR 0612, with the other 19 readers of that
|
||||
state (ADR 0615 §1). **Each owner serves them as tools of its own module**, the indexer `manifests`,
|
||||
`modules`, `verdicts` (ADR 0622 §1) and `graph` (ADR 0631 §1), the assigner `placement`, `seat-holders` and `wants`, and declares `runs-as`, so its tools bundle runs in
|
||||
a runtime of its own on the module's own bus account, never in the node's tool runner, and the tool runner never
|
||||
asks through itself (ADR 0621 §1). No new manifest form is added: a process still answers no tool call or seat verb
|
||||
(ADR 0436 §3). **Each verb is granted to exactly its readers** (ADR 0621 §2, as ADR 0615 §3 lists them): `placement`
|
||||
to the grants part, the tool runner and the indexer; `seat-holders` to the tool runner; `wants` to the grants part;
|
||||
`manifests` and `modules` to the grants part, the tool runner and the assigner; `verdicts` to the grants part
|
||||
alone, its `callers` list holding the grants part only (ADR 0622 §1); `graph` to the assigner alone, its `callers`
|
||||
list holding the assigner only, the indexer itself refused, and the assigner's `reads` entry of the indexer's
|
||||
`graph` goes (ADR 0631 §1). These seven are the first-view tools, four of the indexer and three of the assigner.
|
||||
`verdicts` follows the first-view rules whole (ADR 0622 §2, under ADR 0615 §4 to §6): its answer carries the
|
||||
revision, the grants part then follows `manifest-accepted` and `manifest-refused` and asks once more on a gap in
|
||||
their revisions, it raises the condition naming the indexer and `verdicts` past the bound and never reads the
|
||||
indexer's state, and `grants_first_views` and `grants_first_view_again` cover it. `graph` follows them whole too
|
||||
(ADR 0631 §2, under ADR 0615 §4 to §6): its answer carries the revision, the assigner then follows the indexer's
|
||||
`indexer.views` events for the bucket `graph`, which carry the revision, and asks once more on a gap, said in its
|
||||
log; started while the indexer is down, it raises the condition naming the indexer and `graph` past the bound and
|
||||
never reads the indexer's state; and `assigner_first_views` and `assigner_first_view_again` cover it. A reader declares each in `invokes`
|
||||
as `<module>.<tool>` only where it is granted, which is the grant the grants part composes as for any tool, and
|
||||
each tool checks the proved caller against a `callers` list holding exactly its readers and refuses any other, the
|
||||
owner itself included. **The manifest validator of the milestone is its own group's** (ADR 0621 §4): since no field is added,
|
||||
the running manifest validator takes the indexer's and the assigner's manifests in as it does today, and the merge check judges
|
||||
them with the manifest validator built from the group's own head; no release of the manifest validator goes ahead of the milestone. **The
|
||||
owner's runtime answers before any first view of its own** (ADR 0621 §3): at once from its module's own state, as
|
||||
soon as it is connected and before it has asked any other part anything, with the revision of its state it answered at,
|
||||
so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then
|
||||
follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb
|
||||
again on a timer; a gap in revisions makes it ask once more, said in its log (§5). The owners' events are new
|
||||
families of their own, apart from the indexer's `manifest-refused` and `manifest-accepted` and the assigner's
|
||||
`put`: the indexer's `indexer.views` and the assigner's `assigner.views`, one event per changed key with its
|
||||
bucket, key and revision and the key's value whole, or a delete mark for a key removed, so a reader keeps its
|
||||
picture from the events alone and asks the verb only on a gap (ADR 0615 §5). **Both families are declared in
|
||||
their owners' old `streams` sections**, each an entry with `max-age-days` 7 and `max-mib` 256: the old section keeps
|
||||
a stream by limits, which drops its oldest when full (only a work queue refuses the new instead), so a reader that
|
||||
misses a dropped event sees the gap in revisions and asks once more. The indexer's own kept data stays on the old
|
||||
sections (ADR 0595 §10 names it among the foundation); the assigner's goes beside its existing `streams` entry
|
||||
`placement`, under ADR 0619, which extends ADR 0595 §10's foundation to the parts
|
||||
the bus module's own composition, placement, build or delivery needs before it can answer: the assigner, the tool
|
||||
runner, the builder, the checker, mesh-config and the vault. **The size, an estimate**: read on the bus on
|
||||
2026-11-04, the buckets the readers read hold about 3 MiB of current values at the indexer (its manifests about
|
||||
5 KiB a value) and about 1.5 MiB at the assigner (its wants about 0.25 KiB a value). The milestone's one mesh-sdk
|
||||
rebuild rewrites every module's keys, up to three times in its window (design 51 *How many builds*), so a week of
|
||||
it is some 20 MiB on `indexer.views` and some 10 MiB on `assigner.views` with ordinary merges added, each well
|
||||
under its 256 MiB. Each event carries one value whole, the largest today a manifest of tens of KiB, under the bus's
|
||||
largest message, 1 MiB (the bus server's `max_payload`); an owner whose value with its event would pass it emits
|
||||
the key's revision alone, as a gap, so the reader asks the first view once, and the owner writes its finding
|
||||
naming the key.
|
||||
A waiter on an existing event, `manifest-accepted` or the
|
||||
assigner's `put`, never wakes on them. **When an owner's revisions go back**: should an owner's state be rebuilt
|
||||
by the bus module's `recreate`, its revisions start again; a reader that hears a revision lower than the one it
|
||||
holds takes it as a gap and asks the first view again, and keeps what that answer says, whatever revision it held
|
||||
before. A `recreate` that could not keep the data answers empty at revision 0, so the grants part keeps its list
|
||||
in force (below): a login revoked meanwhile stays until the owner's first change, and the grants part's
|
||||
first-view condition stays raised meanwhile, naming the owner, the empty answer and the operator's step: let the
|
||||
owner write its state again from its sources (the indexer by taking each module in again, `indexer_take_in`; the
|
||||
assigner by its next composition of the wants), then ask the grants part's first view again,
|
||||
`grants_first_view_again`. It is cleared by an answer above revision 0. A reader that starts while its owner is down raises a
|
||||
condition naming the owner and the verb past its bound and keeps asking until it answers, never reading the
|
||||
owner's state instead; once it had its first view, it keeps working on what it heard (§6). The bound is longer
|
||||
than an owner's start (two minutes), so a normal start, readers and owners coming up together, notifies nobody;
|
||||
the owner's answer clears the condition. An answer that is empty at revision 0 (an owner that holds nothing yet)
|
||||
is a first view of nothing, never a reason for the grants part to drop a login it holds: it keeps the list in
|
||||
force until an answer at a revision above 0. Each reader's tools say the owner, verb and revision its first view
|
||||
holds, when it asked and when it was answered (a read tool), and ask the first view again by hand (a re-read tool,
|
||||
recorded as a hand act): the grants part's `grants_first_views` and `grants_first_view_again`, the tool runner's
|
||||
`mesh_first_views` and `mesh_first_view_again`, the indexer's `indexer_first_views` and
|
||||
`indexer_first_view_again`, and the assigner's `assigner_first_views` and `assigner_first_view_again`. The tool
|
||||
runner's two are tools of the mesh MCP server, beside `mesh_runtimes`, since the tool runner serves its own state
|
||||
there: the mesh MCP server gains them, and the operator sees them among its tools. **On a fresh mesh** the
|
||||
assigner starts before any new-form bus state exists and needs no binding of the bus module first: its own state
|
||||
and `assigner.views` are on its old sections, which the grants part creates, so it answers the indexer, the grants
|
||||
part and the tool runner their first views while the bus module still waits on it. The order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and,
|
||||
for the four, whose verb edges lie on cycles the graph leaves out (ADR 0614 §3), by the owner's answer at once and
|
||||
the reader's wait (ADR 0615 §2).
|
||||
- §6 **The other 19 readers ask the owners' read tools, one per bucket they read** (ADR 0615 §1, §2). Beside the seven
|
||||
first-view tools above, each owner serves a read tool for every bucket of its state one of the 19 reads today,
|
||||
named `indexer_read_<bucket>` or `assigner_read_<bucket>` and marked as a read: fifteen. The indexer's nine:
|
||||
`indexer_read_manifests`, `indexer_read_modules`, `indexer_read_predefined`, `indexer_read_config_candidates`,
|
||||
`indexer_read_candidates`, `indexer_read_packages`, `indexer_read_graph`, `indexer_read_heard` and
|
||||
`indexer_read_taken`. The assigner's six: `assigner_read_placement`, `assigner_read_seat_holders`,
|
||||
`assigner_read_wants`, `assigner_read_findings`, `assigner_read_asks` and `assigner_read_differences`. These are
|
||||
the nine buckets of the indexer and the six of the assigner the 19 read (§ 12.6), `modules`, `wants` and
|
||||
`placement` among them for the status view; the indexer's tenth, `verdicts`, is read by the grants part alone,
|
||||
through its first-view tool. **They are ordinary module tools**: granted, as any tool is, to every
|
||||
reader that declares it in `invokes`, and callable by a session; the `callers` list of ADR 0621 §2, ADR 0622 §1 and ADR 0631 §1 covers
|
||||
only the seven first-view tools. **The status view keeps all its tools**, their names, arguments and answers
|
||||
unchanged, its `indexer_*` and `assigner_*` tools included: as a reader it answers them by asking the owners' read
|
||||
tools instead of reading their buckets. Checked live after the delivery: a session's call of each status view tool
|
||||
answers, and `mesh_search` finds each owner's read tool by its bucket's words.
|
||||
- §7 **Only the owner reaches it** (ADR 0596 §2). The membership grants the owner's account its own entries and no
|
||||
other module's: the new form has no readers. What another module needs of that data it asks through the owner's
|
||||
tools or seat verbs, and what the owner lets others hear is its declared events, subscribed to through
|
||||
`consumes` (ADR 0363, unchanged). A part watches only its own state, and hears another's events (ADR 0596 §4).
|
||||
|
||||
### 12.3 The seat's verbs, events and fill
|
||||
|
||||
- §8 **The seat gains eight verbs.** The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). It is one of the ten seats
|
||||
defined whole in the milestone of ADR 0612, its contract a package of its own in `mesh-contracts` (ADR 0613 §1,
|
||||
ADR 0627 §1, ADR 0625 §1; design 26 *A seat's definition*), which settles where its definition lives (ADR 0595 §12). An entry's arguments are the module, its local
|
||||
name, its storage type, its template, its options and its configuration; what is held answers the settings held and
|
||||
the fill (count, bytes, the cap); `drop` and `recreate` carry the warrant and why.
|
||||
|
||||
| Verb | Does | Who may call it |
|
||||
|---|---|---|
|
||||
| `list` | every entry, its template, its settings, its fill against its cap | any caller whose role grants it (ADR 0386) |
|
||||
| `show` | one entry, the same | the same |
|
||||
| `check` | compares what the bus holds with what was asked, entry by entry | the same |
|
||||
| `orphans` | entries the bus holds that no manifest asks for; never one whose manifest the manifest validator refused or outgrew, nor one of the foundation's old sections | the same |
|
||||
| `create` | creates one entry now, for an entry an accepted manifest asks for | an act of the bus module's process alone, answered on no subject |
|
||||
| `apply` | applies a difference that only grows: a larger cap, a longer age | the same |
|
||||
| `recreate` | rebuilds an entry whose settings differ in a way that cannot grow, from its data where the storage type allows | the same, on the operator's warrant, which the process verifies itself |
|
||||
| `drop` | removes one orphan | the same, on the operator's warrant, which the process verifies itself |
|
||||
|
||||
- §9 **Four of them are acts of the process alone.** `create`, `apply`, `recreate` and `drop` stay in the seat's definition as acts its holder's process alone performs,
|
||||
answered on no subject (ADR 0626 §4, ADR 0436 §3): they are not verbs whose subjects a credential is granted, but
|
||||
acts on the bus's own create, update and delete subjects, which only the process's account holds, so no module can
|
||||
ask the bus for them. A person asks for them through the bus module's tools, which hand the request to the process
|
||||
through its own state (below); `recreate` and `drop` act only on the operator's warrant (ADR 0259 §6), asked
|
||||
through the operator channel. Before `recreate` or `drop` acts, the process takes a stream snapshot of the entry
|
||||
(ADR 0595 §5), and it acts only once the snapshot is whole. A drifted entry is said, never quietly fixed: only a
|
||||
difference that grows is applied without a person.
|
||||
|
||||
- §10 **Events**: `created`, `refused` (with why), `drifted` and `drift-cleared`, `full` and `full-cleared`, `orphaned`
|
||||
(cleared by a `drop`, or by a manifest asking for the entry again, which emits `created` for it), `recreated` and
|
||||
`dropped` (ADR 0595 §6, as its progressive insight of 2026-11-03 has it, waiting for the operator's Approve);
|
||||
each carries the module, the local name, the bus name, the template, and the settings asked against those held.
|
||||
**They follow every seat's events**: each is said on `mesh.seat.mesh-broker.event.<event>`, on the shared stream
|
||||
`EVENTS` (§ 3), kept 7 days, and heard through `consumes` `mesh-broker.<event>` like any seat's event. **The bus
|
||||
module keeps their history apart**, because a drift or an orphan is read back days later by a person: a stream of
|
||||
its own in its old `streams` section, on its module subjects, kept 30 days (ADR 0363's bound), created by the grants
|
||||
part as today, with a cap of 256 MiB, to which the process appends every event it says. Its own kept data stays on
|
||||
the old sections (ADR 0595 §10), because it cannot wait for its own process to create it. That stream drops its
|
||||
oldest when full, which loses no live state, because every drift, orphan and fill stands as a condition until it
|
||||
clears; the conditions hold the live state. The tool `history` of one entry reads it back: every event the bus
|
||||
module said of the entry, in order. This settles where they are kept, which ADR 0595 §12 left to the design, and
|
||||
needs no new manifest form: a module's stream never takes a seat's subjects, and every seat keeps one form for its
|
||||
events.
|
||||
|
||||
- §11 **Fill is the bus module's one periodic read** (ADR 0595 §6): every minute, for every entry that refuses when full,
|
||||
because the bus says nothing as a store fills. The condition `full` is raised at a raise mark and cleared below a
|
||||
lower clear mark, so an entry sitting at the mark does not flap; the marks are settings of the bus module, by default
|
||||
a raise at 75 percent of the cap and a clear below 70 (ADR 0595 §6, as its progressive insight of 2026-11-03 has it).
|
||||
The grants part's `mesh-grants.history-full` moves to it: the new condition is raised first, then the old key cleared;
|
||||
no manifest on main asks for a history stream or objects today, so the move loses nothing. A bare stream that refuses
|
||||
when full is read the same way. **The conditions**, each naming the module and the entry: `full` (cleared below the
|
||||
clear mark), `drifted` (cleared by `drift-cleared`), `orphaned` (cleared by a
|
||||
`drop` or by a manifest asking for it again) and a refused create (cleared by a later `created`).
|
||||
|
||||
- §12 **Its tools** (tools-are-how-a-module-is-spoken-to): the four reading verbs, marked reads, answer every entry the
|
||||
bus module created and its fill, and `history` its events for one entry; `create` and `apply`, marked acts, and
|
||||
`recreate` and `drop`, marked dangerous and so granted only by name (ADR 0386 §6, ADR 0387), are its tools for a
|
||||
person. **A person's request reaches the process through the process's own state** (ADR 0626 §4), because a process
|
||||
account answers no tool call (ADR 0436 §3). Such a tool, in the bus module's bundle, holds no grant of the bus's
|
||||
create, update or delete subjects, and neither does the tool runner's account: it writes the request into the bus
|
||||
module's state `requests` (an old `state` section, as `asked` is, ADR 0595 §10), stamped with the caller the tool
|
||||
runner proved and recorded (ADR 0386 §8), and watches that key for the process's answer, never asking again on a
|
||||
timer. The process watches its own state `requests` (ADR 0596 §4) and takes a request up when it is written. The stamp names
|
||||
who asked, for the record; it is no boundary, because every session on a machine reaches the bus as the tool
|
||||
runner's account (ADR 0436 §9). **So the process verifies the operator's warrant itself**: for `recreate` and `drop`
|
||||
it trusts nothing in the request, reads the operator channel's record of the ask under its own account and checks
|
||||
that the warrant binds exactly this act on this entry (ADR 0259 §6), as the mail sender does (ADR 0436 §9); a request
|
||||
without a valid warrant it refuses, deleting nothing and writing why into its answer; with one, it takes the entry's
|
||||
stream snapshot first. **The process wakes on the warrant, never on a timer**: a warrant is said on the operator
|
||||
channel's `decided` for the asker that raised the ask (ADR 0259 §6; ADR 0620 §2), here the bus module itself, which
|
||||
hears its own `decided` on a durable consumer of the process, whose filter the grants part derives from the
|
||||
process's `uses` of the operator channel seat (not from a `consumes`, which a process has none of), and runs its pass over the waiting `recreate` and
|
||||
`drop` requests when one arrives, so a warranted `drop` acts within seconds of the operator's answer, and one given
|
||||
while the process was down is heard when it is back. **A warrant acts once** (ADR 0259 §6): before it acts, the
|
||||
process claims the warrant's ask id by writing the key `acted.<ask id>` in the state `spent` of the process's own bus state, which the tools bundle is not given (a person's request and the
|
||||
record of what it spent are never in one bucket, and no tool can write the record), created by compare-and-set after the snapshot and before any deletion, and a request naming an ask it already
|
||||
acted on is refused, deleting nothing and saying which act that warrant already performed. **An ask lasts 12 hours**,
|
||||
shorter than the 24 hours `requests` keeps a request: a request whose ask ended unanswered is ended and said, and a
|
||||
warrant is not acted on once its ask ended; the tool raises the ask before it hands the request over. A request
|
||||
that cannot act yet says why in its field Waiting, and a hand act the process could not record is written on the
|
||||
request in its field Unrecorded. The tool `nats_requests` reads the waiting and ended requests, and the warrants
|
||||
spent from `spent`, which is declared `read-only: true` (design 32 § 4) so that the tool reads it and holds no publish on it, and drops a waiting request by hand, recorded as a hand act. `create` and `apply` need no warrant, since they only make or grow what an accepted manifest
|
||||
asks for, and the process refuses either for an entry no accepted manifest asks for. A tool acts only for a caller
|
||||
whose role grants it; the process records each act as a hand act (`mesh-hand-acts`) and answers what it did. What
|
||||
stands between the tool runner's account and the process's delete power is that check of the warrant, so its tests
|
||||
are this section's central check (§ 12.7, in the main file). **The mesh deletes kept data only through `drop` and `recreate`** (ADR 0595 §4, as its
|
||||
progressive insight of 2026-11-03 has it): the bus module's older remove tools (its stream, bucket and consumer
|
||||
removals, ADR 0564) refuse an entry of the new form, naming `drop`'s tool, and keep working for the old sections. The bus seat's `apply` (a difference that grows) and the
|
||||
ledger's `apply` (§ 12.4) are two verbs on two subjects, granted apart; this design writes them qualified. Its health
|
||||
names the process's last pass, the entries it could not create, and the entries past the raise mark.
|
||||
|
||||
### 12.4 The ledger
|
||||
|
||||
- §13 **The ledger is a storage type of its own.** A **ledger** is a storage type whose state is derived from its history (ADR 0597). It is built on a stream and
|
||||
key-value buckets, and asked for with the template ledger.
|
||||
|
||||
```
|
||||
module ──apply(instance, operation)──▶ tool runner on its own machine
|
||||
│ 1 checks the operation against the type
|
||||
│ 2 reads the history's last sequence for the instance; replays if the state is behind
|
||||
│ 3 appends to the history, that sequence expected ──▶ history (stream, a subject per instance)
|
||||
│ 4 derives the state by the operation's effect ──▶ state (key-value, updated at the revision read)
|
||||
│ 5 writes the instance's snapshot ──▶ snapshot subject (latest only, never expires)
|
||||
▼
|
||||
answers: taken, with its sequence (and "state behind" when the state bucket is full)
|
||||
module's process: a watcher keeps each declared index (a second key-value bucket) from the history
|
||||
```
|
||||
|
||||
- §14 **A type** is a schema and the operations allowed on it; an **instance** of a type is named by a path
|
||||
(`listing/a17`, `decision/0290`); every change is an **operation**, kept in the instance's history.
|
||||
- §15 **A ledger effect** is what an operation does to the state, one of a fixed set: set fields, set an entry of a
|
||||
map, append to a list, remove (ADR 0597 §2). An effect copies only the fields its type names, and may add the
|
||||
operation's sequence and author. Where a type pairs a text field with its digest, `apply` refuses an operation
|
||||
whose digest does not match. A module's own rules are checked by the module before it calls `apply`, never by the
|
||||
effects. Only the owning module calls `apply` on its ledger, for every effect, remove among them; a person
|
||||
removes through the owner's tools. This settles who may call the remove effect, which ADR 0597 §13 left to the
|
||||
design.
|
||||
- §16 **One writer wins, and the state never goes back** (§5): the expected sequence makes the bus refuse a racing
|
||||
writer; the state is written as an update of the revision read, never a plain put; when that update loses to
|
||||
another writer, `apply` answers success with its operation's sequence and re-reads, never appending again.
|
||||
- §17 **A full state bucket loses no operation** (§6): `apply` answers that the operation is taken, with its sequence,
|
||||
and that the state is behind because the bucket is full; the next `apply` after room is made catches it up.
|
||||
- §18 **The author is proven** (§7): `apply` stamps the owning module's authenticated account and the account that
|
||||
called the owner's tool. The owner passes that author on by naming the tool call it serves: the tool runner, which
|
||||
served that call and proved its caller, stamps the caller it proved, so the module cannot set the author to words of
|
||||
its own; an `apply` naming no call in flight carries the owner's account alone, and one naming another module's
|
||||
call is refused. A copy of old records (design 55 § 8.1) is stamped with the owner's account and keeps the old
|
||||
author as a separate field marked imported, never as the proven author.
|
||||
- §19 **Every instance has a snapshot** (§8): the latest state on a subject of its own, never expiring, written by every
|
||||
`apply`. It makes a kept ledger's rebuild fast and an expiring ledger's possible; a repair that finds it behind
|
||||
the state rewrites it.
|
||||
- §20 **Only the tool runner's ledger credential publishes on a ledger's history** (§9): one per module assignment, that
|
||||
is per node the module runs on, the account `ledger.<node>.<module>` in the tool runner's own secret `ledgers` on
|
||||
that node (ADR 0597 §9, as its progressive insight of 2026-11-03 has it, waiting for the operator's Approve), held by
|
||||
the tool runner and never by the module's own code, whose account may read its ledger and not publish on it. A
|
||||
person's `rotate` of that secret is refused by the vault, as a bus account's is. The grants part issues it with the
|
||||
module's membership on that machine, scoped to the subjects of that module's ledger
|
||||
histories and snapshots, and removes it when the module is unassigned from the machine. Only the
|
||||
owner's account reads the history, the snapshot and the state.
|
||||
- §21 **An index** is a second key-value bucket kept by a watcher in the owning module's process, rebuildable from the
|
||||
history; it raises a condition when it lags behind (§4).
|
||||
|
||||
### 12.5 The calls, and where they live
|
||||
|
||||
- §22 **The new calls live in a package of their own.** The new calls live in a small mesh-sdk package of their own, served by the tool runner and imported only by the
|
||||
modules that use them; the module library, which 112 modules import, does not change (ADR 0595 §9). They are the
|
||||
object store's (put, get, list, delete, info, watch); a stream's read-back and fill; a work queue's size and dead
|
||||
letters; and the ledger's: `apply`, `get`, `history`, `watch`, `query`, and its repairs `rebuild` (the state from
|
||||
the history or the snapshot), `reindex`, `snapshot` and `lag` (ADR 0597 §10). The tool runner serves the repairs as
|
||||
tools of every module that owns a ledger, limited to its own ledgers, so no owner declares them and the next ledger's
|
||||
owner has them on its first day. mesh-sdk's catalog and membership
|
||||
packages change once for the typed `requires` entry and the binding, rebuilding about 30 modules.
|
||||
|
||||
### 12.6 The move, and the order of the build
|
||||
|
||||
- §23 **The build, each step undoable on its own** (ADR 0595 §10): (1) mesh-sdk's typed `requires`, binding and calls
|
||||
package; (2) the manifest validator's rules; (3) the bus module's process, verbs and events, creating only for the new form, with the grants part's writing of
|
||||
the binding on `created`; (4) the tool runner's calls, the ledger's `apply` and its ledger credential among them; (5)
|
||||
modules move, each owner first. Each undo: undoing step 2 refuses the new form again, which no module uses before
|
||||
step 5; undoing step 3 leaves what it created in place, and a module moved after it fails loudly on its missing
|
||||
binding; undoing step 4 leaves a moved module without its calls, which the placement holds off that machine. The
|
||||
placement holds a moved module off a machine whose tool runner does not serve the typed binding and the calls the
|
||||
module asks for (a ledger's `apply` among them), and says so; where the placement reads what a tool runner serves is
|
||||
left to the build of step 4 and tracked on issue 1966.
|
||||
|
||||
- §24 **A module moves when it is next touched** (ADR 0595 §11), keeping its local names and carrying its old settings
|
||||
as options, so its bus objects and data stay and no drift is raised. It asks for each entry once: an old section
|
||||
and the new form are never both used for one local name. During the move the grants part keeps creating the old
|
||||
sections' entries, so each entry has one creator. The indexer counts the modules on each old section, a tool reads
|
||||
the count, and the manifest validator refuses a section once its count is 0. The foundation's own kept data (the bus module's,
|
||||
the grants part's and the indexer's) stays on the old sections for good and is not counted: those modules cannot
|
||||
wait for a process that needs them first. ADR 0619, extending ADR 0595 §10's foundation, adds the parts the bus module's own composition, placement, build or delivery needs before it can answer:
|
||||
the assigner, the tool runner, the builder, the checker, mesh-config and the vault; their new state and streams of
|
||||
this milestone go on their old sections too (§ 12.2; design 51 *The move*; design 32).
|
||||
|
||||
- §25 **Readers move after their owners** (ADR 0596 §3). Today 195 `reads` entries in 37 modules read another module's
|
||||
bus state (counted 2026-11-03 02:00). **A correction of fact**: the indexer's and the assigner's state, counted again
|
||||
from every manifest on main later that day, is read by 23 modules, 10 buckets of the indexer (`verdicts`, read by the grants part, among them) and 6 of the assigner,
|
||||
not by the 19 the first plan counted; and 151 state entries were counted, not 158 (the template table of § 12.1 keeps
|
||||
the first count, which chose the templates). The owners first gain their events, first-view tools and read tools: the
|
||||
indexer a read tool per bucket its readers read, the assigner the same (§ 12.2 lists them). Then all 23 readers move in the milestone of ADR 0612, the
|
||||
four foundation readers included, by the first view of § 12.2 (ADR 0615 §1), the grants part's of the indexer's
|
||||
`verdicts` by its first-view tool `verdicts` (ADR 0622) and then `manifest-accepted` and `manifest-refused`, and the
|
||||
assigner's of the indexer's `graph` by its first-view tool `graph` (ADR 0631) and then `indexer.views`; their order is the module graph's, not
|
||||
a review reading an owner delivered (ADR 0615 §2). The status view's move keeps every tool it serves, each answered by
|
||||
asking the owners' read tools of § 12.2. **The node-engine's reads granted in code stay**: the grants part's code
|
||||
grants each node's node-engine, with no manifest declaring them (`CandidateReads` and the composed reads beside it),
|
||||
the indexer's `candidates` and `verdicts` and its own node's keys of the assigner's `wants`. They are not among the
|
||||
23 readers of ADR 0615 §1 and move in the next milestone, queued on issue 1966. The manifest validator still refuses a moved module's subscription to an event
|
||||
its owner does not declare. Every other reader moves when its module is next touched. These moves are tracked by
|
||||
issue 1966.
|
||||
|
||||
## The lead's answers
|
||||
|
||||
- **L1** (issue 1966, met three times on 2026-11-12: a merged module's new build waited for its part grant): a candidate's
|
||||
manifest that a merge makes a module's trunk manifest is newly accepted in §1, so the indexer's merge pass says
|
||||
`manifest-accepted` from the verdict it holds even when that verdict was written for the candidate before the merge
|
||||
and is unchanged; a reader that receives it below the revision it holds treats it as a gap (§12.2) (P3, P1).
|
||||
@@ -7,9 +7,28 @@ code:
|
||||
- mesh-tools src/broker-amqp.ts (to be replaced)
|
||||
- mesh-catalog modules/nats (to be written)
|
||||
- mesh-sdk src (the protocol's NATS binding, step 3)
|
||||
- mesh-tools node-tools/internal/bus (a module's state, ADR 0201)
|
||||
updated: 2026-10-10
|
||||
- mesh-tools node-tools/internal/bus (a module's state, ADR 0201; the new calls and apply, ADR 0595, ADR 0597)
|
||||
- mesh-sdk (the typed bus-state entry, its binding and the new calls' package, ADR 0595)
|
||||
- mesh-delivery (the manifest validator's rules and the grants part's binding, ADR 0595)
|
||||
- mesh-contracts seats/mesh-broker (the seat's definition: `list` answers each entry's binding)
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0595"
|
||||
- "ADR 0626"
|
||||
- "ADR 0631"
|
||||
- "ADR 0620"
|
||||
- "ADR 0259"
|
||||
- "ADR 0627"
|
||||
- "ADR 0436"
|
||||
- "ADR 0619"
|
||||
- "ADR 0615"
|
||||
- "ADR 0621"
|
||||
- "ADR 0622"
|
||||
- "ADR 0614"
|
||||
- "ADR 0613"
|
||||
- "ADR 0612"
|
||||
- "ADR 0596"
|
||||
- "ADR 0597"
|
||||
- "ADR 0289"
|
||||
- "ADR 0264"
|
||||
- "ADR 0235"
|
||||
@@ -38,6 +57,17 @@ under which subject, with which guarantee, under whose account; how a node joins
|
||||
reaches a tool; how the mesh moves from the bus it has to this one; and how each claim is checked.
|
||||
Prose and diagrams only; no configuration is pasted.
|
||||
|
||||
**Amended, 2026-11-03** (ADR 0595, ADR 0596, ADR 0597): what a module keeps on the bus is its **bus state**, a
|
||||
provision of the bus module asked for by template, created by the bus module, read by its owner alone; and the
|
||||
**ledger** is a storage type of its own. § 12 is the design; the `KV_<module>_<name>` row of § 3 and the module's
|
||||
state in § 4 hold for a module still on the old sections.
|
||||
|
||||
**Amended, 2026-11-11** (ADR 0595 §1, §7; ADR 0615 §3 to §6; the operator, 2026-11-11, in the terminal: "Ask the
|
||||
bus module"): the grants part no longer learns a binding only from the bus module's live events; at every start and
|
||||
when asked again by hand it takes a first view of every binding from the seat `mesh-broker`'s `list`, so a missed
|
||||
`created` loses no binding for good (milestone bindings-first-view). The same change puts this design in the shape
|
||||
of playbook 02: § 12.2 to § 12.6 moved, unchanged and numbered, into the part of milestone bus-state.
|
||||
|
||||
## 1. What the bus is for
|
||||
|
||||
**The bus is where the mesh happens.** Not a transport the mesh sends things over — the place a
|
||||
@@ -129,7 +159,7 @@ mesh already has a shape for that — a seat's `accept` subjects, on a work queu
|
||||
holders, which is what a build queue shared by several machines *is*. Keeping a second mechanism for it
|
||||
meant two things to reason about and two places for a permission to be wrong. A build's outcome is the
|
||||
seat's own event — which is why the control branch loses its copy too: one publish reaches whoever
|
||||
asked, the controller that records it and the catalogue that places it — the fan-out a shared exchange gave for free, as a derived subject rather than a
|
||||
asked, the controller that records it and the catalog that places it — the fan-out a shared exchange gave for free, as a derived subject rather than a
|
||||
configured topology.
|
||||
|
||||
**Revised 2026-09-26** ([design 29](32-what-a-module-declares.md)): a module's events and tools
|
||||
@@ -179,7 +209,7 @@ Core NATS is at-most-once. Everything the mesh must not lose lives in a JetStrea
|
||||
| EVENTS | `mesh.mod.*.event.>` and `mesh.seat.*.event.>`; and `mesh.again.>` (ADR 0264) | 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-08, ADR 0264:* that stream is DEAD_LETTERS, below; each consumer also filters `mesh.again.<consumer>.>`, where a message it gave up on is delivered again to it alone. *2026-10-01:* a build's whole log is here too, as the build seat's `log.<build id>` events (ADR 0157) — one subject per build, a week of retention, read back by `builds --log <id>` with a consumer that is gone when the reading is done |
|
||||
| DEAD_LETTER_NOTICES | the server's maximum-deliveries advisory | work queue, a week; one consumer (the controller), no `max-deliver` | the server's word that a consumer gave up on a message, kept whether or not a controller is listening, until the controller has copied the message (ADR 0264) |
|
||||
| DEAD_LETTERS | `mesh.events.dead.<stream>.<consumer>` | limits: no age, 256 MiB, refuses new when full; a day's de-duplication | every message a consumer gave up on, with its consumer, subject, deliveries and when, until a person delivers it again or drops it with why (ADR 0264); a `max-deliveries` condition is open per consumer while it holds any |
|
||||
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | 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 |
|
||||
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | 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 catalog on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data. *2026-11-03, ADR 0595:* a module's kept data in the new form is created by the bus module from `requires`, by template (§ 12) |
|
||||
|
||||
Tool calls and heartbeats stay on core NATS: a lost heartbeat is the next heartbeat; a lost tool
|
||||
call is a timeout the caller already handles.
|
||||
@@ -193,6 +223,9 @@ holding `mesh-broker` — has an account of its own whose only grant is the snap
|
||||
names, a stream's information, the snapshot request and its acknowledgements, and its own inbox. The
|
||||
night's backup takes every stream through it ([to-be 43](43-backups-against-mistakes.md)). The
|
||||
controller stays the only writer of stream definitions; the writers table holds that at composition.
|
||||
*Amended 2026-11-04, ADR 0626 §2:* that account keeps exactly this grant; beside it, the bus module's process holds
|
||||
a process account of its own for the bus state it creates (§ 12.2), so the module holding `mesh-broker` holds two
|
||||
accounts, each doing only what its record grants.
|
||||
|
||||
### The store window, and what moving it into the server changes
|
||||
|
||||
@@ -266,7 +299,7 @@ expresses this exactly, per subject, and better than a vhost could:
|
||||
permissions for each consumed event's subject, its tool subjects, and that same inbox prefix.
|
||||
Nothing else. A module that tries to publish outside its emits is refused by the server, not by
|
||||
convention.
|
||||
- **A module's state** (ADR 0201), for whichever principal carries the module — today the machine's
|
||||
- **A module's state** (ADR 0201; for the new form, § 12.2: the owner's account alone, no reader, ADR 0596), for whichever principal carries the module — today the machine's
|
||||
runtime, whose grant is the union of its modules': binding to the bucket, reading a key directly,
|
||||
and an ordered consumer for listing and watching, created and deleted on the bucket's own stream
|
||||
and nothing else's; and, for the owner's instances only, publishing under the bucket's own
|
||||
@@ -321,7 +354,7 @@ signing hierarchy for nothing.
|
||||
|
||||
## 5. The broker as a module
|
||||
|
||||
`nats` is a catalogue module claiming the seat `mesh-broker`
|
||||
`nats` is a catalog module claiming the seat `mesh-broker`
|
||||
(ADR 0079: the seat
|
||||
is the server, and the server changes). It declares one container (a single binary; **JetStream on a host
|
||||
directory bind, not a named volume** — revision, second review:
|
||||
@@ -410,7 +443,7 @@ bridged. It is three things:
|
||||
for anyone else — and nothing on control, nodes or builds. It is issued, sealed to the person's
|
||||
own key, and revoked, like a module's.
|
||||
2. **A client that speaks the bus**: a small program on the workstation that connects as that user
|
||||
over TLS, lists tools by asking the catalogue (`mesh.tools.mesh-catalog.catalog_tools`), and
|
||||
over TLS, lists tools by asking the catalog (`mesh.tools.mesh-catalog.catalog_tools`), and
|
||||
turns each tool into a call — as an MCP server for an agent, and as a command line for a person.
|
||||
It uses the sdk's `Broker` contract on the NATS runtime, so it is the same code path a module's
|
||||
tools use, not a second protocol.
|
||||
@@ -606,7 +639,7 @@ find what changed and why.
|
||||
decides it.
|
||||
- The heartbeat interval and the controller's "quiet" threshold on core NATS without persistence —
|
||||
the same numbers as today are proposed.
|
||||
- Whether the person's client is a catalogue module (runs on an enrolled workstation node) or a
|
||||
- Whether the person's client is a catalog module (runs on an enrolled workstation node) or a
|
||||
standalone program (runs anywhere with credentials). Both, in that order, is proposed.
|
||||
- Leaf nodes: a NATS leaf per machine would make every module's connection local and survive the
|
||||
hub's restart. Deliberately out of scope; noted so it is not forgotten.
|
||||
@@ -617,3 +650,162 @@ find what changed and why.
|
||||
that needs it? ADR 0039's test —
|
||||
*does editing it recompile unrelated modules, and does it change often* — probably says no for
|
||||
one instance; worth asking again if a second module needs the same shape.
|
||||
|
||||
## 12. Bus state: what a module keeps on the bus, and the bus module that provides it
|
||||
|
||||
*Added 2026-11-03* (ADR 0595, ADR 0596, ADR 0597, from research 082; the operator, 2026-11-02 20:45: "The names
|
||||
simply represent named configurations or templates"). Until now a manifest declared what a module keeps on the bus
|
||||
in four sections with four vocabularies (`state`, `logs`, `streams`, `objects`), the grants part created each
|
||||
entry once and never changed it, and nothing compared what the bus holds with what was asked. This section is the
|
||||
design of the one form that replaces them; [design 32](32-what-a-module-declares.md) § 4 says how a manifest asks
|
||||
for it, and [design 55](55-the-knowledge-base.md) § 2.1 is its first ledger.
|
||||
|
||||
**Bus state is a provision of the bus module** (ADR 0595 §1). The **bus module** is the module holding the seat
|
||||
`mesh-broker` (today `nats`). A module asks it for each piece of its **bus state** the way it asks postgres for a
|
||||
database: an entry in `requires`, by its local name, its storage type, its template and the template's options.
|
||||
The bus module creates it; the module receives the names on the bus in its membership; nothing else creates it.
|
||||
|
||||
### 12.1 Three layers
|
||||
|
||||
```
|
||||
layer 3 templates state events work queue log objects ledger
|
||||
│ │ │ │ │ │
|
||||
layer 2 storage types key-value │ │ │ object store ledger
|
||||
built on the bucket │ │ │ │ (a stream and
|
||||
stream │ │ │ │ │ key-value buckets)
|
||||
└──────────┴───────────┴──────────┴──────────┴────────────┘
|
||||
layer 1 base type stream
|
||||
```
|
||||
|
||||
A **storage type** is how the bus keeps the data: the **stream** is the base, and the **key-value bucket**, the
|
||||
**object store** and the **ledger** are built on it, each with calls of its own (ADR 0595 §2). A **configuration**
|
||||
is the settings a storage type is given. A **template** is a storage type with a configuration, predefined and
|
||||
named: six exist, from the uses counted on main (158 state entries, 6 event streams, 4 logs, 3 work queues). An
|
||||
**option** is one setting of a template a module may change, within its bound. A module gives a configuration of
|
||||
its own only where no template fits: the bare stream, or a ledger whose history expires.
|
||||
|
||||
**What keeps the data safe is fixed, never an option** (ADR 0595 §3). Each template fixes what happens when it is
|
||||
full, and the manifest validator refuses a manifest that gives it:
|
||||
|
||||
| Template | On | Fixed | Options, their bounds (default) |
|
||||
|---|---|---|---|
|
||||
| state | key-value | refuses when full; a value at most 256 KiB; the bucket at most 64 MiB; one copy, on disk | past values 1 to 64 (1), 0 refused; time to live (none); a lifetime per key; one bucket per machine |
|
||||
| events | stream | drops the oldest when full; the module's own subjects | age 1 to 30 days (7); cap 1 to 8192 MiB (1024); a grant (ADR 0363 §4) |
|
||||
| work queue | stream | removed once taken; refuses when full; one taker; no grant | deliveries 1 to 10; duplicate detection up to its age; cap; age |
|
||||
| log | stream | refuses when full; never rewritten; an entry at most 256 KiB; one subject per key | cap 1 to 8192 MiB (1024); age 1 to 3650 days (none) |
|
||||
| objects | object store | refuses when full; kept for good | cap 1 to 8192 MiB, required; a grant |
|
||||
| ledger | ledger | as log, plus its type and a snapshot per instance; its state and index buckets as state | cap; keeping days only in a ledger's own configuration |
|
||||
| (none: a bare stream) | stream | the module's own subjects; within the mesh's bounds | its own configuration, which must give "when full" (refuse or drop the oldest), a cap of 1 to 8192 MiB and an age of 1 to 3650 days or none, and may give a grant; the manifest validator refuses one missing any of the three |
|
||||
|
||||
The template log is the **module log** of ADR 0297. The mesh, never the module, names the bus objects and their
|
||||
subjects (ADR 0201 §4): `<module>_<local name>`, and a local name holding the separator is refused so no two
|
||||
modules' names meet.
|
||||
|
||||
### 12.2 to 12.6 Who creates it, the seat's verbs and events, the ledger, the calls and the move
|
||||
|
||||
```
|
||||
manifest validator ─verdict: accepted with the module's bus-state entries whole─▶ bus module
|
||||
│ creates each entry
|
||||
▼
|
||||
grants part ◀──────────────── created, recreated, dropped (module, local name, bus name, binding)
|
||||
│ ◀───── at its start, and when asked again: mesh-broker.list (every entry, its binding)
|
||||
│ writes the binding into the module's membership
|
||||
▼
|
||||
the tool runner reads the membership ──▶ the module's calls reach its own entries, and only those
|
||||
```
|
||||
|
||||
These sections are the lines of milestone **bus-state**, in its part
|
||||
[25-milestones/bus-state.md](25-milestones/bus-state.md), where they keep their numbers. The shape, in short: the
|
||||
indexer's `manifest-accepted` carries a module's bus-state entries whole to the bus module, whose process creates
|
||||
each entry under the module's names and says `created`, `refused`, `drifted` and `drift-cleared`, `full` and
|
||||
`full-cleared`, `orphaned`, `recreated` and `dropped` on the seat `mesh-broker`. The seat's reading verbs `list`,
|
||||
`show`, `check` and `orphans` are answered by the bus module's tools; its acts `create`, `apply`, `recreate` and
|
||||
`drop` by its process alone, the last two on the operator's warrant and after a stream snapshot. The grants part
|
||||
writes each entry's binding into the module's membership, kept in its own state `memberships`, from those events
|
||||
and, at every start and when asked again by hand, from its first view of the seat's `list` (milestone
|
||||
**bindings-first-view**, in its part [25-milestones/bindings-first-view.md](25-milestones/bindings-first-view.md)).
|
||||
The ledger is a storage type whose state the tool runner derives from its history; the new calls are a mesh-sdk
|
||||
package of their own; and the foundation parts take their first views of each other's state from the owner's own
|
||||
tools, then follow its events.
|
||||
|
||||
### 12.7 How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| A manifest's bus state is valid, once | the manifest validator's unit tests, each refusal naming the field: an unknown template, an option outside its bound, past values 0, "when full" given, a fixed setting given, a name used twice across storage types, two modules' bus names alike, a `reads` in the new form, an effect outside the fixed set (ADR 0595 §8, ADR 0596 §2, ADR 0597 §2) |
|
||||
| The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused |
|
||||
| The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; the process connects on its own process account, composed by the grants part with that account's grants and limits, never on an account without them; a `create` handed through `requests` is answered on the same key, which the tool watches; `recreate` and `drop` without the operator's warrant are refused, and a warranted `drop`, its warrant read from the operator channel's own record of the ask under the process's account and never from a stand-in, takes the entry's stream snapshot before it deletes, and acts within seconds of the bus module's `decided`, with no timer between; the same warrant handed in a second request is refused and deletes nothing; each event said is on `mesh.seat.mesh-broker.event.<event>` and appended to the bus module's history stream, and `history` reads it back; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool |
|
||||
| A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) |
|
||||
| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`modules/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`modules/mesh-delivery-assigner`) and the grants part's (`modules/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an owner's event carries the changed value whole, and a reader applies a put and a delete from the events alone and never asks the verb; a revision lower than the one held makes the reader ask its first view again; an existing waiter on `manifest-accepted` or on the assigner's `put` does not wake on the new families; for the indexer's `verdicts` (ADR 0622), called by the grants part while the node's tool runner is stopped, it answers, and from any other account, the indexer's own included, it is refused; a missed revision on `manifest-refused` makes the grants part ask `verdicts` once more; the grants part and the indexer started at the same moment, `verdicts` answers within the bound with its revision; with the indexer down past the bound the grants part raises the condition naming the indexer and `verdicts` and never reads the indexer's state, and the answer clears it; for the indexer's `graph` (ADR 0631), called by the assigner it answers with its revision, and from any other account, the indexer's own included, it is refused; a missed revision of the bucket `graph` on `indexer.views` makes the assigner ask `graph` once more; started with the indexer down, the assigner raises past the bound the first-view condition naming the indexer and `graph` and reads nothing of the indexer's state, and the answer clears it; the read tool shows the revision each reader holds, and the re-read tool asks once and records the hand act; in the assigner's whole-module test, the assigner starts before any new-form bus state exists, with no binding of the bus module, and the indexer, the grants part and the tool runner each get their first view of it. Live, after the delivery: the indexer's `indexer_old_sections`, which the milestone extends to count the `reads` entries on each owner's state, reads 0 declared `reads` entries on the indexer's state and 0 on the assigner's, and says the node-engine's reads granted in the grants part's code remain (§ 12.6); and each owner's read tool of § 12.2 answers what its bucket holds, each status view tool answers a session's call as before the move, and `mesh_search` finds each owner's read tool |
|
||||
| The owners' first views are their own tools, in a runtime of their own (ADR 0621) | cases of the indexer's and the assigner's whole-module integration tests: each first-view tool, called from a reader's account while the node's tool runner is stopped, answers (§1); each answers its granted readers and refuses every other account, the owner's own included (§2); the two runtimes started at the same moment, each asking the other's first view at start, both answer within the first-view bound with their revisions (§3); and the milestone's group passes its merge check with no change to the manifest validator's known fields for these verbs (§4) |
|
||||
| The grants part's first view of the bindings (design 25 bindings-first-view) | the cases of design 25 bindings-first-view §8: a case in the grants part's existing whole-module integration test, where a binding whose `created` was never said is written after a start; unit rows of the bus module's tools and the grants part, §9's rows among them (a binding composed from an answer without `binding`); live, the next start after the delivery, and §9's live case after the grants part's delivery alone |
|
||||
| A foundation part's first view comes within its bound | live, after the delivery: each reader's read tool (`grants_first_views`, the mesh MCP server's `mesh_first_views`, `indexer_first_views`, `assigner_first_views`) shows, for the indexer's and the assigner's verbs, the time from its ask to the owner's answer under the two-minute bound, and no first-view condition raised by the delivery |
|
||||
| The bus module's first view | cases in the nats module's one integration test: a replayed verdict older than the first view is not applied; an `indexer_bus_state` that does not answer turns the health unhealthy naming it and is asked again on the backoff; a set-aside manifest missing from the first view keeps its `asked` key, an empty `manifest-accepted` removes it; a deleted module's entry, after its empty `manifest-accepted`, is listed by `orphans` and raises `orphaned`, and a set-aside module's entry is neither; `nats_first_view_again` asks at once and records the hand act |
|
||||
| `manifest-accepted` is never lost silently | unit tests of the indexer: a failed publish is published again, and one still failing writes the finding; `indexer_say_accepted` says it again for one module from the verdict held, and refuses a module with no accepted verdict |
|
||||
| A person's request goes through `requests`, and the process verifies the warrant itself and acts on it once (ADR 0626 §4, ADR 0259 §6) | the process's unit tests: a `recreate` and a `drop` written to `requests` with no warrant, with a warrant for another act or entry, with a warrant the operator channel's record does not hold, and with a warrant whose ask id the process already acted on are each refused, delete nothing and say why; a `create` or the seat's `apply` for an entry no accepted manifest asks for is refused; the stamp is the caller the tool runner proved, whatever the request's body names |
|
||||
| The process's account, and the bus module's only bus declaration (ADR 0626 §1 to §3) | the manifest validator's unit test: the bus module's manifest with its process's `bus` and `keeps` is accepted; `keeps` on any other process or module, any other bus declaration of the bus module, and a subject of its process on which a tool is answered are refused; the composition test: the process is granted the create, update, delete and snapshot subjects under modules' names, nothing on which a tool or a verb is answered, and no subscription to a restricted stream's subjects beyond the snapshot request, its acknowledgements and its inbox; the snapshot account is granted the same subjects as before; no tool runner and no tool of the bus module holds the bus's create, update or delete subjects |
|
||||
| The process's fixes of review round 1 hold | the nats module's whole-module integration test and unit rows: an event or condition the bus did not take is kept, named by the health tool and said again at the next fill pass; a second accepted manifest asking for an entry raises no overdue condition; the warrant's wake on `decided` acts within seconds without a call to the pass by the test; a second request naming a spent ask is refused naming the act it spent; an ask past 12 hours is not acted on |
|
||||
| Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there |
|
||||
| Overdue and stopped are loud | live, carried by the change of the first module that moves in step 5, never by a merge made for the test alone: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old |
|
||||
| A person's tools reach the process, and only by role | live: a role without the dangerous grant is refused `drop`'s tool; the tool runner's account holds no grant of the bus's create, update or delete subjects |
|
||||
| The author is the proven caller | the ledger's integration test: an `apply` naming another module's tool call is refused; the author stamped is the caller the tool runner proved, whatever the module passed |
|
||||
| Only the owner reaches its data | live: another module's account reading an entry in the new form, a ledger's history, snapshot or state among them, is refused; a raw publish on a ledger's history is refused |
|
||||
| The ledger keeps its promises | one integration test against the real bus (ADR 0597's How it is checked): two writers race and one is refused; two interleaved applies leave the state at the higher sequence; a write cut between history and state is repaired; a full state bucket answers taken with its sequence and catches up; an idle instance whose operations expired rebuilds from its snapshot; `rebuild`, `reindex` and `lag` repair by hand; a forged author is refused; every `apply` leaves the snapshot equal to the state; an index behind raises its condition and `reindex` clears it |
|
||||
| Only the owner applies | the ledger's integration test: another module's ledger `apply` on the knowledge base's ledger is refused |
|
||||
| A bare stream is complete | the manifest validator's unit test: a bare stream missing "when full", a cap or an age is refused, naming the field |
|
||||
| The calls rebuild only their users | a read of the delivery plan: only the importers of the calls package rebuild when it changes |
|
||||
| The move keeps data | live: a module moved from `state` keeps its bucket and data and raises no drift; a new entry in an old section is still created by the grants part; the count falls by one; at 0 a manifest using the section is refused and the foundation's are still accepted; a moved module is held off a machine whose tool runner does not read the typed binding |
|
||||
|
||||
**The test budget** (50 integration and 500 unit tests per repository): the design adds one integration test in the
|
||||
nats module's repository and one in mesh-tools (the ledger), and table cases, not new functions, to the manifest validator's unit
|
||||
tests in mesh-delivery and to mesh-sdk's decoding tests. Each repository's count against the budget is read in the
|
||||
prepare step of issue 1966, and a repository over it merges tests to stay within it.
|
||||
|
||||
**Acceptance.** The operator asks the bus module's `list` and sees every module's kept data with its template, its
|
||||
settings and its fill; a module that moved keeps its data; a drift or an orphan reaches the operator as a condition
|
||||
and is changed only on their word; and the knowledge base keeps its records as a ledger (design 55 § 10).
|
||||
|
||||
## Milestones
|
||||
|
||||
Each milestone not yet delivered has its part in [25-milestones/](25-milestones/), holding every line it builds
|
||||
(playbook 02, *The shape of a design*); § 1 to § 11 and the five steps of § 9 stay in this file.
|
||||
|
||||
- **bus-state** — § 12.2 to § 12.6, built under issue 1966: the bus module creates every module's bus state, the
|
||||
seat `mesh-broker` gains its verbs and events, the grants part writes the bindings, the ledger, the calls and the
|
||||
move. Its part: [25-milestones/bus-state.md](25-milestones/bus-state.md).
|
||||
- **bindings-first-view** — a recovery fix under issue 1966: the grants part takes a first view of every binding
|
||||
from the seat `mesh-broker`'s `list` at every start and when asked again by hand, so a binding whose `created` it
|
||||
never heard is written all the same. Its part:
|
||||
[25-milestones/bindings-first-view.md](25-milestones/bindings-first-view.md). Its acceptance: a module whose
|
||||
bus-state entry the bus holds, and whose `created` the grants part never heard, finds the entry's binding in its
|
||||
membership after the grants part's next start, with nobody's hand.
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
|
||||
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
|
||||
ruled on every finding. What was found, and what answers it:
|
||||
|
||||
| Domain | Found | Answered by |
|
||||
|---|---|---|
|
||||
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
|
||||
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
|
||||
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
|
||||
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
|
||||
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
|
||||
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
|
||||
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
|
||||
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
|
||||
|
||||
**Answered since, 2026-11-04** (ADR 0626, and the registry milestone's ruling on where the seat's events go): the
|
||||
process's own tool subject of the security and seams rows is gone, since a process account answers no tool call; a
|
||||
person's request travels through the process's state `requests` and the process verifies the warrant itself
|
||||
(§ 12.3); the process holds a process account of its own beside the snapshot account (§ 12.2); and the seat's
|
||||
events are on the shared stream like every seat's, with the bus module's 30-day history its own stream (§ 12.3).
|
||||
|
||||
**Answered since**: the margin of `full` and the events of `drop` and `recreate` were the operator's answers of
|
||||
2026-11-03 and are now ADR 0595 §6's progressive insight, with the correction of fact for ADR 0595 §4 (the bus
|
||||
module deletes through `recreate` as well as `drop`); both wait for the operator's Approve and are written in § 12.2
|
||||
and § 12.3. The milestone's amendment (ADR 0613 to ADR 0615: the first view, the bus-state edge, the deletion paths,
|
||||
the caller until the identity seat, the reader count) awaits its own review by architectural domain, which rewrites
|
||||
this section.
|
||||
|
||||
@@ -15,8 +15,26 @@ code:
|
||||
- mesh-catalog modules/gitea/module.json
|
||||
- mesh-controller internal/inventory/migrations/0063-a-machines-names-are-one-seats.sql
|
||||
- mesh-controller internal/inventory/migrations/0064-the-resolver-file-is-the-uplinks.sql
|
||||
updated: 2026-10-10
|
||||
updated: 2026-11-09
|
||||
decisions:
|
||||
- "ADR 0641"
|
||||
- "ADR 0636"
|
||||
- "ADR 0634"
|
||||
- "ADR 0635"
|
||||
- "ADR 0596"
|
||||
- "ADR 0593"
|
||||
- "ADR 0625"
|
||||
- "ADR 0573"
|
||||
- "ADR 0606"
|
||||
- "ADR 0609"
|
||||
- "ADR 0595"
|
||||
- "ADR 0627"
|
||||
- "ADR 0608"
|
||||
- "ADR 0613"
|
||||
- "ADR 0620"
|
||||
- "ADR 0612"
|
||||
- "ADR 0614"
|
||||
- "ADR 0605"
|
||||
- "ADR 0287"
|
||||
- "ADR 0223"
|
||||
- "ADR 0220"
|
||||
@@ -98,7 +116,7 @@ nobody argued for is an entry nobody can explain.
|
||||
|
||||
**What deserves one** (ADR 0161). A provision the
|
||||
mesh's own code dereferences by name is delivered by a mesh seat its provider claims — the store, the
|
||||
bus, the vault, the artifact store, the catalogue. A provision a module merely offers may have several
|
||||
bus, the vault, the artifact store, the catalog. A provision a module merely offers may have several
|
||||
providers, and a consumer with several is a person's choice. A fact of the shape *exactly one machine
|
||||
is X* — the hub — is not a seat, because a seat is held by a module assignment and points at it; it is
|
||||
a placement with a capacity of one, kept by the store, refused by name when a second is placed, and
|
||||
@@ -114,7 +132,7 @@ capability is.
|
||||
ADR 0110): a module
|
||||
declares its own seats with their protocols, so the seats a mesh has are the mesh's own **plus
|
||||
every registered module's**. The set is still closed — a seat named nowhere is refused — but it is
|
||||
computed from the catalogue rather than maintained by hand, which is the property 0110 actually
|
||||
computed from the catalog rather than maintained by hand, which is the property 0110 actually
|
||||
needed and the table could not keep.
|
||||
|
||||
**And the mesh's own half is data, named for its scope.** Revision, 2026-09-27, reconciling two
|
||||
@@ -125,7 +143,7 @@ the set out of compiled code into a table the controller owns, so a rename is on
|
||||
rebuild of everything that names one.
|
||||
|
||||
So the set has two halves and neither is written out here: the mesh's own, which the controller holds
|
||||
as rows, and every registered module's, which is computed from the catalogue. What this document keeps
|
||||
as rows, and every registered module's, which is computed from the catalog. What this document keeps
|
||||
is what a seat *is* — the rest would be a third copy, stale the first time somebody renamed one, which
|
||||
is the fault ADR 0122 exists about.
|
||||
|
||||
@@ -139,32 +157,391 @@ convention, which later seats departed from.
|
||||
|---|---|---|---|
|
||||
| `mesh-controller` | — | mesh | — | the controller |
|
||||
| `mesh-store` | — | mesh | — | the store the mesh's own records live in |
|
||||
| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus |
|
||||
| `mesh-broker` | — | mesh | `mesh-bus`, and `bus-state` (ADR 0595 §1, ADR 0614 §5) | the broker carrying the mesh's own bus; its holder, the bus module, creates the bus state modules ask for (design 25 § 12) |
|
||||
| `mesh-vault` | — | mesh | `secret` | the vault (in the seed since ADR 0161; the earlier *reserved* named an effect no rule produced) |
|
||||
| `mesh-artifact-store` | `the-artifact-store` (renamed 2026-09-30, ADR 0156) | mesh | `artifact-store` | the artifact registry |
|
||||
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
|
||||
| `mesh-npm-package-registry` | `npm-package-registry` | mesh | `npm-package-registry` | the forge |
|
||||
| `mesh-git` | `git` | mesh | `git` | the forge |
|
||||
| `container-image-registry` | `mesh-artifact-store` for images (ADR 0605; the old name readable until every node-engine reads the new one) | mesh | `artifact-store` | the artifact registry (`distribution`) |
|
||||
| `oci-artifact-registry` | `mesh-artifact-store` for bundles and archives (ADR 0605) | mesh | `artifact-store` | the artifact registry (`distribution`) |
|
||||
| `mesh-catalog` | `the-catalogue` | mesh | — | the indexer |
|
||||
| `npm-package-registry` | — (its rename to `mesh-*` stays deferred, ADR 0156 §8) | mesh | `npm-package-registry` | the forge |
|
||||
| `go-package-registry` | — (ADR 0605) | mesh | `go-package-registry` | the forge |
|
||||
| `git` | — | mesh | `git` | the forge |
|
||||
| `mesh-build-machine` | `the-build-machine` | node | — | a builder |
|
||||
| `mesh-resolver` | — | mesh | — | the mesh's resolvers, each holding every node's internal domain (ADR 0194); replicated, held on the anchor and the home server (ADR 0223) |
|
||||
| `mesh-dns-resolver` | — | mesh | — | the mesh's resolvers, each holding every node's internal domain (ADR 0194); replicated, held on the anchor and the home server (ADR 0223) |
|
||||
| ~~`mesh-dns-port`~~ | `the-dns-port` | node | — | retired by ADR 0194: the local resolver became the mesh's one; deleted from the set, and from the store's table, once nothing claimed it (ADR 0220) |
|
||||
| `node-hostname` | `node-hosts-file` (renamed by ADR 0223; the former name an alias) | node | — | owns a machine's names: `/etc/hostname`, written from its holder's `hostname` setting with no default and taking effect at the next boot, and in `/etc/hosts` the machine's own lines and the operator's kept region, changed through its verbs `entries`, `add`, `remove` (ADR 0199). Held by the module `hostname`, formerly `hosts` |
|
||||
| `node-resolver` | — | node | — | a machine's own resolver, held only where something requires `split-dns`: it writes `/etc/resolv.conf` there (the uplink's holder steps back from it) and routes a VPN's domains to the VPN's servers over its link through its verbs `routes`, `route`, `unroute` (ADR 0247). Held by the module `systemd-resolved` |
|
||||
| `mesh-intrusion-prevention` | `the-intrusion-prevention` | node | — | an intrusion-prevention service |
|
||||
| `mesh-packet-filter` | `the-packet-filter` | node | — | the packet filter |
|
||||
| `mesh-private-network` | `the-private-network` | node | — | the private network the mesh runs over |
|
||||
| `the-private-network` | — (it keeps its name, ADR 0636 §6) | node | — | `mesh-wireguard`, the private network the mesh runs over |
|
||||
| ~~`mesh-resolver-configuration`~~ | `the-resolver-configuration` | node | — | retired by ADR 0223: `/etc/resolv.conf` is written by the holder of `node-uplink`, the program that would otherwise rewrite it, and the need of the uplink beside it (ADR 0220) goes with it; deleted from the set, and from the store's table, once nothing claims it |
|
||||
| `mesh-showcase` | `the-showcase` | node | — | the showcase module |
|
||||
|
||||
The controller holds **the mesh's own** entries in code, and a test asserts their size and that
|
||||
every one names the record that made it a seat. A module's seats are not here and never will be —
|
||||
they are read from the catalogue. **This table and
|
||||
they are read from the catalog. **This table and
|
||||
ADR 0126 govern, and code that
|
||||
disagrees is what is wrong.** The implementation in progress predates several
|
||||
things here: seats held by assignments rather than claimed by definitions, the `mesh-vault` seat and
|
||||
its reservation, and the foundation's seats delivering nothing. It is brought to this table before it
|
||||
merges.
|
||||
|
||||
## A seat's definition (from 2026-11-03)
|
||||
|
||||
**A seat is defined once, in one language-neutral file, and every part learns it from the bus** (ADR 0608, from
|
||||
research 081). Every seat has its definition file in its domain's contracts module, written by hand, whether it is
|
||||
defined whole or at the seat list's grain (ADR 0635 §2). In the milestone of ADR 0612, 25 seats are defined whole
|
||||
(ADR 0613 §1, ADR 0620, ADR 0627 §1, ADR 0634 §6, ADR 0635 §5); every other seat is defined at the seat list's grain.
|
||||
|
||||
- **The file** is a schema with conformance fixtures: the seat's verbs and their messages, its events and their
|
||||
bodies, its states and the forms of its provisions, and names its glossary domain (ADR 0636 §5). It lives in the
|
||||
contracts module of its domain (*Where a seat's
|
||||
code goes*, below). Its Go and
|
||||
TypeScript types are generated from it and committed beside it; nobody writes them by hand. The format is JSON, its
|
||||
messages and bodies JSON Schema, as the seat list's verb inputs already are, with the format's own schema checked in;
|
||||
the fixtures are data, never functions.
|
||||
- **A definition names its states** (ADR 0608 §1; ADR 0634 §6 leaves the file's format to this design). A `states`
|
||||
entry is what any holder of the seat keeps and what the seat's verbs answer from: its local name, its template of
|
||||
ADR 0595 (state, events, work queue, log, objects or ledger), who writes it, each form of its keys with the schema
|
||||
of the value under it, how long it keeps its history, and its size cap. The holder writes every state; only a state
|
||||
of the events or log template may name each caller as the writer of its own keys, granted on keys under the
|
||||
caller's own name and no other. No entry names a holder module, so another module can hold the seat by importing
|
||||
the contract alone (ADR 0625 §7). The generated package carries, per state, its local name, a builder and a parser
|
||||
per key form, and the value types, decoded strictly; the fixtures hold one value per key form.
|
||||
`operator-channel`'s `records` becomes one `states` entry in the same change, so the format has one form for a
|
||||
state. The format's own schema refuses an entry with an unknown template or a key form without a value, naming it.
|
||||
**A state is the holder's, asked through the seat's verbs** (ADR 0596 §2): the module that reads another module's
|
||||
bucket directly today keeps doing so only as a reader in transition, through its own manifest's `reads`, until its
|
||||
module moves to the owner's verbs or events (ADR 0596 §3, issue 1966). The bucket's name stays
|
||||
`<holder>_<state>`, the holder's own, so nothing moves under a seat's rename. **How it is checked**: the manifest
|
||||
validator refuses a holder whose `requires` does not declare each of its seat's states with the same local name and
|
||||
template, naming the state; the format's own schema refuses a caller-written state of another template and a state
|
||||
with no size cap.
|
||||
- **What is defined whole.** Ten seats came first (ADR 0613; ADR 0620, on the operator's answer of 2026-11-04 to move
|
||||
them all; ADR 0627 §1): `npm-package-registry`, `go-package-registry`, `container-image-registry` and
|
||||
`oci-artifact-registry` (one common part of their shared verbs, written once, so the four cannot drift),
|
||||
`mesh-broker`, the four seats the milestone adds a verb or an event to, each with every verb its holder's manifest
|
||||
on the trunk declares today, plus the new ones, and `node-engine`. Fifteen more are defined whole in the same
|
||||
milestone because a name leaving mesh-sdk (*Where a seat's code goes*) is their type, state layout or message
|
||||
(ADR 0634 §6, ADR 0635 §5), each with its verbs, events, messages and the layouts of its states:
|
||||
`mesh-module-assigner`, `mesh-wants`, `mesh-node-enroller`, `mesh-catalog`, `node-build-go`, `node-build-image`,
|
||||
`node-build-node`, `mesh-hand-acts`, `mesh-provisioner`, `mesh-conditions`, `mesh-self-check`, `mesh-healers`,
|
||||
`mesh-watchdogs`, `the-private-network` and `mesh-delivery`. Five seats already whole gain the state layouts
|
||||
mesh-sdk held for them: `node-engine`, `mesh-vault`, `mesh-delivery-checker`, `container-image-registry` and
|
||||
`oci-artifact-registry` (ADR 0634 §6). **No event moves subject in this milestone** (ADR 0620), but those of the
|
||||
two renamed seats (*Two seats renamed*, below): a seat's own events are only those published on its seat subject
|
||||
today, and a holder's own events stay under the holder's own subjects, listed beside the seat they belong with,
|
||||
never moved onto it. The four seats the milestone adds to:
|
||||
- `mesh-delivery-checker` (held by the checker): the verbs `try`, `try-read` and `try-end`; no seat event. The
|
||||
holder's own, beside it: its family `checker.asks`, new, with `tag-wanted` (design 51 *Tagged versions*) and
|
||||
`verdict` (*Out of step is learned from events*, below); and its stream `checks`, which already declares
|
||||
`preview.**` and `owed.**`, only its `emits` lacking `preview.written`, which the same change adds.
|
||||
- `mesh-config` (held by mesh-config): the verbs `config` and, new, `renames`; no seat event. The holder's own,
|
||||
beside it: `config-changed`, and its family `config.events`, new, with `renamed`, `retiring` and `retired`
|
||||
(design 32).
|
||||
- `mesh-vault` (held by the vault): the verbs `get`, `secret-ask`, `issued`, `rotate`, `move-in`, `node-key`,
|
||||
`own`, `issue-account`, `sign`, `enrolment-token`, `node-account`, `rotate-node`, `node-account-confirm`,
|
||||
`abandon` and, new, `retire`; no seat event. The holder's own, beside it: `provisioned`, `rotated`,
|
||||
`secret-changed`, `account-hashed`, `own-replaced`, `account-issued`, `signed`, `rotation-begun`,
|
||||
`node-account-ready`, `rotation-done` and, new, `own-retired` (design 32). The seat list mesh-sdk compiled
|
||||
also names `accept` and `move-asks`, which no holder serves and nothing calls: the definition drops them.
|
||||
- `operator-channel` (held by the router, the messenger module): the verbs `open`, `history`, `notify`, `tell`
|
||||
and `asks`; the asks it accepts, `ask`, `cancel` and `condition`; its seat event `decided`; `by-caller` for
|
||||
`ask`, `cancel`, `condition` and `decided`; and its state `asks`, as the holder's seat entry has them. The
|
||||
holder's own, beside it: `refused`, and `offered` and `closed`, new, on its restricted stream (design 55 § 11).
|
||||
Its definition is written from the holder's seat entry.
|
||||
|
||||
And `node-engine` (held by the node-engine on every node) has the verb `manifest-reads` and its seat event
|
||||
`manifest-reads-changed`, which the milestone gives it (ADR 0618 §3, §4; design 51). The 25, by the contracts
|
||||
module that holds each (ADR 0634 §7, ADR 0635 §5):
|
||||
|
||||
| contracts module | domain | seats | held by |
|
||||
|---|---|---|---|
|
||||
| `mesh-delivery-contracts` | Delivery | `mesh-catalog`, `mesh-delivery-checker`, `mesh-delivery`, `mesh-wants`, `git`, `node-build-go`, `node-build-image`, `node-build-node`, `npm-package-registry`, `go-package-registry`, `container-image-registry`, `oci-artifact-registry` | the indexer, the checker, the gate part, the assigner, the three build modules, the forge (three), `distribution` (two) |
|
||||
| `mesh-health-and-repair-contracts` | Health and repair | `mesh-conditions`, `mesh-self-check`, `mesh-healers`, `mesh-watchdogs`, `mesh-hand-acts` | the condition keeper, mesh-self-check, mesh-healers, the watchman, mesh-hand-acts |
|
||||
| `mesh-provisioning-contracts` | Provisioning | `mesh-provisioner` | mesh-provisioner |
|
||||
| `mesh-identity-and-access-contracts` | Identity and access | `mesh-vault`, `mesh-node-enroller` | the vault, the module `mesh-enrolment` |
|
||||
| `mesh-connectivity-contracts` (mesh-networking repository, ADR 0636 §6) | Connectivity | `the-private-network` | `mesh-wireguard` |
|
||||
| `mesh-operator-and-conversation-contracts` | Operator and conversation | `operator-channel` | the router |
|
||||
| `mesh-contracts` | Core, and Config | `node-engine`, `mesh-broker`, `mesh-config`, `mesh-module-assigner` | the node-engine, the bus module, mesh-config, the assigner |
|
||||
|
||||
ADR 0641 retired the Placement domain: module assignment is Core's, so `mesh-module-assigner` is defined in
|
||||
`mesh-contracts`, and the wants are Delivery's, so `mesh-wants` is defined in `mesh-delivery-contracts`; the module
|
||||
`mesh-placement-contracts` is not created (ADR 0641 §1, §2). From research 085's milestone (design 47) the seat
|
||||
`mesh-grants` is defined whole in `mesh-identity-and-access-contracts` (ADR 0636 §5), and the seat `git` gains in
|
||||
`mesh-delivery-contracts` the verbs by which every delivery part reaches the forge (*The git seat*, below). How it is
|
||||
checked: the indexer refuses a definition of these seats from any other contracts module, naming both (ADR 0636 §5),
|
||||
and its domain check refuses a definition naming the domain Placement (ADR 0641 §5).
|
||||
|
||||
**How it is checked**: each of these definitions' conformance fixtures names every event's subject, the seat's and
|
||||
the holder's own, and a case of the holder's whole-module test takes a fixture from the running holder (an event
|
||||
it publishes) and matches it, subject and body, against the definition; the indexer's generator tests cover each
|
||||
of the 25 (ADR 0634 §10, ADR 0635 §6). The builder and the indexer's own events hold no seat:
|
||||
the builder's `numbered` and `old-form-closed`, the indexer's `manifest-accepted` and its family `indexer.views`
|
||||
are those modules' own, declared in their own manifests, which
|
||||
ADR 0613 does not govern, and no seat definition carries them. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all
|
||||
at once (ADR 0608 §2), holding the seat list's grain: its name, whether it is held once or on every node, its verbs
|
||||
with their inputs and its provision. Its file is written by hand in its domain's contracts module, or in
|
||||
`mesh-contracts` where its domain has none; the files the registry branch generated once from mesh-sdk's seat list
|
||||
are their starting text, and nothing generates them again (ADR 0635 §2). Its messages, events and state layouts are
|
||||
added to a seat's definition when its domain's contract next changes, which moves that domain's seats whole into its
|
||||
contracts module in that change (ADR 0613 §3); which domain moves when is tracked by issue 167 (ADR 0613 §4).
|
||||
- **Two seats renamed** (ADR 0634 §9): `mesh-placement` becomes `mesh-module-assigner`, and `mesh-enrolment`
|
||||
becomes `mesh-node-enroller`; the module `mesh-enrolment` keeps its name. Their definitions, the subjects of their
|
||||
verbs and events, every holder's claim, every caller's `invokes` and grant, the claude-code instructions' text, the
|
||||
mesh MCP server's description and the glossary take the new names in the same delivery group, and nothing answers
|
||||
to the old ones: a call on an old subject fails loudly, naming the seat. **No state moves**: a state's bucket is
|
||||
the holder's (`<holder>_<state>`), so placement keeps every revision. The old subjects' events keep only their
|
||||
stream's own retention. **The order is the module graph's**: the grants of the new subjects are an ordering edge
|
||||
that comes before the holders move, and the callers come after the holder that serves the renamed verbs; an older
|
||||
node-engine takes its new build through `mesh-wants`, which is not renamed. **A machine enrolling with an installer
|
||||
built before the rename** asks the old enrolment seat's subject, which no holder answers: its enrolment fails
|
||||
loudly, naming the seat, and nothing joins; it enrols with an installer of the new build. **How it is checked**: the
|
||||
manifest validator refuses a claim, an `invokes`, a `consumes` or a grant naming a seat no definition names, so an
|
||||
old name fails
|
||||
(ADR 0634 §10); the acceptance after the delivery is that `mesh-module-assigner.placement` answers and
|
||||
`mesh-placement.placement` is not found, placement's revisions are those it had before, and no `seat-holders` key,
|
||||
finding or condition stays under an old name.
|
||||
- **Every part learns the seat list from the bus, through the seat `mesh-catalog`** (ADR 0635 §1, as ADR 0636 §4
|
||||
changes it). The node-engine, the tool runner, claude-code, the manifest validator and every other reader of the
|
||||
list of seats call, through mesh-sdk's runtime, the verb `seat-definitions` of the seat `mesh-catalog`: without a
|
||||
seat it answers every trunk's seat with its digest, with a seat that seat's definition. They hear the seat's events
|
||||
`seat-definition-changed` and `seat-definition-removed`, said for the trunks' definitions. A reader names no
|
||||
module: it calls the seat, never its holder, the indexer, by name. A running part acts on what is merged, so a
|
||||
reader takes no definition from a pull request's head; the manifest validator inside a delivery group's check is
|
||||
the one reader that reads the members' heads. No package carries the seat list, and mesh-sdk imports no contracts
|
||||
module.
|
||||
**A reader without the list acts on nothing new, and keeps running what it runs** (ADR 0636 §1, changing ADR 0635
|
||||
§3). A reader that starts without the list applies, composes, grants and judges nothing new, except for the
|
||||
bootstrapped modules below; it keeps running what it already runs, says in its health what it waits for, and a
|
||||
node-engine after a reboot starts again the compositions it last applied. Each node writes one finding when its
|
||||
readers have waited five minutes (ADR 0564 §2); the condition keeper raises it as a condition, which reaches the
|
||||
operator as every condition does, and clears it at the first full read. A reader away longer than the events'
|
||||
retention reads the whole list again. A check that cannot read the definitions it needs fails, naming them, rather
|
||||
than passing without them.
|
||||
**The bootstrapped definitions** (ADR 0636 §2, changing ADR 0608 §2) are the foundation's seats (research 075) and
|
||||
`mesh-catalog`, `mesh-conditions` and `operator-channel`: every reader carries them from its start and acts on their
|
||||
holders without the list, so after a full restart the indexer comes back and serves the list, and the condition
|
||||
keeper and the router, with the channels it sends through (ADR 0259), carry a wait's condition to the operator.
|
||||
**A missing or refused definition blocks new claims only** (ADR 0636 §3): when a seat's definition is missing,
|
||||
removed or refused, readers refuse a new claim, `invokes` or grant of that seat, and never stop or remove a module
|
||||
that already runs holding or calling it.
|
||||
**Each reader's list has two tools** in mesh-sdk's runtime bundle (ADR 0636 §7): one reads which definitions a
|
||||
reader holds, their source and digests and what it waits for; the other, granted to the operator and the healers,
|
||||
makes it read the list again now.
|
||||
**How it is checked** (ADR 0635 §6, ADR 0636 §8; *Acceptance tests*, below): the node-engine's whole-module test,
|
||||
against a stand-in of the seat, acts on nothing but the bootstrapped modules without the list and on all once it
|
||||
answers, and starts its last compositions after a restart; the indexer's whole-module test starts with only the
|
||||
bootstrapped seats and answers `seat-definitions`; with the indexer kept down, the node's finding is written after
|
||||
five minutes and the condition keeper raises it to the operator; a refused definition stops no running module; a
|
||||
reader refuses a pull request head's definition; mesh-sdk's unit test refuses a module's name in the runtime; both
|
||||
tools answer.
|
||||
- **The generator** is each language's build module's: the Go build module's package for Go, the Node and TypeScript
|
||||
build module's for TypeScript (the logic is the module's whose work it is). The generator's version is a build input
|
||||
of every contracts module, so a changed generator rebuilds each contracts module once (ADR 0603 §3). Each build
|
||||
module's test step regenerates and compares, for every module carrying the contracts marker (below): a committed
|
||||
file that differs, a file missing, or a file in the package tree the generator did not write fails the step, naming
|
||||
each path.
|
||||
- **Out of step is learned from events, and the indexer runs no generator.** For a pull request, the check's step
|
||||
fails with the out-of-step paths; the checker puts them in its verdict and emits its declared event `verdict`
|
||||
carrying them (its `preview.written` is declared in the same change). For a trunk, the builder's build-end event
|
||||
carries a trunk build's out-of-step paths, which is how a generator's change is seen, since no check of the
|
||||
contracts module runs then. The indexer consumes both and writes the trunk's finding.
|
||||
- **On the bus**: the indexer reads every seat definition from every trunk and open pull request, from the forge
|
||||
bridge's messages, and keeps them in its own state `seat-definitions`, keyed by seat and source (the trunk, or a
|
||||
pull request's head), as their only writer; a definition is at most 256 KiB. As the holder of `mesh-catalog` it says
|
||||
`seat-definition-changed` (seat, source, digest), kept seven days, only when a digest changed, and
|
||||
`seat-definition-removed` when a trunk's definition goes, both as events of the seat (ADR 0636 §4, changing ADR 0608
|
||||
§3). **Only the trunk's entry is the seat's contract**: the readers of the seat list read the trunks' through
|
||||
`seat-definitions`; the contracts modules' builds and the review ask `indexer_seat_definition`, which answers the
|
||||
trunk's unless a pull request's head is named, and says which it answered. **The manifest validator inside the
|
||||
delivery group's one check reads the seat definitions at every member's head** (ADR 0636 §4), so a group that
|
||||
changes a definition is judged by what it merges. A
|
||||
pull request's entries are removed on the forge bridge's event that it closed or merged. **A seat of the mesh's own
|
||||
contracts is defined only in its domain's contracts module, and a move there is a handover** (ADR 0636 §5, changing
|
||||
ADR 0608 §2): a definition names its glossary domain; the indexer takes a seat of the mesh's
|
||||
own contracts only from that domain's contracts module, `mesh-contracts` for a domain with none, and refuses a
|
||||
definition of it from any other module, with a finding naming both. A definition moving from `mesh-contracts` to its
|
||||
domain's contracts module is a handover: the indexer keeps the last trunk definition until the new one is merged,
|
||||
in either order of the two trunks. A seat defined outside the mesh's own contracts is taken from its own contracts
|
||||
module (ADR 0625 §7). A definition
|
||||
that does not parse fails its pull request's check; the trunk's stays. The merge is the only approval. The
|
||||
foundation's own definitions are bootstrapped before the bus exists, as the bootstrap of a fresh mesh has it
|
||||
(research 075).
|
||||
- **Findings**, written by the indexer, from which the condition keeper raises and clears the conditions (ADR 0564): a
|
||||
trunk's seat definition that does not parse, `seat-definition-unparsed` (a pull request's is its check's verdict, not
|
||||
a condition), cleared by a definition that parses; generated types on a trunk out of step with their definition,
|
||||
`generated-types-out-of-step`, cleared only by a clean build end of the same contracts module, per module and not per repository, since several
|
||||
contracts modules share one; a seat defined outside its domain's contracts module, naming both modules (ADR 0636 §5). **Tools** of the indexer: `indexer_seat_definition`
|
||||
(read one), `indexer_seat_definitions_again` (read a trunk's or a pull request's again now) and `indexer_out_of_step`
|
||||
(the generated types out of step).
|
||||
- **Contracts modules.** A seat's contract is published by a contracts module, which holds the seats' definitions,
|
||||
their contracts and their client packages and no seat (ADR 0625 §4, §5); the seat's holder implements the contract.
|
||||
**The contracts marker**: a contracts
|
||||
module's package artifact names the directory of its definitions in the field `generated-from`, and the manifest validator
|
||||
refuses a module carrying it that claims a seat, naming the seat. **Each domain whose seats the milestone defines
|
||||
whole has a contracts module of its own, created in the milestone** (ADR 0634 §7, ADR 0635 §5; the table above);
|
||||
`mesh-contracts` keeps the Core domain's seats, `mesh-config`, and the definitions of every seat whose domain has
|
||||
none yet. Each declares its packages in Go and TypeScript, cut per seat as *Where a seat's code goes* says, in
|
||||
package series `0.1`, published through the two package seats (design 51) and taken by its users by version, or
|
||||
inside one delivery group by the group's head.
|
||||
- **A contracts module and a domain SDK are modules like any other, found by their manifests** (ADR 0634 §8).
|
||||
`mesh-delivery-contracts` and the Delivery domain's SDK are folders of the mesh-delivery repository, and the other
|
||||
domains' contracts modules folders of the mesh-contracts repository, but the Connectivity domain's:
|
||||
`mesh-connectivity-contracts` is a folder of the mesh-networking repository, beside `mesh-wireguard`, which holds
|
||||
`the-private-network` with its manifest and peer generator its own (ADR 0636 §6, changing ADR 0634 §8). Each is a package-only module, as mesh-sdk long
|
||||
is: published from its trunk, rebuilding the modules that use it like any package (ADR 0573 §2, §3), and delivered
|
||||
to no node. A step of the build or the delivery that treats a module in a folder unlike one at a repository's root
|
||||
is a gap of the build and delivery system, fixed in the milestone, never worked around. **How it is checked**: the
|
||||
delivery group's one check builds and publishes every new module from its folder, and the build modules'
|
||||
regenerate-and-compare step runs on every contracts module the milestone creates (ADR 0634 §10).
|
||||
- **mesh-sdk takes no seat-specific type** (ADR 0613 §5): a unit test of mesh-sdk refuses an exported identifier or
|
||||
string constant naming a seat the definitions define. The two checks every change already runs, the
|
||||
build modules' compare step and that test, are where a stray file and a stray type are refused; mesh-catalog's own
|
||||
checks cannot see another repository.
|
||||
- **A contracts cycle holds nothing.** `mesh-delivery-contracts` is published through the forge module's package seats
|
||||
and built by the Go and Node build modules, and the forge module, `distribution` and the Go build module import it. A
|
||||
package edge and a build module's edge are triggers, never waits, so that cycle starts and is not refused (ADR 0614
|
||||
§6), and a package is numbered per source revision, so the triggers end (design 51 *The versions*).
|
||||
- **Older readers keep working**: a moved contract keeps its subjects, keys and message shapes, and a reader ignores
|
||||
a field it does not know. The two renamed seats are the exception, and every one of their users moves in the same
|
||||
delivery group (ADR 0634 §9, as it changes ADR 0608 §6).
|
||||
- **A vendor-specific** provision or contribution lives in its module's own extension package, published by version
|
||||
like any package; a later holder of the seat may publish its own the same way. A seat's definition stays
|
||||
vendor-neutral, which the seams review checks.
|
||||
|
||||
### Where a seat's code goes
|
||||
|
||||
*Added 2026-11-04, widened 2026-11-05* (ADR 0625, ADR 0627, ADR 0634, ADR 0635; the operator, 2026-11-04: "many small
|
||||
sdk's instead of 1 large sdk", and shared logic lives with its owner). A module is rebuilt only for what it uses, so
|
||||
shared code lives with its owner: a seat's code is cut per seat, in the contracts module of its domain; what one
|
||||
module owns is that module's own package; what one domain's modules and seats share is that domain's SDK; mesh-sdk
|
||||
keeps only what modules or seats of more than one domain use.
|
||||
|
||||
```
|
||||
contracts module of a domain (mesh-<domain>-contracts; mesh-contracts for Core, mesh-config, and a domain with none)
|
||||
├── definitions and fixtures written by hand, every seat's at its grain, the generation's source
|
||||
└── one package per seat defined whole, per language generated: types, subjects, state layouts, a typed call per verb
|
||||
└── its client package (where callers or holders share logic over the seat) written by hand
|
||||
|
||||
domain SDK (mesh-<domain>-sdk) written by hand: logic and forms one domain's modules and seats share, owned by no module
|
||||
a module's own package written by hand: logic and state layouts one module owns, where it holds no seat for them
|
||||
mesh-sdk the runtime every module uses, and what modules or seats of more than one domain share;
|
||||
it imports no contracts module and reads the seat list from the bus
|
||||
```
|
||||
|
||||
- **Shared code lives with its owner** (ADR 0634 §1), and the first home that fits is the one: a seat's types, the
|
||||
layouts of its states and the messages of its verbs and events are its contract's, and the logic its callers or
|
||||
holders share is its client's; logic and state layouts one module owns, where that module holds no seat for them,
|
||||
are that module's own package, which other modules import from it; logic and forms no seat carries and no one
|
||||
module owns, the seat-neutral parts seats' clients or holders share included, are their domain SDK's when only one
|
||||
domain's modules or seats use them, and mesh-sdk's when more than one domain's do or every module does.
|
||||
- **Each seat defined whole has a package of its own, per language** (ADR 0625 §1, ADR 0627 §1): its generated
|
||||
contract, and a module imports only the packages of the seats it uses, so a change of that seat rebuilds only its
|
||||
users. Where the fingerprint counts a language's module rather than its package, each such package is a module of
|
||||
its own in the same folder. In the milestone of ADR 0612 these are the 25 seats above.
|
||||
- **A seat at the seat list's grain has its definition and no package** (ADR 0635 §1, §2): its readers learn it
|
||||
from the bus, and a change of it rebuilds nothing.
|
||||
- **The imports run one way per package** (ADR 0625 §3, as ADR 0634 §2 and ADR 0635 §1 change it): a generated
|
||||
package imports nothing of mesh-sdk; mesh-sdk imports no contracts module; a domain SDK imports only mesh-sdk and
|
||||
contract packages; a client imports its own seat's contract, mesh-sdk's runtime and its own domain's SDK. Where a
|
||||
language's module is the unit of import, the clients are a module of their own in the folder, so the two never
|
||||
form a cycle.
|
||||
- **A client package** (ADR 0625 §4, as ADR 0634 §2 changes it) is the one hand-written package a seat may carry
|
||||
beside its contract, owned by the seat's contracts module, never by a holder. It holds the logic two or more
|
||||
callers or holders would otherwise each write over that seat, and only where such logic exists; it uses only its
|
||||
own seat's contract, mesh-sdk's runtime and its own domain's SDK, never another seat's client, and never names a
|
||||
holder, a module's tool or a module's subject. A seat's types and typed calls are its contract's, never its client's.
|
||||
- **A domain SDK** (ADR 0634 §2) is a package-only module, `mesh-<domain>-sdk`, named after the domain's heading in
|
||||
the glossary, which may sit in a repository with that domain's modules. Its importers are its domain's modules and
|
||||
the holders and callers of its domain's seats; it imports only mesh-sdk and contract packages, names no module's
|
||||
tool or subject, and holds no seat's type or state layout and no state layout one module owns. The first is
|
||||
`mesh-delivery-sdk`, in the milestone of ADR 0612: the moving of a file in parts (below), the build steps' and the
|
||||
composer's shared logic, each made seat-neutral over its callers' types (ADR 0635 §4), and the rules the Delivery
|
||||
domain's modules share.
|
||||
- **A module's own package** (ADR 0634 §1) holds what that module owns and no seat carries: its state layouts that
|
||||
other modules read and the logic over them. Its fingerprint counts only that package's files (ADR 0593 §2), so a
|
||||
change elsewhere in the owner module rebuilds none of the package's importers. Another module of the same repository uses it by path, from the same
|
||||
commit (ADR 0609 §2); a module of another repository uses it only by its name and a published version (ADR 0573
|
||||
§1), so the owner's manifest declares it as a package artifact (ADR 0606 §4).
|
||||
- **The clients of the milestone of ADR 0612** (ADR 0627 §1, ADR 0634 §4, ADR 0635 §4), each moved out of mesh-sdk
|
||||
with every importer in the same delivery group, mesh-sdk keeping no copy and no alias: `operator-channel` (an ask
|
||||
and its awaited answer, mesh-sdk's `asks` and `asker`); `mesh-vault` (the calls its callers share; mesh-sdk's
|
||||
hand-written vault types are replaced by the vault contract's, not moved into the client); `node-engine` (the
|
||||
checks of its compose-candidate ask and answer); `npm-package-registry`, `go-package-registry`,
|
||||
`container-image-registry` and `oci-artifact-registry` (publishing a file in parts, one client per seat, each
|
||||
binding the Delivery domain's SDK's transfer to its own seat's verbs and types); `mesh-hand-acts` (recording a
|
||||
hand act); and `mesh-provisioner` (mesh-sdk's provider runtime and its harness, which only providers use).
|
||||
`the-private-network` has none: the layout of the value it provides, which holds no key material, is the seat's
|
||||
contract in `mesh-connectivity-contracts` (ADR 0634 §1), the private network's manifest and peer generator are
|
||||
`mesh-wireguard`'s own, and the seat has a client only where its callers share logic (ADR 0636 §6, changing ADR 0635
|
||||
§4).
|
||||
- **Moving a file in parts is seat-neutral, in the Delivery domain's SDK, on both sides** (ADR 0627 §2, as ADR 0634
|
||||
§3 changes it): it serves only the four registry seats, all of the Delivery domain. The sending side and the going
|
||||
on from what a holder already holds, and the holder's side (taking the parts into the holder's own objects,
|
||||
checking their hashes, dropping an upload left idle, saying when its objects fill), name no seat, verb or seat's
|
||||
type and take subjects, shapes and checks from their caller. Each registry seat's client binds the sending side,
|
||||
and each holder (the distribution module, the forge's two package seats) binds the receiving side to the seat it
|
||||
holds, so no holder copies another's transfer.
|
||||
- **In a contracts module's published packages every file outside a client is generated** (ADR 0625 §5): the build
|
||||
modules' regenerate-and-compare step fails any other hand-written file there, naming it. The definitions and their
|
||||
fixtures sit outside the published packages and are not judged by that step.
|
||||
- **The contracts modules follow the glossary's domains** (ADR 0625 §6, ADR 0634 §7): a domain's seats move into
|
||||
`mesh-<domain>-contracts`, named after the domain's heading in the glossary, when that domain's seats move; a seat
|
||||
of the Core domain, or of a domain with no contracts module yet, is in `mesh-contracts`. The seven the milestone of
|
||||
ADR 0612 creates are in *A seat's definition*, above; the order of the other domains stays issue 167's (ADR 0613 §4).
|
||||
- **A seat defined outside the mesh's own contracts has a contracts module of its own** (ADR 0625 §7), which may
|
||||
share a repository with its first holder; its definition, contract and client never live in the holder module, so
|
||||
another module can hold the seat by importing only the contract, and the holder can be retired while the contract
|
||||
stays.
|
||||
- **A role only one module could ever play is no seat** (ADR 0625 §8): it is that module's own tools and events,
|
||||
reached by the module's name (ADR 0596 §2). The test is whether another module could take the role over, not how
|
||||
many hold it today, so every seat in force stays a seat, `mesh-broker`, `node-engine` and `route-proxy` included.
|
||||
- **mesh-sdk takes only what modules of more than one domain use** (ADR 0625 §9, as ADR 0627 §2, ADR 0634 §1, §4, §5
|
||||
and ADR 0635 §4 change it): the bus connection, the tools bundle's runtime, state and its helpers, the answer of a
|
||||
module's health tool, the `Membership` type with the names its runtime reads to learn its own membership, the
|
||||
package `views` (which takes its hand-act recorder and its finding writer from its caller), `outward`, the parts of
|
||||
`forgekey` that modules of more than one domain use, and a seat-neutral part seats of more than one domain share,
|
||||
as long as it names no seat, verb or seat's type. In the milestone of ADR 0612 its `go/delivery` is moved whole and
|
||||
deleted, each of its names to the home this section gives it, and every other part naming a seat or a holder's
|
||||
state leaves for its owner, every importer moving in the same delivery group; after it, mesh-sdk holds no package,
|
||||
type or function over a seat defined whole, and no seat list (ADR 0627 §4, ADR 0634 §4, ADR 0635 §1).
|
||||
|
||||
**How it is checked** (ADR 0625 §11, ADR 0627 §3, ADR 0634 §10 and ADR 0635 §6, all built in the milestone of ADR 0612):
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| One package per seat defined whole | a unit test of the generator (one program in the Go build module, emitting both languages and run by the Node build module too) emitting one package per seat defined whole, covering each of the 25; the manifest validator refuses a contracts module whose package holds the generated files of two seats defined whole, naming them |
|
||||
| The imports run one way | a mesh-sdk unit test that no package of mesh-sdk imports a contracts module; the generator tests check no generated package imports mesh-sdk; a domain SDK's importers and imports by the review below |
|
||||
| mesh-sdk holds no logic over seats, and no part that left it | mesh-sdk's package-list test refuses by name `go/delivery`, `asks`, `asker`, the vault's types and its shared calls, the compose-candidate checks, the provider runtime, and every other package or part ADR 0634 §4 and ADR 0635 §4 move; its seat-naming test refuses the defined seats' names in mesh-sdk, so a copy kept without importing a contract fails too |
|
||||
| A client uses only its own seat's contract and its domain's SDK | the catalog check of a client: its own test refuses a client importing another seat's client or another domain's SDK, and one naming a module's tool or subject, and passes every client of the milestone |
|
||||
| The parts transfer is seat-neutral | its unit test, in the Delivery domain's SDK, runs both sides against two different subjects and shapes the caller gives, and the seat-name check holds it seat-neutral; the review refuses a holder that takes parts in without it |
|
||||
| Only a client is written by hand in a published package | each build module's regenerate-and-compare test fails on a hand-written file outside a client package and passes a contracts module holding its definitions and fixtures; the step runs on every contracts module |
|
||||
| A module in a folder is built and published as one at a root | the delivery group's one check builds and publishes every new contracts module and the domain SDK from its folder, and `mesh-wireguard` and `mesh-connectivity-contracts` from the mesh-networking repository |
|
||||
| A seat is defined only in its domain's contracts module | the indexer refuses a second module's definition of a seat, with its finding naming both, and a move from `mesh-contracts` holds in either merge order (ADR 0636 §5) |
|
||||
| A holder keeps its seat's states | the manifest validator refuses a holder whose `requires` does not declare each of its seat's states with the same local name and template |
|
||||
| A contracts module holds no seat; a role only one module could ever play has none | the manifest validator's refusal of a contracts module that claims a seat; review both ways: a new seat definition's pull request is asked whether another module could take the role over, and a module's pull request is asked the same of each new tool or event family other modules call |
|
||||
| Each new exported name sits where this section puts it | the review of every pull request (playbooks 07 and 08, the code reviewer's definition) asks the home of each new exported type, field, constant, package or module against this section, a domain SDK's importers included |
|
||||
|
||||
**Acceptance tests**, each at its level (unit, or a case of the module's one whole-module integration test against the
|
||||
real bus and store) and in its repository. The move raises no repository's test count, since mesh-delivery is already
|
||||
over its budget: each case below replaces or joins an existing one.
|
||||
|
||||
| Test | Level | Repository |
|
||||
|---|---|---|
|
||||
| research 081's 14, 21, 25 and 26 | as research 081 sets them | as research 081 sets them |
|
||||
| mesh-sdk's seat-naming, import and package-list tests; no module's name in the runtime | unit | mesh-sdk |
|
||||
| a reader of the seat list, against a bus the test gives: a reader away longer than the events' retention reads the whole list again; a reader refuses a pull request head's definition; both list tools answer, the re-read one only for the operator and the healers | unit | mesh-sdk |
|
||||
| the node-engine, against a stand-in of the seat `mesh-catalog`: without the list it acts on nothing but the bootstrapped modules, then on all once the stand-in answers; after a restart it starts its last applied compositions again; a refused definition stops no running module | a case of the node-engine's whole-module test | `mesh-host` (the node-engine's repository) |
|
||||
| the indexer starts with only the bootstrapped seats and answers `seat-definitions`, without a seat (names and digests) and with one; with the indexer kept down, the node's finding is written after five minutes and the condition keeper raises it to the operator | cases of the indexer's whole-module test | mesh-delivery |
|
||||
| a trunk definition that does not parse writes its finding and a fixed one clears it; a trunk build end naming out-of-step types writes the finding, a clean build end of another contracts module of the same repository leaves it, and a clean one of the same module clears it | a case of the indexer's whole-module test | mesh-delivery |
|
||||
| `indexer_seat_definition` answers the trunk's entry while a pull request holds another; a pull request's entries go when it closes; a definition over 256 KiB is refused; a seat-list-grain definition is read from a contracts module like a whole one; a removed definition says `seat-definition-removed` and its seat takes no new claim afterwards; a second module defining a seat is refused with its finding, and a definition moving from `mesh-contracts` to its domain's contracts module holds in either merge order; each of the indexer's tools answers | cases of the indexer's whole-module test | mesh-delivery |
|
||||
| the manifest validator refuses a claim, an `invokes`, a `consumes` or a grant naming `mesh-placement` or the seat `mesh-enrolment`, and a holder missing one of its seat's states in `requires` | unit | mesh-delivery |
|
||||
| the checker's `verdict` names out-of-step files; the group's check reads seat definitions at every member's head | a case of the checker's whole-module test | mesh-delivery |
|
||||
| each build module's compare step fails on a differing, a missing and an unwritten file, naming each, per contracts module | unit | mesh-catalog |
|
||||
| a change in one folder module rebuilds and publishes only it and its importers; a module with both a deployed and a package artifact (the grants part or disk-load) is built, delivered and published | a case of the Go build module's whole-module test | mesh-catalog |
|
||||
| after the delivery, `mesh-module-assigner.placement` answers and `mesh-placement.placement` is not found; placement keeps its revisions; no `seat-holders` key, finding or condition stays under an old name | live, read through the mesh's tools | the mesh |
|
||||
| `mesh-wireguard` is built and delivered from the mesh-networking repository | the group's one check, then live | mesh-networking |
|
||||
|
||||
## The foundation's seats
|
||||
|
||||
`mesh-controller`, `mesh-store` and `mesh-broker` name which assignment the mesh *itself* uses: the
|
||||
@@ -312,6 +689,13 @@ 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.
|
||||
|
||||
**The seat's verbs, from research 085's milestone.** The delivery parts reach the forge through the seat `git`'s
|
||||
verbs alone, never by the forge module's own tools: reads for the parts that declare them in their `invokes`, and
|
||||
acts (a status, a comment, the delivery's view, a line on the commit's note, a merge) for the forge bridge alone, as
|
||||
the holder's trusted value `callers` names; the forge's events (a pull request updated, merged or closed, the forge's
|
||||
own webhooks, a repository writable again) are the seat's events. Design 47, *The verbs*, lists them with their
|
||||
callers and how they are checked.
|
||||
|
||||
**Not yet designed:** a credential for cloning a private repository. The mesh's own repositories are
|
||||
public. The natural place for a clone credential is a `secret` from the vault, and that is a decision
|
||||
still to take.
|
||||
@@ -326,7 +710,7 @@ checked as their tables say:
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| The set is closed, and every mesh entry names its decision | 0118: a unit test on the mesh's own entries. **The refusal of an unknown seat is at registration, not in the parser** — correction of fact, 2026-09-27: a module may hold a seat *another* module declares, which is the point of naming the seat and not its provider, so whether a claimed name exists is a fact about the whole catalogue and a manifest in isolation cannot be judged on it. Registration tests cover an invented name and a name another module declares; the parser still refuses a claim on the mesh's own `mesh-*`/`node-*` namespace and a scope that disagrees with a declaration in the same manifest. |
|
||||
| The set is closed, and every mesh entry names its decision | 0118: a unit test on the mesh's own entries. **The refusal of an unknown seat is at registration, not in the parser** — correction of fact, 2026-09-27: a module may hold a seat *another* module declares, which is the point of naming the seat and not its provider, so whether a claimed name exists is a fact about the whole catalog and a manifest in isolation cannot be judged on it. Registration tests cover an invented name and a name another module declares; the parser still refuses a claim on the mesh's own `mesh-*`/`node-*` namespace and a scope that disagrees with a declaration in the same manifest. |
|
||||
| The set is derived, and enumerating it is a query | 0118: the overview lists the mesh's own plus every registered module's, asserted against a fixture mesh. |
|
||||
| `mesh-*` is the mesh's, and a module may not declare one | 0118: a registration test refusing a manifest that declares any `mesh-*` seat, naming the prefix. |
|
||||
| Two modules cannot declare the same seat | 0118: a registration test; the second is refused and the first untouched. |
|
||||
@@ -339,7 +723,7 @@ checked as their tables say:
|
||||
| `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 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-<manager>` 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 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 catalog 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. |
|
||||
| A node seat names only literal absolute paths; every path has one seat naming it outright, and stepping back does not cycle | 0287: a unit test over the compiled set; registration tests refusing a module's seat naming a path at mesh scope, with `${`, or already named by another seat. |
|
||||
@@ -347,3 +731,22 @@ checked as their tables say:
|
||||
| The uplink's step-back comes from the seat | 0287: the composition tests for the uplink and the resolver on one machine pass with the case in the collision check removed. |
|
||||
| Holdings are derived, and the overview lists every seat | 0118: the `seats` command test, including an unheld seat. |
|
||||
| A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. |
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 for research 081 (ADR 0605 to 0611; *A seat's definition*) was reviewed with designs 51
|
||||
and 32 by the four reviewers by architectural domain; the findings and what answers them are listed in design 51's
|
||||
*Reviewed by architectural domain*. For this design: only the trunk's definition is the contract (security), the
|
||||
indexer learns out-of-step types from the check's verdict event and runs no generator (seams), findings go to the
|
||||
condition keeper (health), a pull request's entries are removed when it closes (running qualities).
|
||||
|
||||
The milestone's amendment (ADR 0612, ADR 0613, ADR 0614, ADR 0620: the seats defined whole, the rest at the seat
|
||||
list's grain, the contracts marker, out of step learned from the checker's `verdict` and the builder's build end, the
|
||||
seat list's one source), with the amendment of 2026-11-04 (ADR 0625, ADR 0627: ten seats defined whole, a package per
|
||||
seat, a package for the seats at the seat list's grain in `mesh-contracts`, the seven clients and the seat-neutral parts transfer, *Where a
|
||||
seat's code goes*), and the amendment of 2026-11-05 (ADR 0634, ADR 0635: shared code with its owner, the domain
|
||||
SDK, a module's own package, a contracts module per domain the milestone moves, 25 seats defined whole with their
|
||||
states, the seat list read from the bus, two seats renamed), with ADR 0636 (the seat list through the seat
|
||||
`mesh-catalog`, a reader without it acting on nothing new, a seat defined only in its domain's contracts module, the
|
||||
Connectivity domain in the mesh-networking repository; accepted at its version 15) and the lead's rulings on the first review by architectural
|
||||
domain written in, await their final review, which rewrites this section.
|
||||
|
||||
@@ -47,7 +47,7 @@ A requirement has three parts:
|
||||
|
||||
**A contract is shared, not per module.** A database's contract is the database's, whoever requires
|
||||
it. **The controller holds every contract**, one per provision name, declared where the provision is
|
||||
defined in the catalogue. Today contracts are implicit in each provider's served fields; the first
|
||||
defined in the catalog. Today contracts are implicit in each provider's served fields; the first
|
||||
phase below makes them explicit, because nothing can be checked against a contract that is not
|
||||
written down. A provider is checked against the contract it claims to answer. A module's own
|
||||
specification may narrow a contract (a password of at least this length, a directory owned by this
|
||||
@@ -213,13 +213,13 @@ a placed directory under the node's root. Each is this design's provider in the
|
||||
placeholders have, not yet the one requirement form below; they are phase 1's first cases.
|
||||
|
||||
*Phase 3, in part (2026-09-30):* every definition's **own** data directory is placed; the conversion
|
||||
moved no data, proven by resolving both catalogues with the controller's rule and comparing
|
||||
moved no data, proven by resolving both catalogs with the controller's rule and comparing
|
||||
([issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md)).
|
||||
What the mesh writes *for* a module was still placed by the definition
|
||||
([issue 174](../../04-ISSUES/174-the-meshs-own-files-for-a-module-are-placed-by-the-definition/00-report.md)),
|
||||
the gap this design answered on 2026-09-26; built later the same day: a directory saying
|
||||
`place: "mesh"` is `<root>/mesh/<module>`, a directory beneath a placed one states its path as
|
||||
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogues resolved and compared —
|
||||
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogs resolved and compared —
|
||||
shows forty-eight definitions naming the paths they named before.
|
||||
|
||||
*A preference has a default; the operator's own value has none (2026-10-08,
|
||||
@@ -323,7 +323,7 @@ value in a container's environment is refused when the definition is parsed, wit
|
||||
containers, login, broker account and settings are keyed by it, as today, and a login still fits the
|
||||
backend that keeps it (ADR 0049):
|
||||
the bound is the one the offer of each provision it requires states, a keyless provision states none,
|
||||
and an overflow is refused by the catalogue check before merge and left out of the provider's grants,
|
||||
and an overflow is refused by the catalog check before merge and left out of the provider's grants,
|
||||
reported, at composition — never a refusal of the provider's machine
|
||||
(ADR 0225).
|
||||
|
||||
@@ -371,7 +371,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
|
||||
store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalog
|
||||
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)).
|
||||
@@ -433,7 +433,7 @@ as waiting, and nothing is delivered until the answer arrives.
|
||||
| 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 |
|
||||
|
||||
Each is retired only once nothing uses it. Until then both are accepted, and a catalogue test lists
|
||||
Each is retired only once nothing uses it. Until then both are accepted, and a catalog test lists
|
||||
the definitions still using the old form. That list shrinks to empty, and then the old form is
|
||||
removed from the parser.
|
||||
|
||||
@@ -453,7 +453,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
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
|
||||
3. **Definitions move, and seats move to assignments.** Every catalog definition is rewritten, adopted
|
||||
and running assignments placed where their data already is, and each claim becomes a seat the
|
||||
module can hold, held by the assignment that holds it today. *Ends when* the list of definitions
|
||||
using an old form is empty, the old forms are removed, and the store module runs on two lab
|
||||
@@ -464,7 +464,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| Every requirement has one of the four provider kinds | The parser refuses any other. |
|
||||
| A definition names no host path, node or mesh | The catalogue tests of ADR 0112. |
|
||||
| A definition names no host path, node or mesh | The catalog tests of ADR 0112. |
|
||||
| A module provider is chosen by named seat, pin, co-location, only one, a person's choice | Resolution tests for each step: a requirement naming a seat served by its holder even with another provider on the consumer's node, and refused when the seat is unheld; several candidates and none local, where assignment lists them with the seat's holder first and records the choice as a pin, and refuses without one. |
|
||||
| A provider answers within its contract | A controller test: an answer carrying a field its contract does not name, or missing one it does, is refused and not delivered. |
|
||||
| A module narrows a contract and never widens it | The parser refuses a module specification that loosens a contract's field. |
|
||||
@@ -484,9 +484,9 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
|
||||
| The controller and a node's host take the same path | 0113's tests: the controller's definition declares requirements and no own secret; a node's bus account is made by the vault and delivered sealed to that node. |
|
||||
| Moving the vault or the broker is break-glass | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
|
||||
| A secret that cannot be rotated says so | A vault test: rotating a secret marked not rotatable by the mesh is refused, naming why. |
|
||||
| Only the controller reads the seat placeholder | A catalogue test, from phase 3: no definition uses the seat placeholder. |
|
||||
| Only the controller reads the seat placeholder | A catalog test, from phase 3: no definition uses the seat placeholder. |
|
||||
| Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. |
|
||||
| The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. |
|
||||
| The old forms retire | The catalog test listing definitions still using one. It must be empty before a form is removed. |
|
||||
|
||||
## Not settled here
|
||||
|
||||
|
||||
@@ -169,11 +169,11 @@ paper is wrong until there is a second mesh to find out.
|
||||
|
||||
The derivation refuses two things where they can still be named: two users with one name — the
|
||||
server reads the file as one of them and which one depends on the order — and a module
|
||||
assigned but absent from the catalogue, which would compose a user with no authority and fail
|
||||
assigned but absent from the catalog, which would compose a user with no authority and fail
|
||||
on its first publish with an authorisation error that says nothing about a missing manifest.
|
||||
A user the mesh has minted no password for is *named* rather than dropped or written as a user
|
||||
anybody is: an ordinary situation with an obvious remedy, and the caller decides whether a
|
||||
partial file is worth writing. A seat's protocol is gathered across the whole catalogue, not
|
||||
partial file is worth writing. A seat's protocol is gathered across the whole catalog, not
|
||||
from one manifest, because a seat is declared by one module and held by another.
|
||||
|
||||
**Out, and what each needs.**
|
||||
@@ -447,7 +447,7 @@ pays for itself furthest away.
|
||||
derived to subjects, the three namespaces, permissions computed from a declaration, and a
|
||||
manifest that contains no subject. Done in the controller's composer (permissions, streams,
|
||||
consumers), in the runtime's client (subjects derived from the credential, never named by a
|
||||
module), and as a catalogue test asserting all 72 manifests hold no subject — because the
|
||||
module), and as a catalog test asserting all 72 manifests hold no subject — because the
|
||||
rule held by construction, and a rule held by construction is one a later field breaks
|
||||
quietly.
|
||||
- [x] 3.9 **seats declared by modules** — the manifest now carries `seats` (name, scope,
|
||||
@@ -470,7 +470,7 @@ pays for itself furthest away.
|
||||
all: a holding is derived at resolution, never stored, so nothing recorded points at an old
|
||||
name (recorded as a progressive insight on ADR 0126). A **kept** rename table tells a
|
||||
manifest written against an old name what it became, because a module lives in its own
|
||||
repository and may be registered long after the catalogue stopped using one.
|
||||
repository and may be registered long after the catalog stopped using one.
|
||||
|
||||
**A seat and the interface it delivers are different names.** The `git` seat became
|
||||
`mesh-git` while the `git` *provision* it delivers did not change, and the same for the
|
||||
@@ -522,7 +522,7 @@ it, and the beds that need a mesh living on NATS can finally run.
|
||||
(ADR 0129). 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 builder needs a reply queue for nothing and a grant over nobody's inbox.
|
||||
request carried, the controller records it and the catalog 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
|
||||
@@ -532,7 +532,7 @@ it, and the beds that need a mesh living on NATS can finally run.
|
||||
|
||||
The outcome carries the module name, because only the manifest says what was built and one
|
||||
message now has three readers. A failed build names none: it produced no module version, and
|
||||
the catalogue would otherwise place something that was never made.
|
||||
the catalog would otherwise place something that was never made.
|
||||
- [x] 4.3 an installation completes over the bus, with the same outcome as the path it replaces —
|
||||
**the installer can raise it**: a foundation template that stands up the server, writes the
|
||||
server's own settings and the mesh's first user list beside them, and starts a controller
|
||||
@@ -573,7 +573,7 @@ it, and the beds that need a mesh living on NATS can finally run.
|
||||
correct. It uses the client a module's runtime uses, so what a person may do is answered by the
|
||||
same permission list that answers it for a module and an audit has nothing separate to read.
|
||||
|
||||
Three decisions in it worth keeping. It lists what the **catalogue** has rather than what this
|
||||
Three decisions in it worth keeping. It lists what the **catalog** has rather than what this
|
||||
credential may call: somebody seeing only their own tools cannot tell "not installed" from "not
|
||||
yours", and those need different people to fix them. A failed call says which of three things
|
||||
happened — nobody serves it, this credential may not, or the tool was slow — because the
|
||||
@@ -597,10 +597,10 @@ it, and the beds that need a mesh living on NATS can finally run.
|
||||
|
||||
**The catch-up half needed nothing built, and that was the answer.** It existed because a queue on
|
||||
the bus the mesh runs on today receives only what is published after it is bound, so everything
|
||||
built before the catalogue existed was announced to nobody — and on a fresh mesh that is always
|
||||
the foundation, because those are the things the catalogue needed in order to exist
|
||||
built before the catalog existed was announced to nobody — and on a fresh mesh that is always
|
||||
the foundation, because those are the things the catalog needed in order to exist
|
||||
([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)). A
|
||||
whole mechanism followed: the catalogue asks, the controller re-publishes.
|
||||
whole mechanism followed: the catalog asks, the controller re-publishes.
|
||||
|
||||
A stream is a log and a consumer is a position in it. A consumer created later starts at the
|
||||
beginning, so the builds are simply there — asked of a running server rather than assumed, since
|
||||
@@ -636,8 +636,8 @@ left to be re-derived under time pressure:
|
||||
| 1 | this one | prose; nothing deploys |
|
||||
| 2 | the sdk | comments only, and no module rebuilds for it |
|
||||
| 3 | the client library | it is what a module calls to emit, and it is where the subject is derived. Until it lands, a locally-named event is published under the local name itself |
|
||||
| 4 | the catalogue | every manifest and every module's code, renamed together. Safe only once the runtime derives |
|
||||
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalogue makes every unconverted module unregisterable |
|
||||
| 4 | the catalog | every manifest and every module's code, renamed together. Safe only once the runtime derives |
|
||||
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalog makes every unconverted module unregisterable |
|
||||
| 6 | the node-engine | last, because nothing else waits on them |
|
||||
|
||||
Two properties make the sequence safe rather than merely ordered, and both are pinned by tests. A
|
||||
@@ -682,11 +682,11 @@ healthy while reacting to nothing.
|
||||
- [x] 5.4 **the old broker and everything that named AMQP leave the mesh** (ADR 0131,
|
||||
superseding ADR 0127): the two modules that
|
||||
required `amqp` are removed, the broker's module is unassigned and removed (**done 2026-09-28**; the predecessor's own tooling, which rode the same adopted broker, went dark with it, as ADR 0130 accepted), registration refuses
|
||||
a manifest that provides or requires `amqp`, and a whole-catalogue check asserts none does. Not
|
||||
a manifest that provides or requires `amqp`, and a whole-catalog check asserts none does. Not
|
||||
a retirement condition — a decision, taken, with the operator's "I don't care if the predecessor
|
||||
breaks" on record (ADR 0130).
|
||||
Retiring with it: the build outcome's second announcement under the module's own name, which
|
||||
existed only so a catalogue deployed before the rename and one after both heard it.
|
||||
existed only so a catalog deployed before the rename and one after both heard it.
|
||||
|
||||
> **The remote tooling goes with it too.** The predecessor's own mesh talks over that broker, so
|
||||
> shutting it down ends the path that reaches this installation's machines from a workstation.
|
||||
|
||||
@@ -5,7 +5,7 @@ code:
|
||||
- mesh-controller internal/inventory
|
||||
- mesh-controller internal/catalogue
|
||||
- mesh-controller cmd/mesh-controller
|
||||
updated: 2026-10-17
|
||||
updated: 2026-11-06
|
||||
decisions:
|
||||
- "ADR 0181"
|
||||
- "ADR 0182"
|
||||
@@ -174,7 +174,7 @@ the client/identity side and the CA are the open pieces.
|
||||
`ssh-client` template can emit a `Host` block per node with the right login name. Composed
|
||||
end-to-end in the controller's tests.
|
||||
|
||||
**Written but not shipped:** the `ssh-client` catalogue module itself exists on a branch of the
|
||||
**Written but not shipped:** the `ssh-client` catalog module itself exists on a branch of the
|
||||
module repository; its pull request was closed with a hold until this design is deployed, and
|
||||
nothing has deployed it since. The predecessor's generator still writes every workstation's ssh
|
||||
client blocks today — which is where [issue 172](../../04-ISSUES/172-the-ssh-client-block-matches-one-spelling-of-a-machine/00-report.md)
|
||||
@@ -209,7 +209,7 @@ alias and its trust, and a fresh machine has no operator dotfiles at all — the
|
||||
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 seat list, and must be gotten right. The mechanism half is now settled — ADR 0120 is what lets
|
||||
the ssh files be templates with no controller format — so what remains to decide here is the
|
||||
model:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller]
|
||||
updated: 2026-10-17
|
||||
updated: 2026-11-06
|
||||
decisions:
|
||||
- "ADR 0238"
|
||||
- "ADR 0221"
|
||||
@@ -30,7 +30,7 @@ webhook".
|
||||
seat or schema:
|
||||
|
||||
1. `module moved <module> <commit>` — 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).
|
||||
trigger, so this is manual; the catalog's webhook does it automatically — see below).
|
||||
2. `build --behind` (or `build <repo> [--ref] [--path <subdir>]`) — 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
|
||||
@@ -41,7 +41,7 @@ seat or schema:
|
||||
**A breaking change** — a manifest schema the controller parses differently (a fact's shape, a
|
||||
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).
|
||||
4. Land the code (controller + catalog together — they are one change).
|
||||
5. Rebuild + deploy the new controller (steps 1–3). **The moment it is live it refuses the
|
||||
still-old-shape stored manifests, and composition freezes for every node that runs an affected
|
||||
module.** Running services are untouched; only new declarations stop.
|
||||
@@ -63,7 +63,7 @@ automating this must solve.
|
||||
|
||||
### 1. The trigger today is HAL's, not the mesh's
|
||||
|
||||
Build-on-push works for the catalogue because its repository has a Gitea webhook pointing at
|
||||
Build-on-push works for the catalog because its repository has a Gitea webhook pointing at
|
||||
`http://host.docker.internal:9877/webhook/gitea` — and **that receiver is `hal-gitea-tools.service`**
|
||||
(`~/.hal/modules/hal/gitea/tools/server.js`), a *predecessor* component. The nox builder consumes
|
||||
build work; it does not receive Git events. So the mesh's own build pipeline currently rides on a
|
||||
@@ -78,12 +78,12 @@ HAL service, and:
|
||||
seat — `mesh-forge-trigger` or folded into the git seat's holder) that receives Git events and turns
|
||||
them into build work over the broker, for **every** repository including `mesh-controller`. Replacing
|
||||
`hal-gitea-tools` is the concrete first build. Its logic already exists to copy: match the pushed
|
||||
repository (and changed paths, for a monorepo like the catalogue) against the build-context
|
||||
repository (and changed paths, for a monorepo like the catalog) against the build-context
|
||||
repository of every registered module, and rebuild the matches.
|
||||
|
||||
### 2. The builder validates too — and a breaking change deadlocks it
|
||||
|
||||
The builder embeds the same catalogue package the controller does, so **it validates a
|
||||
The builder embeds the same catalog 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
|
||||
@@ -104,7 +104,7 @@ does not know. The old builder cannot build the new builder. Escapes:
|
||||
|
||||
Either way, self-update for breaking changes needs a **transition discipline** so a push does not
|
||||
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
|
||||
one release — deprecated aliases in the seat list, 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
|
||||
automate the freeze.
|
||||
@@ -126,7 +126,7 @@ automate the freeze.
|
||||
Revision, ADR 0162. The
|
||||
trigger exists: the forge announces a merge on the bus and the controller acts on it (ADR 0157 made
|
||||
the build narrate; this makes the merge a plan). A module's dependencies are one relation in the
|
||||
catalogue — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
|
||||
catalog — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
|
||||
what changed and everything reachable from it along those edges, sorts the set into tiers, writes the
|
||||
plan to the store, asks the first tier and returns. Each outcome advances the plan; a tier whose
|
||||
rolled-out modules a later tier is built by waits until the machines report them applied; a
|
||||
|
||||
@@ -12,8 +12,16 @@ code:
|
||||
- mesh-tools src/main.ts
|
||||
- mesh-catalog modules/mesh-catalog
|
||||
- mesh-tools node-tools/internal/runtime (a module's state, ADR 0201)
|
||||
updated: 2026-10-12
|
||||
- mesh-sdk (the typed bus-state entry in `requires` and its binding, ADR 0595)
|
||||
updated: 2026-11-04
|
||||
decisions:
|
||||
- "ADR 0609"
|
||||
- "ADR 0612"
|
||||
- "ADR 0613"
|
||||
- "ADR 0618"
|
||||
- "ADR 0606"
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0307"
|
||||
- "ADR 0240"
|
||||
- "ADR 0233"
|
||||
@@ -62,7 +70,7 @@ onto this in step 3 of ADR 0116.
|
||||
This is the load-bearing rule.
|
||||
ADR 0112 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.
|
||||
the catalog, and the mesh would have hundreds of copies of a decision it made once.
|
||||
|
||||
| declared | derived |
|
||||
|---|---|
|
||||
@@ -94,7 +102,7 @@ lives under the emitting module's own name and the consumer carries that couplin
|
||||
|
||||
**How the rule is checked, because it was not.** *Added 2026-09-27, same source.* Two checks, because
|
||||
the mistake happens at two scales. Per manifest, at registration: an event is a local name, and the
|
||||
old bus's form is refused with the name to write instead. Across the whole catalogue, as a test:
|
||||
old bus's form is refused with the name to write instead. Across the whole catalog, as a test:
|
||||
where a consumed event's emitter is present, it must emit that event. The second cannot demand a live
|
||||
emitter for everything — a module lives in its own repository and may be installed long before the
|
||||
one whose events it wants — so it says nothing about an absent emitter and everything about a present
|
||||
@@ -117,7 +125,7 @@ caller already handles). The kind token is what makes `mesh.mod.*.event.>` a saf
|
||||
first draft of this table had no token, which reads better and cannot be implemented.
|
||||
|
||||
**The test this must pass: the manifest survives the wire changing.** Reorganise the subject space
|
||||
and every manifest in the catalogue is still correct. That is the property
|
||||
and every manifest in the catalog is still correct. That is the property
|
||||
ADR 0039 gave the sdk, applied to
|
||||
declarations.
|
||||
|
||||
@@ -172,7 +180,7 @@ can be wrong.
|
||||
|
||||
**And one publish reaches three audiences without anybody's inbox being opened.** A build's outcome is
|
||||
the seat's own event: whoever asked matches it by the id their request carried, the controller records
|
||||
it, the catalogue places it in the graph. That is the fan-out a shared exchange gave for free, written
|
||||
it, the catalog places it in the graph. That is the fan-out a shared exchange gave for free, written
|
||||
as a subject the mesh derived instead of a topology somebody configured — and it is why a holder needs
|
||||
no permission to publish into an asker's inbox, which is the one grant design 25 §4 refuses by name.
|
||||
|
||||
@@ -227,7 +235,7 @@ service" versus "one worker per machine".
|
||||
| credential | sealed, per consumer | none | none | none | none |
|
||||
| reply | — | none | none, or an event later | a report | awaited |
|
||||
| retention | — | age and size | work queue, explicit ack | **last per subject** | none |
|
||||
| declared | `provides`/`requires` | `emits`/`consumes` | seat `accepts` / `uses` | the mesh's own; a module's `state` / `reads` | `serves` |
|
||||
| declared | `provides`/`requires` | `emits`/`consumes` | seat `accepts` / `uses` | the mesh's own; a module's bus state in `requires` (ADR 0595), the old `state` / `reads` until it moves | `serves` |
|
||||
|
||||
**Job** is the one ADR 0041 had no room
|
||||
for. Its table has events at 1:many and provisions at 1:1; a module submitting work to a service
|
||||
@@ -249,7 +257,7 @@ registered for every machine, sent as an event, never reached a machine assigned
|
||||
consumer did not exist yet when the event passed — and a licence binding sent as events replays a
|
||||
week of rotations where only the latest matters. So a module names the state it **owns** with
|
||||
`state`, and another module's it **reads** with `reads: <module>.<name>`. Each is a key-value bucket
|
||||
the controller creates from the catalogue, mesh-wide, existing from registration so a reader can
|
||||
the controller creates from the catalog, mesh-wide, existing from registration so a reader can
|
||||
watch before the owner runs anywhere ([design 25](25-the-bus-on-nats.md) §3). Every instance of the
|
||||
owner writes; a reader reads and watches. A key may name a machine by the module's own convention;
|
||||
the mesh keeps one bucket per name, not one per machine, because "every server, for every machine"
|
||||
@@ -278,6 +286,42 @@ module's assignment** — what a module stored is data
|
||||
(ADR 0030) — and one whose
|
||||
declaration is gone is reported, never removed by the mesh.
|
||||
|
||||
**A module asks for its bus state in `requires`, and reads no other module's.** *Amended 2026-11-03, ADR 0595
|
||||
and ADR 0596, from research 082.* The `state` and `reads` of the two paragraphs above, and the sections `logs`,
|
||||
`streams` and `objects` beside them, are four vocabularies for one need: what a module keeps on the bus. They are
|
||||
replaced by one form, a **provision** of the bus module, as a database is postgres's:
|
||||
|
||||
- **An entry of `requires`, in its typed long form** (ADR 0595 §1): the local name, the storage type, the template
|
||||
(state, events, work queue, log, objects, ledger) and the template's options, within their bounds; a ledger also
|
||||
names its type (its schema, its operations each with its ledger effect, its indexes). The templates, their fixed
|
||||
settings and their options are [design 25](25-the-bus-on-nats.md) § 12.1. mesh-sdk's catalog package carries the
|
||||
entry's type and its strict decoding; every rule that judges it lives in the manifest validator (ADR 0595 §8).
|
||||
- **The binding is what the module receives**: the names on the bus for each local name, written into its
|
||||
membership by the grants part once the bus module says `created` (design 25 § 12.2). The module still names only
|
||||
locally (§ 1). A module whose binding is missing at start fails loudly, naming the entry.
|
||||
- **One name, one form.** A module asks for each entry once: an old section and the new form are never both used for
|
||||
one local name, and one name serves one storage type only.
|
||||
- **No readers.** The new form has no `reads`, and the manifest validator refuses one (ADR 0596 §2). Only the owner's account
|
||||
reaches its bus state. **A module's events are subscribed to; its data is asked for**: what another module needs
|
||||
of a module's kept data it asks through that module's tools or seat verbs (§ 4's tool relationship, `serves`),
|
||||
and what a module lets others hear is its declared events, heard through `consumes` (ADR 0363, unchanged; ADR 0596
|
||||
§1). A bucket's layout is never a contract another module depends on; a tool or a declared event is.
|
||||
- **A state may be read-only to the module's tools.** A state's declaration may carry `read-only: true`: only the
|
||||
processes of the module whose `bus.state` names that state write it, and the module's tools read it, holding no
|
||||
publish on its subjects. The manifest validator refuses a read-only state that no process of the module names. How it is checked:
|
||||
the manifest validator's unit tests for the refusal, and the composition test that a tools bundle's account holds a read on the
|
||||
state and no write. The bus module's state `spent` (design 25 § 12.3) is the first user. (A design limit of the
|
||||
milestone's review; no record decides it yet.)
|
||||
- **The move is a module at a time** (ADR 0595 §11, ADR 0596 §3), each when it is next touched, keeping its local
|
||||
names and its old settings as options so its data stays. A reader moves after its owner: the owner declares the
|
||||
event or gains the tool its readers need first, and the manifest validator refuses a moved module's subscription to an event
|
||||
its owner does not declare. An old section is refused once no module but the foundation's uses it.
|
||||
|
||||
What follows for the paragraphs above: for a module that has moved, ADR 0201 §2 (another module's state read by
|
||||
naming it in `reads`) no longer holds, and the bucket "every instance of the owner writes; a reader reads and
|
||||
watches" becomes "every instance of the owner writes and reads". The runtime's `get`, `put`, `delete`, `keys`,
|
||||
`watch` and `history` stay the calls of the template state.
|
||||
|
||||
**A module declares the data it holds.** *Added 2026-10-06,
|
||||
ADR 0233.*
|
||||
State on the bus is one kind of data; the rest lives on a machine's disks or with a provider, and the
|
||||
@@ -326,7 +370,7 @@ Every shape above appears once, in order, and no step knows where the next one r
|
||||
**A change lands.** The module holding `mesh-git` emits `pushed` — repository, ref, commit. An
|
||||
event, because it is a fact about git and git's identity is the meaning.
|
||||
|
||||
**The change becomes work.** The controller consumes `pushed`, asks the catalogue which modules
|
||||
**The change becomes work.** The controller consumes `pushed`, asks the catalog which modules
|
||||
are built from that repository and path
|
||||
(ADR 0069), and submits one
|
||||
**job** per affected module to the `mesh-build-machine` seat. The builder stays simple: it builds
|
||||
@@ -366,7 +410,7 @@ not to an address it was given at genesis. Held and retried while the store rest
|
||||
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 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).
|
||||
the catalog subscribes to `built` (ADR 0134).
|
||||
The facts are second-hand by design — one emitter, one ordering — and a machine that cannot reach the bus
|
||||
produces none, so absence is not health.
|
||||
|
||||
@@ -437,6 +481,136 @@ meaning into shape wherever it can go — a required `channel` field is caught,
|
||||
interpretation of an existing one is not. Saying so is better than implying the fingerprint is
|
||||
complete, because a team that believes it is complete stops reviewing for the case it misses.
|
||||
|
||||
### A package, its series, and the series its users accept (from 2026-11-03)
|
||||
|
||||
(ADR 0606 §4, ADR 0609, from research 081.) This is the manifest's half of design 51's versions.
|
||||
|
||||
- **A package is declared as an artifact**: its name, the kind `package`, its language (`go` or `typescript`), its
|
||||
paths and its **package series** (`0.1`). An image, a bundle or an archive may declare a package series the same
|
||||
way; one that declares none is numbered in `0.1`. Nobody types a version: the builder counts it (design 51).
|
||||
- **A package is named by its name in its own language** (a design limit of the registry milestone, 2026-11-04): a
|
||||
Go package by the module path of its `go.mod`, a TypeScript package by the name in its `package.json`. The
|
||||
indexer reads that name at each merge, the builder numbers the package under it, the package's registry seat
|
||||
publishes it under it, and a user's `packages` names it so. One name per package across the mesh, because the
|
||||
language's own tools fetch by that name and no other. How it is checked: the manifest validator refuses a name in `packages`
|
||||
that no package of the shelf is named, or that a member of the change's delivery group does not name at its head.
|
||||
- **Raising a package series** is a reviewed change of the package's manifest, for a change that breaks its users.
|
||||
The checker's verdict names every change of a package series it sees, so the review of the change sees it.
|
||||
- **A user names the mesh packages it takes in a section of its own, `packages`**, each with the package series it
|
||||
accepts. It is not `uses`, which grants publishing on a seat's `accepts` subjects (§ 3); a package is not a seat. A
|
||||
user builds, and is checked, against the newest version of that series; a publish in another series does not move
|
||||
it, and moving is a reviewed change of the user's manifest. A user that names none accepts the series of the
|
||||
package's first numbered version, so nothing is edited in today's users. The indexer's tool `indexer_package_users`
|
||||
lists, per package, its users and the series each accepts, so a user left on an older series is seen.
|
||||
- **Inside one repository** a module uses its sibling package by path, from the same commit, and waits for no publish.
|
||||
- **A user outside the package's delivery group is not built before the merge**: it is built after it, when the new
|
||||
version is in its series. A user that breaks fails its own build, is not wanted, keeps its previous build, and a
|
||||
condition tells the operator. A group's members are built in the group's check, against the group's tagged version.
|
||||
- **A contracts module's package** names the directory of the seat definitions it is generated from in the field
|
||||
`generated-from`, the contracts marker (design 26).
|
||||
- **The manifest validator reads the new words** (ADR 0578 §1): each package series well formed and only on the four kinds that may
|
||||
carry it, a package naming `go` or `typescript`, and each name in `packages` a package some manifest declares. A
|
||||
running manifest validator that does not know a word sets the manifest aside to wait, never refuses it, while a newer indexer
|
||||
build is merged and not yet delivered; and a node-engine is handed a manifest using these words only once it
|
||||
states the capability to read them (ADR 0614 §4).
|
||||
- **Cost**: the new manifest fields are read through mesh-sdk's manifest types, so their change rebuilds every module
|
||||
once; it is the one rebuild the milestone of ADR 0612 already takes for the contracts move (ADR 0612 §1, ADR 0613 §1).
|
||||
|
||||
### A config value renamed (from 2026-11-03)
|
||||
|
||||
(ADR 0609 §4.) A config value's declaration may name its old name, `renamed-from`. On the merge, mesh-config moves the
|
||||
value it holds under the old name to the new name in its own state, without the operator giving it again, emits
|
||||
`renamed` (module, old name, new name, when; its family `config.events`, kept seven days) and records it. This design decides what ADR 0609 leaves open:
|
||||
|
||||
- **Which values**: a module's own config values only, never a secret, never another module's value. A rename keeps
|
||||
the value's trust: a value set without the operator's Approve stays untrusted under its new name (ADR 0348 §8), and
|
||||
the checker refuses a rename that would change a value's trust.
|
||||
- **How long the old name is read** (the **rename window**): until every node-engine whose node has a want of the
|
||||
module reports applied a build of it that reads the new name. mesh-config hears the status view's `delivered`
|
||||
events and, at its start and on each, asks its `node_haves` for every node, away ones included, since `delivered`
|
||||
counts only the nodes present (design 47); until then mesh-config answers both names. A node away when the last
|
||||
`delivered` was said should close its window when it returns, with no other delivery: mesh-config also wakes on
|
||||
the node-engine seat's event `mesh.seat.node-engine.event.manifest-reads-changed`, said when a node's stated
|
||||
capabilities change and when it returns after being away (ADR 0618 §4), as for the old seat name (design 51), and
|
||||
on it reads that node's stated capabilities through the seat's verb `manifest-reads` (ADR 0618 §3) and its applied
|
||||
builds through `node_haves`, never a watch of the node-engine's have state (ADR 0596). mesh-config declares
|
||||
`seat:node-engine.manifest-reads` in its `invokes`: that is the grant the grants part composes, and with it the
|
||||
grants part grants mesh-config the subscription to the event's subject (its unit row is in design 51). A
|
||||
node-engine that does not yet serve `manifest-reads` counts as stating nothing: the window stays open and
|
||||
the seat's verb `renames` names the node. ADR 0618 does not cover a
|
||||
returned node that applies the module's new build only after its return event, since that build changes what the
|
||||
module reads, not what the node-engine states: such a node closes the window on the next `delivered`, whichever
|
||||
delivery says it. A node holding a window open
|
||||
for seven days is mesh-config's finding naming the node, cleared when the window closes; `config_rename_close`
|
||||
drops a node from a window by hand, recorded as a hand act.
|
||||
- **The checker asks the seat, never the module.** The seat `mesh-config` gains the optional verb `renames` (the
|
||||
open renames, and the nodes holding each window open), in its seat definition (design 26). The checker asks it for
|
||||
every change carrying `renamed-from` or dropping one: a holder that does not serve it is refused by the tool runner
|
||||
naming the verb, and the checker then refuses `renamed-from` naming that; while a window is open, the same answer
|
||||
refuses dropping `renamed-from`, naming the nodes. A check that cannot ask fails, and says so. So no manifest uses
|
||||
`renamed-from` in the milestone that brings it, whose running mesh-config serves no `renames`; the first that does
|
||||
is its live case. The checker declares `seat:mesh-config.renames` in its `invokes`, so by the served-verb edge
|
||||
(ADR 0614 §2) its new build waits until a build of mesh-config that serves `renames` runs healthy; no cycle closes,
|
||||
since mesh-config waits only on the indexer's `indexer_config_readers`. The "not served" answer is then met only by
|
||||
the running checker in the milestone's own check, and after a revert. `config_renames` names, per rename, the nodes
|
||||
still holding the old name open.
|
||||
- **A refused move** (the old name holds nothing, or a secret) is mesh-config's finding, from which the condition keeper
|
||||
raises the condition, cleared once the value moved or `renamed-from` left the trunk's manifest. **Tools of
|
||||
mesh-config**: `config_renames` (what moved, from which name, when), `config_rename_again` (move one value by hand
|
||||
when a move was refused) and `config_rename_close` (above).
|
||||
|
||||
### A value no longer read (from 2026-11-03)
|
||||
|
||||
A config value or secret no manifest on any trunk reads any more is a **retiring value**. mesh-config learns it from
|
||||
the indexer's tool `indexer_config_readers`, which it asks on each of the indexer's `manifest-accepted` events; the
|
||||
indexer says `manifest-accepted` for every newly accepted manifest, its bus-state entries empty when it asks none (the
|
||||
bus module reads an empty one as "this module asks nothing now", design 25 § 12.2). A tool the indexer does not serve
|
||||
yet answers "not known", and nothing retires on it. It is marked retiring only once no node-engine reports applied a
|
||||
build that reads it, as the rename window is (§ above). mesh-config then emits `retiring` (family `config.events`,
|
||||
kept seven days) and asks the operator once, through the operator channel, whether to remove it, naming any node that
|
||||
still read it until lately; it is removed only on the operator's Approve. **mesh-config holds no secret**, so the
|
||||
retiring flow and its one ask stay mesh-config's, and on the Approve it removes a value itself and retires a secret
|
||||
through the seat `mesh-vault`'s new optional verb `retire` (one module's own secret, by node, module and name), carrying
|
||||
the operator's warrant, which the vault checks (ADR 0259 §6) before it removes anything. The vault's caller list for
|
||||
`retire`, a trusted config value in ADR 0363's form, names the holder of the seat `mesh-config` alone, checked against
|
||||
the caller the tool runner proved; the warrant is bound to the node, module and name it was asked for, so it retires
|
||||
nothing else. mesh-config then emits `retired` on `config.events` (module, name, node, the ask), and the vault emits
|
||||
`own-retired` (node, module, name, the ask's id, the caller it proved) beside its `rotated` and `own-replaced`, on the
|
||||
module events stream (ADR 0363) under the vault's own subjects, kept as that stream keeps every module's events. An ask unanswered for seven
|
||||
days is asked once more, and then written as a finding, cleared by an answer, a cancel, or the value read again; a
|
||||
sleeping node holding a retirement open for seven days is a finding naming it. `config_retiring` lists what is
|
||||
retiring, since when, and its ask, and asks it again or cancels it.
|
||||
|
||||
**Where mesh-config keeps it.** Its new state (`renames`, `retiring`, `findings`) and its stream `config.events` are
|
||||
declared in its old `state` and `streams` sections in this milestone, as the builder's and the checker's new state is
|
||||
(design 51 *The move*), so that neither mesh-config nor the deciding parts that ask it wait on the bus module's
|
||||
process, which is delivered in the same milestone. They move to bus state when each module next moves (ADR 0595 §11).
|
||||
`config-changed` stays on the shared events stream, unchanged, so its consumers do not move.
|
||||
|
||||
**How the rename and the retiring are checked**, as cases of mesh-config's first whole-module integration test (C20,
|
||||
C24 below), of the checker's and of the vault's existing whole-module tests: the checker's ask of `renames` meeting a
|
||||
holder that does not serve it refuses `renamed-from` naming the verb, one meeting an open window refuses dropping
|
||||
`renamed-from` naming the nodes, and a check that cannot ask fails saying so; `retire` with the warrant removes the
|
||||
secret and emits `retired` and `own-retired`, without it, with a warrant for another name, or from a caller not on the list it is
|
||||
refused; a waiter on the vault's `rotated` or `own-replaced` does not wake on `own-retired`; a node that was away,
|
||||
returning with a build that reads the new name, closes the rename window on its `manifest-reads-changed` event when
|
||||
it applied that build before the event (ADR 0618 §4), and on the next `delivered` when it applies it after; `config_rename_close` drops a node from a window and records the hand act; a window held seven days and a
|
||||
retirement held seven days each write their finding naming the node, cleared when it closes.
|
||||
|
||||
### The mesh delivery cases this design carries
|
||||
|
||||
Research 081 listed the mesh delivery cases; these are the ones it handed to a design, each with its acceptance test,
|
||||
each a case of its module's one whole-module integration test unless it says live.
|
||||
|
||||
| case | what holds | acceptance test |
|
||||
|---|---|---|
|
||||
| C6, a package series raised | users on `0.1` keep the newest `0.1`; a reviewed change moves a user | builder: a `0.2.0` publish moves no `0.1` user, its check takes the newest `0.1` too, a reviewed change naming `0.2` moves it, a user naming no series builds against the newest `0.1` |
|
||||
| C14, a provision's form gains a field | the provider serves it in a new version; the consumer reads it once rebuilt; an older consumer ignores it | mesh-sdk: a consumer built against the older form reads a provision with the new field |
|
||||
| C18, a new required config value with no default | the check names the nodes that lack it; the want waits; the operator is asked once | live: a module gaining such a value waits on each node lacking it, the operator is asked once naming the nodes, and once given the module is applied |
|
||||
| C20, a config value renamed | the value moves, keeps its trust, both names read until every node runs the new build | mesh-config: a renamed value is present under the new name with no operator input and its trust; the old name answers until the node-engines report the new build; dropping `renamed-from` early, or changing trust, is refused; a refused move writes its finding |
|
||||
| C22, a secret only a person can give | the want waits; the operator is asked once what to give and where | live: a module requiring such a secret waits, one ask names it and where, and once given the module starts |
|
||||
| C24, a config value or secret no longer read | it is retiring, the operator is asked, removed only on the Approve | mesh-config: with one node-engine still reporting a build that reads the value it is not marked retiring and no ask is sent; once that node reports the new build it is marked retiring, `retiring` is emitted and one ask reaches the operator; an ask unanswered seven days is asked once more, then written as a finding; the value stays until an Approve removes it |
|
||||
|
||||
## 9. Provisioning over the bus
|
||||
|
||||
Provisioning rides the bus, and the provider stops having an address.
|
||||
@@ -482,7 +656,7 @@ addresses will believe a class of bug is fixed when it is untouched.
|
||||
controller's address — recorded somewhere, at some moment, and wrong as soon as anything moved.
|
||||
Now it publishes to `mesh.seat.mesh-controller.report` and the bus routes it to whoever holds the
|
||||
seat. Nothing anywhere records where the controller is, so nothing can record it *wrongly*. The
|
||||
same is true of the builder, the catalogue, the telegram sender. This class is not mitigated; it
|
||||
same is true of the builder, the catalog, the telegram sender. This class is not mitigated; it
|
||||
is gone, because the information is no longer stored.
|
||||
|
||||
**Something that is not on the bus: the address stays, exactly as before.** A module that requires
|
||||
@@ -580,11 +754,11 @@ billing existing under that name.
|
||||
|
||||
## 12. How it is checked
|
||||
|
||||
- **A manifest holds no subject.** A catalogue test: no manifest contains a string matching the
|
||||
- **A manifest holds no subject.** A catalog test: no manifest contains a string matching the
|
||||
subject grammar. The rule is worthless if it is followed by convention.
|
||||
- **A preparation is given what the module is given.** A composition test: what the preparation
|
||||
entrypoint receives equals what the module's own code receives, asserted rather than written twice —
|
||||
which is the drift a hand-written step invites, three times over in the catalogue today.
|
||||
which is the drift a hand-written step invites, three times over in the catalog today.
|
||||
- **A convergence that changed nothing says nothing.** Two identical reports, one emitted fact: what is
|
||||
guarded against is a fact per minute per machine, which is a stream nobody reads.
|
||||
- **Permissions are exactly the three namespaces.** A composition test per module: the derived
|
||||
@@ -602,6 +776,45 @@ billing existing under that name.
|
||||
- **A module reaches only the state it declared.** The composer's test: an owner's runtime may write
|
||||
its buckets, a reader's may only read, and nothing else is granted; the runtime's test over a real
|
||||
bus: a name not issued and a reader's write are refused with the reason.
|
||||
- **Bus state is asked for once, in one form, and read by its owner alone** (ADR 0595 §8, ADR 0596). The manifest validator's
|
||||
unit tests: a `reads` in the new form, a local name used twice across storage types or in an old section and the
|
||||
new form, an unknown template and an option outside its bound are each refused, naming the field. Live: a moved
|
||||
module receives its binding after `created`; another module's account reading its entries is refused; a moved
|
||||
reader hears its owner's declared event and asks its owner's tool. The rest is [design 25](25-the-bus-on-nats.md)
|
||||
§ 12.7.
|
||||
- **State is current at once.** The runtime's test over a real bus: a watch hands the current values
|
||||
without deletions, then an end-of-current marker, then changes. Live: a machine assigned after a put
|
||||
reads it at start.
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
|
||||
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
|
||||
ruled on every finding. What was found, and what answers it:
|
||||
|
||||
| Domain | Found | Answered by |
|
||||
|---|---|---|
|
||||
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
|
||||
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
|
||||
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
|
||||
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
|
||||
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
|
||||
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
|
||||
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
|
||||
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
|
||||
|
||||
**Left to the operator**, because each changes an accepted record: a margin between raising and clearing the
|
||||
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
|
||||
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
|
||||
as well as `drop`. Each is noted on issue 1966.
|
||||
|
||||
The amendment of 2026-11-03 for research 081 (ADR 0606, ADR 0609; the package, rename, retiring and delivery-case
|
||||
sections of § 8) was reviewed with designs 51 and 26 by the four reviewers by architectural domain; the findings and
|
||||
what answers them are listed in design 51's *Reviewed by architectural domain*. For this design: a rename keeps a
|
||||
value's trust (security), `packages` is a section of its own, not `uses`, and mesh-config hears the status view's
|
||||
`delivered` events (seams), a value retires only once no node-engine runs a build reading it (security), and `renamed`
|
||||
and `retiring` have a family and an ask that is asked again (events).
|
||||
|
||||
The milestone's amendment (ADR 0612, ADR 0613: the seat verb `renames` the checker asks, the vault's `retire`,
|
||||
`manifest-accepted` for every manifest, mesh-config's new state in its old sections) awaits its own review by
|
||||
architectural domain, which rewrites this section.
|
||||
|
||||
@@ -135,7 +135,7 @@ by side until nothing is bound to the old one.
|
||||
*Amended 2026-10-07 (ADR 0246,
|
||||
[issue 298](../../04-ISSUES/298-a-new-seat-verb-deadlocks-across-two-repositories/00-report.md)):*
|
||||
**additive takes two steps when the seat and its holder are in different repositories.** That is the
|
||||
case for every mesh seat whose holder is a catalogue module. §3 makes every promised verb a condition of
|
||||
case for every mesh seat whose holder is a catalog module. §3 makes every promised verb a condition of
|
||||
holding, and a claim may name no verb the seat does not promise. So a verb added to the seat in the
|
||||
controller refuses today's holder, and a holder that serves it is refused by today's controller. Neither
|
||||
change can land first. An added verb therefore lands in three steps, each its own pull request:
|
||||
@@ -163,7 +163,7 @@ still a new version.
|
||||
of the subject table.
|
||||
- **An added verb lands in steps, and each step is checked (§7).** The controller's claim check refuses
|
||||
a claim that serves a verb the seat does not promise, and a holder that misses a required verb. It runs
|
||||
at registration, at handover, and in `module check`, which the catalogue's merge check runs over every
|
||||
at registration, at handover, and in `module check`, which the catalog's merge check runs over every
|
||||
manifest with the controller the mesh runs. So a step out of order fails the pull request that took
|
||||
it. That an optional verb stays optional when read back from the store is held by mesh-controller's
|
||||
`TestAnOptionalVerbStaysOptionalInASetReadFromTheStore` (#114), and that a seat's row gains a verb's
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 34
|
||||
milestone: every-event
|
||||
updated: 2026-11-11
|
||||
code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code]
|
||||
decisions:
|
||||
- "ADR 0670"
|
||||
- "ADR 0671"
|
||||
- "ADR 0286"
|
||||
- "ADR 0367"
|
||||
- "ADR 0043"
|
||||
- "ADR 0259"
|
||||
- "ADR 0332"
|
||||
- "ADR 0363"
|
||||
- "ADR 0376"
|
||||
- "ADR 0489"
|
||||
- "ADR 0571"
|
||||
- "ADR 0578"
|
||||
- "ADR 0614"
|
||||
---
|
||||
|
||||
# Design 34, milestone every-event — the tool runner hears every event
|
||||
|
||||
**What it builds.** A session's `mesh_wait` waits on any event of any seat or module, by its name, with no list to
|
||||
keep: the tool runner's bus account is granted the event subjects of the whole mesh, every module's own stream
|
||||
included (ADR 0671 §1), and the list of families in the tool runner's manifest, the manifest validator's rule about it
|
||||
and the grants part's computation of subjects from it go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); this
|
||||
part holds every line the milestone builds, cited as "design 34 every-event §n".
|
||||
|
||||
**Where.** Four repositories and hq, one delivery group: mesh-sdk (the shared event subjects; the field, type and
|
||||
membership entry that go), mesh-delivery (the grants part, seat `mesh-grants`; the manifest validator in the indexer),
|
||||
mesh-tools (the tool runner, module `node-tools`) and mesh-catalog (the claude-code module's Stop hook, §20), with hq
|
||||
for the playbook and agent lines (§24). Only these reference the list today: no other manifest carries the key
|
||||
`waits`. The milestone is built on each repository's main as it stands. Design 47's milestone G edits the grants
|
||||
part's account composition and the tool runner's manifest and manifest test too; its lead rebases it onto this
|
||||
milestone's heads when G's develop restarts.
|
||||
|
||||
**The approval mark on events.** *A marked event waits for the operator's warrant* (§32 to §37) writes the operator's
|
||||
answers of 2026-11-11: a wait respects the approval mark, and the gate that asks and checks it (design 46's milestone
|
||||
7, [`7-approval-mark.md`](../46-milestones/7-approval-mark.md)) is built in design 47's milestone G and delivered in one
|
||||
group with G and this milestone ("Build the gate in G"). So §25's grant of a restricted stream's subjects and the gate
|
||||
that holds its mark reach the machines together.
|
||||
|
||||
## The event subjects, in mesh-sdk
|
||||
|
||||
1. **One place names the mesh's event subjects.** mesh-sdk's package `buscompose` gains a file `events.go` with three
|
||||
exports, used by the grants part (§6) and the tool runner (§13), so neither spells the event namespace itself:
|
||||
- `EveryEvent`, the subjects the tool runner's account hears: `mesh.seat.*.event.>` and `mesh.mod.*.event.>`, in
|
||||
that order (ADR 0670 §1);
|
||||
- `EventSubjects(name)`, the subjects one event name lands on. A name is `<emitter>.<event>`, optionally followed
|
||||
by further tokens (`operator-channel.decided.claude-code`); each token matches `[A-Za-z0-9_-]+`. It answers four
|
||||
subjects: `mesh.seat.<emitter>.event.<rest>`, the same followed by `.>`, `mesh.mod.<emitter>.event.<rest>`, and
|
||||
the same followed by `.>`, where `<rest>` is every token after the first; given a namespace (`seat` or `module`,
|
||||
§13) it answers only that namespace's two. It refuses, with an error naming the rule, a name of fewer than two
|
||||
tokens, an empty token, or a token outside that form (so `*` and `>` are refused);
|
||||
- `EventName(subject)`, the inverse for a heard subject: for `mesh.seat.<emitter>.event.<rest>` or
|
||||
`mesh.mod.<emitter>.event.<rest>` it answers the name `<emitter>.<rest>` and its namespace; any other subject is
|
||||
answered as not an event.
|
||||
|
||||
Why four subjects: a name does not say whether its emitter is a seat or a module, and both namespaces are heard
|
||||
(§6); and a seat's event that names its asker or kind after the event (ADR 0259 §3), or a module's that names its
|
||||
module and branch, is heard by the event's name alone, the `filter` narrowing it by body. How it is checked: one
|
||||
table-driven unit test of the three, in `events_test.go`, one of whose rows holds that every subject
|
||||
`EventSubjects` answers for its cases is covered by a subject of `EveryEvent`.
|
||||
2. **The manifest loses `waits`.** `catalogue.Manifest.Waits` and its comment go from `go/catalogue/manifest.go`;
|
||||
the comment on `ToolRunner` loses "and the only module that may declare `waits`". `go/catalogue/waits_test.go` is
|
||||
deleted, and one row is added to the nearest table over raw manifests, `go/catalogue/invokes_test.go`'s
|
||||
`TestAManifestMaySayWhatItInvokes` (Q-T): a manifest
|
||||
`{"module":"node-tools","version":"1","tool-runner":true,"waits":["a.b"]}` decodes with `UnknownField()` naming
|
||||
`waits` (ADR 0670 §2). The decoder itself is unchanged: a stored manifest that still carries `waits` is read
|
||||
without it (ADR 0578 §3).
|
||||
3. **The baseline of manifest reads stays as generated.** `go/catalogue/manifest_reads_baseline.txt` keeps
|
||||
`word:waits`: it is the contract of the engines built before any stated what it reads (ADR 0614 §4), which did read
|
||||
`waits`. In `own_manifest_test.go` the list of baseline entries this contract no longer reads (today the two
|
||||
renamed seats) gains `word:waits`, its comment naming ADR 0670 §2 as the record that retired the word.
|
||||
4. **The wait types go.** `go/buscompose/waits.go` (the type `Wait`) is deleted and `Users.Waits` goes from
|
||||
`go/buscompose/users.go`; `Membership.Waits` and the type `WaitIssued` go from `go/membership/membership.go`
|
||||
(ADR 0670 §2). Nothing replaces them: no membership names what its account hears.
|
||||
|
||||
## The grant, in the grants part (seat `mesh-grants`)
|
||||
|
||||
5. **What the grants part computed from the list goes.** `internal/buscompose/waits.go` is deleted whole (`Wait`,
|
||||
`WaitSubject`, `waitGrants`, `waitsIssuedFor`); `Declared.Waits` goes from `internal/buscompose/users.go`, and with
|
||||
it the term `len(d.Waits) > 0` of `runnerServes`; `m.Waits = waitsIssuedFor(d)` goes from `membership.go`, and the
|
||||
alias `WaitIssued` from `sdk_membership.go`; `waitsOf` and the field `Waits:` of `declaredFor` go from
|
||||
`internal/grants/records.go` (ADR 0670 §2). `safeSubject` in `nats.go` stays: the bus-state grants use it.
|
||||
6. **The tool runner's runtime hears every event subject.** In `internal/buscompose/nats.go`, where a runtime's
|
||||
permissions are composed from the modules it carries, the call of `waitGrants` is replaced by: when one of the
|
||||
carried modules says `tool-runner` (`Declared.ToolRunner`), `buscompose.EveryEvent` (§1) is appended to the
|
||||
runtime account's subscribe list. Nothing else is added for it: no publish, no durable consumer, no queue, no other
|
||||
subject; a runtime carrying no tool runner gets neither subject (ADR 0670 §1, §3; ADR 0043 §2, §3, §5, §8 as ADR
|
||||
0670 §3 now reads them). The grants part writes them into the bus's user list as every grant (ADR 0571 §9); they
|
||||
are read with `mesh-grants.grants_user_list`, and composed at once with `mesh-grants.grants_compose_now`.
|
||||
7. **The tool runner's membership names no events.** The grants part's first composition after its delivery rewrites
|
||||
every tool runner's membership in its state `memberships` (key `<node>.node-tools`, ADR 0571 §10) without the key
|
||||
`waits`, since the field is gone (§4); every other part of it is unchanged. It is read with the grants part's
|
||||
reading of memberships and rewritten at once with `mesh-grants.grants_compose_now`.
|
||||
8. **A composition test holds the grant.** In `internal/buscompose/buscompose_test.go`, one table-driven test composes
|
||||
a runtime carrying the tool runner and one other module, and a runtime carrying the other module alone, and holds:
|
||||
the first's subscribe list contains `mesh.seat.*.event.>` and `mesh.mod.*.event.>`; no subscribe subject of either
|
||||
is wider than those (none of `>`, `mesh.>`, `mesh.*.>`, `mesh.seat.>`, `mesh.mod.>`, nor any `$JS.` subject beyond
|
||||
what the carried modules' own consumes give); the first's publish list holds no subject of another module's or
|
||||
seat's events; the first derives no durable consumer beyond its carried modules' `consumes`; and the second has
|
||||
neither subject (ADR 0670 §1, §3).
|
||||
9. **The real bus holds it too.** The grants part's existing real-bus test (`cmd/mesh-grants/composed_integration_test.go`)
|
||||
gains cases, no new test: the tool runner's account, as composed, subscribes
|
||||
`mesh.mod.mesh-delivery-view.event.delivered` and receives an event published there; it is refused a publish on
|
||||
that subject; it is refused a subscription on `mesh.seat.channel.accept.show.<kind>` and on
|
||||
`mesh.seat.intake.proof.code.<kind>` (an accept and a proof are never events, ADR 0670 §1); and another module's
|
||||
account is refused a subscription on an event it does not consume (ADR 0670 §1, §3).
|
||||
|
||||
## The manifest validator, in the indexer
|
||||
|
||||
10. **The rule about the list goes.** `waitProblems` and its call in `judge/rules.go` are deleted, and with them the
|
||||
regular expression `waitToken` where nothing else uses it; the table rows that judged `waits`
|
||||
(`judge/rules_test.go`, the `waits` cases) are deleted, and one row is added: a manifest that carries `waits` is
|
||||
refused by the manifest validator as the checker runs it (`Shelf`), naming the unknown field `waits`, and set aside
|
||||
to wait as the indexer runs it (`ShelfWaiting`), as any field it does not know (ADR 0578 §1); the tool runner's
|
||||
manifest without it is accepted (ADR 0670 §2). Only revisions taken in after the indexer's delivery are judged so:
|
||||
a manifest the validator accepted before keeps its verdict (ADR 0578 §1).
|
||||
|
||||
## The tool runner (module `node-tools`)
|
||||
|
||||
11. **Its manifest's changes, every one.** `node-tools/module.json` drops the key `waits` and its five entries (ADR
|
||||
0670 §2); gains `operator-channel` in `uses`, for the asks of marked waits (§35; design 46 7-approval-mark §7); gains
|
||||
in its old `state` section the per-machine state `approvals` of design 46 7-approval-mark §13 (the tool runner is a
|
||||
foundation part, ADR 0619 §2, §3); and gains the stream `node-tools-findings` and
|
||||
the `consumes` of `mesh-conditions.findings-asked` that design 47 G §387 writes. Its `version` is `0.1`, as design 47 G §368
|
||||
and §624 have every manifest's. Nothing else in it changes.
|
||||
12. **The publication time is kept.** `bus.Heard` gains `Published`, read from the event's header `x-time`, which
|
||||
mesh-sdk's emit sets (`go/process/emit.go`) and the status view's own emit too; `HearFirst` reads it from the
|
||||
message's headers, and leaves it empty for an event that carries none.
|
||||
13. **`mesh_wait` takes any event name.** `console.Surface.waitAnswer` (`internal/console/wait.go`) no longer reads the
|
||||
membership. Each name of `subjects` becomes its subjects through `buscompose.EventSubjects` (§1); a refused name is
|
||||
answered as a failure quoting that error. An optional argument `from`, `seat` or `module`, narrows every name to
|
||||
that namespace, for an emitter whose name is both a seat's and a module's. A call naming more than 16 names is
|
||||
refused, saying the bound (each name is four subscriptions for the call; 16 names is 64, far above what a watcher
|
||||
names). The same subject named twice is subscribed once. The map from subject to name goes: the heard event is
|
||||
named by `buscompose.EventName` of the subject it arrived on. The timeout, the filter and the timed-out answer are
|
||||
unchanged (ADR 0367 §1). The refusal of names outside a list, and its words naming the manifest, go (ADR 0670 §2).
|
||||
14. **A misspelt name is refused, not waited on for ever.** Before it subscribes, `waitAnswer` refuses a name whose
|
||||
emitter is neither a seat nor a module of the catalog half the tool runner holds from G (its first view by
|
||||
`mesh-catalog.revision`, followed on `catalog-revision`; design 47 G §196), naming that source and its revision.
|
||||
A name whose emitter is known but whose event no seat definition and no module's entry in the catalog half
|
||||
declares (its `emits` and its streams' events, design 46 7-approval-mark §5) is waited on,
|
||||
and its timed-out answer says that no declaration names that event. While the catalog half is not yet read (the
|
||||
first views' state, `mesh_first_views`), names are not checked, and the answer says `unchecked` with why; a
|
||||
marked wait is then held as §34 says.
|
||||
15. **The answer names the heard event.** A heard event is answered as today, `heard` with `subject`, `body` and
|
||||
`at`, where `subject` is now the heard event's whole name (`mesh-delivery-view.delivered`,
|
||||
`operator-channel.decided.claude-code`), with two new fields: `from`, `seat` or `module`, the namespace it
|
||||
arrived on, and `published`, the event's own time (§12), absent when it carries none. The `read` line stays (an
|
||||
event only wakes you, ADR 0367 §4); for a name beginning `operator-channel.decided` it says instead that the
|
||||
operator's answer is read only with `claude_code_answer`, never from the event's body (ADR 0670 §5; ADR 0332 §5,
|
||||
§6).
|
||||
16. **A refused subscription is said, and a wait is never a hot loop.** `HearFirst` subscribes every subject of the
|
||||
call; a subscription the bus refuses is noted, and the others go on waiting. When every subscription of the call is
|
||||
refused, it waits out the call's timeout (or the caller's going) and then answers the refusal, so a watcher that
|
||||
loops on it asks once a timeout, never at once again. Its refusal words become: "the bus refused this account a
|
||||
subscription on <subjects>: the grants part grants this machine's tool runner every event subject (novox/hq ADR
|
||||
0670 §1), so its grant is not composed yet; read the state from its owner meanwhile"; no words name a wait grant or
|
||||
ADR 0367 §2. A heard event's answer and a timed-out answer list under `refused` any subject the bus refused while
|
||||
others stood.
|
||||
17. **The tool's description says what it hears.** `waitTool`'s description of `subjects` says: one or more event
|
||||
names, `<seat or module>.<event>`, such as `mesh-delivery-view.delivered`; an event whose subject goes on after
|
||||
the name (an asker, a kind, a module) is heard by the name too; the filter narrows by the body's fields; `from`
|
||||
narrows to a seat's or a module's. Its own description keeps "an event only wakes you" and adds that an operator's
|
||||
answer is read only with `claude_code_answer`. The words "only those the mesh granted this machine's tool runner"
|
||||
go.
|
||||
18. **The hearing serves `mesh_wait` alone.** `HearFirst` has one caller, `waitAnswer`. The modules the runtime carries
|
||||
share its account, so the bus no longer bounds which events their code could hear; the runtime still gives a
|
||||
carried module's code only what its membership names, and this is accepted under ADR 0670 §1, every session
|
||||
already hearing every event. How it is checked: the code review of every change to the tool runner reads
|
||||
`HearFirst`'s callers.
|
||||
19. **What goes from the tool runner's code, and its tests.** The type `bus.WaitIssued` (`internal/bus/wait.go`) and
|
||||
`Membership.Waits` (`internal/bus/bus.go`) go. In `console/manifest_waits_test.go` the test of the five families
|
||||
becomes a test that the manifest has no key `waits`, its comment citing ADR 0670 §2; its tests of what the tool
|
||||
runner consumes and of the runner's own secrets stay. In `console/wait_test.go`: `waitingRuntime` issues no waits,
|
||||
and the answering test waits on `mesh-delivery-view.delivered` and on `operator-channel.decided` with no membership
|
||||
naming either, its rows holding `subject`, `from`, `published` and `read` (a `decided.<asker>` event heard by
|
||||
`operator-channel.decided`); `TestMeshWaitRefusesAnEventOutsideTheGrantedFamilies` becomes the table of refusals:
|
||||
a name with `*`, with `>`, of one token, 17 names, an emitter no source knows (§14), and a bus-refused subscription
|
||||
answered after the timeout with §16's words; `TestMeshWaitWithNoFamiliesIssuedWaitsOnNothing` is deleted. In
|
||||
`bus/wait_test.go`, `TestTheBusRefusesAWaitOutsideTheGrant` stays as §16's refusal test, with a timeout of a
|
||||
fraction of a second, its comment citing ADR 0670 §1. The manifest test also holds each change of §11: no `waits`,
|
||||
`operator-channel` in `uses`, the state `approvals` in the old `state` section, per machine with its time to live,
|
||||
and none in `requires`, and the finding stream. The
|
||||
tool runner ends milestone G with no more tests than it starts with (design 47 G §419, §626): these tests replace
|
||||
existing ones, and every test deleted counts.
|
||||
|
||||
## The claude-code module's Stop hook
|
||||
|
||||
20. **The hook no longer doubts the grant.** In mesh-catalog `modules/claude-code/cmd/claude-code/stop.go`,
|
||||
`watcherAdvice` says for a wait with an event: "mesh_wait on <event> and reread <read> after every answer, or start
|
||||
a watcher that does", with no condition on the tool runner's grant; its comment cites ADR 0670 §2 beside ADR 0489
|
||||
§3. Its row in `stop_test.go` holds the new words.
|
||||
|
||||
## Events carry no secret
|
||||
|
||||
21. **The prepare lists every event.** The milestone file's prepare (playbook 07, step 0) gains a section *Events and
|
||||
their fields*: every event each module declares (its `emits` and its streams' events) and each seat's definition
|
||||
declares, read from the module catalog: the catalog revision of the change's branch (`mesh-catalog.revision`),
|
||||
which carries the seat definitions too, and nothing else, no manifest and no bus read, so the events G and design
|
||||
46's milestone 7 add are listed (the finding streams of design 47 G §383 among them), each a holder's event with
|
||||
its subject form; and every event mesh-sdk publishes on a module's behalf (the announcement's `states` on
|
||||
`mesh.mod.<module>.event.states`, found by the callers of its subject builders); each with the fields of its body
|
||||
as the emitting code builds it, by file and line (ADR 0670 §4). *Why:* the operator, 2026-11-11: "only the indexer
|
||||
uses the manifest to build the module catalog".
|
||||
22. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review
|
||||
(playbook 07, step 2) reads that list again at the group's heads, and confirms that no event's body carries a
|
||||
credential, a token that grants an act, code, or a message marked secret; an intake `choice`'s one-use token is not
|
||||
such a token (ADR 0259 §3, §5). The list is the module catalog's (§21); the review reads no bus. An event found
|
||||
carrying a secret holds the milestone's delivery and goes to the operator as a design question
|
||||
(ADR 0670 §4).
|
||||
23. **The stream events are read too.** §21's list also names every event kept in a module's own stream
|
||||
(`mesh.stream.<module>.event.<event>`, ADR 0363 §2), the seat holders' events published there among them, and
|
||||
whether its stream is restricted (its declaration carries a `grant`, ADR 0363 §4). The mail stream's events are
|
||||
named in it as heard with their content: a mail's own text, a sign-in link or a code someone sent by mail among
|
||||
it, is the operator's mail and not a secret in §22's sense, which is a credential, token or code the mesh itself
|
||||
issues or keeps (ADR 0671 §3, which narrows ADR 0670 §4).
|
||||
24. **From then on every change is asked the same.** In this milestone's hq pull request: playbook 07's review step
|
||||
gains the question, for any change that adds an event or a field to one, "does it carry a secret?"; and the watcher
|
||||
lines that describe the list (`.claude/agents/mesh-watcher.md`, playbook 07's *Following and reporting*: "the
|
||||
families a watcher may wait on are those the mesh granted the tool runner") say instead that `mesh_wait` hears any
|
||||
event by name (ADR 0670 §2, §4).
|
||||
|
||||
## Stream events
|
||||
|
||||
*By ADR 0671: the operator chose every module stream, mail too, on 2026-11-11.*
|
||||
|
||||
25. **The tool runner also hears every module stream, a restricted one included.** `buscompose.EveryEvent` (§1)
|
||||
gains a third subject, `mesh.stream.*.event.>`, after the two of §1, and the grants part's composition (§6) appends
|
||||
it with them. Nothing else is added for it: no durable consumer, no grant to acknowledge, no membership word, no
|
||||
publish, and no subscribe deny of a restricted stream (ADR 0671 §1). A restricted stream's grant value (ADR 0363
|
||||
§4) decides who consumes it, not who hears it, so changing or withdrawing that value changes nothing in the tool
|
||||
runner's grant (ADR 0671 §2); a module that consumes a restricted stream is composed as today (ADR 0376 §2 as ADR
|
||||
0671 §2 reads it). The bus does not hold the approval mark on a restricted stream; the tool runner's own gate does,
|
||||
before it subscribes for a wait (§32 to §37; the operator, 2026-11-11), and that gate is delivered in the same
|
||||
group as this grant (design 46 7-approval-mark §23). *Why:* ADR 0286 §8 has the bus hold a mark, and the bus
|
||||
cannot grant a subscription for one approved wait alone, so the operator chose the tool runner's gate.
|
||||
26. **A name hears its stream form.** `EventSubjects` (§1) answers six subjects, adding
|
||||
`mesh.stream.<emitter>.event.<rest>` and the same followed by `.>`; `from` (§13) takes `stream` too; `EventName`
|
||||
answers a stream subject with namespace `stream`. A seat's holder event kept in its holder's stream is named by its
|
||||
holder module (`mesh-delivery-checker.verdict`). The bound of §13 stays 16 names (96 subscriptions).
|
||||
27. **The tests hold it.** §8's test holds exactly the three subjects for the runtime carrying the tool runner, beside
|
||||
a fixture module that declares a restricted stream, and that no durable consumer and no subject to acknowledge on
|
||||
that stream are derived for the tool runner's runtime (ADR 0671 §1). §9's real-bus test gains cases: the tool
|
||||
runner's account, as composed, receives an event published on a fixture module's stream, and one published on a
|
||||
fixture restricted stream whose grant value has no entry for the tool runner, and still receives one after that
|
||||
value is changed and the accounts are composed again; it is refused a durable consumer and an acknowledgement on
|
||||
that restricted stream; and a module whose `consumes` names the restricted stream with no entry in its grant value
|
||||
gets no durable consumer and no read grant on it (ADR 0671 §1, §2).
|
||||
|
||||
## Delivery
|
||||
|
||||
28. **In G's delivery group, no order named beyond the module graph's.** The members are pushed and checked in design
|
||||
47 milestone G's group, with design 46's milestone 7, whose edges (design 46 7-approval-mark §23) order the gate
|
||||
before the seat definition that marks `execute`; this milestone adds no order of its own between its members, and no
|
||||
transitional field is kept (the operator, 2026-11-03: a change is designed as
|
||||
it should be, its delivery is the delivery system's): a part built with the new mesh-sdk reads a stored manifest of
|
||||
the tool runner that still carries `waits` without it (ADR 0578 §3), and a node-engine is given a want only for a
|
||||
manifest whose words it reads (ADR 0614 §4).
|
||||
29. **What `mesh_wait` answers between the deliveries.** On a node whose tool runner is new and whose grant is not yet
|
||||
composed, the subjects the old grant names are still heard and the rest are listed under `refused` (§16); on a
|
||||
node whose tool runner is older and whose membership the new grants part rewrote, the old runner answers at once
|
||||
that it was issued no event families. Both are loud. A watcher reads its owner after every answer (ADR 0367 §4) and
|
||||
ends at a call failed twice (its definition), so neither becomes a loop; a laptop asleep through the window gets
|
||||
its new tool runner when it wakes, and its watchers read their owners meanwhile in the same way.
|
||||
30. **Going back is going forward** (design 46 7-approval-mark §24 for the gate's part). A fault met after the delivery is fixed by a new change delivered the same way.
|
||||
Reverting the group instead reverts every member, the indexer first, since the manifest validator delivered here
|
||||
sets aside the old tool runner's manifest carrying `waits` (§10); until the revert reaches a node, `mesh_wait`
|
||||
there answers as §29 says.
|
||||
|
||||
## Nothing built ahead of the group
|
||||
|
||||
31. **No tool is built ahead of the group.** The extension of the nats module's `nats_streams` this line held, for
|
||||
§22's bus read, is not built: the secrets review reads the module catalog alone (§21, §22). *Why:* the operator,
|
||||
2026-11-11: "only the indexer uses the manifest to build the module catalog". *How it is checked:* the milestone's
|
||||
prepare names no member outside the group.
|
||||
|
||||
## A marked event waits for the operator's warrant
|
||||
|
||||
*By the operator's answers of 2026-11-11: "can it also respect any 'requires approval' config on the topics?", and
|
||||
"Build the gate in G".* These lines use the gate design 46 7-approval-mark builds for a marked call (its §8 to §19),
|
||||
delivered in the same group. *Why they depart from the records, said once for the section:* ADR 0286 §1 marks tools
|
||||
and verbs, and the operator extended the mark to a module's events and streams; ADR 0671 §1 and §3 have every session
|
||||
hear every module stream, mail included, with no exception, and the operator narrowed that to a wait the operator
|
||||
approves, for a marked event or a restricted stream.
|
||||
|
||||
32. **A module marks an event, and a restricted stream is marked whole.** A module's manifest `needs-approval` (ADR
|
||||
0286 §1) may also name one of its own events, by the name its `emits` or one of its `streams`' `events` gives it,
|
||||
with `*` and `**` as there. Every stream a module declares with a `grant` (ADR 0363 §4; `ModuleStream.Restricted`)
|
||||
is marked whole, every event it carries, with no entry in `needs-approval`; a stream without a `grant` is not, a
|
||||
work-queue one included. A seat's definition marks no event. The indexer's manifest validator refuses, as the
|
||||
checker runs it, a manifest whose `needs-approval` names an event that neither its `emits` nor its streams'
|
||||
`events` carry, naming the event, as ADR 0286 §4 refuses a mark on a tool the module does not serve (design 46
|
||||
7-approval-mark §4's table gains the row). A module that marks only events needs neither `operator-channel` in
|
||||
`uses` nor `approval-mark/1`: the tool runner asks for the wait, not the module.
|
||||
33. **The tool runner reads the marks from the catalog half on the bus.** The tool runner derives each module's marked
|
||||
subjects from the catalog half it holds (§14; the catalog graph entry carries `needs-approval` and each stream's
|
||||
`grant`, design 46 7-approval-mark §5): for a marked event `<event>` of module `<module>`,
|
||||
`mesh.mod.<module>.event.<event>` and `mesh.stream.<module>.event.<event>`, each event pattern as the bus spells it
|
||||
(`WirePattern`); for a stream with a `grant`, every subject of `ModuleStream.Subjects()`; and each of these also
|
||||
followed by `.>`, since an event may name its node or asker after its name (design 47 G §383) and
|
||||
`ModuleStream.Subjects()` answers only the patterns the stream stores. It derives them again on every revision it
|
||||
hears, and no module's name is written in its code; it is design 46 7-approval-mark §5's derivation, extended to
|
||||
events, following the running version as that line says. A mark added while a wait waits applies from the next
|
||||
call, which that call's answer says. *Why:*
|
||||
ADR 0286 §5 had the mark issued in a membership; no controller issues one now, a membership names no event (ADR
|
||||
0670 §2), and the catalog half, which the indexer alone builds from the manifests, keeps the mark out of any bundle
|
||||
as §5 required; from G the catalog half is where the tool runner reads which modules exist (design 47 G §196), and
|
||||
no part but the indexer reads a manifest (the operator, 2026-11-11).
|
||||
34. **Which wait is marked.** A wait is marked when any subject it subscribes (§13, §26, after `from`) overlaps a
|
||||
marked subject of §33, that is, some subject matches both, a wildcard on either side. mesh-sdk's `events.go` (§1)
|
||||
gains `Overlaps(a, b)` for it. Before the catalog half is read (`mesh_first_views`), a wait with any subject
|
||||
outside `mesh.seat.` waits, within its own timeout, for the first view and is then judged; nothing is asked while
|
||||
the marks are unread, and a wait still unjudged when its timeout ends is refused then, saying the marks are not
|
||||
yet read, since a mark that cannot be read runs nothing (ADR 0286 §40; design 46 7-approval-mark §19). A wait
|
||||
`from: seat` is never marked.
|
||||
35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as design 46
|
||||
7-approval-mark §8 makes one: the address `mesh_wait`, the node, the module `node-tools` and the tool `mesh_wait`,
|
||||
its build (the tool runner's own fingerprint, read from its `MESH_FINGERPRINT` at its start, design 47 G §205 and
|
||||
§625; while it is unknown, a marked wait is refused, saying so), and every
|
||||
argument given (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the
|
||||
bound. The tool runner refuses, asks, judges and claims it as that part's §9, §10, §11 and §13 to §17 do for a
|
||||
marked tool, asking on the seat `operator-channel` under its own module's name, `node-tools`, whose manifest gains
|
||||
`operator-channel` in `uses` for it (that part's §7). The ask's headline is "Wait on <names> on <node>?"
|
||||
(shortened as that part's §10 says), and its explanation names every event name one per line, the filter, and the
|
||||
bound as the timeout in minutes, then the act's fingerprint. On a warrant it accepts, it subscribes, and the wait's
|
||||
timeout runs from then. One warrant is one wait (that part's §15): a watcher looping on a marked event is approved
|
||||
call by call. Its answer carries the line naming the approval and the warrant is recorded through
|
||||
`mesh-hand-acts.warranted` (that part's §16), and `mesh_approvals` (that part's §18) lists the waits asked beside
|
||||
the calls. *Why:* the operator, 2026-11-11: a marked wait runs only on the operator's warrant for that one wait,
|
||||
shown with the exact names it waits on and its bound.
|
||||
36. **A refused marked wait is said, and never a hot loop.** A marked wait with no warrant the tool runner accepts
|
||||
subscribes to nothing. Its first call publishes the ask at once; every such call answers, as an error, design 46
|
||||
7-approval-mark §12's or §17's words with "this wait" for "the call", when its timeout ends or its caller goes, as
|
||||
§16 answers a refused subscription, or as soon as the router's record of its ask says it was chosen: the wait hears
|
||||
the warrant event `operator-channel.decided.node-tools` for its ask's id and then reads the record, an event only
|
||||
waking it, and answers at once, so the same call made again runs within the warrant's 30 minutes. *Why:* ADR 0286
|
||||
§14 answers within 5 seconds, which a watcher's loop would turn into a call every few seconds for the ask's hour;
|
||||
ending at the choice keeps a long wait from spending the warrant's window. An unmarked wait is heard as §13 to §16
|
||||
say, with no ask. Every machine's marked waits ask under the one asker `node-tools`, so they share its open asks
|
||||
and the router's holding back of the oldest beyond three (ADR 0666 §12, as G builds it), and the wait reads the
|
||||
router's record through `Client.Where` under `node-tools`.
|
||||
37. **The tests hold it.** Unit, as the budget counts them (`meshtest` runs its bus in the test's process): §19's table
|
||||
of refusals in `console/wait_test.go` gains rows, no test function: a wait on a fixture module's marked event, and
|
||||
one on a fixture stream with a `grant`, each with no warrant, subscribes nothing, asks once and answers design 46
|
||||
7-approval-mark §12's words after its timeout; a wait with the most timeout a wait takes (ten minutes, ADR 0367
|
||||
§1), its ask chosen at its second minute, answers at the choice, and the same call made then runs; the same wait
|
||||
with a warrant bound to its exact names, filter and timeout subscribes and answers the event published after it,
|
||||
so the restricted stream's event is heard only after the warrant; with a warrant for other names, another filter
|
||||
or another timeout it is refused; the same names `from: seat` ask nothing; a wait named `<module>.<event>.<node>`
|
||||
under a marked `<module>.<event>` is marked; and before the catalog half is read, a marked wait asks nothing, is
|
||||
judged when the first view comes within its timeout, and is refused at its timeout when it does not. A marked wait while
|
||||
the tool runner's own fingerprint is unknown is refused, saying so. The warrant is
|
||||
written on the test bus as the router's record. `Overlaps` gains rows in §1's table (`mesh.stream.mail.event.mail.>` overlaps
|
||||
`mesh.stream.mail.event.mail.*.message`; `mesh.mod.mail.event.x` does not overlap `mesh.stream.mail.event.x`), and
|
||||
§32's refusal a row in the indexer's `judge/rules_test.go`.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- **A watcher wakes on an event no manifest names** (the operator's acceptance; live, read by tool call): at the first
|
||||
delivery after every node holds the new tool runner, a watcher's `mesh_wait` on `mesh-delivery-view.delivered`
|
||||
answers the status view's `delivered`, its `at` less than a second after its `published`, with no rereading on a
|
||||
timer; the tool runner (`node-tools`) is delivered to every node, and the grants part and the indexer to theirs,
|
||||
read with `mesh-delivery.show` and each node's `node-engine.composition` (§6, §13, §15).
|
||||
- **The grant is exactly what the record names** (unit, and integration on a real bus): the composition test of §8
|
||||
and the real-bus cases of §9 pass.
|
||||
- **The list is gone** (unit): the manifest validator as the checker runs it refuses a manifest carrying `waits`, the
|
||||
indexer sets it aside, and the tool runner's manifest is accepted without it (§2, §10, §11).
|
||||
- **`mesh_wait` refuses loudly and never loops** (unit): §19's table of refusals passes.
|
||||
- **No event carries a secret** (the code review): the milestone file's *Events and their fields* is complete and the
|
||||
code review's confirmation at the group's heads is written in it before the grants part is delivered (§21, §22).
|
||||
- **An answer is never taken from an event** (the code review, and unit): in the members nothing reads an operator's
|
||||
answer from a `decided` event's body, and `mesh_wait`'s answer for one says to read it with `claude_code_answer`
|
||||
(§15).
|
||||
- **A stream event wakes a watcher** (live, read by tool call): a watcher's `mesh_wait` on
|
||||
`mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26; ADR 0671 §1).
|
||||
- **The grant does not follow a restricted stream's grant value** (integration on a real bus): §27's cases pass
|
||||
(ADR 0671 §2).
|
||||
- **A marked wait runs only on the operator's warrant** (unit): §37's rows pass.
|
||||
- **A wait on mail, as the operator sees it** (live, read by tool call and on the phone): a session's `mesh_wait` on
|
||||
`mesh-mail.mail`, the mail module's restricted stream, asks "Wait on mesh-mail.mail on <node>?" on the phone with
|
||||
its bound, and hears nothing before; it ends refused when its timeout ends or as soon as the operator chooses; the
|
||||
same call made after the Approve hears the next mail, and the next call asks again (§32 to §36).
|
||||
@@ -1,9 +1,21 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: implemented
|
||||
code: [mesh-tools, mesh-host, mesh-controller]
|
||||
updated: 2026-10-17
|
||||
status: in-progress
|
||||
code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery, mesh-catalog modules/claude-code]
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0670"
|
||||
- "ADR 0671"
|
||||
- "ADR 0286"
|
||||
- "ADR 0043"
|
||||
- "ADR 0259"
|
||||
- "ADR 0332"
|
||||
- "ADR 0571"
|
||||
- "ADR 0363"
|
||||
- "ADR 0376"
|
||||
- "ADR 0489"
|
||||
- "ADR 0578"
|
||||
- "ADR 0614"
|
||||
- "ADR 0197"
|
||||
- "ADR 0195"
|
||||
- "ADR 0175"
|
||||
@@ -28,7 +40,7 @@ it put it there (ADR 0152).
|
||||
|
||||
## 1. What it is
|
||||
|
||||
A module, `mesh-console`, in the catalogue. Its image is the tool runner's own — the client that
|
||||
A module, `mesh-console`, in the catalog. 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 `<node>.mesh-console`,
|
||||
@@ -42,7 +54,7 @@ consumer is the operator's agent, [36 — The operator's agent on a machine](36-
|
||||
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:
|
||||
Its manifest says three things nothing else in the catalog says together:
|
||||
|
||||
- `invokes: ["*"]` — it calls every tool on the mesh, and the bus grants exactly that publish side;
|
||||
- `listens` on a port `from: machine` — the filter opens nothing for it, because loopback is not outside;
|
||||
@@ -74,7 +86,7 @@ its own under that module's name, `mesh.mod.<module>.tool.tools`, answering the
|
||||
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 mesh MCP server asks the catalogue which modules the mesh holds, then asks each.** `catalog_modules`
|
||||
**The mesh MCP server asks the catalog 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
|
||||
@@ -106,7 +118,7 @@ answers is reached through them by its address: `<seat>.<verb>` for a seat held
|
||||
`<node>/<seat>.<verb>` for one held per machine, `<node>/<module>.<tool>` for an assignment, and
|
||||
`<module>.<tool>` as well for a module whose instances are interchangeable. A module that is not
|
||||
interchangeable is called with its machine or refused with the machines it runs on. Each discovery
|
||||
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalogue
|
||||
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalog
|
||||
stays reachable through the `mesh` client and a setting, unannounced.
|
||||
|
||||
**What exists is what announced itself** (ADR 0197):
|
||||
@@ -119,6 +131,64 @@ listed once per machine does not hold on the live mesh MCP server — postgres a
|
||||
optional, answered by whichever instance replies. The address replaces that statement rather than
|
||||
repairing it.
|
||||
|
||||
## 3b. Waiting on the bus
|
||||
|
||||
*By ADR 0367, as ADR 0670 and ADR 0671 change it.* Beside the five discovery tools of §3a and `mesh_runtimes`, the mesh MCP
|
||||
server offers a seventh, `mesh_wait`: a session names one or more events, an optional `filter` of body fields and a
|
||||
`timeout` (four minutes when absent, ten at most), and is answered the first matching event or told it timed out
|
||||
(ADR 0367 §1). A session calls it in a loop from a background command (ADR 0367 §3), and after every answer or time-out
|
||||
reads the state once from the verb that owns it: an event only wakes it (ADR 0367 §4).
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
E[a seat's, a module's or a module stream's event<br/>mesh.seat.<seat>.event.… / mesh.mod.<module>.event.… / mesh.stream.<module>.event.…] -->|heard for the call| TR[the tool runner's account]
|
||||
G[seat mesh-grants] -->|grants the three event subjects| TR
|
||||
M[the catalog half, seat mesh-catalog] -->|the marked events and restricted streams| TR
|
||||
TR -->|a marked wait only on the operator's warrant| OC[seat operator-channel]
|
||||
TR -->|mesh_wait answers it by name| S[a session]
|
||||
S -->|then reads the state| O[the verb that owns it]
|
||||
```
|
||||
|
||||
**The tool runner hears every event of the mesh, and no list says which** (ADR 0670 §1, §2). The grants part (seat
|
||||
`mesh-grants`) grants the account of the runtime carrying the tool runner a plain subscription on the event subjects
|
||||
of every seat and every module, and on nothing else of theirs: no accept, proof, call, work queue or bucket, and no
|
||||
publish. `mesh_wait` subscribes when called and closes the subscription when it answers, so the account keeps no
|
||||
durable consumer and no queue of its own (kept from ADR 0367 §2), and any event name is accepted: a new event is
|
||||
heard from its first publication, with no change to a manifest. The tool runner's manifest names no events, the
|
||||
manifest validator has no rule about them, and no membership lists them. A name whose emitter no seat and no module
|
||||
bears is refused, so a misspelt wait does not run for ever.
|
||||
|
||||
**Who hears.** Whoever may call the mesh MCP server: any process on the machine, through its loopback endpoint (§2),
|
||||
which is the session there. The tool runner's standing grant of every event is accepted under ADR 0670 §1: what a
|
||||
session hears through it, it may hear.
|
||||
|
||||
**Module streams too, mail included** (ADR 0671 §1, §2). The tool runner also hears the events kept in a module's own
|
||||
stream (ADR 0363 §2), the seat holders' events published there among them, a restricted stream such as mail's
|
||||
included. A restricted stream's grant value decides who consumes it, never who hears it, so the tool runner's hearing
|
||||
needs no `consumes` and no entry in that value, and keeps no consumer of it.
|
||||
|
||||
**A marked event is heard only on the operator's warrant** (the operator, 2026-11-11). The approval mark (ADR 0286) covers a module's events and streams as well as its tools and
|
||||
verbs: a module may mark one of its events in `needs-approval`, and a restricted stream is marked whole. The tool
|
||||
runner reads the marks from the catalog half on the bus (seat `mesh-catalog`), and a `mesh_wait` on a marked event, or on any subject
|
||||
of a marked stream, runs only on the operator's warrant for that one wait, asked on the seat `operator-channel` with
|
||||
the exact names it waits on and its bound, and checked before the tool runner subscribes; without one it is refused
|
||||
and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose; the
|
||||
gate is design 46's milestone 7, built in design 47's milestone G and delivered in one group with G and milestone
|
||||
every-event, so the subjects and the gate reach the machines together. This departs from ADR 0286 §1 and §8 (tools and
|
||||
verbs only, held by the bus) and from ADR 0671 §1 and §3 (every module stream heard with no exception) by the
|
||||
operator's answer. An unmarked event is heard with no ask.
|
||||
|
||||
**What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of
|
||||
every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, token or
|
||||
code the mesh itself issues or keeps, and no message marked secret, which the code review asks of every change that
|
||||
adds an event or a field (ADR 0670 §4 as ADR 0671 §3 narrows it). A mail's own content is the operator's mail and is
|
||||
heard as it is, a sign-in link or a code someone sent by mail among it (ADR 0671 §3). And a heard event never answers anything: the operator's answer to an ask is read only
|
||||
with `claude_code_answer`, from the router's record and held to the question's digest (ADR 0670 §5, ADR 0332 §5,
|
||||
§6), and an asker acts only on a warrant from its own `decided` subject through its own consumer (ADR 0259 §6).
|
||||
|
||||
What is built for this, line by line, is milestone every-event's part,
|
||||
[`34-milestones/every-event.md`](34-milestones/every-event.md).
|
||||
|
||||
## 4. Where it runs
|
||||
|
||||
On whichever machines an operator sits at, by assignment. It is not on the control node by default and
|
||||
@@ -145,6 +215,14 @@ address gets a refused connection, which is the truthful answer.
|
||||
| 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 |
|
||||
| on a real bus, `mesh_wait` answers the first matching event within a second of its publication, and after every answer a session reads the owner's state | ADR 0367 §1, §4 |
|
||||
| live: a watcher's `mesh_wait` on the status view's `delivered`, an event no manifest names, wakes on it with no rereading on a timer | ADR 0670 §2, milestone every-event |
|
||||
| a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>`, `mesh.mod.*.event.>` and `mesh.stream.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3; ADR 0671 §1 |
|
||||
| on a real bus, the tool runner's account hears a restricted stream's event with no entry in its grant value, still after the value changes, and is refused a durable consumer and an acknowledgement on it | ADR 0671 §1, §2 |
|
||||
| the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 |
|
||||
| the manifest validator, as the checker runs it, refuses a manifest that carries `waits` as an unknown field, and accepts the tool runner's without it | ADR 0670 §2 |
|
||||
| the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 |
|
||||
| nothing takes an operator's answer from a `decided` event's body; `claude_code_answer` is its one reader | ADR 0670 §5 |
|
||||
|
||||
## What shipped, 2026-09-30
|
||||
|
||||
@@ -159,10 +237,19 @@ machine with nothing else on it does; the mesh MCP server binds whatever it is g
|
||||
|
||||
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 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
|
||||
on the live mesh; and a module registered from the catalog 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.
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | What it builds | State |
|
||||
|---|---|---|
|
||||
| the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) |
|
||||
| found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; [as-is 13](../00-as-is/13-the-console.md) |
|
||||
| waiting on the bus | §3b's `mesh_wait` on a list of five families in the tool runner's manifest (ADR 0367) | delivered (issue 745); [as-is 13](../00-as-is/13-the-console.md) |
|
||||
| every-event | §3b as ADR 0670 and ADR 0671 change it: the tool runner hears every event, module streams included, a marked one on the operator's warrant, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) |
|
||||
|
||||
## What this does not settle
|
||||
|
||||
- **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through
|
||||
|
||||
@@ -132,7 +132,7 @@ a hypothesis (ADR 0153);
|
||||
the mesh is asked and changed through the controller seat's verbs; the forge through the forge module's
|
||||
tools; a licence through the `anthropic-licence-manager` seat's verbs, never by editing a file. The hard
|
||||
rules in new words: a file the mesh manages is changed through the verb that owns it or through the
|
||||
catalogue, never on disk; a store's database is never written by hand; main is never pushed; the mesh
|
||||
catalog, never on disk; a store's database is never written by hand; main is never pushed; the mesh
|
||||
creates no symlinks and nobody else does; a package is declared, not installed by hand. The glossary's
|
||||
words, none of the predecessor's.
|
||||
|
||||
@@ -231,7 +231,7 @@ 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 mesh MCP server's provision in the catalogue; this
|
||||
**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalog; 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.
|
||||
|
||||
@@ -19,7 +19,7 @@ decisions:
|
||||
|
||||
# 37 — The operator's machine
|
||||
|
||||
**Every configurable thing on a node is a module, the home included, and the same catalogue serves
|
||||
**Every configurable thing on a node is a module, the home included, and the same catalog 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 runner
|
||||
per node serving every module's tools outside any container
|
||||
@@ -117,7 +117,7 @@ resolver is asked by the second graphical module; the display server itself is g
|
||||
all four today. Nothing home-scoped composes before it.
|
||||
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 mesh MCP server. Proven when the packet-filter verbs answer
|
||||
runtime per node; the catalog 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.
|
||||
|
||||
@@ -52,7 +52,7 @@ as design 28's: nothing here is new ground; every package reshapes something sta
|
||||
| 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 |
|
||||
| the catalog | 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 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
|
||||
@@ -140,11 +140,11 @@ with the shortcut off answers the same.
|
||||
a module whose own code is an image artifact is refused, whatever image it is built on.
|
||||
|
||||
*Amended 2026-10-02, at WP3.* The gate refuses the pattern **spreading**, not standing: a module
|
||||
new to the catalogue in that shape, or one that had already moved to a bundle and returns to it,
|
||||
is refused; a module the catalogue already holds in that shape — judged from the manifest it
|
||||
new to the catalog in that shape, or one that had already moved to a bundle and returns to it,
|
||||
is refused; a module the catalog already holds in that shape — judged from the manifest it
|
||||
holds and what that module's newest build stood on — is rebuilt without complaint. The day the
|
||||
runtime arrives some thirty such modules stand, each moves in its own change from WP4 on, and a
|
||||
gate refusing every rebuild in the meantime would stop the catalogue's pipeline to make a point
|
||||
gate refusing every rebuild in the meantime would stop the catalog's pipeline to make a point
|
||||
this record already makes.
|
||||
|
||||
**Proof.** Composition tests: a node with three assigned modules, one holding a seat, yields one
|
||||
@@ -159,7 +159,7 @@ the runtime is registered.
|
||||
**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 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
|
||||
catalog, `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"
|
||||
([glossary](../../00-META/glossary.md)).
|
||||
|
||||
@@ -178,7 +178,7 @@ runtime accepts. *Built 2026-10-02* (mesh-tools `c46f950`, mesh-controller `ca7e
|
||||
`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
|
||||
retired from the catalog. Three things the step found are issues
|
||||
[203](../../04-ISSUES/203-a-fresh-assignment-is-pushed-before-its-credential-exists/00-report.md),
|
||||
[204](../../04-ISSUES/204-a-controller-handover-re-sent-every-node-a-stale-declaration/00-report.md) and
|
||||
[205](../../04-ISSUES/205-a-package-resource-fails-against-a-stale-package-database/00-report.md).
|
||||
@@ -224,7 +224,7 @@ the node-engine re-creates the runtime's process on every reconcile (resolved th
|
||||
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
|
||||
the runtime, no `mesh-fail2ban` container, the runtime serving both bundles. Two holders moved; of the
|
||||
thirty-three tool containers the catalogue held, thirty-one remain, and all but these two carried their
|
||||
thirty-three tool containers the catalog held, thirty-one remain, and all but these two carried their
|
||||
module's configuration and secrets in the container's environment, which a bundle does not have — the
|
||||
question research research 020 opened
|
||||
and ADR 0192
|
||||
@@ -235,7 +235,7 @@ container's, the runtime hands each bundle its own. That is WP4b below.
|
||||
|
||||
*mesh-controller, mesh-tools, mesh-catalog. One day. The rest of ADR 0175, under ADR 0192.*
|
||||
|
||||
**What changes**, in order: the manifest's tools artifact gains `env` and the catalogue check
|
||||
**What changes**, in order: the manifest's tools artifact gains `env` and the catalog check
|
||||
refuses a secret's content in it; the composer resolves a bundle's `env` per machine and carries it
|
||||
beside the bundle's archive, `restart-on` included; the runtime hands each bundle its own
|
||||
environment — the contributor's argument for an imported bundle, the child's environment for a
|
||||
|
||||
@@ -0,0 +1,559 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 39
|
||||
milestone: bus-and-switch
|
||||
updated: 2026-11-11
|
||||
code: [mesh-catalog modules/claude-licence-manager, mesh-catalog modules/claude-code, mesh-catalog modules/mesh-vault]
|
||||
decisions:
|
||||
- "ADR 0183"
|
||||
- "ADR 0206"
|
||||
- "ADR 0209"
|
||||
- "ADR 0054"
|
||||
- "ADR 0550"
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0437"
|
||||
- "ADR 0564"
|
||||
- "ADR 0570"
|
||||
- "ADR 0230"
|
||||
- "ADR 0380"
|
||||
- "ADR 0253"
|
||||
- "ADR 0289"
|
||||
- "ADR 0349"
|
||||
---
|
||||
|
||||
# Design 39, milestone bus-and-switch — the licence manager on the bus, switching on its own usage
|
||||
|
||||
**What it builds.** The seat `anthropic-licence-manager` keeps everything it holds in the bus's own storage, by the
|
||||
kept-data templates, and its store database goes; every grant and token moves into the vault, as ADR 0550 §3 has it;
|
||||
and the manager itself moves every consumer off a licence whose session or weekly usage reaches a threshold, to the
|
||||
licence with the most room, says each switch as an event and to the operator, and raises a condition when no licence
|
||||
has room. Its tools read all it keeps and start by hand all it does. The whole shape is in the design's main file
|
||||
([39](../39-the-anthropic-licence-manager.md)); this part holds every line the milestone builds, cited as "design 39
|
||||
bus-and-switch §n".
|
||||
|
||||
**Why now.** The operator, 2026-11-11 22:10 to 22:20 CET: "build it in the licence manager, but make it configurable
|
||||
of course, also remove the postgres database in favor of a bus store, implement useful tools, as much relevant state
|
||||
as possible"; earlier that evening: "when the current licence usage is at 99%, switch to a licence that still has
|
||||
room" (first said at 98, then 99), "on all nodes". That night every node was bound to one licence that reached 99 % of
|
||||
its weekly window, and a session meant to switch them missed the readings: so the manager switches on its own
|
||||
readings, and never a session. Issue 1409 (no warning before a licence's limit) is fixed in the same change (§30).
|
||||
|
||||
**Where.** One repository, mesh-catalog, and three of its modules: `claude-licence-manager` (the seat's holder),
|
||||
`claude-code` (the agent module on every node, the consumer) and `mesh-vault` (the seat `mesh-vault`), with hq for this
|
||||
part, the main file and the glossary. The order between them is the module graph's, each an edge the delivery plan
|
||||
derives (§50).
|
||||
|
||||
**The lead's answers, by the principles.** Four questions no accepted record settled were answered on 2026-11-11 by
|
||||
the lead of issue 1409, against hq's principles, as the operator's way of working since 21:15 that evening has it (the
|
||||
lead answers develop and design questions against the principles; no decision record is written for a detail). Each
|
||||
line they reach names its answer:
|
||||
|
||||
- **A1, the vault's own-account rule** (§15 to §17). ADR 0550 §3 waits until the licence manager and each agent
|
||||
module run under a bus account of their own (ADR 0349 §11, issue 569); both run in their machine's tool runner,
|
||||
under its account. Settled by principle 6 (inside the mesh every agent and module is trusted; who may do what is not
|
||||
a question) and the operator's instruction that who may do what is not the operator's question (approved
|
||||
2026-11-11 21:15 CET): the vault serves licence secrets to the tool runner's account. Its check that the asking
|
||||
machine is the node the binding names stays, as a guard against mistakes, not as a permission.
|
||||
- **A2, the switch without a record** (§24 to §33). ADR 0183 §21 (moving a consumer is a person's act), ADR 0206 §16
|
||||
(no `licence.switched` event) and ADR 0206 §18 (every later change is a person's act) are in force. The operator
|
||||
decided, 2026-11-11 about 21:10 to 22:20 CET: switch automatically at 99 %, on all nodes, configurable; and said that
|
||||
night not to write records for what they decided. So the operator's answer of 2026-11-11 replaces those three
|
||||
clauses for this module, and no record is written.
|
||||
- **A3, how the first start reads today's rows** (§1, §11, §14). A build whose manifest no longer requires
|
||||
`postgres-database` is composed with no credential for it, and `mesh-store.query` refuses the grant column. Settled
|
||||
by principle 7 (the simplest thing that works): this milestone keeps `requires postgres-database`, and the `secret`
|
||||
`grant-key` that opens the grants at rest, as the carry-over's read-only source; dropping both, and the database's
|
||||
retirement on the operator's Approve, is the module's next change once `licences_carried` (§44) shows the
|
||||
carry-over done. The delivery gap this names: no path hands a build the credential of a provision its manifest
|
||||
dropped, so a module cannot read its retiring database at the first start of its new form.
|
||||
- **A4, no wait for design 47's milestone G** (§9, §50). Settled by principle 7: this milestone ships soon, built only
|
||||
on what main holds today (mesh-sdk, the vault, the operator channel, and today's way a module says a condition,
|
||||
ADR 0437 §9), so it uses neither G's writer nor its stream.
|
||||
|
||||
## The kept data on the bus
|
||||
|
||||
1. **The manifest asks for its kept data by template, and keeps its database only as the carry-over's source.**
|
||||
It gains one `bus-state` entry per piece of kept data, each in the typed long form (ADR 0595 §1): the local name,
|
||||
the storage type, the template and its options, as §2 to §9 list them. The old section `state` (today
|
||||
`bindings`) and the `reads` entry `claude-code.holdings` go: the module moves to the new form whole (ADR 0595 §11),
|
||||
keeping the local name `bindings` so its data stays. `requires` keeps `postgres-database` and the `secret`
|
||||
`grant-key`, with their `contributes`, `binds`, `secrets` and the resource `database-url`, read only by §11 and
|
||||
never written (A3); the run-once step `prepare`, which makes the tables, goes, since nothing writes them again.
|
||||
`uses` gains `operator-channel` (§9, §28). Why: the operator, 2026-11-11, and principle 1; the templates are
|
||||
ADR 0595 §2; A3. How it is checked: the manifest validator accepts the manifest at the merge check; a catalog test
|
||||
finds no `reads`, old `state` or `prepare` in it; a unit test finds no write to the database in the code.
|
||||
2. **State `licences`: one key per licence, no secret in it.** Template `state`, option `history` 1. The key is the
|
||||
licence's key form (§10). The value: `name`, `kind` (`subscription` or `api-key`), `account` (the account id),
|
||||
`address`, `organisation`, `refreshFingerprint`, `accessExpiresAt`, `refreshExpiresAt`, `failures` (refreshes
|
||||
failed in a row), `notifiedAt`, `adoptedAt`, `adoptedFrom` (the node), `rotatedAt`, `generation` (the vault
|
||||
secret's, §15), and `usage`, the latest reading of §24 (`sessionPct`, `sessionResetsAt`, `weeklyPct`,
|
||||
`weeklyResetsAt`, `sonnetPct`, `sonnetResetsAt`, `readAt`, `readError`). Written by the manager alone, at each
|
||||
adoption, rotation, failure and reading; deleted by no act of the milestone (a licence is never removed today, and
|
||||
none is added here). It replaces the table `licence`, less its column `sealed`, which goes to the vault (§12).
|
||||
Why: ADR 0550 §3 keeps the licence records without a secret in the manager; principle 4 keeps the latest reading
|
||||
beside its licence so `licences` answers from one key. How it is checked: unit, a rotation and a reading each write
|
||||
the key and no field holds a token; the after-move scan of §20.
|
||||
3. **State `bindings`: one key per consumer.** Template `state`, option `history` 8, so the last moves of a consumer
|
||||
are read with the state's `history` call. The key is the consumer's: the node's name for a node's agent, `<node>.<worker>`
|
||||
for a worker (ADR 0183 §19). The value: `licence` (its name), `kind`, `generation` (one more at every rotation and
|
||||
every switch of that consumer, ADR 0206 §13), `since`, `why` and `by` (who or what moved it last: a person's
|
||||
name, `login` for ADR 0209's move, `usage` for §26). Written by the manager alone; deleted by `release`. It replaces
|
||||
the table `binding` and today's old-section state of the same name. Why: ADR 0206 §13; the eight past values are
|
||||
the design's choice, for `licences_switches` to show a consumer's last moves. How it is checked: unit, `bind`,
|
||||
`switch`, `release` and §26 each write or delete the key with its `why` and `by`.
|
||||
4. **State `leases`: one key per lease, ended by its holder or its lifetime.** Template `state`, option `history` 1,
|
||||
with a lifetime per key of 2 minutes (the time one exchange with the vendor is given today). The key is
|
||||
`licence.<licence key>` or `account.<account id>`; the value names the holder (the process) and `until`. A lease
|
||||
is taken by creating the key, which fails while another holds it, before the licence is read (ADR 0183 §6); the
|
||||
holder deletes it at the end; a holder that dies leaves it to its lifetime. It replaces the table `lease`. Why:
|
||||
ADR 0183 §6 keeps one rotation source; a create that fails is the bus's own compare-and-set. How it is checked:
|
||||
unit, two refreshes started together rotate once and the second answers "another run holds its lease"
|
||||
(the main file's first acceptance test).
|
||||
5. **State `offered`: one key per grant a node offered.** Template `state`, option `history` 1, a lifetime per key
|
||||
of 90 days. The key is the refresh token's fingerprint; the value: `node`, `account`, `outcome` (`adopted`,
|
||||
`dead`, `refused`), `why`, `at`. Written at each candidate's outcome (ADR 0206 §9, §10); deleted by its lifetime,
|
||||
after which a fingerprint offered again is tried once more and recorded again. It replaces the table `offered`.
|
||||
Why: the manager never exchanges a dead candidate twice within its lifetime; 90 days is longer than a refresh
|
||||
token lives. How it is checked: unit, a dead candidate is not exchanged at its second report.
|
||||
6. **Log `usage`: every reading, kept 35 days.** Template `log`, options `max-age-days` 35 and `max-mib` 256, one
|
||||
subject per licence key. Each entry: `at`, the flattened reading of §24 and `raw`, the vendor's answer whole
|
||||
(ADR 0054). Written at every reading, never rewritten. It replaces the table `usage`. Why: ADR 0054 keeps the raw
|
||||
reading beside the normalised one; 35 days hold five weekly windows, and at five minutes for four licences
|
||||
the log stays under a quarter of its cap. How it is checked: unit, a reading appends one entry under its licence's
|
||||
subject; the bus module's fill condition (ADR 0595 §6) warns before the log is full.
|
||||
7. **Log `audit`: every act, kept for good.** Template `log`, option `max-mib` 64, no age, one subject per kind:
|
||||
`adopted`, `refused`, `failed`, `rotated`, `bound`, `switched`, `released`, `drift`, `carried`, `settings`. Each
|
||||
entry: `at`, `by`, `why` and the act's own fields (for `switched`: §27's). Written after each act, never rewritten.
|
||||
It replaces the table `audit`. Why: ADR 0183 §5 keeps the audit of every switch; a log keeps it in order and for
|
||||
good. How it is checked: unit, each act appends one entry under its kind.
|
||||
8. **State `carried`: the carry-over's record.** Template `state`, option `history` 1, one key `postgres`: `at`, and
|
||||
for each table of today's database the rows read and the entries written. Written once, at the end of §11; never
|
||||
deleted, since its presence is what keeps a second start from carrying over again. Why: ADR 0437 §7's way of
|
||||
carrying a module's state over once. How it is checked: §13.
|
||||
9. **Its conditions are said on the operator channel, and kept in its own state `conditions`.** The manager says
|
||||
each condition of §11, §29 to §31 and §33 as any module does on main today (ADR 0437 §9): published on
|
||||
`mesh.seat.operator-channel.accept.condition.claude-licence-manager` (the subject the operator channel's contract
|
||||
names, through its client in mesh-contracts), carrying the condition's `key`, `severity` (`urgent` or `warning`),
|
||||
`scope` and `id`, `raised` or `cleared`, its `summary` and ADR 0253's words (`headline`, `needs`, `explanation`
|
||||
opened by its verdict, `resolved` and `actions`); the router alone decides whether and where the operator is told.
|
||||
It keeps each condition it said raised in its state `conditions` (template `state`, option `history` 1, one key per
|
||||
condition key, its body and `since`), written before it is said and deleted when it is said cleared. At its start
|
||||
it judges each kind anew from its own state and readings, says raised again each it still holds and cleared each
|
||||
key of `conditions` it no longer holds. A raise or clear is said again only when what it says changes (ADR 0289
|
||||
§1). Why: A4 (today's way a module raises a condition, without G's writer); the state lets a restart clear what
|
||||
it said. How it is checked: unit, a condition is written in `conditions` and published once with these fields;
|
||||
a start with a key it no longer holds says it cleared; whole (manager and the router on a test bus), a raised
|
||||
condition reaches the desk channel and its clearing clears it.
|
||||
10. **A licence's key form.** A licence's name is its account's address, or its id where it has none, or the name
|
||||
given at `adopt` for an API key (today's `NameFor`); a name holds characters a key may not. Its key is the name
|
||||
with each `@` written as `_at_` and every other character outside letters, digits, `-` and `.` written as `_`. An
|
||||
adoption whose key another licence already has is refused, said in the audit and as `licence.refused`. Every
|
||||
verb and tool still takes and answers the name; the key is the store's alone. Why: a key of the bus takes only
|
||||
those characters; the form is the design's choice, readable in `licences_kept`. How it is checked: unit, a table
|
||||
of names and their keys, and the refused clash.
|
||||
|
||||
## Carrying over today's rows
|
||||
|
||||
11. **At its first start the manager carries over every row before it does anything else.** When the state
|
||||
`carried` has no key `postgres`, the manager, before it hears an event, reads a holding report, refreshes or reads
|
||||
usage, reads every row of the six tables of its database `claude_licences` and writes them as §12 says, then the
|
||||
key `carried.postgres`. Until that key exists it serves its read verbs and tools only, answering every other
|
||||
with "the carry-over has not ended", and says the condition `carry-over-failed` (§9; urgent; "Needs you: the licence
|
||||
manager could not carry over its licences; no consumer is moved and no token is refreshed until it does", naming
|
||||
the table and the error) when a read or a write fails, cleared when the key is written. Why: a manager that ran on
|
||||
an empty state would take every holding report for a candidate (ADR 0206 §5) and exchange refresh tokens it
|
||||
already holds; failure is loud (principle 3). Its source is the database read with the module's own credential, which this milestone keeps for it alone (A3). How it is checked: whole (manager, a store database filled from a copy of today's rows), every row
|
||||
is carried and the key written; with the database unreachable the condition is raised and no vendor call is made. A table that does not
|
||||
exist (a fresh install) is read as no rows.
|
||||
12. **What each table becomes.**
|
||||
- `licence` → a key of `licences` (§2) per row; each row's `sealed` grant is opened with today's `grant.key` and
|
||||
handed to the vault as §15 says, its generation 1; the row's other columns become the key's fields.
|
||||
- `binding` → a key of `bindings` (§3) per row, its `generation` kept, `why` "carried over", `by` the manager.
|
||||
- `lease` → nothing: a lease lives two minutes, and none taken before the start still holds.
|
||||
- `offered` → a key of `offered` (§5) per row, its lifetime counted from the row's `at`.
|
||||
- `usage` → one entry of the log `usage` (§6) per row of the last 35 days, oldest first, its `at` kept.
|
||||
- `audit` → one entry of the log `audit` (§7) per row, oldest first, its `at` kept, under the subject its `what`
|
||||
names (`drift` for any other).
|
||||
Why: no licence, binding, grant or history is lost (the operator, 2026-11-11); usage older than the log's age
|
||||
would be dropped by the log at once. How it is checked: whole, the counts in `carried.postgres` equal the rows read
|
||||
for every table but `lease`, and `mesh-vault.get` of each licence's grant (§16) answers it.
|
||||
13. **A second start carries over nothing.** With `carried.postgres` present the manager never reads the database,
|
||||
and a carry-over that failed halfway is started again from the beginning, every write of §12 being idempotent
|
||||
(a key put again with the same value, a log entry not appended twice: before appending, the manager reads its
|
||||
log's last entry for that subject and skips rows at or before its `at`). Why: ADR 0437 §7. How it is checked:
|
||||
whole, a second start writes nothing; a start stopped after `bindings` and started again leaves every count equal
|
||||
to the rows read.
|
||||
14. **The database's fate, and that this delivery moves data.** In this milestone the database `claude_licences`
|
||||
stays, read once by §11 and never written. Once `licences_carried` (§44) shows the carry-over done and §20's scan
|
||||
finds no grant left outside the vault, the module's next change drops `requires postgres-database` and
|
||||
`grant-key`; the provision is then retiring, its account removed only on the operator's Approve (ADR 0570 §5),
|
||||
and the database is renamed aside by the postgres module's `postgres_retire_database` and deleted only by a
|
||||
person (ADR 0230 §14). The delivery of this milestone moves data, so it is said to the operator before it starts
|
||||
(which module, its machine, that its licences, bindings and grants move to the bus and the vault), and the
|
||||
manager's machine is backed up before the module moves, as for every module that holds data. Why: A3,
|
||||
ADR 0570 §5, ADR 0230; the operator's instruction that a delivery moving data is said first. How it is checked:
|
||||
the delivery's plan names the backup; after the delivery `mesh-store.databases` still lists `claude_licences`.
|
||||
|
||||
## Grants and tokens in the vault
|
||||
|
||||
*Lines §15 to §20 build ADR 0550 §3, on the tool runner's account (A1).*
|
||||
|
||||
15. **The vault holds two secrets per licence, handed to it by the manager alone.** `licence.<licence key>.grant`
|
||||
holds, for a subscription, the refresh token, the access token and both expiries, and for an API key the key;
|
||||
`licence.<licence key>.access` holds a subscription's access token and its expiry, or the API key. The vault
|
||||
serves a new verb on its seat, `licence-put`: `licence`, `part` (`grant` or `access`), `generation` and the value
|
||||
sealed to the vault's key; answered for a call from the machine that holds the seat `anthropic-licence-manager`
|
||||
(as the seat's placement names it), and refused from any other machine naming the rule, a guard against a
|
||||
mistaken caller rather than a permission (A1); it keeps the newest generation and refuses an older one; and it says its event
|
||||
`secret-changed` with the secret's name and generation, never the value. The manager calls it after every
|
||||
adoption, rotation and carry-over (§12), the `grant` first. Why: ADR 0550 §3 (the manager hands what it gets back
|
||||
to the vault, keeping nothing at rest); the names, the verb and its fields are the design's choice. How it is
|
||||
checked: unit (vault), `licence-put` from another machine is refused and an older generation is refused; unit
|
||||
(manager), a rotation calls it twice and keeps no token after the call returns.
|
||||
16. **The manager reads a grant from the vault when it needs one, and keeps it only for that act.** To refresh,
|
||||
adopt again or read usage, the manager calls `mesh-vault.get` for `licence.<licence key>.grant`, sealed to its own
|
||||
public key, under the licence's lease (§4), and drops the value when the act ends. Why: ADR 0550 §3, ADR 0183 §6.
|
||||
How it is checked: unit, the manager's state and log hold no token after a rotation.
|
||||
17. **A consumer's agent module gets its token from the vault, which serves only the licence a binding names.** The
|
||||
vault answers `mesh-vault.get` for `licence.<licence key>.access` to the tool runner's account of a machine,
|
||||
and only when that machine is the node the asking consumer's binding names and the licence is the one that
|
||||
binding names: a guard against a node applying another's licence by mistake, not a permission (A1). Its rule that a
|
||||
value goes only to a module under a bus account of its own (ADR 0349 §11) does not apply to the licence
|
||||
secrets. It learns the bindings from the
|
||||
manager's `binding.changed` (§21), which it consumes, and at its start from `anthropic-licence-manager.bindings`,
|
||||
its first view, keeping them in its own state `licence-bindings` (template `state`, one key per consumer, the
|
||||
licence and generation, deleted on a release). The agent module never receives a refresh token (ADR 0206 §11).
|
||||
Why: ADR 0550 §3; A1; ADR 0596 §2 (the vault asks the manager, never reads its state); the state is the
|
||||
design's choice. How it is checked: unit (vault), a token is refused to a machine no binding names and for a licence its
|
||||
binding does not name (the main file's acceptance test), and given after a `binding.changed` naming it.
|
||||
18. **`current` answers which licence and its health, never the token.** The seat verb `current` takes `consumer`
|
||||
and answers `licence`, `kind`, `generation`, `health` (`ok`, `failing`, `expiring`) and `accessExpiresAt`; its
|
||||
argument `public_key` goes. Why: ADR 0550 §3. How it is checked: unit, no answer of `current` holds a token.
|
||||
19. **The agent module applies a new token when the vault says it changed or its binding moved.** The agent module
|
||||
on a node asks `mesh-vault.get` for its licence's `access` sealed to its own key, when it hears a
|
||||
`binding.changed` for its consumer with a newer generation than it applied, when it hears the vault's
|
||||
`secret-changed` for the licence it holds, and at its start; it then writes the credentials file or the key-helper
|
||||
as design 36 §5 has it, unchanged. Why: ADR 0550 §3, ADR 0206 §14 as ADR 0596 §2 changes it (an event and a first
|
||||
view in place of a watched key). How it is checked: whole, `switch` changes a workstation's token end to end (the
|
||||
main file's acceptance test).
|
||||
20. **What ends with the move.** The manager's `grant.key`, its `secret` requirement and the code that seals a grant
|
||||
at rest go; the `public-key` verb and the manager's keypair stay, since a login offered by a node is sealed to it
|
||||
(ADR 0183 §23, ADR 0550 §3). After the delivery the vault's `secret_holders` lists both secrets of every licence
|
||||
and the manager's own secret `grant-key` is retired with `mesh-vault.retire` on the operator's word. Why:
|
||||
ADR 0550 §3 (a scan after the move that the manager holds no grant). How it is checked: `licences_kept` (§41)
|
||||
shows no field holding a token in any entry, and `secret_holders` the two secrets per licence.
|
||||
|
||||
## Bindings and holdings said as events
|
||||
|
||||
21. **Every change of a binding is said as `binding.changed`.** The manager declares in `emits` the event
|
||||
`binding.changed`, said after the key of `bindings` is written or deleted: `consumer`, `licence` (empty on a
|
||||
release), `kind`, `generation`, `from` (the licence before), `why`, `by` and `at`; never a token. Why: ADR 0596 §2,
|
||||
§3 (the owner gains the event its readers need before they move); the name and fields are the design's choice.
|
||||
How it is checked: unit, `bind`, `switch`, `release`, a login's move and §26 each say one event per consumer
|
||||
moved.
|
||||
22. **The agent module follows its binding by that event, and asks once at its start.** `claude-code` gains
|
||||
`consumes` `claude-licence-manager.binding.changed`, acting only on the event naming its node's consumer, and at
|
||||
its start asks `anthropic-licence-manager.bindings` with `consumer` its node for its first view; its `reads`
|
||||
entry `claude-licence-manager.bindings` goes. A node that was away asks once when it is back (ADR 0206 §15), by its
|
||||
start or its reconnect. Why: ADR 0596 §2, §4. How it is checked: unit (claude-code), an event for another
|
||||
consumer is ignored; whole, a node started after a switch applies the new licence from its first view.
|
||||
23. **A node's holdings are said as events, and asked for by the manager.** `claude-code` declares in `emits`
|
||||
`holdings-changed`, said at its start and whenever its holdings change (ADR 0206 §2), carrying its holdings
|
||||
report whole as ADR 0206 §1 lists it, never a token, and gains `consumes`
|
||||
`claude-licence-manager.holdings.asked`, on which it says `holdings-changed` again. The manager declares
|
||||
`holdings.asked` (`at`, `by`) and says it after its carry-over (§11) and on `licences_resync` (§42), and consumes
|
||||
`claude-code.holdings-changed` in place of today's `reads`. Its state `holdings` stays `claude-code`'s own. Why:
|
||||
ADR 0596 §2, §3; a first view by events, since the holdings are one key per node; the names are the design's
|
||||
choice. How it is checked: whole (manager and a fixture agent module), a `holdings.asked` is answered by one
|
||||
`holdings-changed` per node, and a report carrying an unknown refresh token's fingerprint makes a candidate
|
||||
(ADR 0206 §4).
|
||||
|
||||
## Switching on usage
|
||||
|
||||
*Lines §24 to §33 are the operator's answer of 2026-11-11, which replaces ADR 0183 §21 and ADR 0206 §16 and §18 for
|
||||
this module (A2): the manager moves consumers by itself on its own readings, and says every switch as an event.*
|
||||
|
||||
24. **The manager reads each subscription's usage on a cadence, and keeps the reset times.** Every
|
||||
`usage_minutes` (§33), for each licence of kind `subscription`, the manager reads the vendor's usage with the
|
||||
licence's access token (§16) and flattens it: `sessionPct` and `sessionResetsAt` (the five-hour window),
|
||||
`weeklyPct` and `weeklyResetsAt` (the seven-day window), `sonnetPct` and `sonnetResetsAt`. It writes the entry of
|
||||
§6, the key's `usage` of §2, and says `usage.read` with those fields, never a token. A failed reading writes
|
||||
`readError` (the vendor's status and reason) and keeps the last percentages. This is the manager's one timer: the
|
||||
vendor says nothing as usage grows. Why: ADR 0054; the operator's "reset times"; the reset times of the weekly and
|
||||
sonnet windows are read beside the session's, which alone is read today. How it is checked: unit, a vendor
|
||||
answer is flattened with its three reset times; a failed answer keeps the percentages and writes the reason.
|
||||
25. **A licence's room, and when it is known.** A licence's room is 100 less the higher of its `sessionPct` and
|
||||
`weeklyPct` in its latest reading. It is known only for a licence of kind `subscription`, read successfully within
|
||||
`usage_stale_minutes`, whose refresh has not failed `failures_to_notify` times in a row and whose refresh token is
|
||||
not expired. A licence whose room is not known is never a target of §26: an API key has no window to read and is
|
||||
billed per use, so it is never one. Why: the operator's "a licence that still has room"; a licence's room is known
|
||||
only from a reading; the sonnet window is shown and does not count, since the operator named the session and
|
||||
weekly windows. How it is checked: unit, a table of readings, ages and healths and the room each gives.
|
||||
26. **On each reading, a licence at its threshold moves every consumer bound to it.** After writing a reading of
|
||||
licence L, when `switch_auto` is on, L has one or more consumers bound to it, and L's `sessionPct` or `weeklyPct`
|
||||
is at or above `switch_at_pct`, the manager chooses the target: among the other licences whose room is known and
|
||||
whose `sessionPct` and `weeklyPct` are both below `room_below_pct`, the one with the most room, the first by name
|
||||
on a tie. It then moves every consumer bound to L to the target, each as the `switch` verb does (its binding's
|
||||
generation one more, `why` naming L, the window, its percentage and its reset, `by` `usage`), says
|
||||
`binding.changed` per consumer (§21) and one `licence.switched` (§27), and tells the operator (§28). A consumer
|
||||
that inherits its node's licence (ADR 0183 §20) follows its node. A licence below its threshold, or with no
|
||||
consumer, moves nothing; a consumer is never moved back by the rule, only off a licence at its threshold, so no
|
||||
consumer flaps between two licences. Why: the operator, 2026-11-11 ("switch to a licence that still has room",
|
||||
"on all nodes"; the manager itself, never a session); the order of choice and the tie are the design's choice.
|
||||
How it is checked: unit, a table of licences, readings and bindings and the moves each gives; whole (manager, a
|
||||
fixture vendor), a reading of 99 % moves four bound consumers to the licence with the most room in one act.
|
||||
27. **Every switch is said as `licence.switched`.** The manager declares `licence.switched` in `emits` again and
|
||||
says it once per act that moves consumers, by `switch` or by §26: `from`, `to`, `consumers` (the list), `by` (a
|
||||
person's name or `usage`), `why`, and for §26 `window` (`session` or `weekly`), `pct`, `resetsAt` and the target's
|
||||
`room`. The same fields are the log entry `switched` (§7). Why: the operator, 2026-11-11 ("each switch said as an
|
||||
event"); this replaces ADR 0206 §16 for this module (A2). How it is checked: unit, one event per act with the consumers moved.
|
||||
28. **Every switch the manager makes is told to the operator.** After an act of §26 the manager calls
|
||||
`operator-channel.tell` (ADR 0380): `subject` the seat `anthropic-licence-manager`, `headline` "<consumers> moved
|
||||
to <target>", and one line "<L> reached <pct> % of its <window> window, which resets at <time>; <target> has
|
||||
<room> % room". A switch by the `switch` verb is not told, since the person who asked knows it. Why: the operator,
|
||||
2026-11-11 ("told to the operator"); a notice that needs no answer is `tell` (ADR 0380). How it is checked: unit,
|
||||
§26 calls `tell` once per act, and `switch` never.
|
||||
29. **No licence with room is a condition, cleared when one has room.** When §26 finds no target for L, the manager says the condition `licence.<L key>.no-room` (§9), severity `urgent`: headline "No licence has room for
|
||||
<consumers>"; "Needs you: add a licence, or wait for <L>'s <window> window to reset at <time>"; explanation
|
||||
naming L's percentage and every other licence's room or why it is not known; resolved line "<consumers> have a
|
||||
licence with room again". The manager clears it when a later reading of L falls below `switch_at_pct`, when its
|
||||
consumers have moved (a target was found at a later reading of L), or when L has no consumer left. Why: the
|
||||
operator, 2026-11-11 ("a condition when no licence has room, cleared when one has"); its words are ADR 0253's. How
|
||||
it is checked: unit, with every licence at its threshold the condition is said once and cleared by a reading with
|
||||
room; whole, the router takes its raise and its clearing.
|
||||
30. **A licence near its limit is warned about once per crossing.** When a licence with one or more consumers has
|
||||
`sessionPct` or `weeklyPct` at or above `warn_at_pct`, the manager says the condition `licence.<L key>.near-limit` (§9),
|
||||
severity `warning`: headline "<L> at <pct> % of its <window> window"; "Nothing for you to do: <consumers> move to
|
||||
<target> at <switch_at_pct> %" while §26 would find a target, "Needs you: no other licence has room" when it would
|
||||
not; explanation naming the reset time. It is written again only when what it says changes (ADR 0289 §1), and
|
||||
cleared when both windows read below `warn_at_pct`. Why: issue 1409 (a warning before the limit, cleared when the
|
||||
reading falls back); ADR 0183 §21 (warned once per crossing of a declared threshold). How it is checked: unit, a
|
||||
rising series of readings says the condition once at the crossing and again only when its words change; a reading
|
||||
after the reset clears it.
|
||||
31. **A licence whose usage cannot be read is a condition.** When a subscription's latest successful reading is
|
||||
older than `usage_stale_minutes`, the manager says the condition `licence.<L key>.usage-unread` (§9), severity
|
||||
`warning`, naming the vendor's reason from `readError`; "Needs you" only when consumers are bound to it ("<consumers>
|
||||
are not moved off it while its usage is unknown"); cleared at its next successful reading. Why: principle 3 (a
|
||||
switch that cannot see must say so). How it is checked: unit, three failed readings in a row write it, one
|
||||
success clears it.
|
||||
32. **The rule is started by hand too.** `licences_switch_now` (§39) runs §26 for one licence or all at once, from
|
||||
their latest readings, and `licences_read_usage` (§38) reads first; with `dry` true either answers what §26 would
|
||||
do and does nothing. Why: principle 4 (everything it does by itself can be started by hand). How it is checked:
|
||||
unit, `dry` moves nothing and answers the same moves as the act.
|
||||
33. **The settings, with their defaults, set through mesh-config.** The manifest's `config` gains, each `trusted`
|
||||
and read from the settings file at every reading so a change takes effect at the next one: `switch_auto`
|
||||
(boolean, default true: whether §26 moves consumers; off, §29 and §30 still raise), `switch_at_pct` (number 1 to
|
||||
100, default 99), `room_below_pct` (number 1 to 100, default 90: what a target's windows must both be below),
|
||||
`warn_at_pct` (number 1 to 100, default 90), `usage_minutes` (number 1 to 60, default 5) and
|
||||
`usage_stale_minutes` (number 5 to 180, default 15). The settings file's template and its `takes-config` list
|
||||
them beside today's five. A person sets one with `mesh-config.config`, at mesh or node level. When
|
||||
`room_below_pct` is above `switch_at_pct` or `warn_at_pct` is above `switch_at_pct`, the manager says the condition `settings.<setting>.invalid` (§9) (warning, naming both values) and moves no consumer until it is fixed. Each change of
|
||||
a value it reads is written in the audit under `settings`. Why: the operator, 2026-11-11 ("make it configurable";
|
||||
99 by default); 90 as the room and the warning is the value the session used that night, so a licence warned
|
||||
about is never a target; the bounds are the design's choice. How it is checked: unit, each setting's default and
|
||||
bound, and the invalid pair moving nothing.
|
||||
|
||||
## The tools
|
||||
|
||||
*Each answers through the mesh MCP server; any person and agent sessions may call them. "Reads" and "acts" are each
|
||||
tool's effect (the manifest's mark).*
|
||||
|
||||
34. **`licences` shows every licence with its latest usage and room.** The seat verb answers, per licence, today's
|
||||
fields (`name`, `kind`, `identity`, expiries, `failures`, the consumers bound to it) and gains `usage` (§2's
|
||||
latest reading with its reset times and `readAt`), `room` (§25, or why it is not known), `target` (whether §26
|
||||
would choose it now), `adoptedAt`, `adoptedFrom` and the open conditions about it. Reads. Why: principle 4; the
|
||||
operator's "read every licence with its latest usage and reset times". How it is checked: unit, an answer per
|
||||
licence carries each field and no token.
|
||||
35. **`usage` answers the history.** The seat verb takes `licence` (absent for all), `since` (a time) and `limit`
|
||||
(default 20, at most 2000) and answers entries of the log `usage`, newest first, the flattened reading and, with
|
||||
`raw` true, the vendor's answer. Reads. How it is checked: unit, `since` and `limit` bound the answer.
|
||||
36. **`switch`, `bind` and `release` keep their arguments.** Each also says §21's and §27's events and writes the
|
||||
audit with the caller as `by`. Acts. How it is checked: unit, as §21 and §27.
|
||||
37. **`licences_switches`: every switch and why.** A module tool reading the log `audit` under `switched`: `licence`,
|
||||
`consumer`, `since` and `limit` (default 20), newest first, each with `at`, `from`, `to`, `consumers`, `by`,
|
||||
`why` and, for §26, the reading that moved them. Reads. How it is checked: unit, a switch by `usage` and one by a
|
||||
person are answered with their causes.
|
||||
38. **`licences_read_usage`: read usage now.** Takes `licence` (absent for every subscription) and `dry`; reads as
|
||||
§24 does at once and then applies §26 unless `dry` is true; answers each reading and what §26 did or would do.
|
||||
Acts. How it is checked: unit, as §24 and §32.
|
||||
39. **`licences_switch_now`: apply the rule now.** Takes `licence` (absent for all) and `dry`; applies §26 from the
|
||||
latest readings, even when `switch_auto` is off, since a person asked; answers the moves. Acts. How it is checked:
|
||||
as §32.
|
||||
40. **`licences_settings`: the settings in force.** Answers each setting of §33 and today's five: its value, its
|
||||
default, where it was set (mesh, node or the default, as mesh-config answers it) and whether §33 finds it invalid.
|
||||
Reads. How it is checked: unit, a value set at node level is answered with its level.
|
||||
41. **`licences_kept`: every entry it keeps.** Takes `entry` (one of §2 to §9's local names; absent for all) and
|
||||
`key`; answers each entry's keys (or the subjects of a log with their last entry), one key's value, and its fill
|
||||
against its cap. Reads. Why: principle 4 and ADR 0564 §12 (a read of every bucket and stream a part keeps). How it
|
||||
is checked: a unit test lists every bus-state entry of the manifest and fails on one this tool does not read.
|
||||
42. **`licences_resync`: recover.** Says `binding.changed` again for every binding, `holdings.asked`, says again each condition it holds (§9) and calls `licence-put` again for each licence's `access` (§15); answers
|
||||
what it said. Acts. Why: principle 4 (everything that goes wrong is recovered through its tools): a consumer, the
|
||||
vault or the keeper that missed an event converges on one call. How it is checked: unit, each is said once per
|
||||
binding, licence and condition.
|
||||
43. **`licences_conditions`: the conditions it said.** Answers each key of `conditions` (§9) with its body and
|
||||
`since`. Reads. Why: principle 4 (everything it holds can be read). How it is checked: unit.
|
||||
44. **`licences_carried`: the carry-over's record, and its recovery.** Answers `carried.postgres` (§8), or, when it
|
||||
is absent, the failure `carry-over-failed` names; with `run` true and the key absent, starts §11 again; refused
|
||||
with the key present. Acts with `run`, reads without. How it is checked: unit, `run` is refused once carried.
|
||||
45. **`refresh` and `adopt` stay.** `refresh` (one licence or every due one, under its lease) and `adopt` (an API key
|
||||
from a file on the manager's node or sealed to its `public-key`) are unchanged but for reading and writing the
|
||||
grant in the vault (§15, §16). Acts.
|
||||
|
||||
## A further licence
|
||||
|
||||
46. **A subscription licence is added by logging in on any node.** A person logs in to the new account (a team seat
|
||||
among them) with the agent's own login on any node running `claude-code`. That node's `holdings-changed` (§23)
|
||||
carries a refresh token whose fingerprint the manager does not hold, so it is a candidate; the manager asks that
|
||||
node's `claude_code_grant` for it sealed to its key, refreshes it under the account's lease, and on success it is
|
||||
a new licence, named by its account's address (ADR 0206 §4, §7, §8; §10), its grant handed to the vault (§15). The
|
||||
node it came from is bound to it (ADR 0209); a person who wants that node back on its licence calls `switch`.
|
||||
From the next reading (§24) it has a room and is a target of §26. Nothing is configured and no tool beside the
|
||||
login is needed. Why: ADR 0206, ADR 0209. How it is checked: whole (manager, a fixture vendor), a report with a new
|
||||
fingerprint is adopted, listed by `licences` with its organisation, and chosen as a target at its first reading
|
||||
with room.
|
||||
47. **A team seat's licence is shown as any subscription.** `licences` answers its `organisation` beside its address;
|
||||
`kind` stays `subscription`. Whether the vendor's usage answer for a team seat carries the same windows is not
|
||||
known (a guess that it does); if it does not, §31's `usage-unread` says so and it is never a target. Why: the
|
||||
operator's new team-seat licence. How it is checked: live, after the login, `licences` lists it with a reading.
|
||||
48. **An API key is added with `adopt`**, from a node through `claude_code_add_api_key` or from a file on the
|
||||
manager's node (ADR 0209), and is never a target of §26 (§25). Why: ADR 0206 §6, ADR 0209.
|
||||
|
||||
## Delivery
|
||||
|
||||
49. **One pull request in mesh-catalog, with hq.** The three modules change in one mesh-catalog pull request; hq's
|
||||
pull request carries this part, the main file and the glossary. Why: one milestone, one release (the operator's
|
||||
instruction on designing a change as it should be).
|
||||
50. **The order is the module graph's.** The edges the delivery plan derives: the bus module creates the manager's
|
||||
bus-state entries before its want moves (the provision `bus-state`, ADR 0570 §4); `mesh-vault`'s `licence-put`
|
||||
and its consumption of `binding.changed` are served before the manager calls them (a served verb); and `claude-code`
|
||||
declares `holdings-changed` before the manager consumes it (ADR 0595 §11's judge refuses the reverse). It waits for
|
||||
no other milestone: the operator channel's condition subject, the vault and mesh-sdk are used as main holds them
|
||||
(A4). A node's agent keeps its last token, which lives hours, while the manager moves.
|
||||
51. **Tonight's interim rule ends with the delivery.** Until the delivery, a session follows the readings and calls
|
||||
`switch` (issue 1409's note of 2026-11-11 20:11); once §26 runs live, that session stops, said on issue 1409.
|
||||
|
||||
## Acceptance
|
||||
|
||||
52. **The tests of the milestone**, each run in its module's suite or live after the delivery:
|
||||
- every row of today's database is carried over, counted in `carried.postgres`, and a second start carries
|
||||
nothing (§11 to §13);
|
||||
- after the delivery `licences_kept` and the vault's `secret_holders` show no token outside the vault (§20);
|
||||
- a reading at `switch_at_pct` moves every consumer bound to that licence to the licence with the most room,
|
||||
says one `licence.switched` and one `binding.changed` per consumer, and tells the operator once (§26 to §28);
|
||||
- with every licence at its threshold, `no-room` is raised once and cleared by a reading with room (§29);
|
||||
- a licence crossing `warn_at_pct` is warned about once (§30), and one that cannot be read is said (§31);
|
||||
- each setting of §33 changes the rule at the next reading;
|
||||
- every entry the manager keeps is read by `licences_kept`, and every act it takes by itself is started by a tool
|
||||
(§37 to §45);
|
||||
- live: a login to a new account on a node is adopted and listed with its usage (§46).
|
||||
|
||||
## The lead's answers to the prepare's questions
|
||||
|
||||
The prepare's design questions (2026-11-11), each answered by the lead in one line against
|
||||
[the principles](../../../00-META/principles.md), as the operator's way of working says. A developer builds these as
|
||||
written; they are cited as "design 39 bus-and-switch B*n*".
|
||||
|
||||
- **B1** (`licence-put` is not in the vault's seat definition): the vault's seat definition in mesh-contracts gains the
|
||||
verb, and mesh-contracts joins this milestone as a second member of its delivery group (P5).
|
||||
- **B2** (the validator refuses bus state by template from mesh-vault): `licence-bindings` stays in the vault's old
|
||||
`state` section (P7).
|
||||
- **B3** (who may read a licence's grant): no machine guard on `licence-put` or `get`; the generation guard stays.
|
||||
Inside the mesh every module is trusted, and who may do what is not a question (P6; the operator's instruction
|
||||
who-may-is-not-the-operators-question).
|
||||
- **B4** (the manager has no vault key to seal to): two steps with a key made for the call, as `move-in` does (P7).
|
||||
- **B5** (the vault numbers generations itself): the caller's generation is the record's generation (P8).
|
||||
- **B6** (the vault takes only lower-case names): §10's key form is lower-case (P7).
|
||||
- **B7** (claude-code needs the licence's key and the account's identity): `binding.changed` and `current` carry `key`,
|
||||
and `current` keeps `identity` (P1, P3).
|
||||
- **B8** (`get` names no consumer): the access token goes to machine M when any binding of a consumer on M names that
|
||||
licence (P7).
|
||||
- **B9** (no line names the `invokes` entries or claude-code's `consumes mesh-vault.secret-changed`): each is declared
|
||||
in its module's manifest (P1).
|
||||
- **B10** (`bindings` takes no `consumer`): an optional `consumer` argument; the §3 fields answered unpaged; the vault
|
||||
reads it once at start (P7).
|
||||
- **B11** (one generation counter for every consumer): a counter per consumer, from the higher of its own and its
|
||||
node's last reported generation; `GenerationsAbove` goes (P7).
|
||||
- **B12** (holdings unknown after a restart): the manager says `holdings.asked` at every start (P1).
|
||||
- **B13** (`offered` key holds a colon; two outcomes §5 does not list): the key goes through §10's key form, and all
|
||||
five outcomes are kept (P7).
|
||||
- **B14** (`since` and `adoptedFrom` have no column; the live database's name): `since` is the carry-over's time,
|
||||
`adoptedFrom` comes from the audit, and §14 checks the database the module's binding names (P7).
|
||||
- **B15** (§20 retires `grant-key`, §1 and A3 keep it): only the sealing at rest goes now; `grant-key` is retired in
|
||||
the module's next change, with the store database (P7).
|
||||
- **B16** (`licence.bound`, `licence.released`, `licence.rotated`): dropped; `binding.changed` and the audit log
|
||||
cover them (P8).
|
||||
- **B17** (the conditions' scope and id): scope `module`, id `claude-licence-manager` (P7).
|
||||
- **B18** (no mesh or node level for a module's own setting in mesh-config): the settings are the module's own values
|
||||
on the manager's node, set and read through mesh-config as any module's own setting (P7).
|
||||
- **B19** (`current`'s health thresholds and §34's field names): failing at `failures_to_notify`, expiring within
|
||||
`refresh_warn_days`; today's field names are kept (P7).
|
||||
- **B20** (the audit log is full): the act fails loudly and says so (P3).
|
||||
- **B21** (no tool reads the vault's `licence-bindings`): `vault_first_views` and `vault_first_view_again` cover it (P4).
|
||||
- **B22** (three internal forms): `bindings` stays at form 0; each reader decodes the event bodies with fixtures
|
||||
copied from the writer; nothing to build for an inheriting consumer (P7).
|
||||
- **B23** (what `licence-put` names a licence by): its `licence` argument is the licence's key of §10; the manager forms
|
||||
keys, the vault does not (P8).
|
||||
- **B24** (an equal generation in `licence-put`): with the same value it is unchanged; with a different value it is
|
||||
refused loudly as not newer (P3).
|
||||
- **B25** (the vault matching a token's secret name to a binding it learned at start): the `bindings` verb answers
|
||||
`key` beside the §3 fields, as `binding.changed` does (B7); the vault's `licence-bindings` keeps `licence`, `key` and
|
||||
`generation`, and compares the secret's key with the binding's key. One form travels; the vault never computes §10's
|
||||
key form (P1, P7).
|
||||
- **B26** (the form of `carried.postgres`): `{at, tables: {<table>: {read, written}}}` (P7).
|
||||
- **B27** (the carry-over in the audit log): it appends one `carried` entry, the kind §7 lists (P7).
|
||||
- **B28** (an `offered` row older than 90 days): counted as read, not written (P7).
|
||||
- **B29** (`adoptedFrom` at the carry-over): the newest `adopted` audit row's `node`, else its `from` (P7).
|
||||
- **B30** (when bind and switch say `licence.switched`): only when the consumer moves from another licence (P8).
|
||||
- **B31** (an access token asked before the vault's first view of the bindings is answered): refused, saying why (P3).
|
||||
- **B32** (a first view of the bindings racing an event heard just before it): a first view never removes or replaces a
|
||||
binding whose generation is newer than the view's; the newer by generation stays (P3).
|
||||
- **B33** (what §19's "applied" means): the home holds the token; a home with no token is fetched again at the same
|
||||
generation (P3, ADR 0266).
|
||||
- **B34** (a failed usage reading): no `usage` entry is appended; it is said by `readError` and `usage-unread` (P3).
|
||||
- **B35** (a reading with no weekly window): its room is unknown, and it is never a target (P3).
|
||||
- **B36** (both windows at the threshold): the act names the higher, the session window on a tie (P7).
|
||||
- **B37** (a setting outside its bound): it keeps its default and raises `settings.<setting>.invalid`, cleared when the
|
||||
setting is fixed (P3).
|
||||
- **B38** (a subscription never read successfully since its adoption): `usage-unread` is raised once no reading has
|
||||
succeeded for `usage_stale_minutes` counted from its adoption (P3).
|
||||
- **B39** (a condition's headline longer than the operator channel's 60 characters): the headline names counts and
|
||||
short names only ("No licence has room for 4 machines", "<account> at 99% weekly"); the full list, the licence and
|
||||
the reset time go in the condition's detail. A cut at 60 characters with "…" stays as the last guard so a condition is
|
||||
never refused unsaid (P3).
|
||||
- **B40** (§34's `target`): true for the licence §26 would move a full licence's consumers to once it reaches its
|
||||
threshold: the best licence with room now, whether or not any licence is at its threshold; so the tools say where
|
||||
consumers go next (P4).
|
||||
- **B41** (§9 and §29's whole test and the router): the manager runs whole on the test bus, and its condition is
|
||||
taken off its subject and decoded by the operator channel's client, the contract the router reads; the router, another
|
||||
module, is not run in this module's test, and the report says so (P1).
|
||||
- **B42** (which first view `vault_first_view_again` asks again): a new argument `of` names the view by its owner's
|
||||
state (`mesh-delivery-assigner.assignments` or `claude-licence-manager.bindings`); absent, both are asked again, as
|
||||
the tool did before for its one view (P4).
|
||||
- **B43** (B32 for a consumer absent from the first view): a `licence-bindings` key written by an event after the first
|
||||
view was asked is kept; only a key written before it was asked, and absent from the view, is removed (P3).
|
||||
- **B44** (§12's leases, review round 1): the carry-over carries each lease still live at its run, as a `leases` key with
|
||||
its remaining lifetime, so the new manager never refreshes a licence the old one holds a refresh lease on, whatever the
|
||||
order in which the delivery stops one and starts the other; an expired lease is counted read and not written (P3, P7).
|
||||
- **C1** (the operator, 2026-11-12: "the licence manager module should have a service part and a tools part"; met live
|
||||
when the manager, run inside the machine's tool runner, was issued none of its own logs): the module declares
|
||||
`runs-as claude-licence-manager` with an account of its own, as the vault and the mail reader do. Its own runtime runs
|
||||
the service part (the carry-over at start, the usage timer, the switch rule, the conditions) and serves the tools part
|
||||
beside it, so every kept entry, log and secret of §1 to §20 is issued to its own account, never to the tool runner's.
|
||||
The tools act only through the running service: no tool keeps a second copy of what the service does (P1, P7).
|
||||
- **C2** (review of C1: the key-pair file the manager wrote in its state directory under the tool runner's account,
|
||||
which the node-engine does not hand to the new owner): when the manager cannot read the key pair it finds, it makes a
|
||||
new one in its place and says so in its journal; every use passes the public key per ask, so nothing depends on the
|
||||
old pair (P3, P7). The node-engine handing a program-written directory's contents to a new owner (every module that
|
||||
moves to `runs-as`) is noted on its issue, not built here.
|
||||
- **C3** (met live after C1: the manager, now in its own runtime, was still issued none of its logs): the two logs
|
||||
`usage` and `audit` are declared in the manifest's `logs` section, the form the runtime on main issues today; the
|
||||
states stay templates. The move of logs to templates is design 47 milestone G's (P7).
|
||||
- **C4** (the operator, 2026-11-12: "the session limit has been deprecated by anthropic"; met live: both licences read
|
||||
`sessionPct` empty, so neither had room and the switch could choose no target): a licence's room is 100 less its
|
||||
weekly percentage alone; the session window takes no part in room, the switch threshold, the warning or the
|
||||
conditions, wherever §24 to §33 name it. A reading with no weekly percentage still has unknown room (B35).
|
||||
- **C5** (met live 2026-11-12: `licences_switches` answered every switch carried from the old database with no `to` and
|
||||
no `consumers`): a carried entry keeps the old manager's fields as §12 copies them, a switch written as its
|
||||
`consumer`, its `licence` and `from`; §37's reader answers such an entry with `to` its `licence` and `consumers` the
|
||||
one `consumer`, and its filters by licence and consumer read it the same way. The log is not rewritten (P3, P7).
|
||||
@@ -0,0 +1,244 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 39
|
||||
milestone: parts
|
||||
updated: 2026-11-12
|
||||
code: [mesh-catalog modules/claude-licence-manager]
|
||||
decisions:
|
||||
- "ADR 0183"
|
||||
- "ADR 0050"
|
||||
- "ADR 0054"
|
||||
- "ADR 0040"
|
||||
- "ADR 0192"
|
||||
- "ADR 0198"
|
||||
- "ADR 0259"
|
||||
- "ADR 0349"
|
||||
- "ADR 0436"
|
||||
- "ADR 0570"
|
||||
- "ADR 0230"
|
||||
- "ADR 0595"
|
||||
- "ADR 0672"
|
||||
- "ADR 0675"
|
||||
---
|
||||
|
||||
# Design 39, milestone parts — the licence manager built of its parts
|
||||
|
||||
**What it builds.** The module holding the seat `anthropic-licence-manager` is one manifest built of four parts,
|
||||
each in a form the manifest validator accepts and the builder, the node-engine and the tool runner honour today
|
||||
([design 20](../20-writing-a-module.md) *Who reads a manifest* and *What a module is built of*; playbook
|
||||
[06](../../../00-META/process/06-writing-a-module.md) *Where the parts of the mesh read a manifest differently*): the
|
||||
manager bundle, which runs long and answers the seat's verbs and every act; the tools bundle, which answers the
|
||||
module's reading tools; the settings file; and the module's account with its directory. The carry-over from the store
|
||||
database, done live, leaves the module whole in this milestone, with its requirement of the database and of the secret
|
||||
`grant-key`, and both provisions then retire on the operator's Approve. The whole shape is in the design's main file
|
||||
([39](../39-the-anthropic-licence-manager.md)); this part holds every line the milestone builds, cited as "design 39
|
||||
parts §n".
|
||||
|
||||
**Why now.** The operator, 2026-11-12: "build the anthropic-licence-manager as clean as possible"; a module is one
|
||||
manifest with several parts; the seat stays named for the vendor (ADR 0183 §4, ADR 0050, ADR 0054), and a second
|
||||
vendor is a module of its own. That morning the operator had said "the licence manager module should have a service
|
||||
part and a tools part" (design 39 bus-and-switch C1). Today, after milestone bus-and-switch, one Go bundle `code` does
|
||||
everything in one program: it runs long, answers the seat's ten verbs and the module's eight tools, and keeps the
|
||||
carry-over's code, which ran once, live, at 2026-11-12 09:44:59Z, carrying every row of every table (issue 1409's note
|
||||
of that morning); for that code alone the module still requires `postgres-database` and the secret `grant-key`
|
||||
(bus-and-switch A3, B15).
|
||||
|
||||
**Where.** One repository, mesh-catalog, and one of its modules, `claude-licence-manager`; hq carries this part, the
|
||||
main file and the glossary. No other module changes: every verb, event and provision the manager uses is served today
|
||||
(§17).
|
||||
|
||||
**The shape at a glance.**
|
||||
|
||||
| a module's part | its artifact | how it runs | its account | what it keeps or reads |
|
||||
|---|---|---|---|---|
|
||||
| the manager | `manager`, a Go bundle, binary `claude-licence-manager` | loaded by the module's runtime of its own; runs long and answers the seat's ten verbs and the three acting tools | the module's account `claude-licence-manager`, on the runtime's bus account | writes all its kept data (bus-and-switch §1 to §9) and its two logs; reads its key pair and settings file |
|
||||
| the tools | `tools`, a Go bundle, binary `claude-licence-manager-tools` | loaded by the same runtime; answers the four reading tools | the same | reads the kept data and the logs, writes nothing |
|
||||
| the settings | `files`, an archive of the templates directory | the `settings` file resource, written by the node-engine | owned by the module's account | holds the eleven settings mesh-config sets |
|
||||
| the account and its directory | none | the `user` resource `account` and the `directory` resource `state` | `claude-licence-manager` | the key pair, the settings file, the runtime's `broker` secret |
|
||||
|
||||
## The module's parts
|
||||
|
||||
1. **The module is four parts in one manifest, and runs no process resource, container or run-once step of its
|
||||
own.** `build.artifacts` names exactly `manager`, `tools` and `files`; `resources` names exactly `account`,
|
||||
`state` and `settings`, in that order; the module's runtime of its own is the process the node-engine composes for
|
||||
`runs-as` (design 20 *A runtime of its own*), never one the manifest writes. Why: the operator, 2026-11-12; a module
|
||||
is one piece of software built of parts (ADR 0040); principle 7 (fewer parts). How it is checked: the manifest
|
||||
validator accepts the manifest at the change's check, and the composition step composes it for the manager's
|
||||
machine; a catalog test of the module reads its manifest and fails on any other artifact or resource, a `process`
|
||||
or `container` resource, or a `run-once` field.
|
||||
2. **The manager bundle runs long and answers the seat's verbs.** The artifact `manager` (today's `code`, renamed):
|
||||
kind `bundle`, `language` go, `system` arch, `from` `cmd/manager`, `binary` `claude-licence-manager`, `loads`
|
||||
that binary, and `env` with exactly two words, `MESH_LICENCE_STATE` (`${dir:state}`) and `MESH_LICENCE_SETTINGS`
|
||||
(`${dir:state}/settings.json`). It is loaded by the module's runtime of its own and runs long (ADR 0198 §1, §2: a
|
||||
bundle may serve tools, run long, or both): its usage timer, its refresh cadence, the switch rule on each reading,
|
||||
its consumption of `claude-code.holdings-changed`, the conditions it judges at its start, and every event of
|
||||
bus-and-switch §21 to §27. It registers the seat's ten verbs (`licences`, `bindings`, `bind`, `switch`,
|
||||
`release`, `refresh`, `usage`, `adopt`, `current`, `public-key`) under the seat and the three acting tools of §4
|
||||
under the module. It is the only part that writes the module's kept data and logs, emits, calls the vendor, calls
|
||||
the vault, tells the operator and reads the key pair. Why: one rotation source (ADR 0183 §6); the binary keeps its
|
||||
name so the runtime's journal and the loaded entrypoint stay as they are. How it is checked: unit tests as
|
||||
bus-and-switch has them, moved with the code; the catalog test of §1 reads the artifact's fields.
|
||||
3. **The tools bundle answers the module's reading tools, and writes nothing.** The artifact `tools`: kind `bundle`,
|
||||
`language` go, `system` arch, `from` `cmd/tools`, `binary` `claude-licence-manager-tools`, `loads` that binary, and
|
||||
no `env`: the runtime hands a module's words to all its bundles (ADR 0192), so it reads `MESH_LICENCE_SETTINGS`
|
||||
from the manager bundle's words, and a word two bundles gave different values would be refused at composition. It
|
||||
registers under the module the four reading tools, `licences_switches`, `licences_settings`, `licences_kept` and
|
||||
`licences_conditions`, with the arguments and answers of bus-and-switch §37, §40, §41 and §43. It reads the
|
||||
module's kept data with the runtime's bus-state calls and the logs `usage` and `audit` with the runtime's log
|
||||
calls, each answered as the module whose bundle asks; it calls `seat:mesh-config.config` for `licences_settings`
|
||||
(bus-and-switch B18). It writes no key, appends to no log, emits nothing, subscribes to no event, asks no
|
||||
heartbeat, calls neither the vendor nor the vault and opens neither the key pair nor a secret. Why: principle 4
|
||||
(everything a module holds can be read): the runtime serves the tools of every bundle of a module that loaded, so
|
||||
these four still answer while the manager bundle fails to load or stops; C1 (the tools act only through the running
|
||||
manager) holds, since these do not act; principle 1 governs reading between modules, and both bundles are the one
|
||||
module reading its own data. How it is checked: a unit test of the tools bundle runs each tool against a fixture
|
||||
of the runtime's calls and fails on any write, append, emit or subscribe; whole, with the manager bundle refusing to
|
||||
start, `licences_kept` answers every entry.
|
||||
4. **Every act is the manager's.** The three tools that act, `licences_read_usage`, `licences_switch_now` and
|
||||
`licences_resync` (bus-and-switch §38, §39, §42), are registered by the manager bundle beside the seat's verbs, and
|
||||
no act is answered by the tools bundle. A person's act on a consumer or a licence stays a seat verb (`bind`,
|
||||
`switch`, `release`, `refresh`, `adopt`). Why: C1 (no tool keeps a second copy of what the manager does);
|
||||
ADR 0183 §6; principle 7 (no call between the two bundles, so no path the mesh lacks). How it is checked: the
|
||||
catalog test of §1 finds the module's `tools` to be exactly the four of §3 and the three of this line; a unit test of each
|
||||
bundle lists what it registers.
|
||||
5. **The code both bundles share is one package of the module.** The package `internal/licences`, in the module's
|
||||
one Go module, holds the layouts of the kept data and of the log entries (bus-and-switch §2 to §9), the key form
|
||||
(§10), the names of the entries, and the settings with their defaults, bounds and validity (§33, B37); `cmd/manager`
|
||||
and `cmd/tools` each import it, and neither holds a copy of what it holds. Why: a layout in one place, so the reading
|
||||
tools never read a form the manager no longer writes; it is code the module owns alone (design 20 *Where a seat's
|
||||
code goes*: a module's own package). How it is checked: the review reads that no type of a kept entry is declared
|
||||
in either command package; `go vet ./...` at the module's root builds both.
|
||||
6. **The settings file stays, without the database's template.** The resource `settings` (a `file` at
|
||||
`${dir:state}/settings.json`, mode 0600, owner `claude-licence-manager`, `template` `files/settings.json`,
|
||||
`merge` json, `takes-config` the eleven settings of bus-and-switch §33 and today's five) and the archive `files`
|
||||
(`from` `files`, the one archive the templates are read from, ADR 0349 §2) stay as they are. The template
|
||||
`files/database.url` is deleted. Why: the manager reads its settings at every reading (bus-and-switch §33); the
|
||||
database's address has no reader once §9 holds. How it is checked: the composition step writes the file; the catalog
|
||||
test of §1 finds no file under `files` but `settings.json`.
|
||||
7. **The account, its directory and the runtime's secret stay.** `runs-as` `claude-licence-manager`, `secrets-owner`
|
||||
the same, the own secret `broker` at `${dir:state}/broker`, the `user` resource `account` and the `directory`
|
||||
resource `state` (mode 0700, owner the account, `place` `.`, `unknown` `program`) stay as they are; the key pair
|
||||
`manager-key.json` stays in that directory, read and made only by the manager bundle (C2). No `runtime-health` is
|
||||
added: the node-engine's judgement that the runtime is alive stands as its health. Why: design 20 *A runtime of its
|
||||
own*; principle 7. How it is checked: the catalog test of §1.
|
||||
8. **The seat's verbs are answered on the bus account of the runtime of its own, never by a process account.** The
|
||||
manager bundle answers the verbs through the runtime that `runs-as` gives the module (ADR 0259 §8; ADR 0436 §5:
|
||||
`runs-as` is unchanged and still gets a runtime of its own), as the router answers the operator channel's. The
|
||||
manager is not a `process` resource with `bus`: a process account is granted no subject on which a tool or a seat's
|
||||
verb is answered and holds no seat (ADR 0436 §3), which only ADR 0672 §7 lifts, for the recovery module, and
|
||||
ADR 0675 §1 and §2, for the bus module's keeper; and a process account's grant names events, work queues, states,
|
||||
asks and calls (ADR 0436 §1), never the old-section logs `usage` and `audit` the manager appends to (C3). Why: these
|
||||
records. How it is checked: the catalog test of §1 finds no `process` resource and no `bus` key in the manifest;
|
||||
whole, after the delivery, `anthropic-licence-manager.licences` answers through the mesh MCP server.
|
||||
|
||||
## The carry-over leaves
|
||||
|
||||
9. **The carry-over is done, and its code leaves the module now.** The carry-over (bus-and-switch §11 to §13) ran on
|
||||
2026-11-12 at 09:44:59Z, every table's count equal and no lease live (issue 1409's note of 10:4x CET). In this
|
||||
milestone the code that reads the store database, the carry-over itself, the opening of grants at rest with
|
||||
`grant.key`, the gate that answers "the carry-over has not ended", the condition `carry-over-failed` and its
|
||||
recovery go, with the postgres driver in the module's `go.mod`. No run-once step is built for it: no row is left to
|
||||
carry, a fresh install has no database (bus-and-switch §11: read as no rows), and a run-once process with a bus
|
||||
account could not append to the logs (§8). Why: bus-and-switch A3, B15 and §14 named this change as the module's
|
||||
next; principle 7. How it is checked: the module's `go.mod` requires no postgres driver; a unit test fails on the
|
||||
words `DATABASE_URL_FILE` or `MESH_LICENCE_KEY_FILE` anywhere in the module's code.
|
||||
10. **What leaves the manifest, and what stays of the carry-over.** `requires` loses `postgres-database` and `secret`;
|
||||
`contributes`, `binds` and the whole `secrets` section go; `env` loses `DATABASE_URL_FILE` and
|
||||
`MESH_LICENCE_KEY_FILE`; the resource `database-url` goes; the tool `licences_carried` goes. The kept-data entry
|
||||
`carried` stays, with its one key `postgres` as the record of the carry-over, read by `licences_kept`; nothing
|
||||
writes it again. Why: removing the entry would drop kept data, which needs a warrant and a snapshot (ADR 0595 §5),
|
||||
for nothing gained; principle 7. How it is checked: the catalog test of §1; after the delivery `licences_kept`
|
||||
with `entry` `carried` answers the record of 09:44:59Z.
|
||||
11. **The manager starts without waiting for a carry-over.** At its start the manager bundle judges each kind of
|
||||
condition anew from its own state and says cleared each key of `conditions` it no longer holds (bus-and-switch
|
||||
§9), `carry-over-failed` among them when present; then it says `holdings.asked` (B12) and starts its timers. It
|
||||
reads `carried` never. Why: bus-and-switch §9, B12. How it is checked: unit, a start with a `carry-over-failed` key
|
||||
in `conditions` says it cleared once and deletes the key.
|
||||
12. **The pull request waits on two readings.** Before the push, the lead reads `licences_carried` of the running
|
||||
build answering `carried.postgres` with every table's read equal to its written, and `mesh-vault.secret_holders`
|
||||
listing `licence.<key>.grant` and `licence.<key>.access` for every licence `licences` answers, and writes both
|
||||
readings in the milestone file. Why: bus-and-switch §14 and §20 (the database's requirement goes only once the
|
||||
carry-over is done and no grant is left outside the vault); principle 3. How it is checked: the code review finds
|
||||
both readings in the milestone file.
|
||||
|
||||
## The store database retires
|
||||
|
||||
13. **Both provisions retire, each on the operator's Approve, asked by the provisioner.** Once the new build's want
|
||||
moves, no want needs this module's `postgres-database` nor its secret `grant-key`, so the holder of
|
||||
`mesh-provisioner` marks both keys `retiring` and raises one ask each on the operator channel, saying what is
|
||||
removed from which provider on which node (ADR 0570 §5). On the warrant postgres retires the consumer's account of
|
||||
the database `claude_licences` and the vault removes `grant-key`; on a decline both stay retiring. The module asks
|
||||
nothing. Before the delivery starts, the lead tells the operator that these two asks will come and what each
|
||||
removes. Why: ADR 0570 §5; this replaces bus-and-switch §20's `mesh-vault.retire` of `grant-key`. How it is
|
||||
checked: after the delivery `operator-channel.asks` holds both asks; after each warrant the provision's key is gone.
|
||||
14. **The database's data is removed only by the operator.** Retiring keeps the database `claude_licences` and its
|
||||
data (ADR 0570 §5). Its removal is a person's act, the postgres module's tool `provisioner_delete` called through
|
||||
the mesh (ADR 0230 §5, §34): after both warrants the lead asks the operator for it, saying it removes the store's
|
||||
copy of the licences, bindings, grants sealed with `grant-key`, usage and audit, whose every row is on the bus or
|
||||
in the vault (§12). Why: ADR 0570 §5, ADR 0230; data removed is the operator's. How it is checked:
|
||||
`mesh-store.databases` lists `claude_licences` until the operator's call and not after it.
|
||||
15. **The files the database's requirement wrote leave the state directory with it.** `database.url` is the
|
||||
resource `database-url`, which the node-engine removes as it removes every resource that leaves a declaration
|
||||
(playbook 06, step 5); `database.json`, `database.secret` and `grant.key` are the files it wrote for the
|
||||
requirements of §10, and whether it removes such a file when its requirement leaves is not known (a guess that it
|
||||
does). A file left behind is a fault of the node-engine, noted on its issue and never removed by hand. Why:
|
||||
principle 2. How it is checked: after the delivery, `node-reads.list` of the module's state directory on the
|
||||
manager's machine lists none of the four.
|
||||
|
||||
## Delivery
|
||||
|
||||
16. **One pull request in mesh-catalog, with hq.** The module's change is one mesh-catalog pull request; hq's pull
|
||||
request carries this part, the main file and the glossary. Why: one milestone, one release (the operator's
|
||||
instruction on designing a change as it should be).
|
||||
17. **No order between modules; within the module, the node-engine's.** The change adds no served verb, provision
|
||||
or event and drops none another module uses: the manager still calls `seat:mesh-vault.licence-put` and
|
||||
`seat:mesh-vault.get`, `seat:operator-channel.tell`, `seat:mesh-config.config` and `claude-code.claude_code_grant`,
|
||||
and consumes `claude-code.holdings-changed`, each served or said today, so the delivery plan derives no edge. On
|
||||
the manager's machine the node-engine writes the state directory and the settings file, unpacks both bundles, and
|
||||
restarts the module's runtime once, since its `restart-on` names every bundle's archive; the runtime then loads
|
||||
both. The kept data keeps its names and form, so no restore point is taken (ADR 0672 §1 is for a form change), and
|
||||
the delivery itself moves no data; a node's agent keeps its last token, which lives hours, while the runtime
|
||||
restarts. Why: the module graph orders deliveries; ADR 0595 §11. How it is checked: the delivery plan names one
|
||||
module and no edge.
|
||||
|
||||
## Acceptance
|
||||
|
||||
18. **The tests of the milestone**, each run in the module's suite or live after the delivery:
|
||||
- the manifest is accepted by the manifest validator, built by the builder and composed by the node-engine for
|
||||
the manager's machine, with the artifacts and resources of §1 and nothing else (§1 to §8);
|
||||
- after the delivery the runtime serves the seat's ten verbs and the module's seven tools, the four reading tools
|
||||
from the tools bundle (§2 to §4);
|
||||
- with the manager bundle refusing to start, the four reading tools still answer (§3);
|
||||
- the module's code names no database and no `grant.key` (§9), and `licences_kept` answers the carry-over's record
|
||||
(§10);
|
||||
- after the delivery the provisioner holds both retiring asks, and after the operator's call the database is gone
|
||||
(§13, §14);
|
||||
- the state directory holds none of the database's files (§15).
|
||||
|
||||
## The lead's answers
|
||||
|
||||
- **D1** (§12's vault reading, review round 1: no vault tool lists licence secrets, so `secret_holders` cannot show
|
||||
`licence.<key>.grant` and `.access`): the reading that proves bus-and-switch §20's condition is, for every licence
|
||||
`licences` answers, a rotation after the carry-over with no failure (`rotatedAt` later than `carried.postgres.at`,
|
||||
`failures` 0), since a rotation reads the grant from the vault and puts both secrets back; and the vault's journal
|
||||
handing `licence.<key>.access` to a bound node. Both go in the milestone file before the push (P3, P7).
|
||||
- **D2** (§17's wording): the manager calls `seat:mesh-config.config` no more; the tools bundle does (§3). `invokes`
|
||||
is the module's, so the manifest is unchanged (P8).
|
||||
- **D3** (§15's check, met after the delivery: `node-reads.list` and `node-files.list` refuse the module's state
|
||||
directory as a place a secret is kept, so no tool can say its database files are gone; prepare in the fix's map):
|
||||
- §15 is read with `node-files.list`, not the retired `node-reads` (P7, P8).
|
||||
- `node-files.list` of a place a secret is kept, at any depth beneath it, answers each entry's name, type, owner,
|
||||
group, mode, size and a link's target held to the content rule, never content, and marks the place
|
||||
`secret-place` as `stat` does; this holds for the mesh's places and a machine's credential places, and a process's
|
||||
`/proc/<pid>/` entries stay refused (P4, P6, P7).
|
||||
- `node-files.stat` of a path that does not exist answers that it does not exist, not that file modes refuse it, so
|
||||
§15's "gone" can be read (P3).
|
||||
- The seat contract's `list` text in mesh-contracts says so in the same delivery group; playbook 08's one-repository
|
||||
rule is bent for one line of text, said in the pull request (P5).
|
||||
- The lines the fix touches cite ADR 0394 §7, the record that refuses a secret place (P8).
|
||||
- The fix runs under issue 1409, whose part this check is, with a note on issue 1844; its own pull requests, since
|
||||
the part's pull request has merged (P7).
|
||||
- **D4** (met live: the store held the module's database as `mesh_novox_licmgr`, the name postgres gave it, not the
|
||||
`claude_licences` the manifest asked for): §13 and §14 read the database as the one postgres made for the module,
|
||||
named in `mesh-store.databases`; it was deleted on the operator's yes on 2026-11-12, set aside first by postgres's
|
||||
own tool, because the provisioner never retired it (P3).
|
||||
@@ -1,10 +1,20 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog modules/claude-licence-manager, mesh-vault]
|
||||
updated: 2026-10-26
|
||||
code: [mesh-catalog modules/claude-licence-manager, mesh-catalog modules/claude-code, mesh-catalog modules/mesh-vault]
|
||||
updated: 2026-11-12
|
||||
decisions:
|
||||
- "ADR 0550"
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0437"
|
||||
- "ADR 0564"
|
||||
- "ADR 0570"
|
||||
- "ADR 0230"
|
||||
- "ADR 0380"
|
||||
- "ADR 0253"
|
||||
- "ADR 0289"
|
||||
- "ADR 0349"
|
||||
- "ADR 0209"
|
||||
- "ADR 0206"
|
||||
- "ADR 0183"
|
||||
@@ -15,6 +25,13 @@ decisions:
|
||||
- "ADR 0126"
|
||||
- "ADR 0132"
|
||||
- "ADR 0159"
|
||||
- "ADR 0040"
|
||||
- "ADR 0192"
|
||||
- "ADR 0198"
|
||||
- "ADR 0259"
|
||||
- "ADR 0436"
|
||||
- "ADR 0672"
|
||||
- "ADR 0675"
|
||||
---
|
||||
|
||||
# 39 — The Anthropic licence manager
|
||||
@@ -29,45 +46,62 @@ which is the consumer on every node.
|
||||
|
||||
## Every grant in the vault
|
||||
|
||||
*Decided 2026-10-26 (ADR 0550 §3), on the operator's answer that every secret lives in the vault; built in
|
||||
ADR 0547's second milestone (issue 1126). Until it is delivered, the sections below hold as first written.*
|
||||
*Decided 2026-10-26 (ADR 0550 §3), on the operator's answer that every secret lives in the vault; built in milestone
|
||||
bus-and-switch (§15 to §20 of its part, [39-milestones/bus-and-switch.md](39-milestones/bus-and-switch.md)), the vault
|
||||
serving the licence secrets to the tool runner's account (principle 6; its answer A1). Until it is delivered, the
|
||||
sections below hold as first written.*
|
||||
|
||||
- **The vault keeps every licence's secrets**: the refresh tokens, the API key and the access tokens are vault
|
||||
secrets ([24](24-the-secrets-vault.md)). The manager keeps its bindings, its licence records without a secret
|
||||
(account, kind, health, usage, audit), and nothing at rest that is secret.
|
||||
- **The manager stays the one rotation source.** It alone calls the vendor's token endpoint: to refresh or adopt,
|
||||
it asks the vault for the grant sealed to itself and hands what it gets back to the vault. A grant offered at
|
||||
login, sealed to the manager's key (its `public-key` verb is kept), is handed on to the vault the same way.
|
||||
- **A holder's token comes from the vault.** The agent module on a node registers its public half with the vault,
|
||||
not the seat, and asks the vault for its token sealed to it. The vault serves only the licence the manager's
|
||||
binding names for that holder, and says `secret-changed`, never a value. The seat's `current` verb answers which
|
||||
licence a consumer holds and its health, never the token.
|
||||
- **It waits for accounts of their own.** The vault hands a value only to a module running under a bus account of
|
||||
its own (issue 569), so the move waits until the manager and each agent module run under one.
|
||||
|
||||
*How it is checked:* a unit test that the vault refuses a token to a holder no binding names or to one without an
|
||||
account of its own, and a scan after the move that the manager's tables hold no grant.
|
||||
- **The vault keeps every licence's secrets**, two per licence: its grant, and the access token a consumer is handed
|
||||
([24](24-the-secrets-vault.md)). The manager keeps its bindings and its licence records without a secret.
|
||||
- **The manager stays the one rotation source**: it reads a grant from the vault for one act and hands what the
|
||||
vendor answers back to it, keeping nothing at rest.
|
||||
- **A consumer's token comes from the vault**, which serves only the licence the manager's binding names for that
|
||||
consumer, learned from the manager's `binding.changed`; the seat's `current` answers which licence and its health,
|
||||
never the token.
|
||||
|
||||
## 1. What it is
|
||||
|
||||
A module, `claude-licence-manager`, holding the mesh-scoped seat **`anthropic-licence-manager`**. One
|
||||
holder, on the node the operator assigns it to — the control node is the natural one, and nothing in the
|
||||
definition says so. It requires a database for its own store and the bus; it claims the seat; it serves
|
||||
the seat's verbs. It has no port, no route, no file under anyone's home.
|
||||
definition says so. It claims the seat and serves the seat's verbs. It has no port, no route, no file under anyone's
|
||||
home.
|
||||
|
||||
Its store holds four things:
|
||||
**It is one manifest built of four parts** (ADR 0040; milestone parts, its part
|
||||
[39-milestones/parts.md](39-milestones/parts.md)), each in a form the manifest validator accepts and the builder, the
|
||||
node-engine and the tool runner honour today ([20](20-writing-a-module.md)):
|
||||
|
||||
| table | holds |
|
||||
|---|---|
|
||||
| **licences** | name, kind (`subscription` or `api-key`), the account's identity (id, address, organisation) once adopted, the grant encrypted at rest, when the access token expires, when the refresh token expires, consecutive failures, the refresh lease, when a person was last notified |
|
||||
| **bindings** | one row per consumer: kind (`node-agent`, `node-session`, `worker`), its key (the node, or the node and the worker), the licence, or *inherit* |
|
||||
| **usage** | the vendor's readings per licence per period, raw beside normalised (ADR 0054) |
|
||||
| **audit** | every switch, adoption, refusal and drift, with who asked |
|
||||
| a module's part | its artifact | how it runs |
|
||||
|---|---|---|
|
||||
| the manager | a Go bundle | loaded by the module's runtime of its own (`runs-as`), under the module's account: it runs long (the usage timer, refreshes, the switch rule, the conditions), answers the seat's verbs and every tool that acts, and alone writes what the module keeps (ADR 0198 §1, §2) |
|
||||
| the tools | a Go bundle | loaded by the same runtime: the tools that only read the module's kept data and logs, served while the manager fails |
|
||||
| the settings | the templates' archive | the settings file the node-engine writes from the template and mesh-config's values |
|
||||
| the account and its directory | none | a `user` and a `directory` resource |
|
||||
|
||||
**The grants are encrypted with a key the vault made for the manager** — its one `secret` requirement.
|
||||
The vault keeps that key; the manager keeps the grants. That is ADR 0050's carve-out, one module, one
|
||||
node, the long-lived grants only. ADR 0550 §3 ends the carve-out: the grants move into the vault, and the
|
||||
`licences` table keeps no grant (*Every grant in the vault*, above).
|
||||
The seat's verbs are answered on the bus account of the runtime of its own (ADR 0259 §8, ADR 0436 §5), never by a
|
||||
process account, which answers no seat's verb (ADR 0436 §3, lifted only for the recovery module, ADR 0672 §7, and the
|
||||
bus module's keeper, ADR 0675 §1, §2).
|
||||
|
||||
**It keeps all its data in the bus's own storage, by the kept-data templates, and requires no database**
|
||||
(ADR 0595, ADR 0596; the operator, 2026-11-11). Built in milestone bus-and-switch (§1 to §14 of its part), which carries
|
||||
today's store database over at the manager's first start, reading it and never writing it (done live on
|
||||
2026-11-12); milestone parts drops the carry-over's code and the database's requirement, which then retires on the
|
||||
operator's Approve (ADR 0570 §5), its data removed only by the operator (principle 7; its answer A3):
|
||||
|
||||
| entry | template | holds |
|
||||
|---|---|---|
|
||||
| `licences` | state | one key per licence: kind, the account's identity, expiries, failures, its latest usage reading with its reset times; never a secret |
|
||||
| `bindings` | state | one key per consumer: its licence, kind, generation, and who or what moved it last |
|
||||
| `leases` | state | one key per lease, ended by its holder or its two-minute lifetime |
|
||||
| `offered` | state | one key per grant a node offered, with its outcome |
|
||||
| `usage` | log | every reading, raw beside normalised (ADR 0054), 35 days |
|
||||
| `audit` | log | every adoption, refusal, rotation, binding, switch and setting, kept for good |
|
||||
| `carried` | state | the record of the one carry-over from the store database, kept and never written again (milestone parts §10) |
|
||||
| `conditions` | state | each condition it said on the operator channel, so a restart can clear what it said |
|
||||
|
||||
Every grant and token is the vault's (*Every grant in the vault*, above). Until milestone parts is delivered, the
|
||||
module still requires its store database, its tables `licence` (with the grant encrypted at rest with a key the vault
|
||||
made for the manager, ADR 0050's carve-out), `binding`, `lease`, `offered`, `usage` and `audit`, read by the
|
||||
carry-over's code alone and written by nothing.
|
||||
|
||||
## 2. The licences it manages today
|
||||
|
||||
@@ -135,12 +169,15 @@ own, with its own agent directory and credentials file, which the agent module o
|
||||
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 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
|
||||
(ADR 0024), and an automated policy — move to the
|
||||
least-used licence, stay off a dying one — is designed later if wanted, on the readings this module
|
||||
already keeps.
|
||||
`release`. **A licence at its threshold is left by the manager itself** (the operator, 2026-11-11: "when the current
|
||||
licence usage is at 99%, switch to a licence that still has room", "on all nodes"; built in milestone
|
||||
bus-and-switch, §24 to §33; the operator's answer replaces ADR 0183 §21 and ADR 0206 §16, §18 for this module, with
|
||||
no record, its answer A2): on each of its own usage readings, a licence whose session or weekly usage reaches the threshold (99 % by
|
||||
default) has every consumer bound to it moved to the licence with the most room; each switch is said as
|
||||
`licence.switched` and told to the operator; a licence near its limit is warned about once per crossing, and no
|
||||
licence with room is a condition until one has room. The threshold, the room a target needs and the switch itself
|
||||
are settings. Switching stays a reaction to a reading, never a declaration (ADR 0024). Until the milestone is
|
||||
delivered, exhaustion is observed and moving a consumer is the operator's call.
|
||||
|
||||
## 6. Adopting a grant
|
||||
|
||||
@@ -180,9 +217,14 @@ report, and adopted by refreshing it.
|
||||
|
||||
**Events**, no secret in any: `licence.adopted`, `licence.failing`, `licence.refused`, `usage.read` —
|
||||
the audit logger records them all. *2026-10-04 (ADR 0206):* `licence.rotated` and `licence.switched` are
|
||||
gone; a rotation or a switch is a new generation in the `bindings` state.
|
||||
gone; a rotation or a switch is a new generation in the `bindings` state. *Milestone bus-and-switch:*
|
||||
`binding.changed` says every change of a binding, `licence.switched` comes back for every switch, `holdings.asked`
|
||||
asks every node's agent module for its holdings, and its conditions are said on the operator channel (§9, §21, §23,
|
||||
§27 of its part).
|
||||
|
||||
**State**: `bindings`, which it keeps; the agent module's `holdings`, which it reads.
|
||||
**State**: `bindings`, which it keeps; the agent module's `holdings`, which it reads. *Milestone bus-and-switch:*
|
||||
the kept data of §1, and no other module's state read: the agent module's holdings are heard as its
|
||||
`holdings-changed`, and the agent module follows its binding by `binding.changed` (ADR 0596).
|
||||
|
||||
**The seat's verbs**, the contract every future holder must serve: `licences` (each with kind,
|
||||
identity, expiry, failures, who is bound), `bindings`, `bind`, `switch`, `release`, `refresh` (now, one
|
||||
@@ -191,10 +233,17 @@ or all), `usage` (current and history), `adopt` (a file on the manager's node, o
|
||||
consumer sends — ADR 0206; from ADR 0550 §3, which licence a consumer holds and its health, never the
|
||||
token, which the vault hands). The manager asks a node for a candidate grant by the agent module's own tool.
|
||||
|
||||
The module's own tools beside the verbs (milestone bus-and-switch, §37 to §44): every switch and why, the settings
|
||||
in force, usage read now, the switch rule applied now, every entry it keeps with its fill, the conditions it
|
||||
said, the carry-over's record, and one recovery that says every binding, condition and access token again.
|
||||
|
||||
## 8. Settings
|
||||
|
||||
The refresh cadence; the usage threshold; the notification cooldown. Each declared with a default, so
|
||||
one definition serves and one mesh may differ.
|
||||
one definition serves and one mesh may differ. *Milestone bus-and-switch* (§33 of its part) adds whether the manager
|
||||
switches by itself, the threshold that moves consumers (99), the room a target needs (both windows below 90), the
|
||||
warning's threshold (90), the reading's cadence (5 minutes) and when a reading is too old (15 minutes), each set
|
||||
through mesh-config.
|
||||
|
||||
## How it is checked
|
||||
|
||||
@@ -208,10 +257,24 @@ one definition serves and one mesh may differ.
|
||||
| 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 |
|
||||
| the vault refuses a token to a holder no binding names, or to one without a bus account of its own | ADR 0550 §3 |
|
||||
| after the move, the manager's tables hold no grant | ADR 0550 §3 |
|
||||
| every row of the store database is carried over once, and a second start carries nothing | bus-and-switch §11 to §13 |
|
||||
| a reading at the threshold moves every consumer of that licence to the one with the most room, said as an event and to the operator | bus-and-switch §26 to §28 |
|
||||
| no licence with room raises one condition, cleared by a reading with room; a licence near its limit is warned about once | bus-and-switch §29, §30 |
|
||||
| every entry the manager keeps is read by a tool, and every act it takes by itself is started by one | bus-and-switch §37 to §45 |
|
||||
| the manifest names the manager bundle, the tools bundle, the settings and the account, and no process, container or run-once step; the validator accepts it, the builder builds it and the node-engine composes it | parts §1 to §8 |
|
||||
| with the manager bundle refusing to start, the tools that read still answer | parts §3 |
|
||||
| the module names no store database and no `grant.key`; both provisions retire on the operator's Approve, and the database goes only by the operator's call | parts §9 to §15 |
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | What it builds | State |
|
||||
|---|---|---|
|
||||
| the manager and its consumer | §1 to §8 as first written: the module, its seat, its store database, adoption and the sealed hand-over (ADR 0183, ADR 0206, ADR 0209) | delivered; built as design [40](40-building-the-operators-agent-and-its-licence-manager.md) describes |
|
||||
| bus-and-switch | its kept data on the bus and its store database carried over and retired; every grant and token in the vault (ADR 0550 §3); bindings and holdings as events; the switch on its own usage, its warning and its condition; its tools. mesh-catalog's `claude-licence-manager`, `claude-code` and `mesh-vault` in one pull request, on what main holds today | delivered 2026-11-12; its part is [`39-milestones/bus-and-switch.md`](39-milestones/bus-and-switch.md), its four questions answered by the principles (A1 to A4) |
|
||||
| parts | the module as four parts in one manifest: the manager bundle (running long, the seat's verbs, every act), the tools bundle (every tool that reads), the settings, the account; the carry-over's code and the requirements of the store database and `grant-key` leave, both retiring on the operator's Approve and the database removed by the operator. mesh-catalog's `claude-licence-manager` alone | designed; its part is [`39-milestones/parts.md`](39-milestones/parts.md) |
|
||||
|
||||
## What this does not settle
|
||||
|
||||
- An automated switch on exhaustion (§5).
|
||||
- Whether an OAuth refresh token is single-use; the lab measures it, and §3 holds either way.
|
||||
- How the mesh's own session and a worker read their token on a node once those exist
|
||||
([to-be 15](15-the-agent-session.md), ADR 0003):
|
||||
|
||||
@@ -60,8 +60,8 @@ Measured 2026-10-03 on the four machines and in the repositories.
|
||||
| a bundle's words | paths and constants written with `${dir:…}` and `${port:…}` only; a fact the mesh knows reaches a bundle as a file whose path is a word | the agent module's facts file and settings file |
|
||||
| a bundle calling a tool | `mesh/ask` through the runtime that launched it (ADR 0198); no bundle holds a bus credential | how the manager visits every node |
|
||||
| a module's own long-running code | a bundle the runtime launches and restarts (ADR 0198); the runtime's subscription and grants built, the modules moving in design 38 WP4c's waves | the manager's daemon (WP3, WP4) |
|
||||
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalogue, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
|
||||
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalogue; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
|
||||
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalog, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
|
||||
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalog; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
|
||||
| the predecessor's manager and consumer | the lease per licence, the expiry floor, the lineage comparison, the identity guard, three touchpoints, cooldowns | ported as logic with its tests |
|
||||
|
||||
## The order the work allows
|
||||
@@ -99,7 +99,7 @@ resolves it. The runtime's port stays what its code fixes; assigning it is
|
||||
second question and not this package's.
|
||||
|
||||
**Proof.** The plan for a workstation carrying a consumer of `mcp-endpoint` shows it bound to the
|
||||
runtime's port; the controller's tests and the catalogue's checks pass.
|
||||
runtime's port; the controller's tests and the catalog's checks pass.
|
||||
|
||||
## WP2 — The agent module
|
||||
|
||||
@@ -130,7 +130,7 @@ runtime's port; the controller's tests and the catalogue's checks pass.
|
||||
**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
|
||||
lineage cases from the predecessor; a sealed hand-over opens only with the module's key; no tool's answer
|
||||
contains a token. The catalogue's checks pass.
|
||||
contains a token. The catalog'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
|
||||
@@ -188,11 +188,11 @@ credentials file is left as it was and the agent authenticates through the key-h
|
||||
|
||||
*The live mesh and mesh-catalog. One day.* The module on every node, the predecessor's files removed on
|
||||
the second workstation; a login under a licence's account collected and adopted, and one under the wrong
|
||||
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalogue;
|
||||
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalog;
|
||||
designs 36 and 39 set to `implemented` with the as-is written
|
||||
([playbook 02](../../00-META/process/02-graduation.md)).
|
||||
|
||||
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
|
||||
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalog
|
||||
has no module built on the old placement.
|
||||
|
||||
## The home, personal rules and lessons — WP7 to WP10
|
||||
@@ -259,7 +259,7 @@ WP10 the rest of the nodes, and the as-is (the live mesh)
|
||||
neither list, the watched entries and the pending requests.
|
||||
|
||||
**Proof, before anything runs live.** The module's tests of design 36 "How it is checked" for ADR 0273 §1,
|
||||
§2, §3 and §4, each failing on the commit before. The catalogue's checks pass.
|
||||
§2, §3 and §4, each failing on the commit before. The catalog's checks pass.
|
||||
|
||||
### WP8 — Personal rules and the operator's items, live
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-host, mesh-controller, mesh-catalog]
|
||||
updated: 2026-10-16
|
||||
updated: 2026-11-04
|
||||
decisions:
|
||||
- "ADR 0394"
|
||||
- "ADR 0310"
|
||||
@@ -146,7 +146,7 @@ against WP2's controller before anything is published.
|
||||
the node. The rendering is ADR 0203's and ADR 0204's: module order, a naming line per contribution,
|
||||
`PATH` entries added only when missing. `${machine:…}` in a contributed value is resolved first.
|
||||
- **Refusals.** A variable set by two modules on one node is refused, naming both. A placeholder in a
|
||||
module that does not claim the matching seat is refused, both at the catalogue check and at
|
||||
module that does not claim the matching seat is refused, both at the catalog check and at
|
||||
composition.
|
||||
|
||||
**Proof.** The controller's tests:
|
||||
@@ -157,7 +157,7 @@ against WP2's controller before anything is published.
|
||||
- each refusal, by name;
|
||||
- the seat table carries both seats and refuses a module declaring either.
|
||||
|
||||
The catalogue check over the whole catalogue passes.
|
||||
The catalog check over the whole catalog passes.
|
||||
|
||||
## WP3 — The modules
|
||||
|
||||
@@ -188,7 +188,7 @@ The catalogue check over the whole catalogue passes.
|
||||
- **`zsh-autosuggestions` and `zsh-syntax-highlighting`, new.** Each declares its package and
|
||||
contributes its loader from the distribution's path, in the `normal` and `last` slots.
|
||||
|
||||
**Proof.** The controller's catalogue check over the whole catalogue passes. The modules' tests pass.
|
||||
**Proof.** The controller's catalog check over the whole catalog passes. The modules' tests pass.
|
||||
A rehearsal composition for a node holding all five shows:
|
||||
|
||||
- the `.zshrc` block with the prompt in `normal` and highlighting in `last`;
|
||||
@@ -227,7 +227,7 @@ The user-scoped units of `mesh-host` #72 stay to-be 38's WP6.
|
||||
|
||||
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
|
||||
3. Merge WP3 and WP4, and build the new modules by hand: a new catalog module's first build is asked
|
||||
for, not automatic.
|
||||
4. On one server, assign `node-env`, `zsh`, `zsh-autosuggestions` and `zsh-syntax-highlighting`, and
|
||||
push. Then check:
|
||||
@@ -262,7 +262,7 @@ group**: the lighting daemon's, the container runtime's, a serial port's. It dec
|
||||
│
|
||||
node-engine usermod --append ── recorded on openrazer.account: "the mesh added openrazer"
|
||||
│
|
||||
account judge database lists it? ── running manager holds it? ── no: relogin needed
|
||||
account probe database lists it? ── running manager holds it? ── no: relogin needed
|
||||
│ (module openrazer, kind account)
|
||||
controller module.openrazer.<machine>.unhealthy, cleared on the first look after a new login
|
||||
```
|
||||
@@ -280,7 +280,7 @@ group**: the lighting daemon's, the container runtime's, a serial port's. It dec
|
||||
when the resource is undeclared, is given back. It is not given back while another declared resource
|
||||
asks for it. The account is taken out of that one group (`gpasswd --delete`), read back, and a failure
|
||||
is said and is not fatal, as giving back a shell is (WP1).
|
||||
- **The node-engine, looking.** An account judge sits beside liveness and the failed-unit reading
|
||||
- **The node-engine, looking.** An account probe sits beside liveness and the failed-unit reading
|
||||
(issue 315). On every look it reads, for each account a module puts in groups, the database's groups
|
||||
and the groups of the account's running manager, from that process's status file. It states each as the
|
||||
module's resource of kind `account`: healthy, or unhealthy with *not in the group* or *relogin needed*.
|
||||
@@ -328,7 +328,7 @@ control-node withholds it until a call to it needs a person's approval. The othe
|
||||
writes it into, and a word naming that file for its tools bundle. The bundle registers `execute` only
|
||||
when the file says `serve`. `zsh_config` also answers whether `execute` is served on the node, and why.
|
||||
|
||||
**Merge order.** The controller first, then the catalogue. Either order composes. With the catalogue
|
||||
**Merge order.** The controller first, then the catalog. Either order composes. With the catalog
|
||||
alone and the control-node's value set, the self-check would say the holder there is silent.
|
||||
|
||||
**Proof.** The tests ADR 0268 lists under *how it is checked*. Live, after both are merged and rolled
|
||||
@@ -382,7 +382,7 @@ move, write or delete a file, and a cleanup needed a shell on the machine. Most
|
||||
project folders, downloads, logs, scratch.
|
||||
|
||||
- **A node seat, `node-files`, on every node**, held by a module of its own, `mesh-files`, beside `mesh-reads`
|
||||
in the catalogue; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
|
||||
in the catalog; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
|
||||
stat, readlink, read, search, write, copy, move, delete, make a folder, change mode, change owner, account and group
|
||||
lookup, and the trash's list and restore. Each says what it replaces (ADR 0245): `ls`, `stat`, `readlink`, `cat`,
|
||||
`grep`, `cp`, `mv`, `rm`, `mkdir`, `chmod`, `chown`, `getent passwd`, `getent group`, `id`, and `ssh <node> cp` for a
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 42
|
||||
milestone: zfs-owns-zfs
|
||||
updated: 2026-11-13
|
||||
decisions:
|
||||
- "ADR 0679"
|
||||
---
|
||||
# 42, milestone zfs-owns-zfs — the lead's answers to the prepare's questions
|
||||
|
||||
The lead's answers to the prepare's design questions (Q-n) and to the places where a design line and the code
|
||||
disagree (C-n), for the milestone part [zfs-owns-zfs](zfs-owns-zfs.md): each one line, naming the principle that
|
||||
settles it (`00-META/principles.md`), cited as "design 42 zfs-owns-zfs A-n". The code read is the prepare's, at the
|
||||
`origin/main` commits its milestone file names.
|
||||
|
||||
- **A-1** (Q-1, §8; principles 2 and 7) The pool's name is not held by mesh-config: it is the zfs module's
|
||||
`values` in the home server's assignment (seat `mesh-module-assigner`), a layer mesh-config's rename does not move.
|
||||
So the module keeps `renamed-from: scrub-pool` for the values mesh-config holds, and at the delivery the lead moves
|
||||
the assignment's value: `pool` set to that name through mesh-config for the home server, and the assignment
|
||||
proposed without `scrub-pool` through `mesh-module-assigner.propose`, both before the new version is placed.
|
||||
- **A-2** (Q-2, §14; principle 7) The module's ZED folder also holds the package's `zed-functions.sh`, from the
|
||||
module's files archive, root's, mode 0644, under `if-config: [zed=on]` beside `zed.rc`; the handlers find it where
|
||||
ZED points them, and nothing else changes.
|
||||
- **A-3** (Q-3, §13; principles 2 and 7) `trim-schedule: none` is a set value meaning "no trim by the mesh": the
|
||||
distribution's trim timers are held stopped for `none`, `weekly` and `monthly` alike; only an unset value leaves
|
||||
them as found.
|
||||
- **A-4** (Q-4, §3; principle 3) The validator refuses a `:` in a `config-lines` prefix, beside `}`, `$` and a
|
||||
newline, naming the key.
|
||||
- **A-5** (Q-5, §3; principle 3) The validator refuses `mib-bytes` on a value whose type is not a number, and a
|
||||
`config-lines` key the module does not declare, as it refuses `${config:<key>}` of an undeclared key today.
|
||||
- **A-6** (Q-6, §18, §24; principles 1 and 6) The follower's process bus declares
|
||||
`invokes: ["seat:node-service-manager.start"]`, and the module's `invokes` carries the same for its tools bundle.
|
||||
- **A-7** (Q-7, §14, §18; principle 7) `/etc/zfs/mesh-zed.d` is a directory resource under `if-config: [zed=on]`
|
||||
with `unknown: module`; `/run/zfs-tools` is a directory resource with `unknown: program`, as `/run/zfs-events` is.
|
||||
- **A-8** (Q-8, §18; principle 3) `zfs-tools apply` exits 0 on a refusal and on a wait, which it says by event and
|
||||
in its state `applied` and which make the module unhealthy by §29; it exits non-zero only when a command it ran
|
||||
failed, so the node-engine gates the module on a real failure.
|
||||
- **A-9** (Q-9, §30; principle 7) When the module leaves a node, its kept data (the states `applied`, `schedule` and
|
||||
`findings`, the ledger `snapshots`, the events stream) is kept as every module's kept data is on leaving; a return
|
||||
finds its scheduled ZFS snapshots' records.
|
||||
- **A-10** (Q-10, §27; principle 7; corrected by A-32) The findings stream is the one design 47 G §383 defines for every observer:
|
||||
`zfs-findings`, its events `finding-written.*`, `finding-cleared.*` and `findings-stated.*`, kept 7 days and
|
||||
16 MiB, said by the writer generated from the seat `mesh-conditions`' definition; the module declares it as §383
|
||||
says and builds nothing of its own for it.
|
||||
- **A-11** (Q-11, acceptance test 8; principles 5 and 7) Test 8 runs in the test lab, on a pool built on a
|
||||
file-backed device, never on the home server's pool.
|
||||
- **A-12** (Q-12, §24; principles 3 and 4) A hand act's tool waits at most 30 seconds for the act's event; past it,
|
||||
it answers "started", naming the act and that `zfs_module_events` reads its outcome, and never answers "done"
|
||||
for what it did not see end.
|
||||
- **A-13** (C-1, §4; principle 7) The node-engine states the new reading as `word:resources.if-config` and
|
||||
`form:config-lines` (and `form:config-json`, A-15), computed by `ManifestReads` and marked by `ManifestNeeds`;
|
||||
stopping at the first failure is stated by no capability, since the node-engine build that reads those words is
|
||||
the build that stops.
|
||||
- **A-14** (C-2, §11; ADR 0679 §3; principle 7) The validator accepts a config value with neither a default nor
|
||||
`required` as "not managed until set"; built in the validator's subtasks of this milestone.
|
||||
- **A-15** (C-3, §9; principles 2 and 7) The config file's content is one new node-engine placeholder
|
||||
`${config-json}`: the module's set config values as one JSON object, an unset value absent, lists and objects as
|
||||
JSON; no unset value refuses the module. It is built with §2 and §3 in the node-engine, the validator and
|
||||
mesh-sdk's form list, and stated as `form:config-json`; no existing form writes an unset value as absent.
|
||||
- **A-16** (C-4, §18; principle 7) The follower, which runs from boot, starts the apply unit once at its own start
|
||||
and on the events §27 names; the apply's own wait for an unimported pool (§18) stands for the order after
|
||||
`zfs-import.target`. No new process field.
|
||||
- **A-17** (C-5, §24; principle 7) A hand act writes its act into the state `schedule` under its own key and starts
|
||||
`zfs-mesh-run.service`; the run reads the key, acts under the same lock and guards, and deletes it. No instance
|
||||
units.
|
||||
- **A-18** (C-6, §24; principle 7) `node-service-manager.start` is called with `{scope, unit}`; the tool keeps
|
||||
`why` in its own event. The seat is not changed.
|
||||
- **A-19** (C-7, §26; principle 7) The ledger `snapshots` declares a fixed `max-mib` sized from the bound of 168 per
|
||||
kind (§11); a full ledger takes §21's "ledger full" path.
|
||||
- **A-20** (C-8, §21, §22, §26; principles 1 and 7) A process cannot write a ledger: the ledger is served to a tools
|
||||
bundle alone, through the tool runner (mesh-sdk `buscalls`, `ProcessBus` has no ledger). The scheduled run records
|
||||
`taken` and `removed` by calling its own module's tool `zfs_snapshots_record` (declared in the run's process bus
|
||||
`invokes`), whose bundle applies the operation to the ledger; the tool refuses a snapshot not named `mesh-`. No new
|
||||
field in mesh-sdk.
|
||||
- **A-21** (C-9, §13; principle 2) When one record of a unit goes while another still declares it, the found state
|
||||
passes to the record that stays, as a file is handed to the resource still declaring its path (ADR 0223); built in
|
||||
the node-engine's §2 subtask.
|
||||
- **A-22** (C-10, the test budget; the test-budget instruction) The node-engine's repository is over its budget (about 1,742 unit tests
|
||||
against 500): each new table test merges or replaces at least as many tests in the file it touches, named in the
|
||||
build map, so the count does not rise.
|
||||
- **A-23** (C-11, §9, §11; principle 6) The config file carries `"trusted": false`; each value's own `trusted` (§11)
|
||||
decides who sets it.
|
||||
- **A-24** (C-12, §2, §3; principle 3) `config-lines` and `config-json` are added to the validator's and the
|
||||
composer's placeholder tables, and the validator counts `if-config`, `config-lines` and `config-json` as placing
|
||||
their keys.
|
||||
- **A-25** (A-15, S2; principle 3) `${config-json}` holds no colon, so the form search that finds `${<form>:` does not
|
||||
find it: the manifest's reading also finds the exact token `${config-json}`, and a manifest that holds it needs
|
||||
`form:config-json`, so no node-engine that cannot write it is given the module.
|
||||
- **A-26** (§1, met in develop; principles 3 and 7) §1 stands: a module declares a resource another of its resources
|
||||
needs before that one, since the apply now stops at the first failure. The pacman module declares its package
|
||||
before its config file, and its package fails while the config file is missing, so the stop would keep a node
|
||||
from restoring its package manager: in this milestone the pacman module's config file moves before its package,
|
||||
and the node-engine's probe test of that restore keeps the pacman module's own resource names, in that order.
|
||||
- **A-27** (§11, met in develop; principle 7) `snapshots` is an object, so its `none` is the object holding only the
|
||||
key `none` set to true; keep counts are its other form, and the text `none` is refused as the type refuses it.
|
||||
- **A-28** (§11, met in develop; principle 3) One refused value refuses the whole config: nothing is converged until
|
||||
every value is valid, and each refused value is its own `config.refused`.
|
||||
- **A-29** (§11, §18, met in develop; principle 3) The node's memory is read by the apply and given to the parse; a
|
||||
memory that cannot be read refuses `arc-max` and `arc-min`, never guesses them.
|
||||
- **A-30** (§21, §22, §26, A-19, A-20, met in develop; principles 1 and 7) The ledger `snapshots` uses the ledger's
|
||||
own forms (ADR 0597) and nothing new: one type `snapshot`, its fields `dataset`, `name`, `kind` and `at`, all text;
|
||||
an instance `snapshot/<kind>-<UTC stamp>-<dataset>`, the dataset's `/` written `~` (a character no ZFS name holds);
|
||||
the operation `taken` with the effect `set-fields` on those four fields, and `removed` with the effect `remove`
|
||||
naming no map, so the instance's state goes while its history keeps both operations; one index `by-dataset-kind` on
|
||||
`dataset` and `kind`, read through `mesh/ledger.query`, the newest `at` among a kind's entries being its last.
|
||||
- **A-31** (§21, §22, §28, A-20, met in develop; principles 1 and 7) The scheduled run reads the ledger as it writes
|
||||
it, through its own module's tools: its process bus also invokes `zfs.zfs_snapshots_schedule`, whose answer holds
|
||||
the ledger's entries (§28), and the run takes a kind's last entry and the entries to prune from that answer.
|
||||
- **A-32** (corrects A-10, §27, met in develop; principles 2 and 7) The writer A-10 names is not on mesh-sdk's main: it
|
||||
is built only on design 47 G's branch, a milestone not yet delivered. The follower says its findings in its own
|
||||
state `findings`, keyed and written as every observer on main writes its findings today, and declares no
|
||||
`zfs-findings` stream; G's change of §387 writes that stream into every module that declares a state `findings` on
|
||||
main when it lands, the zfs module among them. Read before A-10 was given: nothing; the lead now reads what an
|
||||
answer names first (playbook 07, step 0b).
|
||||
- **A-33** (§20, met in develop; principle 3) A trim schedule set without its `trim-day` or `trim-time` runs no trim and
|
||||
is said: the parse refuses it as one refused value (A-28), naming the missing value.
|
||||
- **A-34** (§24, A-18, met in develop; principle 7) A hand act's `why` and `by` are kept in its key of the state
|
||||
`schedule`, which the run deletes once it acted, and its outcome is the event the act itself emits (§25); no new
|
||||
event kind.
|
||||
- **A-35** (§21, §22, met in develop; principle 3 and the operator's "we may absolutely not lose any data") A kind's
|
||||
keep count of 0 is that kind's `none`: none is taken, its existing scheduled ZFS snapshots are kept and a warning
|
||||
names them; only a count above 0 prunes past it.
|
||||
- **A-36** (corrects A-30, §26, met in develop; ADR 0597; principle 7) The ledger's `remove` effect names a map and a
|
||||
key (the validator refuses one without), so the instance is one per dataset and kind:
|
||||
`snapshot/<kind>-<dataset>` (the dataset's `/` written `~`), its state a map `taken` keyed by the ZFS snapshot's
|
||||
name; `taken` is the effect `set-entry` into `taken` under the field `name`, with `dataset`, `name`, `kind` and `at`;
|
||||
`removed` is the effect `remove` from `taken` under `name`. A kind's live ZFS snapshots are the instance's map, its
|
||||
last the newest `at` there, so no index is declared.
|
||||
- **A-37** (§18, §20, §26, §27, A-6, A-20, met in develop; ADR 0436 §1, §2; principle 7) A process's bus account is a
|
||||
user the module makes, never root, so the three root processes (the apply, the run, the follower) declare no `bus`.
|
||||
The module makes a user `zfs-keeper` and one process `zfs-keeper` running as it, which holds the module's one process
|
||||
bus account (its events, its states `applied`, `schedule` and `findings`, and its invokes of
|
||||
`zfs.zfs_snapshots_record`, `zfs.zfs_snapshots_schedule` and `seat:node-service-manager.start`). It serves a unix
|
||||
socket in the module's state directory that only root may open (the peer's uid checked), with a closed list of
|
||||
calls: emit an event, put or delete a key of those states, and invoke one of those three. The root processes say
|
||||
everything they say on the bus through it; with the keeper not answering they act as with the bus down (A-8), and
|
||||
warn. The module's tools keep their own account as today.
|
||||
- **A-38** (A-37, §25, §26, met in develop; ADR 0436 §1, ADR 0595 §1, §2; the operator's "a case the delivery cannot
|
||||
handle is a gap in the build and delivery system") A process's `bus` today is judged and granted only against the
|
||||
module-level `emits` and `state`, which ADR 0595 replaced by what a module asks for in `requires`. In this
|
||||
milestone a process's `bus.emits` may name an event of the events stream its module asks for by template in
|
||||
`requires`, and its `bus.state` a state its module asks for there: the validator accepts them (in the indexer's
|
||||
process-bus rule), and the grants (mesh-grants) give the process's account publish on that stream's subjects of
|
||||
its own node and the reads and writes of those states, as they give a module's tools account today. A name its
|
||||
module does not ask for is refused as today.
|
||||
- **A-39** (A-37, A-17, met in develop; principle 7) The keeper's closed list of calls also reads: `get` one key and
|
||||
`keys` of the same three states, since the run reads a hand act's key and the apply its state `applied`.
|
||||
- **A-40** (A-36, met in develop; principle 3) A ledger instance not yet written is the tool runner's answer to
|
||||
`mesh/ledger.get` with a state `null` and sequence 0 (the tool runner's `internal/ledger/read.go` `Get`, in mesh-tools), never
|
||||
an error: the module reads it so, and recognises no error by its text.
|
||||
- **A-41** (§25, A-37, A-38, review round 1; principles 1 and 7; shared code belongs in mesh-sdk) The events stream a
|
||||
module asks for by template `events` captures `mesh.bus.<module>.<local>.>` (the bus module's keeper, `PlanOf`), and
|
||||
that is what the grants of A-38 give. The keeper publishes each event there, as a JetStream publish the bus
|
||||
acknowledges, on `mesh.bus.<module>.<local>.<event>`, the subject named by a new mesh-sdk helper
|
||||
`process.Account.BusSubject(local, event)` beside `EventSubject` and `StreamSubject`; never `process.Conn.Emit`,
|
||||
which publishes on the module's old event subjects.
|
||||
- **A-42** (§25, A-12, A-41, review round 2; principles 1, 3 and 7; the operator's "a case the delivery cannot handle
|
||||
is a gap in the build and delivery system") A tools bundle has no way onto the events stream its module asks for by
|
||||
template: the tool runner's `mesh/publish` publishes on the module's old event subjects and reads no binding (the
|
||||
tool runner's `PublishAs`, `eventSubject`), and its `mesh/subscribe` hears only the old streams. This milestone
|
||||
builds the one path it lacks, by name, beside the bus-state calls a bundle already makes: a call
|
||||
`mesh/stream.publish` {stream (the local name), event, body, id} in mesh-sdk's `buscalls`, which the tool runner
|
||||
answers for the bundle's own bound stream alone by an acknowledged JetStream publish on the binding's subject plus
|
||||
`.<event>`, the id as the message id, under the grant the tool runner's account already holds (`boundOf`). The zfs
|
||||
tools say every event of §25 with it, never `stdio.Emit`. A hand act's tool hears its act's event by reading its own
|
||||
events stream with the calls it has (`mesh/stream.fill` for the last sequence before it starts the act, then
|
||||
`mesh/stream.read` from the next one with the act's subject), for at most the 30 seconds of A-12. The other modules
|
||||
whose bundles emit onto a template events stream (messenger) move to the call in a change of their own.
|
||||
- **A-43** (corrects A-12 and A-42's wait, met in develop; principles 1 and 7: nothing polls) A hand act's tool does not
|
||||
wait: once its act's key is written and the run's unit started, it answers "started" at once, naming the act and that
|
||||
`zfs_module_events` reads its outcome, as §24 says ("the tool answers from the state and events afterwards"). The
|
||||
bundle has no watch on its events stream, and a repeated read for 30 seconds is a poll.
|
||||
- **A-44** (A-42, met in develop; principle 3; the module graph orders the delivery) `mesh/stream.publish` is a family
|
||||
of its own, the capability `stream-publish`, which the new tool runner states; the zfs module's `capabilities` ask
|
||||
for it (and for `bus-state-calls` and `ledger-calls`, the calls it already makes), so the delivery plan places the
|
||||
zfs module's new version only on a machine whose tool runner answers the call, and never one where its events would
|
||||
fail.
|
||||
- **A-45** (§13, review round 3; ADR 0240; principle 3) Each long-running unit the module declares (the services
|
||||
`import-cache`, `mount`, `target` and `zed-on`) declares its health of kind `unit`, as the catalog's other
|
||||
long-running resources do, so no running part of the module goes unwatched.
|
||||
- **A-46** (A-32, §26, §27, the group's check; ADR 0596 §2; principle 7) The state `findings` is declared as every
|
||||
observer on main declares it, in the module's `state` list, not by template in `requires`: the conditions keeper
|
||||
reads an observer's findings from that form only, and a state asked for in `requires` has no readers. The zfs
|
||||
module's other states (`applied`, `schedule`), its ledger and its events stream stay in `requires`; the keeper
|
||||
process's `bus.state` names `findings` as a state of the `state` list. Design 47 G §387 moves it when G lands.
|
||||
@@ -0,0 +1,337 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 42
|
||||
milestone: zfs-owns-zfs
|
||||
updated: 2026-11-12
|
||||
code: [mesh-sdk, mesh-delivery, mesh-host internal/apply, mesh-catalog modules/zfs]
|
||||
decisions:
|
||||
- "ADR 0679"
|
||||
- "ADR 0051"
|
||||
- "ADR 0117"
|
||||
- "ADR 0269"
|
||||
- "ADR 0303"
|
||||
- "ADR 0348"
|
||||
- "ADR 0508"
|
||||
- "ADR 0564"
|
||||
- "ADR 0593"
|
||||
- "ADR 0595"
|
||||
- "ADR 0609"
|
||||
- "ADR 0614"
|
||||
---
|
||||
|
||||
# Design 42, milestone zfs-owns-zfs — the zfs module owns a node's ZFS, and changes nothing that is not set
|
||||
|
||||
**What it builds.** On a node that holds a ZFS pool (today the home server), the zfs module owns ZFS's packages, its
|
||||
kernel module's options file, its host id, its own folder for ZFS's event daemon (ZED), its units and, for one pool,
|
||||
its cache and log devices, its properties, its datasets' properties and a schedule of ZFS snapshots; each is changed
|
||||
only once its config value is set and not empty (ADR 0679). The purpose and the whole shape are the main file's,
|
||||
[*The zfs module*](../42-the-machines-modules-in-order.md#the-zfs-module). This part holds every line the milestone
|
||||
builds, cited as "design 42 zfs-owns-zfs §n".
|
||||
|
||||
**Where.** Built under issue 1979. Four repositories, one delivery group with one check: mesh-sdk, mesh-delivery
|
||||
(the manifest validator) and `mesh-host` (the node-engine) for §1 to §5, issue 1979's three node-engine subtasks
|
||||
and the readers of their words; mesh-catalog's `modules/zfs` for §6 to §31. **Ordering edges** (ADR 0679 §16): the
|
||||
readers in §4's order, then the zfs module's new version only on a node whose node-engine states the three
|
||||
capabilities of §4 (a reader's capability the delivery plan derives, ADR 0614 §4).
|
||||
|
||||
## The prerequisites (ADR 0679 §16)
|
||||
|
||||
1. **The node-engine's apply of a module stops at its first failed resource** (ADR 0679 §16 (a)). When one resource
|
||||
of a module fails, the node-engine applies none of that module's later resources in the declared order; its apply
|
||||
report names the failed resource with its error and each one left unapplied, and the module's phase is `failed`.
|
||||
Resources already applied stay as applied; other modules on the node go on as today. Today the apply goes on, and
|
||||
stopped the distribution's scrub timer after the module's own timer failed. Checked by a unit test (a module of
|
||||
three resources whose second fails leaves the third untouched and reports it as left) and by §5.
|
||||
2. **A resource is declared only while a config value holds** (ADR 0679 §16 (b), §3). A resource of any type may
|
||||
carry the manifest field `if-config`: a list of conditions, each `<key>` (the module's config value is set and is
|
||||
neither `false`, `off`, `none` nor an empty list) or `<key>=<value>` (the value equals it; for a list value, the
|
||||
list holds that entry). The resource is declared while any condition holds, and not at all while none does. A
|
||||
resource that leaves the declaration this way is removed as any resource that leaves it: a file the node-engine
|
||||
wrote whole is deleted, a unit gets its found state back (ADR 0269 §2). The manifest validator refuses an
|
||||
`if-config` key the module does not declare under `config`. Checked by the node-engine's table test (a value unset,
|
||||
empty, `off`, set; a list with and without the entry; a value going from set to unset removes the resource) and
|
||||
the validator's own test.
|
||||
3. **A template writes one line per entry** (ADR 0679 §16 (c), §4). A template file may hold
|
||||
`${config-lines:<key>:<prefix>}`: for a list value one line per entry, `<prefix><entry>`, in the list's order;
|
||||
for a set scalar value one line `<prefix><value>`; nothing for an unset value or an empty list. With a fourth part,
|
||||
`${config-lines:<key>:<prefix>:mib-bytes}`, a number given in MiB is written in bytes. The prefix is literal text
|
||||
with no `}`, `$` or newline, refused by the manifest validator; an entry holding a newline is refused before the
|
||||
file is written, and the file is left as it was. Checked by the node-engine's table test (a list of two, an empty
|
||||
list, an unset and a set scalar, `mib-bytes`, a newline in an entry) and the validator's own test.
|
||||
4. **Every reader of the new words, in order** (design-it-as-it-should-be; ADR 0614 §4). mesh-sdk first: the
|
||||
manifest types gain `if-config` on every resource and the template form `config-lines`, and the capability names
|
||||
`if-config`, `config-lines` and `apply-stops-at-failure`. Then the manifest validator in mesh-delivery's indexer:
|
||||
it reads both words and refuses them as §2 and §3 say. Then the node-engine: it builds §1 to §3 and states the
|
||||
three capabilities. A manifest using `if-config` or `config-lines` reaches a node only through a node-engine that
|
||||
states both; the delivery plan derives the edge from the stated capability, never from a fingerprint. Checked by
|
||||
each repository's unit tests and by the delivery plan naming the edge.
|
||||
5. **One integration test of the node-engine** (how-work-is-done: a change to how modules run carries one). It
|
||||
starts the node-engine whole against the real bus with a module of a resource under `if-config`, a template with
|
||||
`config-lines`, and a resource that fails: the conditioned resource appears when its value is set and goes when it
|
||||
is unset, the template's lines follow the list, and the failing resource stops the module's apply with the later
|
||||
resource left. It is the only integration test this milestone adds.
|
||||
6. **The order on the node** (ADR 0679 §16, §9). The zfs module declares its scheduled run's resource (§20) before
|
||||
the resources that stop the distribution's scrub timers, so by §1 those are stopped only once the run's timer is
|
||||
active. The run's `schedule` is the list form `0,15,30,45 * * * *`, which today's node-engine writes as
|
||||
`OnCalendar=*-*-* *:0,15,30,45:00`, so nothing waits on another fix. Checked by the module's manifest test (the
|
||||
resource order and the schedule string) and by acceptance test 3 of the main file.
|
||||
|
||||
## What the module owns
|
||||
|
||||
7. **The packages** (ADR 0679 §1; ADR 0303 §2). The manifest declares `zfs-utils` and `zfs-dkms` present: the
|
||||
node-engine installs a missing one, upgrades none, removes none. `zfs-utils-debug` is not declared and is left as
|
||||
found. Fate: the README's "It installs nothing" goes; it says the two packages and why the debug package is not the
|
||||
module's. Read by `pacman_packages` as declared by `zfs`. Checked by the manifest test.
|
||||
8. **One pool, renamed `pool`** (ADR 0679 §1; ADR 0609 §4). The config value `scrub-pool` becomes `pool` with
|
||||
`renamed-from: scrub-pool`: type string, trusted, required. mesh-config moves the value under the new name and
|
||||
answers both names during the rename window (design 32, *A config value renamed*); every template and the module's
|
||||
code read `pool`. **Rollback:** a rolled-back version leaves pool and dataset properties and devices as the last
|
||||
apply set them (ADR 0679 §15); once the rename window has closed, going back to the version with `scrub-pool` and
|
||||
`zfs-mesh-scrub` is a new delivery of that version, with its own rename back, never a rollback. Checked by the
|
||||
manifest test (no `scrub-pool` but in `renamed-from`) and `zfs_config` showing `pool` on the home server.
|
||||
9. **The module's config file** (ADR 0679 §9; ADR 0348 §5). `/etc/zfs-tools/config.json`, root's, mode 0644, written
|
||||
by the node-engine from a template holding every config value of the module, lists and objects rendered as ADR 0348
|
||||
§5 says; it replaces `/etc/zfs-tools/scrub.conf`, which the node-engine removes as a resource that left. The apply
|
||||
unit (§18) has `restart-on` this file: its change is the path by which mesh-config's `config-changed` for the
|
||||
module's values reaches the apply. The scheduled run (§20) reads it at each run. `/var/lib/zfs-scrub` stays and is
|
||||
read by the run as today. Checked by the manifest test.
|
||||
10. **What it never writes** (ADR 0679 §1, §2). The package's own `/etc/default/zfs` and `/etc/zfs/zed.d`, with its
|
||||
`zed.rc` and its links, are left as the package ships them, and nothing of them is given back on leaving; ZFS's own
|
||||
`/etc/zfs/zpool.cache` and `/etc/zfs/zfs-list.cache` are read only. `zfs_config` shows each, and a `.pacnew`
|
||||
beside `/etc/default/zfs` or the package's `zed.rc` is a warning (§29). A dataset's `sharenfs` is the seat
|
||||
`node-nfs-server`'s, `sharesmb` the SMB module's (issue 613): neither is on a list of §16, a value naming either
|
||||
is refused (§11), and `zfs_config` never reports either as differing. The pool's files are the operator's (ADR
|
||||
0051). Checked by the call-site test (§19) and §31.
|
||||
|
||||
## The config values and their forms
|
||||
|
||||
11. **Not managed until set and not empty; each of a fixed form** (ADR 0679 §3, §4). No new value has a default in
|
||||
the manifest. An absent value and an empty list both mean not managed: the module writes nothing, converges
|
||||
nothing and reports no difference for it, so nothing is ever removed by a blank value. A value of another form is
|
||||
refused by the module's own parse before any file is written or command run, emitting `config.refused` (§25).
|
||||
Trusted values (every list and object below, `log-mirror` and `hostid`) are set only on the operator's Approve (ADR
|
||||
0348 §4, §8). The module never reads a value from the node into config.
|
||||
- `arc-max`, `arc-min` (number): whole MiB, `arc-min` ≤ `arc-max`, `arc-max` at least 64 and below the node's
|
||||
memory read from `/proc/meminfo`. ZFS's read cache in memory (the ARC) is bounded by them.
|
||||
- `module-options` (list, trusted): entries `name=digits`, the name from the module's fixed list `l2arc_write_max`,
|
||||
`l2arc_write_boost`, `l2arc_headroom`, `l2arc_noprefetch`, `l2arc_rebuild_enabled`, `zfs_txg_timeout`,
|
||||
`zfs_dirty_data_max`, `zfs_scan_vdev_limit`, `zfs_scrub_min_time_ms`, `zfs_resilver_min_time_ms`,
|
||||
`zfs_arc_sys_free`; `zfs_arc_max` and `zfs_arc_min` are refused here, as `arc-max` and `arc-min` hold them. Reason:
|
||||
the ARC, L2ARC, transaction group and scrub parameters ADR 0679 §4 names, and nothing else.
|
||||
- `hostid` (string, trusted): eight hexadecimal digits, not all zero (§15).
|
||||
- `zed` (string): `on` or `off`. `zed-handlers` (list, trusted): from `all-syslog` (the journal handler) and
|
||||
`resilver_finish-start-scrub` (scrub after resilver), the two ADR 0679 §12 names; no notify handler.
|
||||
- `trim-schedule` (string): `none`, `weekly` or `monthly`; `trim-day` and `trim-time` with `scrub-day`'s and
|
||||
`scrub-time`'s forms.
|
||||
- `cache-devices`, `log-devices` (lists, trusted): names under `/dev/disk/by-id`, letters, digits, `-`, `_`, `.`
|
||||
and `:` only. `log-mirror` (boolean, trusted): when true, `log-devices` holds exactly two names, added as one
|
||||
mirror; otherwise each is a log device of its own. Reason: the log's form today is a mirror.
|
||||
- `pool-properties` (object, trusted): `autotrim`, `autoexpand`, `autoreplace` (`on`, `off`, `default`) and
|
||||
`failmode` (`wait`, `continue`, `panic`, `default`).
|
||||
- `dataset-properties` (object, trusted): a dataset of the pool (the pool's name or one beginning `<pool>/`) to
|
||||
properties of §16's list with their forms.
|
||||
- `snapshots` (object, trusted): `none`, or keep counts `hourly`, `daily`, `weekly` and `monthly`, each a whole
|
||||
number from 0 to 168, a kind with 0 not taken; for every filesystem of the pool. The bound 168 (a week of hours)
|
||||
is the design's, so the ledger's cap (§26) stays bounded.
|
||||
Checked by §31's refused-forms tests, one table per value, the empty list among them.
|
||||
|
||||
## Files, units and the event daemon
|
||||
|
||||
12. **The options file** (ADR 0679 §1, §4). `/etc/modprobe.d/zfs.conf`, root's, 0644, carries `if-config: [arc-max,
|
||||
arc-min, module-options]` (§2) and holds a header comment, then by §3 `options zfs zfs_arc_max=` and `options zfs
|
||||
zfs_arc_min=` with the values in bytes (`mib-bytes`), then `options zfs ` with each `module-options` entry. With
|
||||
none of the three set it is not placed. Checked by the manifest test and §31's template test.
|
||||
13. **The units** (ADR 0679 §1, §15; ADR 0117). Declared running and enabled at boot: `zfs-import-cache.service`,
|
||||
`zfs-mount.service` and `zfs.target`, as they run on the home server today, so declaring them changes nothing.
|
||||
Declared with no state, owned and left as found: `zfs-import-scan.service`, `zfs-share.service`,
|
||||
`zfs-volume-wait.service` and `zfs-import.target` (reason: ADR 0679 §15 keeps only the first three running when the
|
||||
module leaves, so no other ZFS unit is one the mesh would stop then). `zfs-zed.service`: running and enabled with
|
||||
`if-config: [zed=on]`, stopped and disabled with `if-config: [zed=off]`, left as found while `zed` is not managed.
|
||||
The distribution's `zfs-trim-weekly@<pool>.timer` and `zfs-trim-monthly@<pool>.timer` are stopped and disabled
|
||||
with `if-config: [trim-schedule]` (the run trims, §20); its scrub timers are stopped and disabled after the run's
|
||||
timer (§6). Read by `<node>/node-service-manager.status`. Checked by the manifest test (each unit, its state and
|
||||
its condition).
|
||||
14. **ZED runs from the module's own folder, without links** (ADR 0679 §13, §12). The folder `/etc/zfs/mesh-zed.d`
|
||||
(root's, 0755), its `zed.rc` and a drop-in for `zfs-zed.service` whose `ExecStart` adds `-d /etc/zfs/mesh-zed.d`
|
||||
carry `if-config: [zed=on]`. The module's `zed.rc` holds the package's variables but every `ZED_NOTIFY_*` and mail
|
||||
line, and `ZED_SCRUB_AFTER_RESILVER=1`, which only the scrub-after-resilver handler reads. Each handler is a file
|
||||
of its own in the folder, a copy of the package's script by its content kept in the module's files archive, mode
|
||||
0755, with `if-config: [zed-handlers=<its name>]`, so only the handlers set are placed. The package's
|
||||
`/etc/zfs/zed.d` is not read while the drop-in holds. The mesh creates no symlinks. The node-engine restarts
|
||||
`zfs-zed.service` on a change of the drop-in, a handler or `zed.rc` (`restart-on`). Checked by the manifest test (no
|
||||
link resource, each handler's condition).
|
||||
15. **The node's ZFS id** (ADR 0679 §1, §3, §4, §17). `/etc/hostid` holds four bytes, binary, which no template writes,
|
||||
so it is no resource of the manifest and the apply (§18) writes it with ZFS's own writer, `zgenhostid -f <value>`,
|
||||
only while `hostid` is set, and only when the pool's label (`zdb -C <pool>`, its `hostid`) is 0 or equals the value:
|
||||
then the next boot imports without `zpool import -f`. When the label shows another value, or cannot be read, it
|
||||
writes nothing and emits `config.refused` naming why ("the pool's import would need -f", "the pool's label cannot be
|
||||
read"). Before the Approve, the value's description and `zfs_config` say the change needs a check of the pool's
|
||||
import at the next boot; the module never reboots. After the reboot, `zfs_pools` shows the pool imported. Emits
|
||||
`config.applied` with target `file`. The apply reads `/etc/hostid` back at every start and after every write: while
|
||||
`hostid` is set, a value there other than the set one emits `config.refused` naming it, because the node-engine's
|
||||
composer cannot see a second writer of a file that is no resource. Each refusal of this line makes `zfs_health`
|
||||
unhealthy (§29), naming the key and why, until an apply finds the label and the file agreeing with `hostid`, or
|
||||
`hostid` is unset. No resource or template of the manifest writes `/etc/hostid`. Checked by §31 (unset: no
|
||||
`zgenhostid`; a label of another id, or one that cannot be read: refused; 0 or equal: one run; a file holding
|
||||
another value: refused; unhealthy, then healthy again; no manifest resource for the file).
|
||||
|
||||
## The pool
|
||||
|
||||
16. **Pool and dataset properties** (ADR 0679 §6). For each set pool property the apply runs `zpool set
|
||||
<property>=<value> <pool>`, and for `default` `zpool set <property>=<ZFS's default> <pool>`. For each set dataset
|
||||
property it runs `zfs set <property>=<value> <dataset>`, and for `default` `zfs inherit <property> <dataset>`, so
|
||||
its source stays default. The dataset list is `compression` (`off`, `lz4`, `zstd`, `zstd-1` to `zstd-19`,
|
||||
`gzip-1` to `gzip-9`, `zle`), `atime` and `relatime` (`on`, `off`), `recordsize` (a power of two from `512` to
|
||||
`16M`, with `K` or `M`), `primarycache` and `secondarycache` (`all`, `metadata`, `none`), `sync` (`standard`,
|
||||
`always`, `disabled`), `logbias` (`latency`, `throughput`), `quota` and `reservation` (`none` or digits with `K`,
|
||||
`M`, `G` or `T`). A property already at its value runs nothing. Each change is read back with `zpool get -Hp` or
|
||||
`zfs get -Hp` and emitted (§25). Checked by §31's convergence tests.
|
||||
17. **Cache and log devices** (ADR 0679 §5). From `zpool status -P -L <pool>` the apply knows each device's kernel
|
||||
path, and from the link under `/dev/disk/by-id` each declared name's. Then, in this order:
|
||||
- an entry of the pool whose kernel path is a declared device's, but named by its kernel name, is removed
|
||||
(`zpool remove <pool> <entry>`) and added again by its stable name;
|
||||
- a declared device not in the pool is added with `zpool add <pool> cache|log [mirror] <names>`, never `-f`, and
|
||||
only after its checks: a device with a partition table or a file-system signature (`wipefs --no-act <device>`)
|
||||
is refused, but for a `zfs_member` signature, which is judged by its label (`zdb -l <device>`): this pool's
|
||||
label is allowed, another pool's refused; a device that is a member of any imported pool is refused;
|
||||
- only while `cache-devices` is set and not empty, a cache entry it does not name is removed;
|
||||
- only while `log-devices` is set and not empty, a log device it does not name, one to rename, or the log when
|
||||
`log-mirror` changes (a removal and a re-add, said as such) is removed only while the pool is ONLINE with no
|
||||
read, write or checksum errors and no scrub or resilver runs; otherwise it is a wait, `config.waiting` {key,
|
||||
waits-on, since}.
|
||||
A declared device that is not present is a wait. Each change is read back from `zpool status -P` and emitted.
|
||||
Checked by §31's convergence tests (the empty list among them) and acceptance test 2.
|
||||
18. **The apply: who, when, and what wakes a wait** (ADR 0679 §9, §17). A `process` resource `zfs-config-apply`, `run:
|
||||
./zfs-tools apply`, as root, oneshot, ordered after `zfs-import.target`, enabled at boot, with `restart-on` the
|
||||
config file (§9); it converges §15 to §17 and writes the ARC values live. It holds an exclusive lock on
|
||||
`/run/zfs-tools/apply.lock`; a second start waits for it. It converges nothing while a scrub or resilver runs on
|
||||
the pool or before the pool is imported: it emits `config.waiting` with what it waits on and since when, and ends.
|
||||
It never runs on a timer. **A wait is ended by an event:** when the event follower (§27) hears a scrub or a
|
||||
resilver finish (ZFS's `scrub_finish` and `resilver_finish` events) or a declared device appear (a udev `add` of a
|
||||
block device whose link under `/dev/disk/by-id` is declared, read with `udevadm monitor --udev
|
||||
--subsystem-match=block`), it starts the apply unit through `<node>/node-service-manager.start`. While the bus or
|
||||
the control-node is down, the apply converges from the config file it last got. It writes `arc-max` and `arc-min`
|
||||
to `/sys/module/zfs/parameters/zfs_arc_max` and `zfs_arc_min` as well as to §12's file, and reads both back; each
|
||||
`module-options` entry whose live value differs is a warning naming it, "takes effect at the next load of the
|
||||
kernel module", never success and never unhealthy.
|
||||
19. **The call-site test** (ADR 0679 §7, §17). Every command the bundle runs is built by one of its argument functions, and
|
||||
the test (`TestTheBundleRunsOnlyReadingVerbsSnapshotAndScrub`, renamed `TestTheBundleRunsOnlyTheFormsOfADR0679`)
|
||||
allows only: `zpool add <pool> cache|log`, `zpool remove` of a cache or log device, `zpool set`, `zfs set` and `zfs
|
||||
inherit` of a listed property, `zfs snapshot`, `zfs set mesh:schedule=<kind>` on a ZFS snapshot named `mesh-`,
|
||||
`zfs destroy -- <dataset>@<snapshot>`, `zpool scrub`, `zpool scrub -p` and `zpool scrub -s` of the pool, `zpool trim`
|
||||
of the pool; and the reads `zpool status`, `zpool list`, `zpool get`, `zpool iostat`, `zpool events`, `zfs list`,
|
||||
`zfs get` and `zfs holds`. It refuses every other `zpool` or `zfs` form and every `-f`, `-r`, `-R` and `-d`, so
|
||||
data vdevs, spares, special and dedup devices, `upgrade`, a dataset's destroy, `rollback`, `rename` and `clear` are
|
||||
never run (each an act of its own, asked of the operator). The same test pins the module's other root forms:
|
||||
`wipefs --no-act <device>`, `zdb -l <device>`, `zdb -C <pool>`, `zgenhostid -f <value>`, `udevadm monitor --udev
|
||||
--subsystem-match=block` and `systemctl show`, and refuses every other `zgenhostid` or `zdb` form: `zgenhostid`
|
||||
only as `-f` with the value §11 validated, `zdb` only as these two reads. Fate: the README's list of allowed forms
|
||||
is replaced by this one.
|
||||
|
||||
## The scheduled run and ZFS snapshots
|
||||
|
||||
20. **The scheduled run** (ADR 0679 §9). The `process` resource `zfs-mesh-scrub` (`./zfs-tools scrub-run`) becomes
|
||||
`zfs-mesh-run` (`./zfs-tools run`), root as today, with §6's schedule. Each run takes the apply's lock; when the lock
|
||||
is held it does nothing and emits `run.skipped` {reason: `lock-held`}. It does nothing while a scrub or resilver
|
||||
runs or before the pool is imported, emitting `run.skipped` with that reason. Otherwise, in order: it starts the
|
||||
scrub when due and returns at once, never waiting on it (today's wait with `zpool wait -t scrub` goes; the scrub's
|
||||
end is heard by the event follower, §27); starts a trim when `trim-schedule` is due (`zpool trim <pool>`, by
|
||||
`trim-day` and `trim-time` in the node's time zone, as the scrub reads its own); and takes and removes the ZFS
|
||||
snapshots (§21, §22) when `snapshots` is set. Checked by §31 (lock held: nothing run; scrub due: started and the
|
||||
run ends).
|
||||
21. **Taking scheduled ZFS snapshots** (ADR 0679 §8). Each kind whose keep count is above 0 is due at the first run of
|
||||
the hour (hourly), the first run after midnight (daily), the first run on Monday (weekly) and the first run on the
|
||||
first of the month (monthly), each read in the node's time zone against the ledger's last entry of that kind (§26).
|
||||
For every filesystem of the pool it runs `zfs snapshot -- <dataset>@mesh-<kind>-<YYYYMMDDTHHMMZ>`, the time in UTC,
|
||||
never recursive, then `zfs set mesh:schedule=<kind>` on it, and records the ledger's `taken` operation. The run
|
||||
takes no ZFS snapshot it cannot record in the same run: with the bus down or the ledger full it takes none and
|
||||
emits `snapshot.skipped.schedule` {pool, capacity, reason}. No ZFS snapshot is taken while the pool is more than 80
|
||||
% full (`zpool list -Hp -o capacity`): a warning says so, and `snapshot.skipped.schedule` is emitted with reason
|
||||
`full`.
|
||||
22. **Removing them, only by their own record** (ADR 0679 §8; the operator, 2026-11-12: "we may absolutely not lose any
|
||||
data"). After taking, the run reads the ledger in the same run (no read, no removal) and, per dataset and kind,
|
||||
removes the oldest past its keep count, all of them in that run. Before any destroy it checks the name against
|
||||
`<a dataset of the pool>@mesh-<kind>-<YYYYMMDDTHHMMZ>`, that it carries `mesh:schedule=<kind>`, is in the ledger,
|
||||
has no hold (`zfs holds`) and no clone (`clones` empty); anything else is refused, emitted and left. Then `zfs
|
||||
destroy -- <dataset>@<snapshot>`, the ledger's `removed` operation, and `snapshot.removed.schedule` {snapshot, kind,
|
||||
space freed in bytes}. When `snapshots` goes back to `none`, the existing scheduled ZFS snapshots are kept, and a
|
||||
warning names them; that change removes nothing. A ZFS snapshot taken by hand is never removed.
|
||||
23. **A person's ZFS snapshot stays apart** (ADR 0679 §8, §10). `zfs_snapshot` refuses a name beginning `mesh-` and
|
||||
emits `snapshot.taken.person`; the run emits `snapshot.taken.schedule`. A waiter on a person's ZFS snapshot waits
|
||||
on `snapshot.taken.person`, which no scheduled one is said on. Fate: the README's "No tool of the mesh destroys a
|
||||
snapshot" becomes §22's rule.
|
||||
24. **Hand acts run as the run does** (ADR 0679 §14). `zfs_snapshots_run_now`, `zfs_snapshots_prune_now` and
|
||||
`zfs_config_apply` each start a oneshot instance of the unit `zfs-mesh-run@<act>.service`, the act from the fixed
|
||||
set `snapshots`, `prune` and `apply`, through `<node>/node-service-manager.start`, with `why`; the instance runs
|
||||
under the same lock and guards as §18 and §20, and the tool answers from the state and events afterwards. Their
|
||||
events carry `.person` (`snapshot.taken.person`, `snapshot.removed.person`, `snapshot.skipped.person`).
|
||||
`zfs_snapshots_pause` writes the key `paused` {by, why, at} in the state `schedule`, which the run reads and then
|
||||
takes and removes nothing; `zfs_snapshots_resume` deletes it.
|
||||
|
||||
## What it says, keeps and shows
|
||||
|
||||
25. **Events, each kind on a subject of its own** (ADR 0679 §10). `config.applied` {target (`pool`, `dataset`,
|
||||
`file` or `unit`), key, before, after, read-back}, `config.refused` {key, value, reason}, `config.waiting` {key,
|
||||
waits-on, since}, `snapshot.taken.person` and `.schedule` {snapshot, by}, `snapshot.removed.person` and
|
||||
`.schedule` {snapshot, kind, space freed in bytes}, `snapshot.skipped.person` and `.schedule` {pool, capacity,
|
||||
reason}, `run.skipped` {reason}, and today's `scrub.requested`. They go on an events stream asked for by template
|
||||
in `requires` alone and created by the bus module (ADR 0595 §1, §2, §4), kept 30 days; today's module-level `emits`
|
||||
move to it. No module consumes `snapshot.taken` or `scrub.requested` today (read in mesh-catalog's manifests on
|
||||
main); a session waiting through `mesh_wait` on `snapshot.taken` hears `snapshot.taken.person` after.
|
||||
26. **Kept data, by template in `requires`** (ADR 0679 §10; ADR 0595 §1). A state `applied`, one key per config value,
|
||||
`{before, after, at, outcome}`, written by the apply and deleted by it when that value goes back to not managed,
|
||||
after its last `config.applied`. A state `schedule`, the key `paused` alone (§24). A ledger `snapshots`, one
|
||||
instance per ZFS snapshot the run took, operations `taken` and `removed`, the run removing the instance of one it
|
||||
destroyed; its cap is the keep counts' sum times two instances per dataset. A state `findings` (§27). The apply,
|
||||
the run and the event follower declare the bus account that writes them; the tools read them.
|
||||
27. **The event follower's findings** (ADR 0679 §12; ADR 0564 §2; design 47 G §383 to §388). The event follower
|
||||
(`zfs-events`, root) is an observer: in its state `findings` it keeps one key `pool.<pool>.<device>` per device
|
||||
of the pool (and `pool.<pool>.pool` for the pool) not ONLINE, its value the state, deleted when the device is
|
||||
ONLINE again or leaves the pool; it says each change on its findings stream as design 47 G §383 says, and at its
|
||||
start it reads `zpool status -P` against its keys and states them (§384). The condition keeper raises and clears
|
||||
the condition from those events, so a pool fault reaches the operator once and never from a ZED handler. It also
|
||||
starts the apply on the events of §18. Its process declares the bus account; today it writes the journal only.
|
||||
28. **Tools** (ADR 0679 §14). `zfs_config`: every owned value with its config value, what runs on the node, its state
|
||||
(`not managed`, `as set`, `waiting` with on what and since, `refused` with why, `takes effect at the next load of
|
||||
the kernel module`), and the files of §10. `zfs_config_apply` (§24). `zfs_snapshots_schedule`: the schedule, its
|
||||
next run, what it would remove next and the ledger. `zfs_snapshots_run_now`, `zfs_snapshots_prune_now`,
|
||||
`zfs_snapshots_pause` and `zfs_snapshots_resume` (§24). `zfs_module_events`: the module's stream, the last N events
|
||||
by kind (named so because `zfs_events` already reads ZFS's own events). `zfs_snapshots_check`: compares the ledger
|
||||
with what ZFS holds and names each entry whose ZFS snapshot is gone and each `mesh-` snapshot not in the ledger,
|
||||
changing nothing. `zfs_findings`: the follower's findings. The existing read tools stay. Each is in `replaces`
|
||||
where it replaces a command.
|
||||
29. **Health** (ADR 0679 §11). `zfs_health` is unhealthy for a `refused` or a failed command in the state `applied`,
|
||||
naming the key and why. Warnings: a wait, naming on what and since when; a `.pacnew` beside `/etc/default/zfs` or
|
||||
the package's `zed.rc`; the last scheduled ZFS snapshot older than its cadence; a failed run; `autoreplace` on or a
|
||||
spare in the pool while `zed` is not `on`; the pool above 80 % with `snapshots` set; scheduled ZFS snapshots kept
|
||||
after `snapshots` went to `none`; a parameter awaiting a load of the kernel module; `paused` set.
|
||||
30. **When the module leaves** (ADR 0679 §15; ADR 0303 §2; ADR 0269 §2). The packages stay. `zfs-import-cache`,
|
||||
`zfs-mount` and `zfs.target` are never stopped or disabled, and the module says so before it leaves (ADR 0679 §15
|
||||
changes ADR 0269 §10 for them). Nothing of the package's files is given back, since the module never wrote them.
|
||||
`zfs.conf`, `/etc/zfs/mesh-zed.d` and the drop-in are removed, and before leaving `zfs_config` says that the next
|
||||
load of the kernel module uses ZFS's defaults. The distribution's scrub and trim timers are put back as the
|
||||
module's declaration found them, started and enabled again where it stopped or disabled them. Pool and dataset
|
||||
properties and devices stay as the last apply set them, also on a rollback (§8). `/etc/hostid` stays, and the
|
||||
module says so before it leaves (`zfs_config`): it is no resource the node-engine removes, and removing it would
|
||||
change the node's hostid, so that the pool's next import could need `zpool import -f` (ADR 0679 §15).
|
||||
|
||||
## How it is checked
|
||||
|
||||
31. **Unit tests**, table-driven, one test per area, within the module's 500: no values gives no `zpool` or `zfs` write
|
||||
but the due scrub; each convergence plan from parsed `zpool status` and `zfs get` output (an add, a kernel name to
|
||||
its stable name, a removal, an empty list removing nothing, a log removal refused while not healthy, a `log-mirror`
|
||||
change, a wait during a scrub); every refused form (a newline, quote, `$`, backtick or semicolon in each field; a
|
||||
parameter or property not on its list; a `sharenfs` value; `-f`; a device with a signature or another pool's label;
|
||||
a hostid of the wrong form, a label of another id or one that cannot be read, and a hostid file holding another
|
||||
value than the set one, each unhealthy and healthy again once the label and the file agree or `hostid` is unset);
|
||||
the call-site test of §19, refusing any other `zgenhostid` or `zdb` form and a `zgenhostid` value not validated; the
|
||||
lock (a second apply waits, a run with the lock held does nothing); the apply started only by `config-changed`,
|
||||
never a timer; one scheduled resource; both ARC writes read back; applying from the last config file with the bus
|
||||
down; the three templates (config file, options file, `zed.rc`); pruning never removes a ZFS snapshot whose name,
|
||||
property or ledger entry does not fit, nor one with a hold or clone, and removes every surplus in one run; no ZFS
|
||||
snapshot taken without a ledger record; `snapshots` back to `none` removes nothing; `zfs_snapshot` refuses a `mesh-`
|
||||
name; a scheduled `snapshot.taken.schedule` does not wake a person's waiter; a parameter awaiting a load is a
|
||||
warning; the `.pacnew` warning; each change emits its event with before and after; one test per tool; no resource or
|
||||
template of the manifest for `/etc/hostid`; the hostid converged at the apply's start after `zfs-import.target`; the
|
||||
leave keeps `/etc/hostid` and says so. The module adds no integration test; §5 is the milestone's, and the main
|
||||
file's live tests stand for the module whole, said in the pull request.
|
||||
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog, mesh-controller, mesh-host]
|
||||
updated: 2026-10-22
|
||||
updated: 2026-11-12
|
||||
decisions:
|
||||
- "ADR 0369"
|
||||
- "ADR 0303"
|
||||
@@ -29,6 +29,12 @@ decisions:
|
||||
- "ADR 0507"
|
||||
- "ADR 0508"
|
||||
- "ADR 0510"
|
||||
- "ADR 0679"
|
||||
- "ADR 0051"
|
||||
- "ADR 0269"
|
||||
- "ADR 0348"
|
||||
- "ADR 0595"
|
||||
- "ADR 0593"
|
||||
---
|
||||
|
||||
# 42. The machines' modules, in order
|
||||
@@ -50,7 +56,7 @@ not before. Modules that need none go ahead now.
|
||||
|
||||
## How every module moves
|
||||
|
||||
1. Written in the catalogue, with its tools and their tests, and checked by the controller's catalogue
|
||||
1. Written in the catalog, with its tools and their tests, and checked by the controller's catalog
|
||||
check.
|
||||
2. Merged, which builds it.
|
||||
3. Assigned to **the first workstation, the proving machine**, and pushed. Its tools and files are
|
||||
@@ -80,6 +86,7 @@ In order:
|
||||
| 9 | `ssh-client` | everything under `~/.ssh` (research 027/03) | a predecessor's entries winning over the mesh's; stale keys |
|
||||
| 10 | scripts | the operator's own scripts, shared and per role (research 027/03) | under no version control, copied by hand |
|
||||
| 11 | `kernel` | kernel, microcode, boot entries, the fallback kernel and its boot entry, and the kernel's run-time settings per machine (transparent huge pages first) | two machines without microcode; huge pages left at the distribution's `always` on a machine whose slow disks they flood (issue 746); only one machine with a fallback kernel |
|
||||
| 12 | `zfs` | on a machine that holds a ZFS pool (today the home server): ZFS's packages, its kernel module's options, its event daemon's handlers, its import, mount, share and trim units, and, for the one pool its config names, that pool's properties, its cache and log devices, its datasets' properties and a schedule of ZFS snapshots, each changed only once its config value is set (ADR 0679) | ZFS installed by hand and declared by no module; no bound on its read cache on a machine that also runs its apps; cache devices named by kernel names that swap between boots; no scheduled ZFS snapshots; the pool left with no scrub schedule by a failed timer |
|
||||
|
||||
`systemd`, `pacman` and `docker` hold the three seats that apply resources
|
||||
(ADR 0207):
|
||||
@@ -131,6 +138,10 @@ before it passed; the boot-entry script in stand-in trees of both loaders), an i
|
||||
keepers of two made-up nodes on a real bus, and, after the delivery, the view answering every node and every
|
||||
boot menu listing the fallback kernel.
|
||||
|
||||
`zfs` comes after `kernel` because its kernel module is built by DKMS against the kernels `kernel` and `pacman` put
|
||||
on the machine; it is placed only on a machine that holds a pool. Its section is below,
|
||||
[*The zfs module*](#the-zfs-module).
|
||||
|
||||
**Added 2026-10-04.** Two more modules for every machine:
|
||||
- `power` holds `node-power` (ADR 0211). Other modules contribute code for
|
||||
its moments (after boot, before sleep, after waking, before shutdown, on mains, on battery), and it
|
||||
@@ -140,6 +151,100 @@ boot menu listing the fallback kernel.
|
||||
waits for a reboot. A live restart in the middle of a full upgrade took down a workstation's
|
||||
logins on the day this was written.
|
||||
|
||||
## The zfs module
|
||||
|
||||
*Added 2026-11-12, ADR 0679; the operator, 2026-11-12: "you could configure the disk by id and make sure the module
|
||||
handles the rest"; "the zfs module should/must own all zfs-configuration files and options"; "we may absolutely not
|
||||
lose any data".*
|
||||
|
||||
**What it is for.** On a node that holds a ZFS pool, the zfs module owns ZFS: what was installed and set by hand and
|
||||
seen by nothing becomes the module's, and the operator's choices about it are config values (ADR 0679 §1). It holds
|
||||
no seat; a seat for it is a later record's. It keeps reading and acting by hand as today: the pools, the datasets, a
|
||||
named ZFS snapshot, a scrub's start, pause and stop.
|
||||
|
||||
**The whole shape.**
|
||||
|
||||
- **What it owns** (ADR 0679 §1): the packages `zfs-utils` and `zfs-dkms`; `/etc/modprobe.d/zfs.conf`, placed only
|
||||
while an ARC or module option is set; `/etc/hostid`; its own folder for ZFS's event daemon (ZED),
|
||||
`/etc/zfs/mesh-zed.d`, with its `zed.rc` and handlers, and a drop-in for `zfs-zed`, placed only while ZED is on;
|
||||
the states of ZFS's import, mount, share, volume and target units and of the distribution's scrub and trim timers;
|
||||
and, for the one pool its config value `pool` names, that pool's cache and log devices, its properties and its
|
||||
datasets' properties from fixed lists. The node-engine's composer refuses a second module owning one of its paths
|
||||
(ADR 0593 §7, ADR 0051 §9), all but `/etc/hostid`, which is no resource: the module's apply reads it back instead
|
||||
(ADR 0679 §1, §17).
|
||||
- **What it never writes** (ADR 0679 §1, §2): the package's own `/etc/default/zfs` and `/etc/zfs/zed.d` with its
|
||||
`zed.rc`, and what ZFS writes itself; it reads and shows them. A dataset's `sharenfs` is the seat
|
||||
`node-nfs-server`'s, `sharesmb` the SMB module's; the pool's files are the operator's (ADR 0051); the weekly
|
||||
upgrade and a kernel held for DKMS are the package manager's (ADR 0508).
|
||||
- **An unset or empty value changes nothing** (ADR 0679 §3): every new config value is "not managed" until the
|
||||
operator sets it through mesh-config, on the Approve for a trusted value (ADR 0348 §4, §8). Placing the new version
|
||||
on a node runs no `zpool` or `zfs` write but the scrub its schedule already runs, and places none of its new files.
|
||||
- **Who applies** (ADR 0679 §9, §17): the node-engine places the packages, files and units as the module's resources,
|
||||
some only while a value holds, all but `/etc/hostid`; the module's root process `zfs-tools apply` converges the pool's
|
||||
devices and properties at its start and on `config-changed`, never on a timer, and writes `/etc/hostid` with ZFS's own
|
||||
writer only while `hostid` is set and the pool's label agrees; the module's one scheduled run, under the same lock,
|
||||
starts the scrub and the trim when due and takes and removes the scheduled ZFS snapshots (ADR 0679 §8).
|
||||
- **What it says** (ADR 0679 §10, §11, §12): every change, refusal and wait is an event, each kind on a subject of
|
||||
its own, on an events stream asked for by template; the last change per value, the schedule's pause and every
|
||||
scheduled ZFS snapshot are kept on the bus in its own states and ledger (ADR 0595). Its health names a refusal or a
|
||||
failed command as unhealthy and a wait as a warning. Pool faults reach the operator once, from the module's event
|
||||
follower's findings through the condition keeper.
|
||||
- **What it never does** (ADR 0679 §7): it converges no data device, spare, special or dedup device, upgrades no
|
||||
pool, destroys no dataset, rolls back, renames and clears nothing; each is an act of its own, decided separately.
|
||||
- **When it leaves** (ADR 0679 §15): ZFS keeps running (the import, mount and ZFS target are never stopped), the
|
||||
packages stay, its own files are removed but `/etc/hostid`, which stays and is said, and the distribution's timers
|
||||
are put back as found.
|
||||
- **Its prerequisites** (ADR 0679 §16): three node-engine capabilities, built first in the same milestone under issue
|
||||
1979, with their readers in mesh-sdk and the manifest validator.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
operator[the operator] -->|sets a config value| config[mesh-config]
|
||||
config -->|the module's config file| engine[the node-engine]
|
||||
engine -->|packages, files, units| node[ZFS on the node]
|
||||
engine -->|restarts on its config file| apply[zfs-tools apply, root]
|
||||
apply -->|zpool add, remove, set; zfs set, inherit; zgenhostid| node
|
||||
sched[the scheduled run] -->|scrub, trim, ZFS snapshots and their removal| node
|
||||
follower[the event follower] -->|scrub ended, device appeared: start| apply
|
||||
apply -->|config.applied, refused, waiting| stream[(events stream)]
|
||||
sched -->|snapshot.taken.schedule, removed, skipped| stream
|
||||
apply --> applied[(state applied)]
|
||||
sched --> ledger[(ledger snapshots)]
|
||||
follower -->|findings| keeper[the condition keeper]
|
||||
tools[the module's tools] -->|read| applied
|
||||
tools -->|read| ledger
|
||||
```
|
||||
|
||||
**Acceptance tests** (ADR 0679, *How it is checked*), each with its level; every live test is read by a tool call:
|
||||
|
||||
1. *Live.* Placing the new version on the home server changes nothing: `zfs_config` shows every value "not managed",
|
||||
and `zpool history` read before and after shows no command but the scheduled scrub.
|
||||
2. *Live.* Setting `cache-devices` puts both cache devices in the pool by their stable names; after a reboot that
|
||||
swaps the drives' kernel names, both stay ONLINE (`zfs_pool_layout`).
|
||||
3. *Live.* The node-engine's apply report shows the module's run timer active before the distribution's scrub timer
|
||||
is stopped, and `systemd-analyze calendar` of the generated `OnCalendar` passes with a next run within 15 minutes.
|
||||
4. *Live.* With `hostid` unset, `/etc/hostid` is not written (`<node>/node-reads.stat`); `zfs_config` shows it "not
|
||||
managed".
|
||||
5. *Live.* With `snapshots` set, the next run's ZFS snapshot is in `zfs_snapshots` with `mesh:schedule` and in the
|
||||
ledger (`zfs_snapshots_schedule`); after its keep count passes, its removal is in both and in `zfs_module_events`.
|
||||
6. *Live.* A value of a refused form makes `zfs_health` unhealthy naming the key; a declared device that is not
|
||||
present makes it a warning naming what it waits on.
|
||||
7. *Live.* With `zed` on, `<node>/node-files.list` of `/etc/zfs/mesh-zed.d` shows its `zed.rc` and only the handlers
|
||||
set, and `<node>/node-service-manager.status` shows `zfs-zed` running with the drop-in; with `zed` off, stopped.
|
||||
8. *Live, on a test placement.* After the module leaves, `zfs-import-cache`, `zfs-mount` and `zfs.target` are still
|
||||
running (`<node>/node-service-manager.status`) and `zfs_pools` shows the pool imported.
|
||||
9. *Unit and integration.* The milestone part's §31 unit tests pass within the module's budget of 500, and its §5
|
||||
integration test passes in the node-engine.
|
||||
|
||||
## Milestones
|
||||
|
||||
| milestone | what it builds | part | state |
|
||||
|---|---|---|---|
|
||||
| zfs-owns-zfs | the zfs module's ownership of ZFS (ADR 0679), with its three node-engine prerequisites and their readers, under issue 1979 | [42-milestones/zfs-owns-zfs.md](42-milestones/zfs-owns-zfs.md) | designed |
|
||||
|
||||
The modules of the tables above were built before designs were cut into milestone parts; their remaining lines move
|
||||
into a part of their own at their next amendment (playbook 02, *The shape of a design*).
|
||||
|
||||
## Phase 2 — both workstations
|
||||
|
||||
In order:
|
||||
@@ -199,11 +304,11 @@ both places of both bars once. The bar writes no block that depends on the hardw
|
||||
the power module's state `power.draw`, one value per machine, which its bundle puts from its own root
|
||||
sampler. The block `shows: state`; the bar's bundle watches it under a read the controller grants the
|
||||
bar for what the modules on its machine offer, and the bar names no module. The battery block is the
|
||||
bar's own, reading the kernel. The catalogue's `checks/paths` refuses a module that names another
|
||||
bar's own, reading the kernel. The catalog's `checks/paths` refuses a module that names another
|
||||
module's path under `/run` or `/var/lib`.
|
||||
|
||||
The node-engine is delivered first, so the machines report the two capabilities. The controller comes
|
||||
next, because it parses `data`, `if-capability` and `renders`. The catalogue comes last. Left open: the
|
||||
next, because it parses `data`, `if-capability` and `renders`. The catalog comes last. Left open: the
|
||||
draw at the wall (a metered plug or a UPS), a GPU-load block from a generic module, and the headsets.
|
||||
|
||||
## Phase 3 — one machine model
|
||||
@@ -213,6 +318,6 @@ and `memory-pressure` (research 027/03).
|
||||
|
||||
## How it is checked
|
||||
|
||||
Each module's own tests and the catalogue check, at merge. On the proving machine, each tool answered
|
||||
Each module's own tests and the catalog check, at merge. On the proving machine, each tool answered
|
||||
through the mesh and each owned file checked in place, before any other machine is assigned. This
|
||||
document's tables are updated as each module lands.
|
||||
|
||||
@@ -146,8 +146,8 @@ a manifest. Not off-site: a lost machine loses its backups with its data, by the
|
||||
|
||||
## How it is checked
|
||||
|
||||
The catalogue check (`module check`, and the catalogue-wide test beside it) refuses a provider that
|
||||
The catalog check (`module check`, and the catalog-wide test beside it) refuses a provider that
|
||||
grants without saying what it keeps for its consumers, an irreplaceable item with neither a backup nor a
|
||||
redundancy, and any backup line written by hand (ADR 0233). The store rule this design stated before
|
||||
was held only by a controller test over the catalogue checked out beside it, never by `module check`. The weekly
|
||||
was held only by a controller test over the catalog checked out beside it, never by `module check`. The weekly
|
||||
restore test above, and the 48-hour status line, are the running checks.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
|
||||
updated: 2026-10-14
|
||||
updated: 2026-11-04
|
||||
decisions:
|
||||
- "ADR 0357"
|
||||
- "ADR 0325"
|
||||
@@ -68,7 +68,7 @@ Owning repositories: **mesh-controller** (the condition store, watchdogs, `docto
|
||||
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
|
||||
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalog's
|
||||
composition check), **mesh-sdk** (the TypeScript providers' loop and its retirement), **mesh-lab** (the
|
||||
replays and the induced-failure scenarios).
|
||||
|
||||
@@ -680,7 +680,7 @@ 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 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
|
||||
non-secret values, seats and their holders, the catalog 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.
|
||||
@@ -768,7 +768,7 @@ replay covers what must not be done to it on purpose, and every rule keeps a liv
|
||||
- **It runs on the build seat**, asked by the controller when the forge's announcer says a pull request's
|
||||
head moved: the repository's own `merge-check.sh` in the mesh's Go toolchain with no container runtime
|
||||
socket, beside a throwaway store and bus of the versions the snapshot says run, then mesh-lab's replays.
|
||||
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalogue change is
|
||||
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalog change is
|
||||
judged by the controller the mesh runs; a node-engine change by it built with the change's validator.
|
||||
- **Replays live where their incident is**: a component's logic in its repository as `TestReplay<issue>`,
|
||||
what the mesh runs (the bus's release, the resolver under musl and glibc) in mesh-lab's `replays/`, all
|
||||
@@ -868,7 +868,7 @@ the `data` verb; `cleanup` extended to a module's own retired data, deleted by t
|
||||
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
|
||||
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
|
||||
sizes), the media catalog 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
|
||||
warning, not compared), and SMART read under an array.
|
||||
|
||||
@@ -931,8 +931,8 @@ 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 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-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 the program it composes with 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 catalog'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` |
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
---
|
||||
layer: to-be
|
||||
design: 46
|
||||
milestone: 7-approval-mark
|
||||
updated: 2026-11-11
|
||||
code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-host, mesh-catalog modules/zsh, mesh-catalog modules/gitea, mesh-catalog modules/mesh-files]
|
||||
decisions:
|
||||
- "ADR 0286"
|
||||
- "ADR 0259"
|
||||
- "ADR 0627"
|
||||
- "ADR 0656"
|
||||
- "ADR 0666"
|
||||
- "ADR 0668"
|
||||
- "ADR 0595"
|
||||
- "ADR 0597"
|
||||
- "ADR 0310"
|
||||
- "ADR 0394"
|
||||
- "ADR 0619"
|
||||
- "ADR 0268"
|
||||
- "ADR 0564"
|
||||
---
|
||||
|
||||
# Design 46, milestone 7 — a call that needs the operator's approval
|
||||
|
||||
**What it builds.** A tool or a seat verb marked in its manifest or its seat's definition runs only on the operator's
|
||||
approval of that one call, with its exact arguments (ADR 0286). The first call asks the operator on the seat
|
||||
`operator-channel` and runs nothing; the same call made again within 30 minutes of the operator's Approve runs once.
|
||||
The tool runner (module `node-tools`) asks, judges and claims; it reads the marks from the catalog half on the bus; and
|
||||
on a machine whose tool runner does not honour the mark the grants part withholds every marked subject. The shape is in
|
||||
the design's main file,
|
||||
[§10, *A call that needs the operator's approval*](../46-the-conversation-with-the-operator.md#a-call-that-needs-the-operators-approval);
|
||||
this part holds every line the milestone builds, cited as "design 46 7-approval-mark §n".
|
||||
|
||||
**When.** It is built and delivered in design 47's milestone G, in G's delivery group, with design 34's milestone
|
||||
every-event, whose marked waits (design 34 every-event §32 to §37) use this gate (the operator, 2026-11-11: "Build the
|
||||
gate in G").
|
||||
|
||||
**Where.** mesh-sdk (the manifest field, the seat definition's field, the per-machine state's keys), mesh-contracts
|
||||
(the login shell's seat definition), mesh-delivery (the indexer's manifest validator and catalog graph entry, the grants
|
||||
part's deny), mesh-tools (the tool runner's gate, its state, its tools, and the mesh MCP server's `mesh_describe` in
|
||||
it) and mesh-catalog (the zsh module, which holds the login shell, and the forge module, §21). Nothing is built in a controller: no controller issues memberships today, bus permissions are the grants part's
|
||||
(seat `mesh-grants`), the hand-act log is the seat `mesh-hand-acts`, and the asker's logic is operator-channel's client
|
||||
in mesh-contracts (ADR 0627 §1).
|
||||
|
||||
**What changes from ADR 0286 as written.** Each line says in its *Why* where something built since ADR 0286
|
||||
(2026-10-10) changes how a clause is met: the mark is read from the catalog half and not from a membership (§5), the
|
||||
digest is Go alone (§8), the claim is kept in the runner's bus state and not a file (§13), `warranted` is a kept call
|
||||
and not a retry on a timer (§16), and a fourth open ask is offered as G makes it (§17). The controller's own verbs (ADR
|
||||
0286 §43) are not built: no controller serves verbs.
|
||||
|
||||
**The bound of the gate, a known limit.** The gate holds for every call that reaches the tool runner's handler. It does
|
||||
not hold for a process that reads the tool runner's bus credential (the file `broker` in its state directory,
|
||||
`own-secrets`) and speaks to the bus itself: that account may subscribe a marked subject, a restricted stream's among
|
||||
them, on a machine whose runner honours the mark. Read on the workstation on 2026-11-11 with `node-reads.stat`, the
|
||||
file is mode 0600, owned by the operator's account, in a directory of mode 0755, and the prepare of 2026-11-11 read
|
||||
the same mode and owner on all four nodes: every process running as the
|
||||
operator's account, every module on the machine's shared account and every agent session among them, can read it and
|
||||
hear a restricted stream without a warrant. This is the same known limit as design 47 G §385 (modules running as the
|
||||
machine's account, issue 1966 note 193, whose fix carries its check), and this milestone does not change it: that a
|
||||
restricted stream is heard only on a warrant holds against every caller once that fix runs modules and sessions on
|
||||
accounts that cannot read the file, and until then against callers that go through the tool runner. The
|
||||
prepare reads the file's mode and owner on every node with `<node>/node-files.stat` and writes them in the milestone
|
||||
file; a node where the file is readable beyond the operator's account is a finding of the prepare. The forge module's
|
||||
own credential (§21) lies in a directory of mode 0700, owned by the forge's account, which the reading account cannot
|
||||
enter; `node-files.stat` answers such a file's mode from this milestone (§21a), and the prepare reads it then.
|
||||
|
||||
## The mark
|
||||
|
||||
1. **A module marks its tools and verbs in `needs-approval`.** mesh-sdk's `catalogue.Manifest` gains `NeedsApproval`,
|
||||
the manifest key `needs-approval`: a list of strings, each a tool the module serves (`<tool>`) or a seat verb it
|
||||
serves (`<seat>.<verb>`), and, under design 34 every-event §32, one of its events. Absent is no mark. *Why:* ADR
|
||||
0286 §1, §49 (each marking is a manifest change, reviewed like any other). Only the contract's manifest reads
|
||||
(`ManifestReads`) gain `word:needs-approval`; the baseline of the engines before any stated what it reads
|
||||
(`manifest_reads_baseline.txt`) is unchanged, since none of them reads the word (Q-BL). *How it is checked:* unit, a
|
||||
row in `go/catalogue/invokes_test.go`'s `TestAManifestMaySayWhatItInvokes`, the nearest table over raw manifests
|
||||
(Q-T): a manifest naming a tool, a seat verb and an event in `needs-approval` decodes all three with `UnknownField()`
|
||||
empty, and one without it has no mark.
|
||||
2. **A seat's definition marks a verb for every holder.** mesh-sdk's `seatdef` reads a verb's `needs-approval`, a
|
||||
boolean, in a seat definition (`seats/<seat>/seat.json` in mesh-contracts); the seat definitions the catalog half
|
||||
carries (design 47 G §194) carry it on the verb. The definition format's own schema in mesh-contracts
|
||||
(`format/seat-definition.schema.json`) gains a verb's `needs-approval`, a boolean (Q-SC). Every strict reader of a
|
||||
seat definition reads the word from G's reader release (design 47 G §340), so no reader refuses a definition that
|
||||
carries it (Q-RR). *Why:* ADR 0286 §2. *How it is checked:* unit, a fixture row in `go/seatdef/seatdef_test.go`'s
|
||||
`TestADefinitionIsReadStrictlyNamingWhatIsWrong` (Q-T), and the schema validates the login shell's definition of §21.
|
||||
3. **Marks add up.** A verb is marked when its seat's definition or its holder's `needs-approval` marks it; a holder
|
||||
cannot unmark what its seat marks. *Why:* ADR 0286 §3. *How it is checked:* unit, a row of §5's table.
|
||||
4. **The manifest validator refuses a mark it cannot honour.** The indexer's manifest validator (`judge/rules.go`)
|
||||
refuses, as the checker runs it, and sets aside to wait, as the indexer runs it, a manifest that: marks a tool its
|
||||
`tools` does not list, or a verb its `claims` do not serve (ADR 0286 §4); marks a tool or a verb, or holds a seat
|
||||
verb its seat marks, without `operator-channel` in `uses` (§7); marks a tool or a verb while every artifact of kind
|
||||
`bundle` it builds is run by one of its `process` resources (`artifact`), so its own program answers its tools and
|
||||
no tool runner loads or gates them (a module such as the forge's, whose tools bundle no process runs, is not
|
||||
refused); or names in `needs-approval` one name that is both one of its tools and one of its events. A tool mark
|
||||
is checked against `tools` where the manifest lists its tools; where only its bundle names them, the tool runner
|
||||
checks each marked tool against the tools the bundle registers at load: a bundle whose marks name a tool it does not
|
||||
register is not loaded, so no tool of that module is served on that node at all, failing closed rather than serving
|
||||
unmarked what the manifest meant to mark, and that is its
|
||||
finding `<node>.<module>.mark-unserved` (said as §16's), cleared when a bundle that serves it, or a manifest without
|
||||
the mark, is loaded. Each refusal names the entry and the rule. *Why:* a mark
|
||||
nothing enforces would be a call that runs unasked while saying it is guarded; ADR 0286 §43 had the controller
|
||||
enforce its own verbs, and no other own-process server has a gate, so such a mark is refused rather than ignored;
|
||||
and an entry that names two things is read one way by one reader and another way by the next (the design's choices
|
||||
under ADR 0286 §4, §40, the refusal kept by lead-47 on 2026-11-11). *How it is checked:* unit, one row per refusal
|
||||
in `judge/rules_test.go`'s `TestRulesManifests` (Q-T), rows that accept the zsh and forge manifests of §21, and a row of §22's mark table for
|
||||
`mark-unserved`.
|
||||
|
||||
## Where the marks are read, and where they are withheld
|
||||
|
||||
5. **From the catalog half on the bus, never from a bundle.** The indexer's catalog graph entry of a module (design 47
|
||||
G §174) carries the validated manifest whole (design 47 G §443), its `needs-approval`, its `emits` and its streams
|
||||
(their events as the manifest spells them, and whether each carries a `grant`) among it; mesh-sdk's `buscompose`
|
||||
exports `WirePattern`, which a reader uses to turn a stream's events into the bus's subjects, the grants part's own
|
||||
copy going (Q-WP). No part but the indexer reads a manifest: every other part reads the entry of the build concerned
|
||||
from the catalog half (design 47 G §442; the operator, 2026-11-11: "only the indexer uses the manifest to build the
|
||||
module catalog"; Q-MR), the grants part's `declaredFor` among them for every grant, not only the marks; the seat
|
||||
definitions of the catalog half carry the verb marks (§2). The tool runner, which from G takes its first view of the
|
||||
catalog half by `mesh-catalog.revision` and follows `catalog-revision` (design 47 G §196), derives from it, for each
|
||||
module it serves, its marked tools and verbs, in a package of its own, `node-tools/internal/approval`, again on
|
||||
every revision it hears. No module's name is written in its code. The marks follow the running version: a bundle's
|
||||
served version is the fingerprint its composed environment carries (design 47 G §424), whose entry the runner reads
|
||||
by `mesh-catalog.entry` (design 47 G §443) when it loads the bundle; it applies that entry's marks and, while the
|
||||
newest revision's entry for that module differs, the union of both, so a mark is never lost before or after the
|
||||
module moves, and a delivery that drops a mark while the old bundle still runs is still asked. When the served
|
||||
entry cannot be read, every marked call on that runtime is refused (§19). A mark added while a call waits applies
|
||||
from the next call, which the answer of §12 says. *Why:* ADR 0286 §5 had the controller issue the mark in a
|
||||
membership so that a bundle could not drop it; no controller issues memberships now, and the judged catalog half is
|
||||
the same guarantee: the bundle never carries the mark (ADR 0656 §6, one source for which modules exist). The union
|
||||
keeps ADR 0286 §3's "marks add up" across a version change. The served version is read from what the bundle runs,
|
||||
not from a placement, which can change before the bundle does (step 0b). *How it is checked:* unit, a table test in
|
||||
`approval` derives the marks from fixture catalog halves and served entries, §3's row, a version change and the
|
||||
union among them, and a served entry that cannot be read.
|
||||
6. **A machine whose tool runner does not honour the mark serves no marked tool or verb.** The tool runner states
|
||||
`approval-mark/1` among its call capabilities (`runtime.CallCapabilities`), which its announcement carries with
|
||||
the tool runner's own fingerprint, and its node's node-engine takes both into the node's record; the assignment
|
||||
half carries, for each node, its capability readings, each with the fingerprint of the runner that announced it
|
||||
(design 47 G §194; Q-CR). The grants part, composing a runtime's account, reads that runtime's node's readings and
|
||||
the fingerprint the node-engine's `running.<node>.node-tools` last said running (design 47 G §309): where the tool
|
||||
runner does not state `approval-mark/1`, where the node has no readings, or where the readings' fingerprint differs
|
||||
from the running one or is absent (an older reading, failing closed as §19; no time is compared), it composes a
|
||||
subscribe deny of every marked subject of every module the runtime carries, the marks read from the catalog half
|
||||
as §5 has them: `mesh.mod.<module>.tool.<tool>` and the same followed by `.*`; the caller-named subject mesh-sdk's
|
||||
`process.Call` sends on first, `mesh.mod.<module>.call.<tool>.>`; for a marked verb of a seat held per node
|
||||
`mesh.seat.<seat>.tool.<verb>`, the same followed by `.<node>`, and `mesh.seat.<seat>.call.<verb>.<node>.>`; and for
|
||||
a marked verb of a seat held once for the mesh `mesh.seat.<seat>.tool.<verb>` and `mesh.seat.<seat>.call.<verb>.>`,
|
||||
since its subjects name no node (Q-DL). The bus's deny outranks its allow of `mesh.mod.<module>.tool.>` for each.
|
||||
Going back to an older tool runner on a node leaves the window until its readings update, in which the old deny or
|
||||
none stands; that window is accepted. `Permissions` gains `SubscribeDeny`, written into the user list as the
|
||||
server's subscribe deny, as `PublishDeny` is for publishing; the user list's `User` gains `subscribe_deny` beside
|
||||
`publish_deny`, in its comparison and in the differences the grants part says (Q-SD). An added `subscribe_deny` is a
|
||||
narrowing: the user-list guard lets it pass at once, on the first composition after the release, and counts it as
|
||||
no right lost. The withheld tools and verbs are named in the grants part's read of that account
|
||||
(`mesh-grants.grants_user_list`, a field `withheld` with each subject and "its tool runner does not state
|
||||
approval-mark/1"), and in the node-engine's `composition` of the module, which reads them through a verb `withheld`
|
||||
that the seat `mesh-grants`'s definition gains: class `read`, callers the node-engine, persons and the operator,
|
||||
input `node`, answer each module's withheld subjects and why, with a fixture in the definition; the node-engine's
|
||||
manifest gains `mesh-grants` in `uses`; when the verb does not answer, the composition says "withheld unknown: the
|
||||
grants part does not answer"; never read from the grants part's state (Q-NE). *Why:* ADR 0286 §7, §8, §9: the bus
|
||||
enforces the mark; the runner's status of ADR 0286 §7 is read from the grants part's account read, since a runner
|
||||
that does not honour the mark is a version that cannot say so. A reading is tied to the runner that made it, not to
|
||||
a time, so a clock or a late reading cannot lift the deny (step 0b). A call that reaches no responder because its
|
||||
subject is withheld is answered by the mesh MCP server naming `withheld` and the grants part's read. *How it is
|
||||
checked:* unit, the grants part's composition table: readings lacking the capability, no readings, readings with
|
||||
another runner's fingerprint and readings with none each compose the deny of every subject above, the caller-named
|
||||
ones, a marked seat verb's `.<node>` form and a mesh-wide seat's marked verb among them, each named in `withheld`;
|
||||
readings with the capability and the running fingerprint compose no deny; the release's first composition passes
|
||||
the user-list guard with the deny added; a node gaining `approval-mark/1`, where only its readings change, has the
|
||||
deny removed; unit, the assigner's table: a reading keeps the announcing runner's fingerprint; whole (node-engine),
|
||||
`composition` shows the withheld subjects, and with the grants part down says "withheld unknown"; integration, the
|
||||
grants part's real-bus test: an account composed with the deny is refused delivery of a call on a marked subject
|
||||
while it holds `mesh.mod.<module>.tool.>`, and so is a call sent through `process.Call` on the caller-named
|
||||
subject.
|
||||
7. **The asker's grants are a user's of `operator-channel`.** Because the manifest says `operator-channel` in `uses`
|
||||
(§4), the grants part composes, on the account of the runtime carrying the module, the publish of
|
||||
`mesh.seat.operator-channel.accept.ask.<module>` and `.cancel.<module>`, the hearing of its warrants on
|
||||
`mesh.seat.operator-channel.event.decided.<module>` and the read of the router's record under `<module>.` in the
|
||||
seat's state `asks`, as for every user (ADR 0259 §3). The runner passes the client its own publish and read on that
|
||||
runtime's account (`Client.Publish`, `Client.Record`), never the bundle's. *Why:* ADR 0286 §6. *How it is checked:*
|
||||
unit, a row in the grants part's composition table for a fixture module with a mark.
|
||||
|
||||
## The act, and the first call
|
||||
|
||||
8. **The act is digested with operator-channel's client.** For a call of a marked tool or verb, the tool runner
|
||||
builds an `operatorchannelclient.Act` with `address` (as called), `node` (the node that serves it), `module` and
|
||||
`tool`, or `seat` and `verb`, `build` (the fingerprint the bundle's environment carries, the version it serves, §5; Q-BD), and `arg.<name>` for each
|
||||
argument given, a string as itself and any other value as its JSON in RFC 8785's canonical form; an argument left
|
||||
out is not in the act. Its digest is `operatorchannelclient.ActDigest`. *Why:* ADR 0286 §10, §11; the digest is in
|
||||
mesh-contracts since ADR 0627 §1. The TypeScript twin ADR 0286 named is not built: the tool runner is Go, and no
|
||||
TypeScript asker computes an act. *How it is checked:* unit, rows in `approval` for the act's fields, an omitted
|
||||
argument, a number, an object and a changed build each giving another digest.
|
||||
9. **A call refused without asking.** On a call with no approval for its act, the runner refuses at once, asking
|
||||
nothing, a call whose arguments hold anything `outward.CheckSecret` refuses (mesh-sdk), or whose arguments are more
|
||||
than 16 KiB. *Why:* ADR 0286 §12; mesh-sdk's `outward` is the content rule ADR 0310 §4 moved there. *How it is
|
||||
checked:* unit, two rows of §22's table.
|
||||
10. **One ask, under the module's name.** Otherwise the runner asks through an `operatorchannelclient.Client` whose
|
||||
`Module` is the marked module, whose `State` is the runner's state of §13, and whose new field `KeyPrefix`, which
|
||||
the client in mesh-contracts gains and puts before every key of its own `State` it writes and reads (empty for
|
||||
every asker today), is `<node>.`; never before the router's record it reads through `Record` and `Where`, whose key
|
||||
stays `<module>.<id>` in the seat state `asks`; unless an ask for the same act is
|
||||
open there (§13's `act.` key), in which case it asks nothing new. The ask: `about` the act's digest; `who`
|
||||
`operator`; `expires` 60 minutes after; `on-expiry` "nothing runs; call again to ask anew", since the client's
|
||||
`Check` refuses an authorising ask without it, and no option is taken on expiry (ADR 0286 §22; Q-OC2); two options, `approve` (label Approve) and `decline` (label
|
||||
Decline), both at level `approve`, each binding the act's digest; the headline "Run <tool> on <node>?",
|
||||
"Run <tool>?" when that is longer than the router allows (`HeadlineLength`, 60), and, when that too is longer,
|
||||
the names cut with "…" to fit (Q-HL), ending "Part shown" whenever an argument is not shown
|
||||
whole; the explanation: the first line of the tool's own description, the node, that it was called through the
|
||||
mesh's tools, which cannot tell who called, every argument one per line as "name: value", and the act's
|
||||
fingerprint, its digest's first 24 hexadecimal digits in groups of four. *Why:* ADR 0286 §13, §15, §16, §20 to
|
||||
§23; the client checks the ask (`Check`) before it publishes. *How it is checked:* unit, a row in `approval` holds
|
||||
the published ask's fields against a fixture call, and a second identical call publishes nothing.
|
||||
11. **An argument is shown whole only where it may leave the mesh.** Each argument is shown whole where
|
||||
`outward.Check` passes it, else in its shape with ‹path›, ‹address› or ‹withheld› in place of what it refuses; the
|
||||
arguments are shown whole when they are at most 2000 bytes and 40 lines, otherwise their first 20 lines and 1200
|
||||
bytes, marked "NOT SHOWN IN FULL", and the explanation then says that the whole is read with `mesh_approvals`
|
||||
(§18) on that node, whose fingerprint must be the one on the phone. *Why:* ADR 0286 §17 to §19. *How it is
|
||||
checked:* unit, rows of §10's test with a path, an address and a long argument.
|
||||
12. **The first call answers "Not run", or that the router has not taken it.** The runner waits up to 5 seconds for
|
||||
the router's record of the ask (§14) and answers the call as an error, never as a result. With the record: "Not
|
||||
run: this needs the operator's approval. Asked on <channel> at <time> (ask <id>, fingerprint …); make the same call
|
||||
again within 30 minutes of the approval; the ask expires at <time>." With no record within the 5 seconds: "Not
|
||||
run, asked; not yet taken by the router (ask <id>, fingerprint …); make the same call again later." The ask stays
|
||||
in the seat's stream of accepts, a work queue the router takes it from when it runs, and the `act.` key stays, so
|
||||
the next call reads the router's record of the same ask and asks nothing new. A mark added since the caller last
|
||||
called is named in the answer ("marked since <revision>"). *Why:* ADR 0286 §14, §37; the caller calls again, as
|
||||
the operator chose on 2026-11-11 ("Call again (as designed)") over a held call that runs on the Approve and returns
|
||||
its outcome (issue 417, 2026-10-16). *How it is checked:* unit,
|
||||
rows of §22's table hold both answers; a row with no router holds that a second call asks nothing new; a row holds
|
||||
a headline whose names are cut with "…" to 60 characters (Q-HL).
|
||||
|
||||
## The runner's record, and the same call again
|
||||
|
||||
13. **The runner keeps its asks and claims in a state of its own, per machine.** The tool runner, a foundation part,
|
||||
declares a state `approvals` in its manifest's old `state` section, per machine, with a time to live of 172,800
|
||||
seconds (48 hours), which the grants part creates as it creates a foundation part's states (ADR 0619 §2, §3; the bus
|
||||
module refuses a per-machine state in the new form today). Its size is the old form's fixed one, 64 MiB
|
||||
(`StateMaxBytes`), which §18 reads from the bucket, and a value is at most 256 KiB: an ask whose kept call would pass
|
||||
it fails closed, naming the cause (ADR 0595 §3; Q-CAP). A runner whose membership binds no `approvals` serves every
|
||||
unmarked call as before and refuses only marked ones (§19).
|
||||
Its keys: `<node>.ask.<id>`, the asked call as the client keeps it (the ask, the act, the whole arguments, when
|
||||
asked, whether acted on, and `recorded` once `warranted` answered, §16; the client's `Kept` gains `act`, `arguments`
|
||||
and `recorded` for it, Q-KP), and `<node>.act.<digest>`, the id of the
|
||||
newest ask for that act. The runner on each machine is the one writer of its machine's keys; a key is deleted by
|
||||
its time to live, never by hand. A per-machine state reaches today only the key named for its machine; mesh-sdk's
|
||||
per-machine form and the grants part's `stateAccess` gain the keys below it, `<node>.>`, for every per-machine
|
||||
state, a bundle's membership (`stateIssuedFor`, ADR 0260 §18) too, in the same form: one more subject per
|
||||
per-machine state on each account, and every module's per-machine keys keep their form; mesh-sdk's rebuild of it
|
||||
rides in G's group. That size is the bucket's, shared by every machine's keys. A write the bus refuses (the size
|
||||
reached, or any other) fails closed under §19 and is said in the answer; `mesh_approvals` reports the bucket's fill
|
||||
against its size read from the bucket, saying that both are every machine's together. A run still unrecorded (§16) an hour before its key's 48 hours end is written in the runner's log
|
||||
as an error naming the ask; this is written only while the runner runs, so a machine asleep through that hour keeps
|
||||
only the `call-unanswered` history of §16, which is accepted.
|
||||
*Why:* ADR 0286 §24, §30 kept the waiting call in the runner's record and claimed a warrant in the node's own file,
|
||||
kept 48 hours, by one writer per node; the client built since keeps asks and marks a warrant acted on by
|
||||
compare-and-set in a module's bus state (ADR 0259 §10, ADR 0627 §1), and a per-machine state (ADR 0595) is one
|
||||
writer per machine, so the file is not built. The keys below the machine's own depart from ADR 0260 §16 (a
|
||||
per-machine state reaches only its machine's key), the design's choice following the operator's answers of
|
||||
2026-11-11 (the gate built in G); the key form is the design's choice too. *How it is
|
||||
checked:* unit, the manifest test of `node-tools` holds the old-form entry (per machine, its time to live) and no
|
||||
`approvals` in `requires`; a row of §22's table: an ask over 256 KiB refused naming the cause; another: with no
|
||||
binding, an unmarked call runs and a marked one is refused; the grants part's composition table holds the
|
||||
`<node>.>` reach and no other node's; integration, §22's whole test reads the keys.
|
||||
14. **The same call again is judged on the router's record.** When a call of a marked tool comes and `<node>.act.`
|
||||
names an ask, the runner reads the router's own record of that ask under the module's name (`Client.Where`, the
|
||||
seat state `asks`, key `<module>.<id>`, whose value in operator-channel's definition gains the `warrant` it already
|
||||
carries, so the definition names what `Where` reads; Q-OC1), never a record of its own or the caller's; it composes the ask again from
|
||||
the act and holds the warrant to it with `operatorchannelclient.For` (the same asker and ask, the ask's digest, a
|
||||
person's choice, an option offered, at its level, before it expired); it requires the option to be `approve` and
|
||||
the act to be the one it bound (`operatorchannelclient.Performs`: the same node, build and every argument); and it
|
||||
requires the call within 30 minutes of the choice. It judges the newest ask it keeps for the act. *Why:* ADR 0286
|
||||
§26 to §29, §32. *How it is checked:* unit, rows of §22's table: approved, approved for other arguments, approved
|
||||
31 minutes ago, declined, expired, another asker's warrant.
|
||||
15. **One approval runs one call.** Before it runs, the runner marks `<node>.ask.<id>` acted on by compare-and-set
|
||||
(`Client.Take`); a warrant marked before is refused, so one approval runs one call however often the call is made,
|
||||
and a warrant replayed onto the bus runs nothing. After "already run", "expired" or "approved too long ago" the next
|
||||
identical call asks anew, and the used mark stays. *Why:* ADR 0286 §30, §31, §33, §36. *How it is checked:* unit, a
|
||||
row of §22's table makes the approved call twice and runs it once; two calls at the same moment run it once.
|
||||
16. **The call runs, and its warrant is recorded, across a restart too.** Once the checks pass, the runner runs the
|
||||
call and answers with the tool's own answer and a line naming the approval ("run on the operator's approval via
|
||||
<channel> at <time>, ask <id>"). It then calls `mesh-hand-acts.warranted` with `asker` (the module) and `ask` (the
|
||||
id) as a kept call in mesh-sdk's kept-call form (ADR 0668 §1, §2, §7; design 47 G §351, §425: the verb takes it once
|
||||
per ask), the key that keeps it open being `<node>.ask.<id>` without `recorded`, and no event replacing it; it is not
|
||||
`busretry`, which stops after 30 seconds (Q-KC). On its answer the runner writes `recorded` on `<node>.ask.<id>`. At each start the runner calls `warranted` again for every acted, unrecorded ask its keys hold,
|
||||
within their 48 hours, each named in its `findings-stated`'s `pending` until it is made (design 47 G §384). One
|
||||
unanswered after five minutes is the runner's finding `<node>.<ask id>.call-unanswered` in its state `findings`,
|
||||
said on its stream `node-tools-findings` (design 47 G §383, §387), which the condition keeper hears; it clears when
|
||||
`warranted` answers. *Why:* ADR 0286 §34, §35; the hand-act log is the seat `mesh-hand-acts`, whose `warranted`
|
||||
reads the router's own record, and its "again every ten minutes for a week" is ADR 0668's kept call now, kept
|
||||
across a restart by the `recorded` mark (ADR 0668 §3, ADR 0564). *How it is checked:* integration, §22's whole test
|
||||
reads the hand-act recorded once, also when the runner is restarted after the run and before `warranted` answers;
|
||||
unit, §18's row of `record`.
|
||||
17. **What a later call is told.** For the same call made again before it can run, the caller is told, as an error:
|
||||
still waiting; declined (the same act is not asked again for 10 minutes, and the answer says so); expired,
|
||||
approved too long ago or already run (each asks again, §15); not yet taken by the router (§12); or refused, with the
|
||||
router's words. An ask the router holds back as the asker's fourth open one is still waiting, and
|
||||
`operator-channel.asks` lists it (ADR 0666 §12, as G builds it). *Why:* ADR 0286 §38, §39; ADR 0286 §40's refusal
|
||||
of a fourth open ask was the router's before G, which now offers a fourth and holds the oldest back. *How it is
|
||||
checked:* unit, rows of §22's table.
|
||||
18. **The tools read and steer what was asked.** The mesh MCP server on each machine gains the tool `mesh_approvals`:
|
||||
with no argument, the calls asked on that machine in the last day, newest first, each with its state and
|
||||
fingerprint, read from §13's state, and the marks it applies, each module's marked tools and verbs with their
|
||||
source (its module's entry in the catalog half or its seat's definition) and the catalog revision read; given an ask's id, its whole
|
||||
arguments and the act's digest; given `record` and an ask's id, it asks `warranted` again for that one ask by hand,
|
||||
in the kept-call form of §16, recorded in the hand-act log first; given `cancel` and an ask's id, it takes that open ask back (`Client.Cancel`),
|
||||
recorded first in the hand-act log through `mesh-hand-acts.hand-act` with why, the verb taking who from the caller
|
||||
it proves (Q-HA), and refused when the record
|
||||
cannot be written. `mesh_describe` answers, for a marked tool or verb, `needs-approval` with its source. *Why:*
|
||||
ADR 0286 §25 (the runner's `approvals`), named as the mesh MCP server's own tools are; a tool reads everything a
|
||||
module holds and does by hand what it does by itself (the operator's instruction of 2026-10-25). *How it is
|
||||
checked:* unit, rows in the mesh MCP server's tool table test: the list, the marks with their sources, an ask read
|
||||
whole, a `record` that asks `warranted` again for one ask, a cancel recorded and one refused without a record, and
|
||||
`mesh_describe` of a marked tool.
|
||||
|
||||
## Failing closed
|
||||
|
||||
19. **Nothing runs when a part of the check is missing, and a stuck gate is said.** A call of a marked tool is
|
||||
refused, runs nothing and says why when: no module holds `operator-channel` (read from the assigner's first view
|
||||
`mesh-delivery-assigner.views.seat-holders`, which the tool runner consumes; the seat list names no holders); the
|
||||
served entry of the module cannot be read (§5), which refuses every marked call on that runtime; the
|
||||
router refuses the ask; the ask cannot be published; the router's record cannot be read; §13's state cannot be read
|
||||
or written, in which case no marked call runs on that machine until it can, the failed read or write being made
|
||||
again in the kept-call form (ADR 0668 §7; design 47 G §351), kept open by the failing key, until it succeeds; or the marks are not yet read. Before
|
||||
the first view of the catalog half is read, a call waits for it within its 5 seconds and is then judged, asking
|
||||
nothing while the marks are unread; still unread, it is refused, naming `mesh_first_views`. When §13's state has
|
||||
been unreadable or unwritable for five minutes, the runner raises its finding `<node>.approvals.approvals-unreadable`
|
||||
(said on `node-tools-findings` as §16's), and clears it when the kept call succeeds; its `findings-stated` at a start
|
||||
judges it anew from a read of the state (design 47 G §384). Each refusal is
|
||||
written in the runner's log with the ask's id and fingerprint, never the arguments. *Why:* ADR 0286 §40, §42; a
|
||||
gate that refuses every call for a reason nobody hears is a silent fault, and the finding and its five minutes are
|
||||
the design's choice. *How it is checked:* unit, one row of §22's table per case, the holder read from a fixture
|
||||
`views.seat-holders` with none and with one; a row where the served entry cannot be read and every marked call on
|
||||
that runtime is refused; and a row where the state fails, the finding is raised after five
|
||||
minutes, the state recovers with no further call, and the finding clears.
|
||||
20. **The terminal is not exempt.** A marked tool called through `mesh-cli` or any caller asks as every call does: the
|
||||
runner cannot tell who called. *Why:* ADR 0286 §41. The gate wraps every handler the runner serves, a bundle's
|
||||
tools and seat verbs and the runner's own tools beside them (the ledger repairs) alike (Q-LR); the act, the ask,
|
||||
the check, the claim, the recording and the refusals of §8 to §19 are all in `node-tools/internal/approval` (Q-PK).
|
||||
*How it is checked:* the code review reads that the gate sits on every handler, before the bundle, and that no path
|
||||
to a bundle or a runner's own tool passes around it; unit, a row of §22's table: a runner's own tool marked in a
|
||||
fixture is refused unasked, never run.
|
||||
|
||||
## Marked first
|
||||
|
||||
21. **`node-login-shell.execute` is marked in its seat's definition.** mesh-contracts' `seats/node-login-shell/seat.json`
|
||||
marks the verb `execute` with `needs-approval`; the zsh module's manifest (mesh-catalog `modules/zsh`), which holds
|
||||
the seat, gains `operator-channel` in `uses`. The control-node keeps withholding `execute`, and whether it serves
|
||||
it again under the mark stays the operator's setting. *Why:* ADR 0286 §44, §45: the reads it carried have their
|
||||
tools on every node (the seat `node-reads` of issue 418, resolved 2026-10-12, which `node-files` replaces whole, ADR
|
||||
0394 §9). *How it is checked:* live, as the acceptance says.
|
||||
- **The forge's two acts on a trunk's protection are marked.** The forge module (mesh-catalog `modules/gitea`)
|
||||
gains a tool `gitea_branch_protection_loosen`, marked in its `needs-approval`, taking `owner`, `repo`, `branch`,
|
||||
`act` (`remove-protection` or `allow-force-push`) and `why` (Q-FG), which alone performs the two acts
|
||||
`gitea_branch_protection_set_full` refuses today: removing a trunk's protection, and allowing a force-push onto
|
||||
a trunk; `gitea_branch_protection_set_full` keeps refusing both and its refusal names the new tool. The manifest
|
||||
gains `operator-channel` in `uses`. No other tool or verb is marked in this milestone. *Why:* the forge's own
|
||||
refusal says these two acts are the operator's and waited for this mark (issue 417, 2026-10-28); a mark is on a
|
||||
whole tool (ADR 0286 §1), so the acts move to a tool of their own, as ADR 0286 §48 splits a tool into an unmarked
|
||||
and a marked form, and tightening a protection stays unasked; the operator, 2026-11-11: "Mark them in G". The
|
||||
principle: loosening a trunk's protection runs only on the operator's own warrant for that one act, never on a
|
||||
session's word. Its bound: a process that reads the forge module's own credential speaks to the forge directly
|
||||
and goes around it, the same known limit as issue 1966 note 193; the prepare reads that credential's mode and
|
||||
owner on the forge's node with `<node>/node-files.stat` (§21a) and writes them in the milestone file. *How it
|
||||
is checked:* the forge module's tool test: `set_full` refuses both acts naming the new tool; the new tool
|
||||
performs each; and live, as the acceptance says.
|
||||
21a. **`node-files.stat` answers a mode under a directory the account cannot enter.** The seat `node-files`' holder
|
||||
(mesh-catalog `modules/mesh-files`) answers `stat` of a path its account cannot reach through `sudo -n` with its
|
||||
fixed `/usr/bin/stat`, as its writes already go (ADR 0394 §2), answering the path's mode, owner, group and kind and
|
||||
nothing of its content; the secret places ADR 0394 §7 names are stated, never read. Its README's "not yet through
|
||||
sudo" loses `stat`. *Why:* the prepare could not read the forge credential's mode (step 0b); ADR 0394 §2 has the
|
||||
holder use sudo where it must. The lead's handover named the retired `node-reads` (ADR 0394 §9), whose successor this
|
||||
is. *How it is checked:* unit, a row in mesh-files' `Stat` test: a file under a directory of mode 0700 owned by
|
||||
another account answers its mode and owner through sudo, with no content.
|
||||
|
||||
## Tests, delivery and going back
|
||||
|
||||
22. **The tests.** The tool runner's tests in `node-tools/internal/approval` are one table-driven unit test of the
|
||||
refusals and answers (§9, §12, §14, §15, §17, §19) and one of the marks (§5), on the tool runner's test bus
|
||||
(`meshtest`), the router's record and the warrant written there as the router writes them; and one integration
|
||||
test that starts the runner against the real bus, with no store, with a fixture marked module: a call, the
|
||||
operator's Approve written as the router's record, the call again, the run, the hand-act, `mesh_approvals`, and a
|
||||
restart between the run and `warranted`'s answer. The client's `KeyPrefix` (§10) has a row in mesh-contracts'
|
||||
client test: every key of its own `State` written and read carries it, an empty prefix writes today's keys, and
|
||||
with a prefix set `Where` still reads `<module>.<id>`. The mesh MCP server's `TestMeshWaitIsAnnouncedAmongSeven` counts eight
|
||||
tools with `mesh_approvals` (Q-T). In milestone G a module ends with no more tests than it starts with (design 47 G §419, §626): these tests replace
|
||||
existing ones, and every test deleted counts. *Why:* the operator's test
|
||||
budget (one whole test over many small ones). *How it is checked:* the check counts each module's tests against
|
||||
its budget.
|
||||
23. **In G's delivery group, ordered by the module graph.** The members are pushed and checked in design 47 milestone
|
||||
G's group, with design 34's milestone every-event. The orders are the module graph's: the indexer's manifest
|
||||
validator and catalog graph entry that read `needs-approval` before any manifest or seat definition carrying it (a
|
||||
reader edge, design 47 G §89, whose list covers `needs-approval`; Q-RE); and the grants part that composes the deny of §6 before the seat definition marking
|
||||
`execute` (an edge of the seat definition's readers), so no machine serves `execute` unasked between the two; and
|
||||
the grants part serving `withheld` before a node-engine calls it (a served-verb edge, §6).
|
||||
*Why:* the operator, 2026-11-11, "Build the gate in G"; the operator, 2026-11-03, a change is designed as it should
|
||||
be and its delivery is the delivery system's. *How it is checked:* the delivery plan names the three edges.
|
||||
24. **Going back is going forward.** A fault met after the delivery is fixed by a new change delivered the same way.
|
||||
Reverting instead reverts G's whole group in the reverse of §23's order: the seat definition's mark first, then the
|
||||
grants part, then the validator. The `approvals` state is then declared by no running module: it is an orphan
|
||||
`orphans` lists, its keys aging out within 48 hours, dropped through ADR 0595's `drop` on the operator's word.
|
||||
*Why:* design 47 G's revert rule; ADR 0595 §4, §5. *How it is checked:* the delivery plan of a revert names the
|
||||
order.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- **A marked call is asked on the phone, does nothing, and runs once on the Approve** (live, read by tool call):
|
||||
`node-login-shell.execute` on a node other than the control-node answers "Not run", the ask is on
|
||||
`operator-channel.asks`, and after the operator's Approve the same call runs once, a third identical call is refused
|
||||
as already run, and `mesh-hand-acts.hand-acts` names the warranted act (§10, §12, §14 to §16, §21).
|
||||
- **The forge's loosening is asked, and the protection is put back** (live, read by tool call): on a scratch repository
|
||||
`acceptance-approval-mark`, created by the acceptance through the forge's `gitea_create_repo` and kept afterwards,
|
||||
never deleted, with full protection set on its trunk by `gitea_branch_protection_set_full`,
|
||||
`gitea_branch_protection_loosen` removing the trunk's protection answers "Not run" and asks on the phone; after the
|
||||
Approve the same call runs once; then `gitea_branch_protection_set_full` sets the full protection again and
|
||||
`gitea_branch_protection_get` reads it back as it was (§21).
|
||||
- **A dropped mark still asks while the old bundle runs** (live, read by tool call): a delivery that drops a module's
|
||||
mark asks a call of that tool until the node runs the new bundle, read with `mesh_approvals` (§5).
|
||||
- **No machine serves `execute` unasked** (live, read by tool call): before this acceptance passes, on every node
|
||||
`execute` is either asked as above or absent, read with each node's `node-engine.composition` and
|
||||
`mesh-grants.grants_user_list`'s `withheld` (§6).
|
||||
- **A marked wait as the operator sees it** (live): design 34 every-event's acceptance of a wait on a mail event.
|
||||
- **The mark cannot be lost on the way** (unit): §5's and §8's tables pass; a bundle carries no mark.
|
||||
- **It fails closed** (unit): §19's rows pass.
|
||||
- **The validator refuses a mark nothing enforces** (unit): §4's rows pass.
|
||||
@@ -1,9 +1,12 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog]
|
||||
updated: 2026-10-13
|
||||
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog, mesh-contracts, mesh-delivery]
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0668"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0347"
|
||||
- "ADR 0345"
|
||||
- "ADR 0344"
|
||||
@@ -15,6 +18,11 @@ decisions:
|
||||
- "ADR 0253"
|
||||
- "ADR 0234"
|
||||
- "ADR 0227"
|
||||
- "ADR 0627"
|
||||
- "ADR 0597"
|
||||
- "ADR 0595"
|
||||
- "ADR 0310"
|
||||
- "ADR 0268"
|
||||
---
|
||||
|
||||
# 46 — The conversation with the operator
|
||||
@@ -221,7 +229,20 @@ when the title and the note alone do not fit, and says that too.
|
||||
|
||||
### Limits, batching, history
|
||||
|
||||
- An asker holds **at most three open asks**; a fourth is refused in words.
|
||||
- An asker holds **at most three open asks**; a fourth is refused in words. *Amended 2026-11-10, ADR 0666 §12, from the
|
||||
delivery of design 47's milestone G:* **no ask is hidden, and the newest is never held back.** An asker still has at
|
||||
most three asks offered at once, but a fourth is offered, not refused: the oldest of its offered asks is held back
|
||||
instead, kept open with its answer tickets withdrawn, its message edited to say it waits behind newer asks of the
|
||||
same asker, and offered again, with new tickets, as soon as one of the three ends. An ask with the same `about` as an
|
||||
open one of the same asker replaces it (§10), so a held-back ask is never one a newer ask already stands for. `asks`
|
||||
lists each asker's held-back asks with since when, and the router's tool `ask-again` offers one now, holding back the
|
||||
oldest offered one in its place. A Decline ends an ask like any answer, and its asker may ask again at once: the
|
||||
pipeline runner's verb `ask-again` asks a declined `approval` step so (design 47, *Approval is a step*). Why the
|
||||
oldest: the newest is what the asker needs now, and the oldest has had the operator's eye longest; holding it back
|
||||
edits its message rather than removing it, so the operator still sees it waits. *How it is checked:* the router's
|
||||
whole-module test offers a fourth ask and holds back the oldest, offers the held-back one again when one ends,
|
||||
replaces an open ask by a newer one with the same `about`, and offers an ask after a Decline; live, a fourth ask and
|
||||
a declined approval each reach the operator (design 47, test 71).
|
||||
- Asks count against the router's hourly cap, as messages do; answers do not.
|
||||
- Asks to one channel within the burst window go out together under a heading ("3 questions waiting"),
|
||||
each as its own message, answerable and editable alone.
|
||||
@@ -424,7 +445,8 @@ what it does in plain words, its assurance level (`acknowledge`, `approve`, `des
|
||||
answer (`operator`); an expiry; what happens on expiry, in words; and optionally what it is about (a
|
||||
condition's key), so a newer ask about it replaces the older. It is refused at once, on its asker's
|
||||
`decided` subject, when its words break a rule, when no ready channel can carry one of its options' levels,
|
||||
or when the asker already has three open.
|
||||
or, until the delivery of design 47's milestone G, when the asker already has three open; from that delivery a fourth
|
||||
is offered and the asker's oldest held back (§6, *Limits, batching, history*; ADR 0666 §12).
|
||||
|
||||
**A warrant** carries the ask's id and asker, the option, `by` ("the operator, via telegram, user id
|
||||
verified"), the channel module, the proofs, the level met and when. No secret. An ask that ends without one
|
||||
@@ -545,43 +567,27 @@ said on the away channel, with its why.
|
||||
|
||||
### A call that needs the operator's approval
|
||||
|
||||
*Added 2026-10-10, ADR 0286.*
|
||||
A module's manifest marks a tool, or a seat verb it serves, as **`needs-approval`**; a seat's definition may mark
|
||||
a verb for every holder. The controller issues the mark in the runner's membership, never from the bundle, and
|
||||
withholds a marked tool from a runner that does not declare `approval-mark/1`, by leaving its subject out of that
|
||||
runner's bus permissions.
|
||||
*Added 2026-10-10, ADR 0286; amended 2026-11-11 to the mesh as built and as design 47's milestone G leaves it.*
|
||||
A module's manifest marks a tool, or a seat verb it serves, in **`needs-approval`**; a seat's definition may mark
|
||||
a verb for every holder. The tool runner reads the marks from the catalog half on the bus (seat `mesh-catalog`), never from
|
||||
the bundle, and on a machine whose tool runner does not state that it honours the mark the grants part withholds every
|
||||
marked subject from its account.
|
||||
|
||||
```
|
||||
agent ──call──► tool runner (serving node) ──ask, under the module's name──► router ──► Telegram
|
||||
agent ──call──► tool runner (serving node) ──ask, under the module's name──► seat operator-channel ──► Telegram
|
||||
▲ │ "Not run: asked, ask <id>" │ Approve
|
||||
│ ▼ ▼
|
||||
└──same call──► runner reads the router's record ◄────────────── warrant (decided.<module>)
|
||||
│ Warrant.For + Option.Performs on the act; within 30 min; claimed once
|
||||
│ For + Performs on the act; within 30 min; claimed once in its own state
|
||||
▼
|
||||
runs the call once ──► mesh-controller.warranted (hand-act log)
|
||||
runs the call once ──► seat mesh-hand-acts, warranted
|
||||
```
|
||||
|
||||
- **The act** is the address, the node, the module and tool, the bundle's build and every argument given, digested
|
||||
with the shared library's `ActDigest` (in Go and TypeScript, one set of vectors).
|
||||
- **The first call asks and does nothing.** One ask per act (its `about` is the act's digest): "Run <tool> on
|
||||
<node>?", the tool's first line, every argument whole where it may leave the mesh or in its shape with
|
||||
‹path›, ‹address› or ‹withheld› and "Part shown" (whole when at most 2000 bytes and 40 lines, else the
|
||||
first 20 lines and 1200 bytes), the fingerprint, and where the whole is read (the runner's
|
||||
`approvals` on that node). Approve and Decline, both at approve; expiry 60 minutes; never a default. The call
|
||||
answers as an error: "Not run", with the ask's id, that the call runs within 30 minutes of the approval, and
|
||||
when the ask expires.
|
||||
- **The same call again** runs once on Approve, read from the router's record, within 30 minutes of the choice,
|
||||
claimed in the node's own file (kept 48 hours, one writer per node); the runner judges the newest ask it keeps
|
||||
for the act, and after "already run", "expired" or "approved too long ago" the next identical call asks anew;
|
||||
then `warranted` records the hand-act. Anything else is
|
||||
refused and said: waiting, declined (not asked again for 10 minutes), expired, already run, or the router's
|
||||
refusal.
|
||||
- **Fail closed:** a secret's shape in an argument or more than 16 KiB of arguments, no operator channel, a
|
||||
fourth open ask, an unpublished ask, an unreadable router record, node file or mark: nothing runs.
|
||||
- **The controller** enforces the same mark on its own verbs, asking as `mesh-controller`.
|
||||
- **Marked first:** `node-login-shell.execute` on every node once the reads it carried have tools (issue 418).
|
||||
`mesh-delivery.release` waits until a warrant the controller holds passes the mark of a verb it calls (issue
|
||||
417), so a Release on a condition is not asked twice.
|
||||
The first call asks and does nothing; the same call made again within 30 minutes of the operator's Approve runs once;
|
||||
everything else is refused and said, and anything the check cannot read runs nothing. `node-login-shell.execute` is
|
||||
marked first. Every line of it is in milestone 7's part,
|
||||
[`46-milestones/7-approval-mark.md`](46-milestones/7-approval-mark.md), built and delivered with design 47's milestone
|
||||
G (the operator, 2026-11-11).
|
||||
|
||||
### The hand-act
|
||||
|
||||
@@ -696,12 +702,13 @@ 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 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 |
|
||||
| **3 — The seats, and the router** | the kinded bench in the seat list; `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 list, 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 catalog 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 |
|
||||
| **5a — Asks on the bus** (ADR 0259) | the kinded bench, caller-named verbs, `proof` subjects and the records users read (controller); a bundle's seat traffic, compare-and-set state and its own account (tool runner); the asker's and channel's types (shared library); the router's asks, tokens, identities, judging, warrants, expiry and linking with its hour (messenger); `telegram` and `desk-channel`; the controller asking for its conditions' actions and acting on warrants | mesh-controller, mesh-tools, mesh-sdk, mesh-catalog | a held delivery released from the phone; the hand-act reads "the operator, via telegram, user id verified"; a desk Silence goes through an ask |
|
||||
| **5 — Authorise, with TOTP** | `authorises` in the verb table; the router's checks for the code; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the TOTP seed as the router'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 (built in 5a) with replies and forced-reply codes, under its own account on the control node; 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; rehearsals: an approve and a destroy answered on the phone, with the hand-acts read |
|
||||
| **7 — A call that needs approval** (ADR 0286) | `needs-approval` in the manifest and the seat set, issued in the membership, withheld from a runner without `approval-mark/1` in its bus permissions (controller); the act's digest in TypeScript beside Go, with shared vectors (shared library); the check, the ask, the runner's `approvals`, the node's used file and `warranted` (tool runner); the controller's own verbs; `execute` marked (the controller's seat set) once issue 418's reads exist | mesh-controller, mesh-sdk, mesh-tools, mesh-catalog | a marked call on a node is asked on the phone, does nothing, runs once when made again after Approve, is refused the third time and asked again, and `hand-acts` shows the warrant |
|
||||
| **7 — A call that needs approval** (ADR 0286), delivered with design 47's milestone G | the mark in the manifest and the seat definition, read by the tool runner from the bus; the manifest validator's refusals; the capability `approval-mark/1`; the act, the ask, the check, the claim in the runner's own state, `warranted` and `mesh_approvals`; `execute` marked. Its part is [`46-milestones/7-approval-mark.md`](46-milestones/7-approval-mark.md) | mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog | its part's acceptance |
|
||||
| **8 — No ask hidden** (ADR 0666 §12), delivered with design 47's milestone G | a fourth ask offered and the asker's oldest held back, offered again when one ends; `asks` lists the held-back asks; the router's tool `ask-again`; the pipeline runner's `approval` asks and their `about` | mesh-catalog (the router) | a fourth ask and a declined approval each reach the operator; `asks` shows the held-back one |
|
||||
|
||||
**Switching phase 5a on: the operator's steps, in order** (amended 2026-10-09, ADR 0259). Each line is typed with
|
||||
`nox` (`mesh-cli`) on the control-node, from the operator's own account, which is the controller's terminal
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,419 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0668"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0664"
|
||||
- "ADR 0661"
|
||||
- "ADR 0656"
|
||||
- "ADR 0633"
|
||||
- "ADR 0619"
|
||||
- "ADR 0614"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0593"
|
||||
- "ADR 0584"
|
||||
- "ADR 0573"
|
||||
- "ADR 0567"
|
||||
- "ADR 0564"
|
||||
- "ADR 0548"
|
||||
- "ADR 0448"
|
||||
- "ADR 0429"
|
||||
- "ADR 0398"
|
||||
- "ADR 0363"
|
||||
- "ADR 0283"
|
||||
---
|
||||
|
||||
# 47, milestone G: pipelines per module: registration, watched branches, steps and policy, what holds a delivery, approval, groups, dependencies, a publish
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
## Pipelines per module
|
||||
|
||||
*ADR 0666, ADR 0667; ADR 0595, ADR 0596, ADR 0448 §2, §4, ADR 0548 §5, ADR 0564 §2, §4, ADR 0283, ADR 0656 §4, §6, ADR 0633 §5,
|
||||
ADR 0664 §4, ADR 0398 §2, ADR 0593 §9; research 086, Direction.*
|
||||
|
||||
§1 From milestone G's delivery. A change of any module, the delivery system's own parts included, goes from a commit to
|
||||
the machines through that module's **pipeline**: the pipeline steps its **policy** names, run for each commit or group
|
||||
on each of the module's **watched branches** by the seat `mesh-pipeline-runner`, ordered by the catalog graph's edges
|
||||
between artifacts, with no hand act (ADR 0666 §1). Milestone G is the one milestone that builds it, beside the two
|
||||
graphs, the revision view and the delivery record (ADR 0667 §1). Until G's delivery the sections this one replaces
|
||||
(*Checks*, *A try*, *The Approve and the merge*, the state table and the trunk-only want) hold as written, and they
|
||||
check and deliver milestone G itself (ADR 0667 §2); each of them says so in a line of its own.
|
||||
|
||||
```
|
||||
a session ──merge-pull (seat git)──► forge ──commit on a watched branch──► forge bridge ──► indexer (mesh-catalog)
|
||||
│ registrations; a catalog graph
|
||||
│ per watched branch; the revision
|
||||
│ validated before take-in
|
||||
▼ take-in.taken
|
||||
pipeline runner (mesh-pipeline-runner) ── asks each pipeline step on the serving seat's work queue, hears step-ended:
|
||||
│ lint, unit tests, integration tests ──► the build seats (node-build-go, node-build-node, node-build-image)
|
||||
│ composition ──────────────────────────► the assigner (mesh-wants)
|
||||
│ code review ──────────────────────────► the runner's own verb `review`, from the reviewer's bus account
|
||||
│ approval ─────────────────────────────► operator-channel: accepted kind ask, then decided
|
||||
▼ pipeline-ended (every step's result), keyed by source revision
|
||||
assigner ──► wants from a revision whose required steps passed, on the watched branch a placement chose
|
||||
gate ──► the delivery record keeps the step and pipeline events
|
||||
```
|
||||
|
||||
### A module's registration
|
||||
|
||||
- §2 **What it holds.** The seat `mesh-catalog` keeps each module's **registration**: its git origin, the path of its
|
||||
manifest, its watched branches and its policy, on the bus, every version with who changed it and the warrant of the
|
||||
operator's Approve (ADR 0666 §2). The indexer is a foundation part, so the registrations are kept in its old sections
|
||||
(ADR 0619 §1 to §3, ADR 0666 §4): one key per module in its old `state` section `registrations`, and every version on
|
||||
its old `streams` section's stream `registration-history`, kept with no age limit.
|
||||
The catalog graph is derived from the registrations and the manifests they point to; the mesh knows modules, and a
|
||||
repository only as a module's git origin.
|
||||
- §3 **Changing one is a trusted act.** The seat's verbs `register` (a module whole), `policy` (one module's step
|
||||
statuses, per artifact), `watch` and `unwatch` (add or remove a watched branch) each raise an authorising ask of tier
|
||||
approve through the seat `operator-channel` (design 46 §10), naming every value the change moves, one line each
|
||||
("`mesh-grants:program` unit tests: `required` → `bypass`"), and write the version only on its warrant; a Decline or
|
||||
an expiry writes nothing (ADR 0666 §2). Until a record binds each forge account to its bus identity, `register` and
|
||||
`policy` refuse code review as a `required` step, naming why, so no review gates while its author cannot be told
|
||||
(ADR 0666 §22; issue 1966, note 157). A manifest moved to another path is the one manifest change that changes the
|
||||
registration; any other manifest change is a commit, judged at its revision.
|
||||
- §4 **Said and read.** Each written change is said as the seat's event `registration-changed` (the module, the version,
|
||||
what moved); the verb `registrations` answers every registration, or one with its history. The indexer derives the
|
||||
modules it keeps from them, and the forge bridge its watched branches, each by a first view of `registrations` and
|
||||
then that event, never by reading the ledger (ADR 0666 §3, ADR 0596).
|
||||
- §5 **An unprotected branch is refused.** `register` and `watch` read a branch's protection through the seat `git`'s read
|
||||
verb `branch` (its head, and whether a rule refuses a direct push and admits only merges), and refuse a watched branch
|
||||
the forge does not protect, naming it (ADR 0666 §3).
|
||||
- §6 **The foundation parts are the manifest validator's list.** The manifest validator's list (ADR 0619 §3) holds
|
||||
exactly the foundation parts, ADR 0619 §1's, the runner, which joins them (ADR 0666 §4), and the forge and image
|
||||
registry modules (§584); the checker leaves the
|
||||
list when it is removed (*How milestone G itself is checked and delivered*). A registration holds no foundation
|
||||
value. How it is checked: a unit test of the manifest validator refuses a template in the manifest of every part on
|
||||
its list, the runner's included, and judges every other part's manifest as any part's.
|
||||
- §7 **No part fixes a name: a guideline the code review reads.** (§507) Every branch, repository and module name a part
|
||||
needs is read from the registrations and the catalog graph, never fixed in its code. This is a guideline, not a
|
||||
step: the code review reads it, and no lint step scans for names (the operator, 2026-11-11 about 00:05 CET: the scan was a flaky check; where
|
||||
guidelines live is research 087's). It departs from ADR 0666 §5's refusal by the lint step, by the operator's choice,
|
||||
and a later record brings it in line. The names G's own work removes (about 39 in 9 parts, found on 2026-11-09,
|
||||
research 086, Findings) are removed by its subtasks, each naming the name it removes.
|
||||
- §8 **The first registrations are one ask.** At its first start on G's build the indexer, holding no registration,
|
||||
proposes one registration for every module its trunk revision holds, from what it took in (the origin and path of
|
||||
each), its watched branch the repository's default branch read through the seat `git`, and the statuses the check
|
||||
judged until then: lint, unit tests, integration tests and composition `required`, code review `optional`, approval
|
||||
`bypass`. Each step the artifact's build seat answers `steps` with none is `bypass` instead, so no module is held by
|
||||
a `required` step it has nothing for; the indexer asks `steps` of each build seat as a kept call (ADR 0668 §1, §2),
|
||||
and the seed waits until every build seat has answered, never reading an unanswered seat as "none": a seat still
|
||||
unanswered after five minutes is the indexer's `call-unanswered`, naming the seat (ADR 0668 §3), and the seat's new
|
||||
build serving `steps` is an edge of kind 6 before the seed. A module whose default branch the forge does not protect
|
||||
is left out of the seed and named in the ask, as `register` would refuse it (ADR 0666 §3). It raises the rest as one
|
||||
ask naming every registration, as the seat `mesh-module-assigner`'s `adopt` proposes placement whole (ADR 0567).
|
||||
While the ask is open the indexer's finding `seed-waits`, naming the ask, is raised by the condition keeper as
|
||||
waiting on the operator (ADR 0283) and cleared by the warrant; a Decline or an expiry leaves the seed unwritten, says
|
||||
so and keeps `seed-waits`, naming `indexer_seed_again`, which raises the ask again, recorded in the hand-act log.
|
||||
Until that Approve the runner starts no delivering pipeline and says why, so the operator is told of this one ask
|
||||
before G's delivery starts. Why: the design's choices under ADR 0666 §3, §7 and ADR 0668 (round 1 of the review by
|
||||
architectural domain, 2026-11-10). How it is checked: whole (indexer), a build seat that never answers `steps`
|
||||
holds the seed and raises `call-unanswered`, one that answers none gives `bypass`; an unprotected default branch is
|
||||
left out and named; `seed-waits` is raised while the ask is open and cleared by the warrant.
|
||||
- §9 **Kept for the life of the mesh**, never derived again (research 086, choice 25): the history stream's cap is 64 MiB.
|
||||
About 120 registrations of about 1 KiB, and an estimated change a week each (a guess, not a measure), grow about 6 MiB
|
||||
a year, so the cap holds about ten years; past 80 % the indexer says `filling`, as every store of *Two graphs, one
|
||||
source of truth* does.
|
||||
- §10 **How it is checked.** Whole (indexer): an unprotected watched branch is refused, naming it; a policy change without
|
||||
the warrant writes nothing, and a declined one writes nothing; a change from `required` to `bypass` asks, showing
|
||||
that line; `registration-changed` is said and `registrations` answers the history; the first start proposes the seed
|
||||
as one ask, and `indexer_seed_again` asks it again after a Decline and after an expiry; a policy making code review
|
||||
`required` is refused. Tests 75, 68 and 83.
|
||||
|
||||
### Watched branches and the catalog graph per branch
|
||||
|
||||
- §11 **Only a watched branch starts a pipeline.** (§509) The forge bridge hears every commit of every watched branch the
|
||||
registrations name, through the seat `git`'s event `branch-moved` (a branch's new head, from the forge's push
|
||||
webhook, which a merge and an admitted push alike say), and takes the commit from the forge's own record by the seat's
|
||||
verb `branch`, never from the event's body (ADR 0666 §6). One event for every way a branch moves, because a merge is
|
||||
not the only one. `branch-moved` is kept on the seat's stream `SEAT_git_branch-moved`, which the bridge hears through
|
||||
a durable consumer, so a move said while the bridge is down is heard when it starts; at its start it also reads each
|
||||
watched branch's head the same way.
|
||||
- §12 **A head with no pipeline is a finding.** (§497) A watched branch's head that starts no pipeline within five minutes is the
|
||||
watchman's finding `pipeline-stands`, naming the branch and its head, raised by the condition keeper (ADR 0564 §2),
|
||||
unless it waits as progress not owed: a member of a gathered group is governed by ADR 0633 §5's 30-minute bound, and
|
||||
a merge behind a pending revision of the same branch by its 60-minute bound (ADR 0666 §24). The finding clears on the
|
||||
runner's `pipeline-started` for that head.
|
||||
- §13 **The catalog graph per watched branch.** The indexer keeps the catalog graph once per watched branch, from that
|
||||
branch's manifests at each source revision (ADR 0666 §6): each revision names its branch, and the stores, caps and
|
||||
pins of *Two graphs, one source of truth* hold per branch.
|
||||
- §14 **A placement names the branch.** Each module of a placement key names the watched branch its node runs, by default the
|
||||
registration's first, set by a placement proposal like every other placement change (ADR 0567, ADR 0666 §6). The
|
||||
assigner computes each node's entries from the catalog graph of the branch its placement names, so a module watched on
|
||||
`main` and `beta` runs `beta` only where a placement chose it.
|
||||
- §15 **A source revision per watched branch.** The gate keeps one source revision per watched branch, `source.<branch>`,
|
||||
in place of `source.main` (ADR 0429 §11 to §13 as ADR 0666 §6 changes them), in the old `state` section `source`
|
||||
already holds, its key's layout being the only change. The gate is no foundation part: the bus module's delivery
|
||||
waits on no act of the gate's, since the indexer takes a revision in from the forge bridge's stream and the assigner
|
||||
writes wants from the runner's events, so the gate's kept data are as §613 has them, state by state (ADR 0666 §4), and serves it by the seat
|
||||
`mesh-delivery`'s verb `source` with the branch, saying `source-moved` on each new revision; the indexer and the
|
||||
node-engine's catch-up read it there, never the gate's state (ADR 0596). Each want names a build-input fingerprint
|
||||
computed at one such revision.
|
||||
- §16 **Validated before any take-in, on every branch.** Before it takes a revision in, on every watched branch, the running
|
||||
indexer asks the manifest validator built from the revision's heads to judge it in the build seat's sandbox (the
|
||||
ask `run`, *What ADR 0656 leaves to this design*). A revision it refuses stays pending under ADR 0633 §5's
|
||||
60-minute bound, its hand acts and its condition `take-in-waits`; the way back is a revert, an ordinary merge
|
||||
(ADR 0666 §6, §26). A revision on the branch the indexer's own placement chose is taken in only by the indexer it
|
||||
names (ADR 0633 §1). This validation is no pipeline step and waits on no pipeline, so every delivering pipeline runs
|
||||
on a revision already taken in.
|
||||
- §17 **A branch whose protection was removed starts nothing.** (§497, §509) On every `branch-moved` the forge bridge reads the
|
||||
branch's protection again; a move of a watched branch whose protection was removed starts no delivering pipeline, and
|
||||
the forge bridge writes the finding `branch-unprotected`, naming the branch, which the condition keeper raises and
|
||||
clears once the protection is back (ADR 0666 §6).
|
||||
- §18 **A pending revision holds its own branch only.** A merge waits behind a pending revision only on the same watched
|
||||
branch; other branches move on (ADR 0666 §6, which changes ADR 0633 §3).
|
||||
- §19 **A refused new indexer holds its branch until a revert lands.** A merge of a new indexer whose own validator refuses
|
||||
its revision has already landed when it is refused, since the validation runs before take-in and after the merge
|
||||
(ADR 0666 §6). It stays pending, and every later merge on that watched branch waits behind it, said by
|
||||
`take-in-waits` at the 60-minute bound, until a revert, an ordinary merge through its pipeline (ADR 0666 §26), lands
|
||||
and is taken in; nothing takes a later merge in past it. This is the cost of validating every revision with the
|
||||
validator built from its own heads, the design's reading of ADR 0666 §6, and the operator is told it with the
|
||||
condition, which names the pending revision and the revert as the way on.
|
||||
- §20 **How it is checked.** Whole (forge bridge): a commit of a branch no registration names starts nothing; a commit on a
|
||||
watched branch is taken from the seat's `branch` read, not the event; a head missed while the bridge was stopped is
|
||||
read at its start. Whole (indexer, assigner): a commit on `beta` gives a revision of the `beta` graph only and moves
|
||||
only the want of the node whose placement chose `beta`; a revision whose own validator refuses it stays pending and
|
||||
holds `beta` while `main` moves on. Whole (watchman): a head with no pipeline for five minutes raises
|
||||
`pipeline-stands`, cleared by `pipeline-started` for that head, and a gathered member does not; a move said while the
|
||||
bridge was stopped is heard from the seat's stream. Tests 64, 68, 76, 78 and 82.
|
||||
|
||||
### Pipeline steps and the policy
|
||||
|
||||
- §21 **The runner.** The seat `mesh-pipeline-runner`, held once for the mesh by the module `mesh-delivery-pipeline`,
|
||||
runs each pipeline (ADR 0666 §7). It is a foundation part, since the bus module's own delivery waits on its pipeline
|
||||
(ADR 0666 §4): its kept data are in old sections (ADR 0619 §2), it waits on no bus module process to start, and it
|
||||
joins the manifest validator's list of foundation parts, which refuses its manifest asking a template (ADR 0666 §4,
|
||||
which changes ADR 0619 §1 and §3). It reads the policy from the registrations, asks each step that is not `bypass` of
|
||||
the seat that serves it, and says each step's start and end and the pipeline's end (*Health and going back*, below).
|
||||
- §22 **The steps and who serves them.** Lint, unit tests and integration tests are the build seats' (`node-build-go`,
|
||||
`node-build-node`, `node-build-image`): each gains the verb `steps`, which answers which pipeline steps an artifact
|
||||
can run by its language's convention (as *The check from release 4* says the tests are found), and the accepted kind
|
||||
`step` on its stream `check`, which runs one. Composition is the assigner's, an accepted kind `step` of the seat
|
||||
`mesh-wants`. Code review comes in through the runner's own verb `review`. Approval is asked as the seat
|
||||
`operator-channel`'s accepted kind `ask`, on its work queue by the runner's name (design 46 §10, *Asks, warrants and answers on the bus*;
|
||||
the seat has no verb `ask`), and heard on its event `decided` (ADR 0666 §7, §10). A step with nothing to do ends at once as
|
||||
`nothing to run`, never as passed.
|
||||
- §23 **The policy.** (§553) Each step's status, per module and per artifact, is `required` (runs; holds the delivery until it
|
||||
passes), `optional` (runs; a failure is shown and may be accepted) or `bypass` (nothing runs; said as bypassed),
|
||||
kept in the registration and never in a manifest: the manifest validator refuses a manifest that names a status
|
||||
(ADR 0666 §7; research 086, R3). A `required` step that ends `nothing to run` holds the delivery and names the step.
|
||||
- §24 **Asked on a work queue, ended by an event.** The runner asks each step on the serving seat's work queue and hears
|
||||
its end as that seat's event `step-ended` (the record's `step ended`), on
|
||||
`mesh.seat.<seat>.event.step-ended.<node>`, the node that ran it in the subject and each holder's account granted only
|
||||
its own node's subject, through a durable consumer of its own, so a step outlives a restart of either side (ADR 0666
|
||||
§7, ADR 0363 §2). A build seat's holder restarted mid-step gets the step again from its work queue and finishes it
|
||||
(ADR 0664 §1). A unit or integration test step says its catalog module's test counts in `step-ended`; `pipeline-ended` carries each
|
||||
catalog module's counts against the test budget, which is counted per catalog module (at most 50 integration and
|
||||
500 unit tests each), and `pipeline-ended` names any catalog module over it (ADR 0666 §8; the test budget, per
|
||||
module, the operator 2026-11-11). A step's outcome is one of `passed`, `failed`, `nothing to run`,
|
||||
`bypassed`, `carried` (with the pipeline it was carried from), `could not run` (naming the part not heard),
|
||||
`waiting on <person>`, `accepted` (a failed `optional` step a person accepted, with who and why), `not asked` (an
|
||||
`approval` step after a failed `required` step), `superseded` and `cancelled`.
|
||||
- §25 **Carried, not run.** A step whose build-input fingerprint and policy did not change is carried by its package
|
||||
digests, as ADR 0548 §5 holds, the runner making the carry instead of the checker: a test step runs only the packages
|
||||
with no kept result for their digest, and a run on request's results are reused when the merged commit's inputs match
|
||||
(ADR 0666 §8; research 086, choice 24). An `approval` step is never carried.
|
||||
- §26 **Approval comes last.** The runner asks the `approval` step only once every other `required` step of the pipeline
|
||||
passed, so the operator approves a commit the mesh already judged, the one place ADR 0661 §3 had the check gate the
|
||||
ask. A failure after an approval is said, as a failed `optional` step is, and the approval stays.
|
||||
- §27 **Kept on the bus, in old sections.** The runner keeps each pipeline's state in its old `state` section `pipelines`,
|
||||
one key per pipeline keyed by its watched branch, source revision and module, written by compare-and-set so a holder
|
||||
replaced mid-pipeline and its successor never both write one; its step log on its old `streams` section's stream
|
||||
`step-log`; each step's output in its old `objects` section `step-outputs`, named in the log; and each package's
|
||||
passing result per level in its old `state` section `kept`, under the digest of what its tests read (ADR 0666 §4, §8;
|
||||
*The runner's kept data*, below). `kept` starts empty: the runner takes no verdict of the checker's, since a step's
|
||||
result is taken only from the bus account of that step's seat holder (ADR 0666 §21), so the first pipelines after G
|
||||
run each package's tests once and carry from then on (the design's choice, 2026-11-10).
|
||||
- §28 **How it is checked.** Whole (runner): a step with unchanged fingerprint and policy is carried and says from where,
|
||||
and an `approval` step is not; a module with no integration tests shows `nothing to run`, and a `required` unit-test
|
||||
step on a module with no tests holds its delivery; a bypassed step is said; a runner restarted mid-step hears the
|
||||
step's end and finishes the pipeline; two runners writing one pipeline key, one loses by compare-and-set; a test step's
|
||||
counts are in `step-ended` and a catalog module over budget is named in `pipeline-ended` (§419). Whole (Go build module): `steps`
|
||||
answers the steps of a fixture artifact, a `step` ask ends with `step-ended` on its own node's subject, one on another
|
||||
node's subject is refused by the bus, and a build module restarted mid-step finishes it. Unit (manifest validator): a manifest naming a status is refused. Tests 65, 66, 73
|
||||
and 88.
|
||||
|
||||
### What holds a delivery
|
||||
|
||||
- §29 **The assigner writes a want only from a passed pipeline.** The runner says each pipeline's end as `pipeline-ended`
|
||||
(the record's `pipeline ended`), keyed by source revision, with every step's result. The assigner consumes it, and
|
||||
writes a want only from a source revision whose every `required` step passed for the modules it moves, on the watched
|
||||
branch a placement chose, by compare-and-set on a newer revision, so an older revision ending after a newer one
|
||||
writes nothing (ADR 0666 §9). The compare-and-set is keyed per module and watched branch: it compares a revision only
|
||||
with the last one written for the same module on the same watched branch, so a revision of `beta` ending after a
|
||||
newer one of `main` writes its own want and refuses neither; a later `pipeline-ended` of the same revision (after a
|
||||
passing `rerun`, below) is taken, since its end count is higher (the design's choice under ADR 0666 §9: source
|
||||
revisions are kept per watched branch, `source.<branch>`, and two branches' revisions have no order between them). This is one more rule beside the assigner's others (ADR 0429 §6), checked per module.
|
||||
From G's delivery it replaces the trunk-only want (ADR 0656 §2 as ADR 0666 §9 changes it). For a group's source
|
||||
revision the runner says one `group-ended` once every member's modules have ended, and the assigner writes the
|
||||
group's wants only on it: a member's own `pipeline-ended` writes nothing (ADR 0666 §9).
|
||||
- §30 **A failed `optional` step holds nothing.** It is shown in `pipeline-ended`, in the delivery record and in the runner's
|
||||
`show`; the operator or a session may accept it by the runner's verb `accept` (§394, §610), recorded in the hand-act log with who and why.
|
||||
- §31 **A newer commit supersedes an older pipeline of a module whose fingerprint it changed.** On a newer commit of the
|
||||
same watched branch the runner ends a module's older running pipeline as `superseded` only when the newer commit
|
||||
changed that module's build-input fingerprint (ADR 0666 §9); a module the commit leaves as it was finishes its
|
||||
pipeline. A newer publish of a package likewise supersedes the rebuild pipelines an older publish of it started. A
|
||||
superseded pipeline's waiting asks are dropped by each build seat's `drop` (ADR 0664 §4), naming
|
||||
the newer commit as the cause, so a cancelled pipeline leaves no ask on a stream (a gap G's prepare found: a cancelled
|
||||
build withdrew only the builder's wait).
|
||||
- §32 **A step nothing starts ends its pipeline.** A step whose prerequisite nothing starts (a seat with no holder, a
|
||||
prerequisite step `bypass`) ends the pipeline refused within five minutes, naming the step and what it waits for,
|
||||
never as a silent wait (research 086, R5); a step whose seat has no holder ends its pipeline failed, naming the seat
|
||||
(ADR 0666 §17).
|
||||
- §33 **A failed head is said to its author, then to the operator.** (§540) A `required` step that failed, or ended `could not
|
||||
run`, on a watched branch's head is the runner's finding `pipeline-failed`, naming the module, the branch, the commit
|
||||
and the step; the condition keeper raises it as the commit's author's for two hours and then the operator's, as ADR
|
||||
0505 §5 and §6 have a delivery whose cure is its author's new head, and clears it when a newer revision of that branch
|
||||
passes that step (ADR 0666 §24). The author is the forge's record of the commit, read through the seat `git`.
|
||||
- §34 **The delivery record keeps every step.** The gate's delivery record consumes the step and pipeline events and keeps
|
||||
them as long as the record (ADR 0666 §9; *The delivery record*).
|
||||
- §35 **How it is checked.** Whole (assigner): a failed `required` step writes no want; a failed `optional` step holds
|
||||
nothing; an older revision's `pipeline-ended` after a newer one's writes nothing, and a revision of one watched branch
|
||||
ending after a newer one of another writes its own want; a group with one member passed and
|
||||
another still running writes no want until `group-ended`. Whole (runner): a newer commit that changed a module's
|
||||
fingerprint supersedes its older pipeline and drops its asks, and one that left it unchanged lets it finish; a step
|
||||
with no holder ends the pipeline naming the seat within five minutes; a failed `required` step on a head raises
|
||||
`pipeline-failed`, the author's for two hours and then the operator's, cleared by a newer passing revision. Tests 66,
|
||||
67, 87, 89 and 98.
|
||||
|
||||
### Approval is a step, and a session merges a pull request
|
||||
|
||||
- §36 **No Approve per merge.** The operator's approval is the pipeline step `approval`, its status in each module's policy;
|
||||
no Approve is asked per merge, and the delivery gate's merge turns and its Approve per merge go (ADR 0666 §10, which
|
||||
changes ADR 0661 §3 to §5). The operator is asked only where a module's policy says `approval`, and for registrations
|
||||
and policy changes.
|
||||
- §37 **The approval is an ask of the runner's.** The runner raises an authorising ask of tier approve as the seat
|
||||
`operator-channel`'s accepted kind `ask`, under its own name, its `about` the module and the watched branch only, so a newer commit's ask
|
||||
replaces the older one's (design 46 §10). The warrant ends the step passed; an ask that is declined, expires or is
|
||||
refused ends it `declined`, and one that is replaced ends it `superseded`. Only a declined `approval` step is asked
|
||||
again, by the runner's verb `ask-again` (ADR 0666 §10). While it waits the runner
|
||||
says `wait-held` waiting on the operator, never a stall (ADR 0283).
|
||||
- §38 **A session merges a pull request.** Any session the tool runner proves, and the operator, merges by calling the seat
|
||||
`git`'s verb `merge-pull` over the mesh, which merges with the forge tools' account (ADR 0398 §2) at the head the
|
||||
caller names. It judges that this is still the pull request's head and that the branch's protection refuses a direct
|
||||
push, the protection read again on every `branch-moved` of the branch, and it judges the head's runs by its modules'
|
||||
policies: it refuses a head when any run at its build-input fingerprint failed a step a policy marks `required`, and
|
||||
merges a head whose `required` steps passed or that has no run yet, a failed `optional` or a `bypass` step refusing
|
||||
nothing; it never waits for a run and never asks an Approve (ADR 0666 §11). The forge keeps only its branch protection.
|
||||
- §39 **Judged from its own state, never by a call.** (§487) The holder of the seat `git` consumes the runner's `step-ended`
|
||||
through a durable consumer of its own and keeps, in its own state `failed-steps`, each failed `required` step by
|
||||
module and build-input fingerprint, with the commits it was seen at and the run that failed it, until none of those
|
||||
commits is a watched branch's head or the head an open pull request names (*The runner's kept data*); so a head
|
||||
merges while the runner is down, judged on what it already heard (ADR 0666 §11, ADR 0596). A head's fingerprints are
|
||||
those its runs named for its commit (kept as §475 says); a step that failed at that fingerprint in any run, a group's or another commit's
|
||||
of the same content included, refuses it, unless a later run of that same step at that fingerprint, asked again by
|
||||
the operator or the healers (the runner's `rerun`), passed: that pass clears the failure from `failed-steps` (ADR 0666
|
||||
§11). The runner's `step-ended` says that the step was a rerun and the caller the runtime proved, and the forge module
|
||||
clears a failure only for a rerun whose caller is the operator or a healer; a plain pass, or a rerun asked by anyone
|
||||
else, clears nothing. A run still running is judged by the steps it has ended, since the merge never waits. A refusal names the run, the module and the step, so the session knows what to fix. The gate merges nothing, and its forge merge token is
|
||||
removed with the checker's buckets and bus account, after the first pipeline after G's delivery has delivered a change
|
||||
end to end and on the operator's word (ADR 0667 §12; *How milestone G itself is checked and delivered*); the forge bridge hears the landed commit as it hears every commit of a watched
|
||||
branch.
|
||||
- §40 **The operator-channel hides no ask.** It keeps design 46's three open asks per asker, never holds the newest back,
|
||||
and replaces an open ask by the same asker's ask with the same `about` (ADR 0666 §12; design 46 §6).
|
||||
- §41 **How it is checked.** Whole (forge module, forge bridge): a session's `merge-pull` at its head merges with no run yet,
|
||||
and with a passed one, and asks no Approve; with the runner stopped a head merges; a head at whose fingerprint any
|
||||
run failed a `required` step, a head that moved and a branch whose protection was removed since its last move are
|
||||
refused, each naming why; a failed `optional` step refuses nothing. Whole (runner): an `approval` step is asked last,
|
||||
a Decline and an expiry each end it `declined` and `ask-again` asks it once more, and a newer commit's ask replaces the
|
||||
older and ends its step `superseded`.
|
||||
Live: a declined approval and a fourth ask each reach the operator. Tests 71 and 91.
|
||||
|
||||
### Groups
|
||||
|
||||
- §42 **Formed from the branch name.** The gate forms a group from a shared branch name carrying its issue number, as it
|
||||
does today without the fixed list of trunk names (ADR 0666 §13).
|
||||
- §43 **A member reaches its group by landing.** A member has reached its group when its commit lands on its modules'
|
||||
watched branch. The forge bridge sets the group on each landed member's commit, reading the members from the seat
|
||||
`mesh-delivery`'s verb `groups`, and publishes `forge.group.merged` when that verb says every member has (ADR 0666 §13,
|
||||
which changes ADR 0633 §4).
|
||||
- §44 **One source revision, one pipeline per branch.** The indexer keeps landed members pending until the group's end and
|
||||
takes them in together as one source revision of that watched branch, saying `take-in.taken`; the runner starts the
|
||||
group's one pipeline per watched branch on that event, and a member of a gathered group starts no pipeline of its own
|
||||
(ADR 0666 §13).
|
||||
When one member's `required` step fails, none of the group's modules is delivered (research 086, R5).
|
||||
- §45 **A group that never completes is said.** By ADR 0633 §5's 30-minute bound and its condition; `bridge_group_end_again`
|
||||
says a group's end again from the gate's group verb, never the gate's `merges` state (ADR 0666 §13, which changes ADR
|
||||
0633 §5).
|
||||
- §46 **How it is checked.** Whole (indexer, gate, forge bridge): a group of three repositories, its own packages included,
|
||||
is one source revision and one pipeline; one member's failed `required` step delivers none; a group with a member that
|
||||
never lands raises `take-in-waits` at 30 minutes. Tests 67 and 81.
|
||||
|
||||
### Dependencies between artifacts
|
||||
|
||||
- §47 **Six kinds of edge between artifact references.** The catalog graph's edges run between **artifact references**,
|
||||
`<module>:<artifact>`, such as `mesh-grants:program`, of six kinds (ADR 0666 §14):
|
||||
|
||||
| Kind | B before A | Derived from |
|
||||
|---|---|---|
|
||||
| 1 | B published before A builds | a package A's build source imports (ADR 0573 §3) |
|
||||
| 2 | B published before A delivers | an artifact A's manifest fetches by reference at its start |
|
||||
| 3 | B delivered before A builds | a build module A is built by, and the toolchain its agreed version names (ADR 0584 §1) |
|
||||
| 4 | B delivered before A delivers | a provision A requires and B provides, bus state among them (ADR 0614 §5), and a format's reader (below) |
|
||||
| 5 | B has acted for A's new version | a seat's acting rule (*What ADR 0656 leaves to this design*): a grant, a credential, a verdict, bus state |
|
||||
| 6 | B runs a version that serves A | a verb or a manifest word A uses and B serves or reads (ADR 0614 §2, §4), the build seats' `kept-artifact` among them |
|
||||
|
||||
Each kind is derived from what manifests and seat definitions already say, and declared per artifact in the manifest's
|
||||
artifact entry, its field `needs`, only where nothing else says it. Today's edges run between modules, and only a
|
||||
package edge names an artifact; at module level the tool runner and the grants part looked like a cycle, per artifact
|
||||
they are a chain (`mesh-grants:program`, then `node-tools:runtime`, then `mesh-grants:tools`).
|
||||
- §48 **Removal runs the other way.** When something goes away the order is reversed: a seat moved to a new holder has its
|
||||
old holder released before the new one claims (ADR 0666 §14).
|
||||
- §49 **The edges alone order.** (§569) The build and the delivery order by these edges alone; a cycle of delivery edges
|
||||
(kinds 2, 4, 5, 6) alone is delivered as one step, and a cycle with any build edge (kind 1 or 3), whatever its other
|
||||
edges, is refused, naming every artifact on it (ADR 0666 §14; research 086, R17). The cycle rule is the indexer's, in the catalog graph it keeps, so the checking
|
||||
program's cycle rule goes with the checker.
|
||||
- §50 **A format's reader first.** A format a running reader cannot read is preceded by its reader, an edge of kind 4 on
|
||||
every watched branch (ADR 0666 §14): the indexer's manifest validator for a new field of a seat definition or a
|
||||
manifest, and on each node the node-engine that reads a catalog graph entry's new fields before any manifest that writes them
|
||||
(ADR 0667 §4).
|
||||
- §51 **How it is checked.** Unit (indexer's graph): a chain per artifact where modules would cycle; two programs needing
|
||||
each other's new verb are one step; two artifacts each needing the other's new package to build are refused, naming
|
||||
both; a seat moved to a new holder releases the old first. Live: the tool runner and the grants part delivered in
|
||||
the order `mesh-grants:program`, `node-tools:runtime`, `mesh-grants:tools`. Test 79.
|
||||
|
||||
### A publish rebuilds
|
||||
|
||||
- §52 **A version is reserved before the build and released when every artifact is published.** A module's version
|
||||
is its manifest's `version`, `MAJOR.MINOR`, and a patch the build system gives: the next after the highest version
|
||||
the catalog holds released for that `MAJOR.MINOR`, 0 for one it holds none of. The builder reserves it when the
|
||||
module's build starts and passes it to the build of every artifact of the module (§370), so code and packed files
|
||||
carry it; each artifact published is said as the event `package-published` of the builder's seat `mesh-builds`
|
||||
(ADR 0666 §15), and the version is released only once every artifact of the module at it is published (§349); only the first watched branch releases (§371). A
|
||||
failed publish leaves the version reserved, and `builder_publish_again` publishes the missing artifacts under the
|
||||
same version. The seat `mesh-builds` is new, held once for the mesh, defined in `mesh-delivery-contracts`; the
|
||||
builder held none until now, and its waits stay on its own stream in this milestone. Why: the operator, 2026-11-10
|
||||
about 21:40 CET, a design choice of G's part (gaps items 22, 23 and 43). This reserves the number when the build
|
||||
starts, where ADR 0666 §15 numbers a package at its publication and design 51 publishes a held digest under a new
|
||||
number: a packed file carries its version, so it must be known before the build; the operator's choice, which a
|
||||
later research brings the records in line with. How it is checked: test 102.
|
||||
- §53 **Every dependent behind is rebuilt against it, on `package-published`.** The builder says `package-published`
|
||||
for every package build of a watched branch: with its released version on the registration's first watched branch,
|
||||
and on another watched branch by its digest alone, the build stored by digest with no version (§371). The runner,
|
||||
which consumes it, asks the catalog's verb `outdated` for that package on that branch, a kept call (ADR 0668 §1,
|
||||
§2), and starts a pipeline for each dependent by kind 1 whose recorded part digest differs from the published one:
|
||||
the catalog compares (§375), the runner starts, and no other part rebuilds anything. A `package-published` whose
|
||||
version is lower than the highest the catalog holds released for that module starts nothing (§382). Nothing else is
|
||||
rebuilt, and a change to tests alone publishes nothing (ADR 0666 §15; research 086, R18). Why: ADR 0666 §15 has the
|
||||
runner consume `package-published`; the digest comparison is the operator's (2026-11-10), and one event for both
|
||||
branches is the design's choice, round 2 of the review by architectural domain, 2026-11-10.
|
||||
- §54 **A second start writes nothing.** The runner starts a pipeline on two events, the indexer's `take-in.taken` (ADR
|
||||
0666 §13) and `package-published` (ADR 0666 §15). When both reach it for the same source revision and module, the
|
||||
second start writes nothing: the runner writes a pipeline's key (watched branch, source revision and module, *The
|
||||
runner's kept data*) by compare-and-set on its absence, so one revision and module has one pipeline. The design's
|
||||
choice under ADR 0666 §13 and §15: the key the runner already keeps is the one place a start is judged, so neither
|
||||
event's producer needs to know of the other.
|
||||
- §55 **Unread packages refuse.** A build that cannot read the published packages refuses and names the unread state
|
||||
(ADR 0666 §15).
|
||||
- §56 **How it is checked.** Whole (builder, indexer): a mesh-sdk merge changing one package publishes one version, and only
|
||||
the artifacts importing that package rebuild; a merge changing only tests rebuilds nothing; a build whose packages
|
||||
cannot be read refuses naming the state. Whole (runner): a `take-in.taken` and a `package-published` for the same
|
||||
source revision and module start one pipeline, the second writing nothing. Tests 70, 80 and 101.
|
||||
@@ -0,0 +1,275 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0667"
|
||||
- "ADR 0668"
|
||||
- "ADR 0666"
|
||||
- "ADR 0660"
|
||||
- "ADR 0635"
|
||||
- "ADR 0625"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0584"
|
||||
- "ADR 0564"
|
||||
- "ADR 0363"
|
||||
- "ADR 0240"
|
||||
---
|
||||
|
||||
# 47, milestone G: the answers to develop's second batch of questions
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
## From develop, the second batch (2026-11-11)
|
||||
|
||||
*The operator, 2026-11-11 about 11:15 CET at lead-47's terminal: a design question that changes nothing the operator
|
||||
sees, nobody's permissions (who may call, publish or read what) and no data kept or removed (an internal wire form, a
|
||||
function's input, a helper's API, a contract field's name or shape, a package's place) is answered as a design line by
|
||||
the design writer, from the design and its records, read by the four reviewers by architectural domain in one round,
|
||||
and told to the operator in one line each; every other question goes to the operator (playbook 07, step 0b). §456 to
|
||||
§458 are the operator's answers of 2026-11-11 at the terminal; §459 to §467 answer questions of that internal form,
|
||||
each the design's choice with its reason, §468 to §479 those of the runner's, the build's, the indexer's, the contracts' and the forge's developers the same way. §480 and §481 answer what §463 and §467 left to the operator, and [G-11](G-11-develop-answers-review.md) the review of these lines.*
|
||||
|
||||
- §456 **A verb that names no `class` is a `read`, and nothing refuses one.** A verb whose definition names no `class`
|
||||
takes the default of `read` (§391), whether it is on main or G adds it: the seat-definition reader, the seat
|
||||
generator and the manifest validator refuse no verb for naming no `class`, and the validator says the default each
|
||||
verb took, as §391 has it. The verbs whose class this design names (§394, §405, §422, §446, §447) name it in their
|
||||
definitions. This amends §391, its "a verb G adds must name its `class`" and its check's refusing row. Why: the
|
||||
operator, 2026-11-11 at the terminal (develop question D-12): a refusal would be "hard-coded logic for no good
|
||||
reason". How it is checked: unit (definition reader), a row where a verb G adds with no `class` takes `read`'s
|
||||
callers; unit (seat generator), a definition with a verb naming no `class` is generated.
|
||||
- §457 **The put session's wire form.** (§489, §490, §518) §345's put session runs on the subject `<bundle call subject>.put.<call id>`.
|
||||
Each chunk is one message whose body is the value's raw bytes, at most half the bus's maximum payload as the
|
||||
connection reads it from the server, with the header `Mesh-Put-Seq` (a decimal from 1, one more each chunk) and, on
|
||||
the last chunk alone, `Mesh-Put-Last: true`. The tool runner answers each chunk with `{seq}`, the sequence it took,
|
||||
or `{error}`, and the bundle sends the next chunk only after a `{seq}`; the tool runner's answer to the last chunk is
|
||||
the call's answer. A chunk whose `Mesh-Put-Seq` is not one more than the last taken is answered `{error}` naming
|
||||
both, and the put is dropped as one not sealed (§345). A get of a value over the limit (§407) runs the same form on
|
||||
`<bundle call subject>.get.<call id>`, the tool runner sending the chunks with the same headers and the bundle
|
||||
answering each. Why: the operator, 2026-11-11 at the terminal (develop question D-13); the out-of-order refusal and
|
||||
the get's mirror are the design's choice, one form both ways. How it is checked: whole (mesh-sdk, tool runner), as
|
||||
§345, and a chunk sent out of order fails the put naming both sequences; unit (tool runner), a chunk over half the
|
||||
maximum payload refused.
|
||||
- §458 **How each artifact carries its version.** An image is tagged with the version `MAJOR.MINOR.PATCH` and labelled
|
||||
`org.opencontainers.image.version` with it; a Go program is linked with `-ldflags -X main.meshVersion=<version>`
|
||||
beside the existing `-X main.builtFor`; a Node program reads its version from its package.json `version`, which is
|
||||
the version (§370). A build that carries no version (§371, §373) stamps none: the Go variable stays empty, and the
|
||||
image and the package.json keep what they carry today. This completes §370's "its tag and label" and "reads it as
|
||||
its own version". Why: the operator, 2026-11-11 at the terminal (develop question D-19); a build without a version
|
||||
is §371's and §373's. How it is checked: whole (Go, TypeScript and image build modules), a built image's tag and
|
||||
label, a Go program's `main.meshVersion` and a Node program's own version are the reserved version.
|
||||
- §459 **`SeatsToPassOn`'s input.** (§536) mesh-sdk's `catalogue.SeatsToPassOn` takes the holdings on record, the
|
||||
assignments (each a node, a module and its placement's watched branch), what the caller heard of each node and
|
||||
module from the node-engine seat's `running` (a fingerprint, `stopped`, or nothing heard since its consumer began),
|
||||
and a reader of entries the caller passes over its own view of the catalog half, which answers an entry by its
|
||||
fingerprint (§443) and a module's entry on a watched branch's newest revision. The function resolves each
|
||||
assignment's entry itself: by the heard fingerprint; with nothing heard, the module's entry on its placement's
|
||||
branch, §199's one fallback, which no caller applies; a `stopped` module claims nothing on that node (§404). Where
|
||||
the reader answers no entry for an assignment, no seat that assignment could claim passes and the holder on record
|
||||
stays, as ADR 0660 §1 has it with no single claimant, and the answer says it, naming the node, the module and the
|
||||
fingerprint. It answers the passes and what it says, as ADR 0660 §3. The function reads no bus. This amends §199
|
||||
and §404. Why: ADR 0660 §3 has one rule both parts apply, and §199 the fallback in that one function; with the
|
||||
entry read through a reader each part passes, the node-engine and the grants part feed it from their own views
|
||||
(ADR 0596 §2), and the function stays a pure rule (develop question D-14). How it is checked: unit
|
||||
(`SeatsToPassOn`), a table with a row per case: a heard fingerprint, nothing heard (the branch's entry taken), a
|
||||
`stopped` holder passing no seat, and an entry not answered keeping the holder on record and saying it.
|
||||
- §460 **An asker says whether its key is still open.** (§531) mesh-sdk's kept-call form takes, beside the ask's key, a
|
||||
function `open` the asker passes, which reads the asker's own state and answers whether that key is still open, and,
|
||||
where one exists, the event that replaces the ask; it asks again only while `open` answers yes and no replacing
|
||||
event was heard. An error of `open` is written in the asker's journal naming the key, and the form asks again: a
|
||||
kept call is one the callee takes once by a key (ADR 0668 §2), so asking again is safe, and not asking would be
|
||||
silent. This amends §425: the replacing event is optional, `open` always given. Why: an ask with no replacing event
|
||||
must still stop when its asker closed it (§351; develop question D-15). How it is checked: unit (kept-call form), a
|
||||
key `open` answers closed is not asked again, a replaced one is not, and an `open` error is logged and asked again.
|
||||
- §461 **mesh-sdk's finding writer.** (§485, §503) The writer is mesh-sdk's `go/views` writer and its TypeScript twin, mesh-sdk's
|
||||
`src/views`, exported from its index as `views`, the same functions in both (§452). An observer makes it with: the
|
||||
binding of its own state `findings`, its bus connection, and the subjects of `finding-written`, `finding-cleared`,
|
||||
`findings-stated` and `findings-asked`, which the generated `mesh-conditions` contract's subject functions make from
|
||||
the observer's module name and node (§452, §383), so the writer names no seat and no module; and the observer's
|
||||
`observe` function. Its acts:
|
||||
- `write`, a finding: writes the key `<scope>.<id>.<kind>` in `findings`, keeping the key's `since` where it
|
||||
exists (§384), and says `finding-written` only when what the finding says changed (§383).
|
||||
- `clear`, a finding's `scope`, `id` and `kind` and `why`: deletes the key and says `finding-cleared`.
|
||||
- Every event is published awaiting the bus's acknowledgement, sent again for one minute when unacknowledged, then
|
||||
written in the observer's journal as an error naming the finding (§383).
|
||||
- `observe` is a function the observer passes, which answers the findings it observes now and its pending keys
|
||||
(each with its `since` and its bound), or an error. The writer calls it at its start and on each ask, writes each
|
||||
stated finding's key, deletes each key neither stated nor pending, and says `findings-stated` (§384), putting a
|
||||
set larger than one bus message in the observer's sent store (§59, §407). An error of `observe` is written in the
|
||||
journal and no set is said, so the keeper's `findings-unstated` follows (§390).
|
||||
- The asks: the writer makes a consumer of its own on `findings-asked` that delivers only the asks published after
|
||||
it is made, the statement at start answering any before (§390), and answers an ask that names no `module` or
|
||||
names its own.
|
||||
- Between the seat's contract and the writer pass only the subjects: an observer calls the generated
|
||||
`mesh-conditions` contract's subject functions and hands their strings to the writer. mesh-contracts' bucket
|
||||
adapter `meshconditionsclient/findings` goes in G, its callers in mesh-delivery making mesh-sdk's writer instead
|
||||
in the same change; a client package of `mesh-conditions` keeps only what two callers would each write beside the
|
||||
contract (ADR 0625 §4). A client package of any seat, `meshwatchdogsclient` among them, is an artifact of its
|
||||
contracts module, as every package that module publishes is, so `go-meshwatchdogsclient` is one of the health and
|
||||
repair contracts module's artifacts.
|
||||
Why: §383, §384 and §390 decide the events and their order; the writer's form is the design's choice (develop
|
||||
questions D-16 and C15), every observer stating through one writer. How it is checked: unit (writer, both languages), the
|
||||
acts above each say their event with §439's fields, an ask naming another module is not answered, an error of `observe`
|
||||
says no set; §383's and §384's checks.
|
||||
- §462 **Where the bus state calls and their capabilities land.** (§488) The calls' names and shapes are in mesh-sdk's
|
||||
`go/buscalls`, which holds only those (its package's own rule): §408's ledger calls are `mesh/ledger.fill` (one
|
||||
ledger's history's fill against its cap, as `buscalls.Fill`, §587) and `mesh/ledger.remove` (one instance removed with its
|
||||
operations and its snapshot, §155, its request and answer §587's), and §407's `mesh/objects.sent-get`. The capability each belongs to is listed
|
||||
where the capabilities are today, in the tool runner in mesh-tools (its families of calls per capability): the two
|
||||
ledger calls under `ledger-calls`, `mesh/objects.sent-get` under `bus-state-calls`. mesh-sdk gains no capability
|
||||
list of families; it names the words (§622). This amends §347 ("mesh-sdk lists") and §408. Why: the tool runner states the capabilities of the calls it
|
||||
answers (ADR 0595 §9) and the shapes alone are shared, so a module importing the calls rebuilds nothing of the tool
|
||||
runner's (develop question D-17). How it is checked: unit (tool runner), its table of families names each new call
|
||||
under its capability; unit (`buscalls`), each request and answer read.
|
||||
- §463 **The input of the build seats' `steps`.** (§480) `steps` takes `artifact`, the key of `detected-steps` (§414), and
|
||||
`fetch` to detect the artifact's steps, `again` (yes or no) to detect again, or `drop` (yes) with `why` (a non-empty
|
||||
text) to delete the key; `fetch` is required unless `drop` is given, and `why` with `drop`. A drop answers `dropped`,
|
||||
the artifact, or `error`, and is written in the hand-act log of the seat `mesh-hand-acts` with the caller the bus
|
||||
proved for the call (never a field of the input), the artifact and `why`. It is defined once, in
|
||||
`mesh-delivery-contracts`' part `build-step`, for the three build seats. It is not the verb `drop` of §338, which
|
||||
drops asks. This completes §326. Why: the design's choice (develop question D-18). How it is checked: unit
|
||||
(`build-step` contract), a `drop` without `why` and an ask without `fetch` or `drop` refused; whole (Go build
|
||||
module), a drop deletes the key and is in the hand-act log with its caller. Its callers are §480's.
|
||||
- §464 **The delivery SDK's two step packages, and where the build modules' shared code goes.** In
|
||||
`modules/mesh-delivery-sdk` (after §427's move), `buildstep` holds what every delivery part reads of a step: the step
|
||||
ask of a pipeline and its end, `step-ended` (§24), and the kept-artifact judgement of §271, as develop built them;
|
||||
`steprun` (§420) holds what only the build modules run: the step run and the fetch and release (§327), the
|
||||
regenerate-and-compare step and its out-of-step answer, and the per-node CPU share as `steprun/buildcpu`. The seat
|
||||
generator moves with them, as the package `seatgen` of `mesh-delivery-sdk` with its command `cmd/seatgen`, and the
|
||||
command `mesh-pack` as `cmd/mesh-pack` there. So all of mesh-build-go's `buildstep` Go module leaves mesh-catalog,
|
||||
the TypeScript and image build modules import only the delivery SDK, and the delivery SDK requires nothing of
|
||||
mesh-catalog. This amends §327's "a build-step package" and the main file's "`mesh-pack`, in mesh-sdk". Why: code
|
||||
the build modules share is the delivery SDK's (ADR 0635 §4; §327), a module importing another module's package is a
|
||||
seam no mesh path carries, and mesh-sdk takes only the runtime every module uses (ADR 0625 §9), which a generator is
|
||||
not; `buildstep` stays light for the parts that only read the step's contract (develop questions D-20 and SD1). How
|
||||
it is checked: a search of mesh-catalog finds no `buildstep`, `seatgen`, `buildcpu` or `mesh-pack` under
|
||||
mesh-build-go; the delivery SDK's go.mod requires no mesh-catalog module; §327's search.
|
||||
- §465 **A group run's build ask names each package member's build, and the build module reads it from the store.**
|
||||
The ask's `fetch` (the part `build-step`'s Fetch) gains `builds`: a list of `{package, module, artifact, digest}`,
|
||||
one per package member of §373, `package` the name in its ecosystem (the Go module path, the npm name), `module` and
|
||||
`artifact` the member's catalog names and `digest` its build's `sha256:` digest; a package `builds` names is named in
|
||||
neither `packages` nor `package`. The build module reads each from the artifact store through the provision it
|
||||
already requires, `artifact-store`, at the reference it stores at,
|
||||
`artifact-store://<module>/<artifact>/blobs/<digest>`, checks the bytes against the digest and lays them as §373
|
||||
says. No registry seat gains a verb, and no grant changes: the store is read as every machine reads it. This
|
||||
completes §373. Why: the digest is the builder's own fact (§373) and the store is addressed by digest already
|
||||
(develop question BLD12). How it is checked: test 103; unit (`build-step` contract), `builds` read; whole (Go build
|
||||
module), a fetched build whose bytes do not match its digest fails the step naming the member.
|
||||
- §466 **A build names what it resolved.** Each built artifact of the build answer (BuiltArtifact, in the step's
|
||||
result) gains `resolved`, a list of `{package, version, digest}`, one per mesh package the build resolved (one
|
||||
that the catalog graph entry's packages name, the edges of kind 1), `digest` the `sha256:` digest of the package's
|
||||
bytes as the build fetched them; ToolchainUsed gains `digest`, the building node's digest of its toolchain program
|
||||
(§472), kind 3's entry, never the build module's own program. The builder carries both, per artifact,
|
||||
in `package-published` as `resolved` and `toolchain`, from which the indexer writes §374's entries of kinds 1 and 3.
|
||||
This completes §374. Why: §374 has the build module name each dependency with its version and digest in its step
|
||||
key, and the indexer read no other part's state (ADR 0596 §2; develop question BLD13). How it is checked: unit
|
||||
(`build-step` contract), both fields read; whole (Go build module, builder, indexer), a released version's kind-1
|
||||
and kind-3 entries carry the digests the build named.
|
||||
- §467 **A step key starts with its asker.** (§481, §483, §484) The build seats' state `steps` (in `node-build-go`, `node-build-node`
|
||||
and `node-build-image`) keys a step `<asker>.<node>.<id>`: `<asker>` the asker the bus proves (§481: `runner`, `indexer`, `assigner`, `builder`), `<node>` the node that runs it, `<id>` the ask's id, so §206's read grant of each
|
||||
asker is its own prefix and each holder writes only its node's keys. Keys of main's form `<node>.<id>` are §483's and §484's. This amends the three
|
||||
seats' `steps` key form on main. Why: §206 and §125 decide that each asker reads only its own step keys (ADR 0363
|
||||
§2); the key's form is what makes that grant a fixed prefix (develop question BLD14). How it is checked: the seats'
|
||||
fixtures; whole (Go build module), a step of each asker keyed under its name.
|
||||
- §468 **The runner's health check is its tool `runner_health`.** (§495) §76's check, run outside the process, is of kind
|
||||
`tool` (ADR 0240) and names the runner's own tool `runner_health`, named as the runner's other tools are
|
||||
(`runner_release`, `runner_catch_up`); it answers as every `tool` health check answers, healthy when the runner
|
||||
answers `show` and each durable consumer §76 names is less than five minutes behind, and otherwise naming each one
|
||||
behind. It is called as every module's health tool is, with no caller added. Why: the design's choice (develop
|
||||
question Q-RN1); one prefix per module's tools. How it is checked: §79's check; the runner's manifest names it.
|
||||
- §469 **An old `state` section declares its cap.** An old `state` section's entry takes `max-mib`, as its old
|
||||
`objects` section's does, and mesh-sdk's `buscompose` makes the bucket with that many MiB as its maximum size; an
|
||||
entry naming none has no cap, as on main, so no bucket of today changes. The runner's `pipelines` names 256 and its
|
||||
`kept` 128 (§85); a write the bus refuses at the cap is said as the runner's `full` (§181), its read tools answering
|
||||
each fill against the cap (§83). Why: §85's caps are the bus's, so a full store refuses loudly (§181) rather than
|
||||
trusting the owner's own count; the field is the one the `objects` section has (develop question Q-RN2). How it is
|
||||
checked: unit (manifest reader), `max-mib` on a state read and a value out of the objects section's bounds
|
||||
refused; whole (runner), a write past a fixture's small cap says `full`.
|
||||
- §470 **The step log is an old `logs` section, kept 100 days.** (§486) The runner's `step-log` is declared in its manifest
|
||||
as an old `logs` section, with `max-age-days` 100 and `max-mib` 128, and is written and read onward by sequence
|
||||
through mesh-sdk's kept log (its `Read` from a sequence), as §330 reads it; the seat `mesh-pipeline-runner`'s
|
||||
definition keeps listing it, keyed by `<pipeline>`. A log already takes an age up to 3650 days and refuses no
|
||||
entry it holds, so the design's 100 days stand, and mesh-sdk's bound on a stream's age (30 days) and the indexer's
|
||||
rule that repeats it stay as on main. This amends §85's "old `streams` section" for `step-log`. Why: §330 reads the
|
||||
log onward by sequence, which is what a kept log is, and the log section declares the age and cap §85 names with no
|
||||
new word (develop questions Q-RN3 and Q-RN6). How it is checked: the catalog's validation at G's heads accepts the
|
||||
runner's manifest; whole (runner), as §330.
|
||||
- §471 **A second reader release carries §469's word.** (§494) The reader release of §340 and §409 is merged; a second
|
||||
release of mesh-sdk's manifest reader, reading `max-mib` on an old `state` section, is merged ahead of G's check in
|
||||
the same way, so the running checker's validator reads the runner's manifest (§340). Why: readers first (ADR 0667
|
||||
§4); the running checker makes G's own check (ADR 0667 §2). How it is checked: unit rows in the release for the
|
||||
word; G's check reads the runner's manifest.
|
||||
- §472 **The toolchain part's digest.** (§492) Each build module states, in its `toolchains` key of each language beside
|
||||
the version and the recipe, `digest`: the `sha256:` digest of the language's toolchain program as it runs it, the
|
||||
program of the module that provides that language's toolchain (for Go, the module providing `go-toolchain`), never
|
||||
the build module's own program. A build's ToolchainUsed carries its node's (§466). The builder's `toolchain-agreed`
|
||||
carries `digests`, each digest the nodes reporting the agreed version report, with those nodes, in place of §376's
|
||||
one digest; it is said only when the agreed version changes (ADR 0584 §1), and kind 3 is stale (§375) when the
|
||||
agreed version differs from the one the release recorded, so a digest that differs between nodes of one version
|
||||
rebuilds nothing and is only recorded. This amends §376 and §441 for kind 3. Why: ADR 0584 agrees a version, not a
|
||||
digest, and a rebuild of every module of a language on a difference no record judges would spend the build
|
||||
capacity for nothing (develop question BL8). How it is checked: unit (build-step contract), `digest` read; whole
|
||||
(builder), a change of the agreed version says `toolchain-agreed` with the digests and a differing digest at one
|
||||
version says nothing.
|
||||
- §473 **The builder takes a candidate's heads from its revision view.** In place of the gate's `source` history,
|
||||
which it reads today to find the trunk heads a candidate's base revision names, the builder takes each build's
|
||||
commits from the catalog half of the revision view the build is for (§348), by `mesh-catalog`'s `revision`; a G
|
||||
pipeline builds a commit as it is, so the builder asks no merge onto a base and sets no `base` in a fetch. Its watch
|
||||
of the gate's `source` and the `reads` of it in its manifest go in the same change (§431). Why: ADR 0596 §2; one
|
||||
source of a revision's commits (develop questions BL15 and BL9). How it is checked: the builder's account holds no
|
||||
read of the gate's state; whole (builder), a run on request builds the commits its revision view names.
|
||||
- §474 **A `*` entry before an event no contract defines keeps its meaning on main.** §386's form, one durable
|
||||
consumer on each declaring module's stream, is taken only for `*.<event>` where `<event>` is defined by a seat's
|
||||
definition or a contract in mesh-sdk; any other `*` entry the validator accepts on main, such as `model-usage`'s
|
||||
`*.usage.*`, keeps its meaning of ADR 0363 §5, the shared stream EVENTS and never a module's own stream, and is
|
||||
accepted as on main; `*.>` and `*` in part of an emitter's name stay refused. G's change does not rewrite
|
||||
`model-usage`'s `consumes`, so it hears what it hears today. This amends §386's "it refuses `*` before any other
|
||||
event". Why: ADR 0363 §5 stays in force for what §386 does not take over, and a refusal would end what a module
|
||||
hears with no answer of the operator's (develop question IX21). How it is checked: unit (manifest validator),
|
||||
`*.usage.*` accepted as the shared form; the grants part's table test makes no per-module consumer for it.
|
||||
- §475 **The forge module learns a head's fingerprint from every `step-ended`.** (§487) The state `failed-steps` of §39 gains
|
||||
a second key form, `seen.<commit>.<module>`, holding the build-input fingerprint the runs named for that commit
|
||||
and module, written from every `step-ended` of any outcome whose commit is then a watched branch's head or the
|
||||
head an open pull request names, and deleted by §135's rule; and each failure entry's commits take every commit a
|
||||
later `step-ended` names at its module and fingerprint. `merge-pull` reads a head's fingerprint from its `seen`
|
||||
key, so a head whose runs all ended before another commit of the same content failed is refused, as §39 says. This
|
||||
completes §39 and §135. Why: §39 decides the refusal and that it is judged from the module's own state, and
|
||||
`step-ended` names one commit and fingerprint at a time (develop questions GIT7 and GIT8). How it is checked:
|
||||
whole (forge module), a head that passed, then another commit at its fingerprint failed a `required` step, is
|
||||
refused; its `seen` key goes when the pull request closes.
|
||||
- §476 **`take-in.taken`'s body and its type.** (§493) The indexer's own `take-in.taken` carries, beside what it carries on
|
||||
main, `branch` (the watched branch), `source` (the source revision taken in) and `entries`, one `{module, commit,
|
||||
fingerprint}` per module of that revision. Its body is defined in the seat `mesh-catalog`'s definition, in its
|
||||
section `bodies` (§439, §454), so the generated contract carries the type the indexer writes and the runner reads,
|
||||
and neither imports the other (§333). Why: a body said on a module's own stream and read by others is a `bodies`
|
||||
entry (§439), never a client's hand-written type (develop question Q-RN4). How it is checked: unit (seat generator),
|
||||
the type generated; whole (runner), a pipeline started from a fixture `take-in.taken`.
|
||||
- §477 **A sent store's cap.** (§495) The bus module plans each `<module>_sent-objects` with a cap of 256 MiB, and a
|
||||
foundation part's old `objects` section `sent-objects` names `max-mib` 256; a send refused at the cap fails its
|
||||
reply or event loudly, naming the store, as §181 has a full store refuse. This completes §59's "the objects
|
||||
template's", which has no default. Why: an object sent is transport kept 7 days (§59), most modules send none, and
|
||||
the largest value the design moves whole is §345's hundred times the message limit, which 256 MiB holds twice
|
||||
(develop question Q-RN5). How it is checked: unit (bus module's plan), every planned sent store has its cap; whole
|
||||
(mesh-sdk), a send past a fixture's cap fails naming the store.
|
||||
- §478 **`reserved` takes a package's name.** (§491, §504) The seat `mesh-builds`' verb `reserved` takes either `module` or `name`
|
||||
(a package's or an image's name in its ecosystem: the Go module path, the npm name, the image name), with
|
||||
`version`; given `name`, the builder finds the module from the catalog half's entries, whose packages carry each
|
||||
name (§348, §412), and answers as for `module`, the artifacts with their digests. A registry holder asks it with the
|
||||
name, version and digest its publish carries, so it reads no catalog and its publish gains no field; a name no entry
|
||||
carries is answered `error`, naming it, and the holder refuses the publish (§381). Its callers stay §381's. This
|
||||
completes §381. Why: the builder already reads each package's name from the catalog half (§348), so the mapping is
|
||||
its own fact (ADR 0596 §2) (develop questions GIT12 and REG1). How it is checked: unit (`mesh-builds` contract),
|
||||
`reserved` with `name` read; whole (builder, registry holder), a Go publish named by its module path is checked
|
||||
against the module's reservation, and an unknown name refused.
|
||||
- §479 **A branch's catalog view is named with its branch.** (§495, §521, §532) In mesh-sdk's `go/views` reader and process reader, a
|
||||
first view of a per-branch state is named `<owner>.<state>.<branch>`, the branch escaped as a key's name is (§73),
|
||||
so `mesh-catalog.catalog-revisions.<branch>` names one view per watched branch, a part reads several in one reader,
|
||||
and `<module>_first_views` lists one row per branch (§358, §365). A view of a state that is not per branch keeps
|
||||
its name `<owner>.<state>`. Why: the condition keeper reads every watched branch's newest revision (§365), and the
|
||||
state's keys are `<branch>.<revision>` already (§343) (develop question CT10). How it is checked: unit (`go/views`),
|
||||
two branches' views of one state in one reader, each its own row; whole (keeper), a module watched only on a
|
||||
second branch is a source.
|
||||
@@ -0,0 +1,315 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0667"
|
||||
- "ADR 0668"
|
||||
- "ADR 0666"
|
||||
- "ADR 0660"
|
||||
- "ADR 0656"
|
||||
- "ADR 0633"
|
||||
- "ADR 0603"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0584"
|
||||
- "ADR 0579"
|
||||
- "ADR 0564"
|
||||
- "ADR 0563"
|
||||
- "ADR 0363"
|
||||
- "ADR 0240"
|
||||
---
|
||||
|
||||
# 47, milestone G: the operator's answers of 2026-11-11 noon, the review of §456 to §479, and develop's next questions
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§480 to §482 are the operator's answers of 2026-11-11 about 11:50 CET at lead-47's terminal. §483 to §495 answer
|
||||
what the four reviewers by architectural domain found in round 1 of [G-10](G-10-develop-answers.md) (§456 to §479).
|
||||
§496 on answer develop's next questions of internal form (playbook 07, step 0b), each the design's choice with its
|
||||
reason. What a question of another kind leaves open waits for the operator and is said in the line.*
|
||||
|
||||
## The operator's answers
|
||||
|
||||
- §480 **Who may drop a build seat's detected steps.** The build seats' `steps` with `drop` (§463) is called by the
|
||||
runner and the indexer, as §338 has `steps`, and by the operator and agent sessions; every drop is written in the
|
||||
hand-act log of the seat `mesh-hand-acts` with the caller the bus proved. The log is written first: a drop whose
|
||||
log write fails deletes nothing and answers `error`, naming the log. This amends §338 and completes §463. Why: the
|
||||
operator, 2026-11-11 at the terminal: §326 has a drop happen on a person's word. How it is checked: whole (Go
|
||||
build module), a drop by a session is in the hand-act log with its caller and deletes the key; a drop by any other
|
||||
account is refused by the bus; with the hand-act log refusing, nothing is deleted.
|
||||
- §481 **A step key's asker is the account the bus proves.** (§600) The `<asker>` of a step key `<asker>.<node>.<id>` (§467)
|
||||
is the account the bus proves sent the ask, never a field of the ask. An ask is published on
|
||||
`mesh.seat.<seat>.accept.<queue>.<asker>` (§600), the asker's name the last token, and the grants part grants each asker of
|
||||
§338 publish on its own name alone, so the subject the holder took the ask from names the asker; an ask's own
|
||||
`asker` field, where one is sent, is not read. Each build seat's holder on a node is granted writes only of that
|
||||
node's keys, `<asker>.<node>.<id>` (and main's `<node>.<id>`, §483). The stream's subjects are §600's. This amends §467 and §206. Why: the operator, 2026-11-11 at the terminal; the name in
|
||||
the subject is how the bus proves who published (ADR 0363 §2). How it is checked: whole (grants part, Go build
|
||||
module), an ask published under another asker's name is refused by the bus; a step of each asker is keyed under
|
||||
its name; a holder's write of another node's key is refused.
|
||||
- §482 **In G a code review refuses nothing on authorship.** The runner's `review` takes a review's result from any
|
||||
of its callers (§394), refuses none for being the commit's author, and records with the result the caller the bus
|
||||
proved, in the step's result, `step-ended` and `show`. Code review is never a `required` step in G: `register` and
|
||||
`policy` refuse it, as §2 says. This amends §70, §72, §394, test 86 ("an author's own review is refused") and the
|
||||
verb table's `review` row, and answers the runner's question of who the author of a commit the runner makes is: no
|
||||
line of G reads a commit's author. Why: the operator, 2026-11-11 at the terminal (develop question RN13); no record
|
||||
binds a forge account to a bus identity (*What is not decided here*). How it is checked: whole (runner), a review
|
||||
by a session passes an `optional` review step and its result names the proven caller; `policy` naming code review
|
||||
`required` is refused, naming why.
|
||||
|
||||
## The review of §456 to §479, round 1
|
||||
|
||||
- §483 **Main's step keys stay readable while main's askers run.** (§600, §601, §544) A build seat's holder of G keys a step whose ask
|
||||
came on main's subject `mesh.seat.<seat>.accept.<kind>`, with no asker's name, as main does, `<node>.<id>`, and a
|
||||
step whose ask names its asker as §481 says; so a builder or checker of before G, which asks on main's subject,
|
||||
finds its answer under `<node>.<id>` through the rollout one node first and after a revert. Every asker of G asks
|
||||
under its name, and the holders of the three build seats are delivered before it by a served-verb edge (kind 6)
|
||||
the delivery plan derives from the asker's `invokes` of the queue (§601); an ask under a name that reaches a holder of before G waits on the work queue, until the grants part's worker filters both forms (§600, §601), and is said by `build-capacity-waits` after 60 minutes. The checker asks on main's
|
||||
subject until it goes (§97). main's subject and its key form are served until the first change after the checker
|
||||
is removed and no builder of before G runs, as §372 has `builder_versions`. Why: a reader is delivered first (ADR
|
||||
0667 §4), and a step key is the answer its asker reads (review round 1, running and testing 2, security and upgrade
|
||||
1). How it is checked: whole (Go build module of G, builder of main), main's builder's build is answered under
|
||||
`<node>.<id>`; whole (runner of G), its steps are keyed under `runner.`; the delivery plan of G names the edge from
|
||||
each build seat's holders to the runner, the indexer and the assigner.
|
||||
- §484 **Every reader of a step key moves to one parser.** The delivery SDK's `buildstep.ParseStepKey` reads both
|
||||
forms, `<asker>.<node>.<id>` and main's `<node>.<id>` (answering no asker), and its readers move to it in G's
|
||||
change: the builder's `buildrun/steps.go` and `internal/build/stuck.go` (its own `builder.` keys and main's form),
|
||||
the delivery SDK's `buildrun/stale.go`, the status view's `usage.go`, and each build module's read tools, which
|
||||
list every key of the state, main's form included, until the state's two-day age takes them. The checker's
|
||||
`bounded.go` stays as on main, since the checker asks on main's subject (§483). The watchman's `mirror.go`, which
|
||||
read the build modules' `steps` (ADR 0596 §2 forbids it), goes: the watchman times a running ask from the build
|
||||
seat's `step-progress` (§496). A key either form does not parse is written in the reader's journal naming it,
|
||||
never passed over in silence. Why: one key form, read once (review round 1, health and events 1). How it is
|
||||
checked: unit (`buildstep`), both forms parsed and a third refused; whole (runner, Go build module, watchman), a
|
||||
step under `runner.` whose worker stops raises `pipeline-stands` within five minutes.
|
||||
- §485 **mesh-sdk's finding writer runs one act at a time, and recovers.** (§609) The writer of §461 runs its acts, `write`,
|
||||
`clear` and a statement, one at a time in the order they are asked (in Go behind one lock, in TypeScript on one
|
||||
chain of promises), so a statement never deletes a key a `write` made meanwhile. It makes its consumer of
|
||||
`findings-asked` before its start statement. When the bus connection is made again, it makes the consumer again,
|
||||
from the newest ask, and states its findings again. When `observe` answers an error, the writer calls it again
|
||||
once a minute until a set is said, each error in the journal. `clear` of a key that does not exist deletes nothing
|
||||
and says `finding-cleared`, so a keeper holding the finding clears it. This completes §461. Why: §384 has the
|
||||
keeper's view rebuilt from statements alone, so a statement that races a write, a consumer lost at a restart of
|
||||
the bus or one error at start would leave a finding wrong until the next ask (review round 1, running and testing
|
||||
3 and 4, health and events 2). How it is checked: unit (writer, both languages), a `write` during a statement is
|
||||
kept; after a reconnect an ask is answered and the findings are stated again; an `observe` failing at start then
|
||||
answering says its set within a minute.
|
||||
- §486 **A full step log is said and refuses no step's end.** (§606) The runner's `step-log` (§470) joins the stores of §366:
|
||||
its fill in `show` and `kept`, `filling` past 80 %, `full` when the bus refuses an append (ADR 0595 §3), `room`
|
||||
below 70 %. A step's end is written in `pipelines` and said as `step-ended` before its log entry is appended, so a
|
||||
refused append ends no step: the runner keeps the entry in that pipeline's key as `unlogged`, starts no new pipeline
|
||||
while the log is full (§366), and appends the kept entries in their order on `room`, from which §330's ageing reads
|
||||
them. Why: a full store is said and refuses loudly (§181), and a step's result is the pipeline's, not the log's
|
||||
(review round 1, running and testing 5, health and events 3). How it is checked: whole (runner), with a fixture's
|
||||
small cap a step ends and is said, `full` is said and a start refused, and on `room` the entry is appended and the
|
||||
start taken.
|
||||
- §487 **The forge module learns a commit's fingerprint from every step end.** (§548) §475's `seen.<commit>.<module>` is
|
||||
written from every `step-ended`, whatever its commit, a run on request's included, so a commit that failed in a run
|
||||
on request before its pull request was opened is known at its merge. A `seen` key is deleted by §135's rule once
|
||||
its commit has been a watched branch's head or the head an open pull request names and is neither, and otherwise
|
||||
30 days after the last `step-ended` that wrote it. `merge-pull` judges a head that has a `seen` key by it; a head
|
||||
with none was named by no run, and merges, its pipeline after the merge judging it (§29), so `merge-pull` refuses
|
||||
nothing it did not refuse before. `git_failed_steps` answers the `seen` keys too, a commit's fingerprints with
|
||||
`commit`, and `git_failed_steps_drop` drops a `seen` key, with why. The kept-data row of `failed-steps` counts them:
|
||||
one key of about 0.2 KiB per commit and module a run named, an estimated 500 a day, under 3 MiB at 30 days. This
|
||||
amends §475 and §135. Why: §39 judges a head by any run at its fingerprint (review round 1, running and testing 1,
|
||||
seams and tools 3). How it is checked: whole (forge module), a run on request of a commit fails a `required` step,
|
||||
a pull request opened at that commit is refused, naming the step; a head that no run named merges; a later commit
|
||||
of the same content is refused too.
|
||||
- §488 **A call family G adds has its own capability word.** (§605) The tool runner states, beside `ledger-calls` and
|
||||
`bus-state-calls`, `ledger-upkeep-calls` (§462's `mesh/ledger.fill` and `mesh/ledger.remove`), `sent-objects-calls`
|
||||
(`mesh/objects.sent-get`) and `put-sessions` (§457's put and get sessions), each only once it answers that family.
|
||||
mesh-sdk's bundle reads the words its node's tool runner states when it connects, and a call of a family whose word
|
||||
is not stated fails at once, naming the word and the node, never a timeout. On each node a module is delivered after G's tool runner by §605's edge.
|
||||
This amends §408 ("no capability word is added") and §462. Why: an older tool runner states the old words and does
|
||||
not answer the new calls (ADR 0595 §9; review round 1, security and upgrade 2). How it is checked: unit (tool
|
||||
runner), each word stated with its family; whole (mesh-sdk), against a tool runner that does not state
|
||||
`put-sessions` a large put fails naming it; the delivery plan of G orders the modules after the tool runner.
|
||||
- §489 **Who publishes on a put or get session.** (§518) On `<bundle call subject>.put.<call id>` only the module's own
|
||||
account publishes the chunks and only the tool runner of its node answers them; on `<bundle call subject>.get.<call
|
||||
id>` only the tool runner publishes the chunks and only the module's account answers. The grants part grants them
|
||||
so, as it grants a module's call subject today (ADR 0363 §2). Why: §457's subjects lie under a subject the module
|
||||
already owns, so the grant follows the record (review round 1, security and upgrade 3). How it is checked: whole
|
||||
(grants part, tool runner), a chunk published by another module's account is refused by the bus.
|
||||
- §490 **The get side of a session.** (§518) A get of a value over the limit (§457) runs so: the bundle subscribes to
|
||||
`<bundle call subject>.get.<call id>` before it makes the call; the tool runner's answer to the call is
|
||||
`{chunked: <count>, size, digest}`, the value's `sha256:` digest; the tool runner then publishes each chunk as a
|
||||
request on that subject, with `Mesh-Put-Seq` and, on the last, `Mesh-Put-Last: true`, and the bundle answers each
|
||||
with `{seq}` or `{error}`, the next sent only after a `{seq}`; the bundle checks the whole against `digest`. A get
|
||||
not completed within the call's bound fails, naming the object. This completes §457. Why: one form both ways
|
||||
(review round 1, seams and tools 2). How it is checked: whole (mesh-sdk, tool runner), a value of a hundred times
|
||||
the limit is got back whole and its digest checked; a chunk lost fails the get naming the object.
|
||||
- §491 **A package's name belongs to one module.** The manifest validator refuses a revision in which two modules'
|
||||
entries carry the same package name in one ecosystem, naming both. `reserved` by `name` (§478) answers `error` when
|
||||
more than one entry carries the name, naming them. Why: a name read as a module must name one (review round 1,
|
||||
security and upgrade 4). How it is checked: unit (manifest validator), two modules naming one Go module path
|
||||
refused; unit (builder), a name two entries carry answered `error`.
|
||||
- §492 **The builder hears the toolchains as an event.** (§604) Each build seat says `toolchain-reported` on
|
||||
`mesh.seat.<seat>.event.toolchain-reported.<node>` (`node`, `language`, `version`, `recipe`, `digest`), declared
|
||||
in the three build seats' definitions in `mesh-delivery-contracts`, at its start and each time its `toolchains` key
|
||||
of a language changes; the builder agrees the version from these events through a durable consumer of its own, and
|
||||
its manifest's `reads` of the build modules' `toolchains` go. `digest` is the digest of the program of the
|
||||
provision the build module requires for that language's toolchain, never the build module's own. This amends §472.
|
||||
Why: ADR 0596 §2 (review round 1, seams and tools 1). How it is checked: whole (builder, Go build module), a changed
|
||||
toolchain is agreed from the event; the builder's account holds no read of a build module's state.
|
||||
- §493 **What `take-in.taken` and `take-in.pending` carry, from every source.** A take-in by hand
|
||||
(`indexer_take_in_pending`) and a revert, which is an ordinary merge, say `take-in.taken` with `entries` as any
|
||||
take-in does, and start pipelines; a hand drop (`indexer_drop_pending`) says it with `dropped` and no `entries`,
|
||||
and starts nothing. A `take-in.taken` with no `entries`, such as one said before G and read again, starts nothing
|
||||
and is written in the runner's journal. `take-in.pending` carries `branch` beside its fields on main, and both
|
||||
bodies are in the seat `mesh-catalog`'s `bodies`; the watchman and the runner read the generated types. This
|
||||
completes §476. Why: §476 named one source (review round 1, health and events 4; security and upgrade's note).
|
||||
How it is checked: unit (runner), a `take-in.taken` with `dropped`, and one with no `entries`, start nothing; whole
|
||||
(indexer), a hand take-in's event carries the entries.
|
||||
- §494 **The second reader release carries the node-engine's claim and reaches the grants part.** §471's release,
|
||||
merged and delivered before G's merge, carries `max-mib` on an old `state` section (§469) and the validator's rule
|
||||
of §508; the grants part, which reads manifests through that reader to make the buckets, is rebuilt with it by the
|
||||
release's publish (kind 1), so the runner's manifest is read by every part that takes it in. Why: readers first
|
||||
(ADR 0667 §4; review round 1, security and upgrade's note). How it is checked: G's check reads the runner's and the
|
||||
node-engine's manifests; whole (grants part), the runner's `pipelines` bucket has its cap.
|
||||
- §495 **Small corrections from the review.** (§530) §472's "kind 3 is stale (§441)" reads §375. §472's `toolchain-agreed` carries its digests as they stood when the version was agreed; a digest that changes later at that version is read from `toolchain-reported` (§492) and starts nothing. A sent store already over its cap when §477's
|
||||
cap is planned keeps no cap until its seven-day age brings it under, the bus module's plan naming it with its fill
|
||||
meanwhile; a sent store is planned only for a module §344 names, and the plan refuses caps whose sum passes the
|
||||
bus's storage limit, naming the sum. §468's "behind" is the age of the oldest message the consumer has not
|
||||
acknowledged. §479's view of a branch with no revision yet is ready and empty, as `revision` answers `none yet`, and
|
||||
raises no `view-waits`; its `view-waits` names the view `<owner>.<state>.<branch>`. A build seat's read tool lists
|
||||
the keys of both forms (§484). The seat generator sits in the delivery SDK as a tool run at build time by the
|
||||
contracts modules, imported by no running module, so no running part depends on the delivery SDK through it
|
||||
(§464). How it is checked: §475's test takes a commit named by a later run's step; §465's case of the artifact
|
||||
store not answering fails the step naming the member; the rest as each line it corrects.
|
||||
|
||||
## Develop's next questions
|
||||
|
||||
- §496 **A running step says its progress.** (§601 to §603) The holder of a build seat says `step-progress` on
|
||||
`mesh.seat.<seat>.event.step-progress.<node>` (`asker`, `ask`, `phase`: `fetch`, `build`, `run` or `report`, and
|
||||
`at`) once a minute while it runs an ask, declared as §603 says; the assigner says the same on `mesh-wants` for a composition step. `step-started` gains `ask`.
|
||||
The watchman's five minutes (§75) run from a step's `step-started` or its latest `step-progress`, so a step that
|
||||
runs longer but says its progress raises nothing; it times every ask of a build seat so, a builder's included, and
|
||||
raises for it what its mirror raised on main. Why: §75 times owed progress, never run length, and names no event
|
||||
for it (develop question WM1). How it is checked: whole (watchman, Go build module), a 20-minute step saying its
|
||||
progress raises nothing, and one whose holder stops raises `pipeline-stands` within five minutes.
|
||||
- §497 **The watchman knows a pending revision from `take-in.pending`.** A watched branch's head is behind a pending
|
||||
revision (§12) while the watchman holds an open take-in wait whose `take-in.pending` names that branch (§493); its
|
||||
head is then governed by `take-in-waits`' 60-minute bound and raises no `pipeline-stands`. A head on a branch whose
|
||||
`branch-unprotected` stands (§17) raises none either. Why: the watchman already times each take-in wait (develop
|
||||
question on §12). How it is checked: whole (watchman), a head behind a pending revision raises nothing for 60
|
||||
minutes, then `take-in-waits`.
|
||||
- §498 **A wait names its clock and its kind.** (§534, §608, §542) `meshwants.Wait`, the runner's wait body and the node-engine seat's
|
||||
gain `clock-from` (the time the bound counts from, §266, §269) beside `bound`, and `step`, one of the acting steps
|
||||
of §203 or `put`, `delivery`, `kept-build`, `backup`; a put's wait (main file, *A put without hold that stands*)
|
||||
is `step: put` with the node, the module and `fingerprint`; a dependent's delivery wait (§376) is `step: delivery`
|
||||
with `part` the dependency, its version and `machines`. `assigner_puts` answers each put with its node, module,
|
||||
fingerprint, `held`, since and bound, from which the watchman seeds. Why: one wait form, told apart by `step`
|
||||
(develop questions WM6 and WM10). How it is checked: unit (contracts), each field read; whole (watchman), a put's
|
||||
wait raises `put-stands` and a pipeline's wait raises nothing for it.
|
||||
- §499 **The node-engine says a kept build's wait, and its version is read from the catalog.** (§541, §609) A node-engine of G
|
||||
waiting for a kept build says `wait-held` on its seat with `step: kept-build`, `node` the build node, and why
|
||||
(`node away` or `build no longer kept`), and `wait-ended` when it applies; its have's value carries the same as
|
||||
`waits`. The watchman takes the keeping node from the builder's `build.ended` (§379), never its done key. Each
|
||||
machine's node-engine version, named by `delivery-waits` and `kept-build-waits`, is the version of the catalog
|
||||
entry (`entry`, §443) whose fingerprint `running.<node>.<node-engine's module>` names, or that fingerprint when no
|
||||
entry answers. Why: ADR 0596 §2 (develop questions WM4 and WM5). How it is checked: whole (watchman, node-engine),
|
||||
a kept build's node away raises `kept-build-waits` after 60 minutes naming the node-engine's version.
|
||||
- §500 **The manifest findings from the indexer's events.** From G the watchman raises `manifest-refused` from the
|
||||
indexer's `manifest-refused`, and `manifest-outgrown` from its `manifest-accepted`, which gains `problems`, what a
|
||||
stricter validator finds in a manifest it still accepts (ADR 0579 §1), cleared by a `manifest-accepted` with none; it
|
||||
seeds both from the indexer's `verdicts`. `manifest-waits` is no longer written: every revision is judged by the
|
||||
validator built from its own heads (§16), so its validator knows every field, and a reader that does not is a
|
||||
reader edge said by its own wait (§94). Why: the indexer's `modules` goes (§196; develop question WM9). How it is
|
||||
checked: whole (watchman, indexer), an outgrown manifest raises `manifest-outgrown` and its mending clears it.
|
||||
- §501 **`watchman_views` answers per part and view.** (§609) It answers, per module and node, each first view the part
|
||||
names: the view (`<owner>.<state>`, or with its branch, §479), `ready` or `waiting` with since when, and how far
|
||||
behind (revisions not applied, and the age of the newest applied), read from each part's `<module>_first_views`
|
||||
tool through the tool runner and from the `view-waits` findings the keeper holds. Why: §83's row names the sources,
|
||||
not the form (develop question WM8). How it is checked: whole (watchman), a part waiting for its view is answered
|
||||
`waiting` with its since.
|
||||
- §502 **A record is tied to its source revision's entries.** The watchman ties a delivery record to the entries of
|
||||
its source revision's `take-in.taken` (§493), and leaves `record-open` unwritten for a record only while one of
|
||||
those entries is held by a wait that raised `acting-part-late`, or a store that holds it says `full`; a record whose
|
||||
revision is not taken in is covered by `take-in-waits`. `record-opened` gains nothing. Why: §148 judges a record by
|
||||
its own entries, never every record at once (develop question WM7). How it is checked: whole (watchman), a record
|
||||
of two entries, one held by `acting-part-late` and one not, raises `record-open` at 24 hours.
|
||||
- §503 **The finding writer is mesh-sdk's, in Go and TypeScript, built in G.** The writer every observer uses is
|
||||
§461's and §485's, built in G in mesh-sdk's `go/views` and `src/views` (exported as `views`), by mesh-sdk's
|
||||
developer; the TypeScript one is built in G, since the forge module and the registry holders are observers in
|
||||
TypeScript. `meshconditionsclient/findings` goes (§461). Why: develop questions GIT14, REG2 and WM11. How it is
|
||||
checked: §461's.
|
||||
- §504 **The builder maps a package's name as §478 does.** Every builder answer asked by a package's name in its
|
||||
ecosystem, `builder_versions` and the registry holders' check of a publish by name, version and digest included,
|
||||
finds the entry carrying that name (§478, §491) and the artifact whose package it is, `<module>:<artifact>`, and
|
||||
answers for that artifact. Why: one mapping, the builder's own fact (develop question BL7). How it is checked: unit
|
||||
(builder), a Go module path and an npm name each answered for their artifact.
|
||||
- §505 **A manifest `version` that is not `MAJOR.MINOR` reserves nothing.** A build of a module whose catalog entry's
|
||||
`version` is not `MAJOR.MINOR` (a manifest from before G, `"1"`) reserves no version and is built and delivered as a
|
||||
build without a version (§371, §458), the builder writing it in its journal naming the module; at G's heads the
|
||||
validator refuses such a manifest (§411). Why: such an entry is only read from a revision before G (develop
|
||||
question of the builder). How it is checked: unit (builder), an entry of version `"1"` reserves nothing.
|
||||
- §506 **The entry carries its manifest's digest.** (§609) The catalog graph entry gains `manifest-digest`, the `sha256:`
|
||||
digest of the manifest's bytes as the forge answered them, set by the indexer at take-in (in `EntryInputs`) and
|
||||
never computed again from the manifest whole, whose encoding may differ; the module's value and §445's
|
||||
`manifests` key are that digest. Why: the bytes, not the map, are what the node-engines before G read (develop
|
||||
question IX16). How it is checked: unit (indexer), the entry's digest is the digest of the bytes read.
|
||||
- §507 **The watched branches before the first registrations.** (§532) Every reader of a trunk's source revision reads, per
|
||||
watched branch the registrations name, `source.<branch>` through the gate's verb `source`; before the first
|
||||
registrations (§95, §353), the watched branches are the branches the gate's `source` answers, which G's switch
|
||||
carries in from `source.main` as the branch `main`, read from the key and never fixed in code (§7). The assigner's
|
||||
trunk-only want, the status view and the forge bridge read so, and the bridge watches the same branches. Why: §7,
|
||||
and one source of the branches for every reader (develop questions Q18 and VW0, and the bridge's FB9). How it is
|
||||
checked: whole (assigner, bridge), before the first registrations a want is written from `source.main`, and no
|
||||
part's code holds `main`.
|
||||
- §508 **The node-engine claims its seat as one module.** (§527, §609) The seat `node-engine`'s definition in mesh-contracts no
|
||||
longer marks it held by the engine; the manifest validator accepts one module of a revision claiming it and
|
||||
refuses a second, naming both, in place of refusing every claim, and §494's release carries the rule. The
|
||||
node-engine's guard reads the holder from the seat list (§364). Why: §364 has the node-engine's manifest claim the
|
||||
seat (ADR 0563 §1), and the mark and the rule refused it (develop question on §364). How it is checked: unit
|
||||
(manifest validator), one claim accepted and two refused; unit (definition), no mark.
|
||||
- §509 **What the forge bridge writes on `forge`.** For each move of a watched branch, after reading the commit by
|
||||
`git.branch` (§11), the bridge writes one message: `merged` when the forge names the commit a pull request's
|
||||
merge, with that pull request, as on main; `pushed` when it names none, with the repository, the branch, the
|
||||
commit and the head before; the indexer takes both in alike. For a move of a branch whose protection was removed
|
||||
(§17) it writes nothing and says `branch-unprotected`; the next move after the protection is back is written as any.
|
||||
Why: one message per move, the pull request where there is one (develop questions FB1, FB2 and FB7). How it is
|
||||
checked: whole (bridge), an admitted push writes `pushed`, a merge `merged`, and a move of an unprotected branch
|
||||
writes nothing.
|
||||
- §510 **A merge the switch carries is performed.** (§608, §542) A `merges` key the old bridge had not performed, an Approve the
|
||||
operator gave before G, becomes an act (§169) that the bridge performs through `merge-pull`, judged as every merge;
|
||||
while `merge-pull` refuses it for missing registrations (§95) the act stays unanswered and is performed again on
|
||||
`registration-changed`. No other merge act is made after G (§167). Why: §169 drops nothing the operator approved
|
||||
(develop question on §167 and §169). How it is checked: whole (gate, bridge), a carried merge lands once after the
|
||||
first registrations.
|
||||
- §511 **The assigner's `written` key.** §352's key holds the newest passed `pipeline-ended` heard for that module and
|
||||
watched branch, its source revision and end count, and `written`, whether its wants were written; a newer end
|
||||
replaces it by compare-and-set, and the wants are written from it once they can be. Why: a restart loses no end heard
|
||||
and not yet used (develop question on §352). How it is checked: whole (assigner), an end heard, then a restart,
|
||||
writes its wants.
|
||||
- §512 **The status view's tiers from the difference.** A tier's module is `{module, build, version, after, waits,
|
||||
left-out}`: `build` the fingerprint, `after` the modules it is ordered after, each with the edge's kind (§378),
|
||||
from `difference`; a wait is `{owner, step, node, on, bound, since, words}`: the seat holding it, its `step`
|
||||
(§498), what it waits on, from the owners' `wait-held` and their read tools, and a pending revision from
|
||||
`indexer_pending` with `owner: mesh-catalog`, `step: take-in`. The view's manifest drops its `reads` of the
|
||||
teller's `answers` and the gate's `asks` and `source`: the Approve per merge goes (§431), its reach is the
|
||||
catalog half's (§196) and its source `source-moved` (§507). A dependent's delivery wait is read from the assigner's
|
||||
`wait-held` with `step: delivery` (§498) and `version-delivered`'s machines away. Why: §186 has the tiers worked out
|
||||
from the difference and the owners' waits (develop questions VW2, VW3 and on §431). How it is checked: test 45; test
|
||||
106 for the delivery wait.
|
||||
- §513 **A state's objects store has a cap, and the binding is mesh-sdk's.** (§607, §547, §591: the cap is §591's) The bus module plans each
|
||||
`<state>-objects` (§58) with the cap §591 names, unless the state's `requires` names `objects-max-mib`. The binding's `objects` and `sent-objects` (§399) are fields of mesh-sdk's
|
||||
`membership.BusBinding`, built in mesh-sdk. Why: the objects template has no default (develop questions NATS1 and
|
||||
on §399). How it is checked: unit (bus module's plan), each planned objects store has its cap; unit (membership),
|
||||
both fields read.
|
||||
- §514 **The teller tells through `notify`, on every channel.** (§522) For `widens` (§71, §335), `recovery-merged` (§81)
|
||||
and a `forge-made` record (§170) the teller calls the seat `operator-channel`'s verb `notify`, named in its
|
||||
manifest's `invokes`, with `every-channel: true`, which the router sends on every channel's `send`, as design 46
|
||||
announces a break-glass act. The words are the operator's to approve and wait for them (develop questions TL1 to
|
||||
TL3). How it is checked: whole (teller, router), each of the three reaches every channel once.
|
||||
- §515 **The condition keeper's sources are its consumers' bindings** (corrected by §663: the bus module's `created`). The keeper learns that the grants part made a
|
||||
source module's durable consumer (§386) from the seat `mesh-broker`'s binding of that consumer to the keeper (§399),
|
||||
heard as its event and read by `list` at the keeper's start, and asks `findings-asked` naming the module on it. At a
|
||||
start with no kept sources and no first view of the catalog half, its sources are the modules its consumers'
|
||||
bindings name, so no list of observers is fixed in its code (§7); the catalog half then adds and removes sources
|
||||
(§365, §388). Why: the binding is the fact that the consumer exists (develop question COND2). How it is checked:
|
||||
whole (keeper, grants part), a module declaring the finding events is asked once its binding is said; started with
|
||||
the catalog's holder stopped and no kept sources, the keeper hears every bound observer.
|
||||
@@ -0,0 +1,587 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0500"
|
||||
- "ADR 0573"
|
||||
- "ADR 0672"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0660"
|
||||
- "ADR 0656"
|
||||
- "ADR 0636"
|
||||
- "ADR 0626"
|
||||
- "ADR 0619"
|
||||
- "ADR 0618"
|
||||
- "ADR 0614"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0564"
|
||||
- "ADR 0505"
|
||||
- "ADR 0404"
|
||||
- "ADR 0363"
|
||||
- "ADR 0240"
|
||||
- "ADR 0235"
|
||||
---
|
||||
|
||||
# 47, milestone G: the operator's words of 2026-11-11 and develop's fourth batch of questions
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§522, §542 and §551 to §553 are the operator's answers of 2026-11-11 at 12:11, 12:33 and 12:41 CET at lead-47's terminal. §543 to §550
|
||||
answer what the four reviewers found in round 3 of [G-13](G-13-review-round-3.md) (§600 to §609), which has no round
|
||||
of its own after it. Every other line answers a question
|
||||
develop met after [G-11](G-11-develop-answers-review.md), of internal form only (playbook 07, step 0b), each the
|
||||
design's choice with its reason. What a question of another kind leaves open waits for the operator and is said in the
|
||||
line. The operator's answer of 12:55 CET (the recovery module, §554 and §555) and the
|
||||
review of §516 to §553, round 1 (§556 to §558), are in [G-14](G-14-recovery-module-and-review.md); the findings of
|
||||
that round are written into the lines they name, here. Questions that an earlier line already answers have no line here: develop reads that line.*
|
||||
|
||||
## Kept data on the bus: restore points and what is set aside
|
||||
|
||||
- §516 **A restore point's bytes, and the forms of `snapshot-kept` and `restore-kept`.** (§551, §554) The seat
|
||||
`mesh-recovery`'s two verbs (§554) take and answer the forms mesh-contracts' definition gave the seat `mesh-broker`'s,
|
||||
which move to `mesh-recovery` and which this line makes the design's: `snapshot-kept` takes `module`, `kept` (the
|
||||
kept data's local name), `form`, `fingerprint`, `why` and `catch-up` as `{tool, arguments}` (§451); its point
|
||||
(the module, the kept data, its form, the fingerprint, `snapshot`, `consumers` with each durable consumer's
|
||||
stream, name and acknowledged sequence, `size` in bytes, `at` and `catch-up`) comes on its event `ended` (§554); `restore-kept` takes
|
||||
`module`, `kept`, `form`, `fingerprint`, `why`, `from-backup` (yes or no) and `snapshot` (the restore point's name),
|
||||
and refuses `aside-only` and `discard-aside`, answering `error` naming them: setting aside without restoring and
|
||||
discarding an aside are the operator's hand tools alone (§554); its result (`restored`, `aside`, `missing`, true when the
|
||||
point is not in the recovery module's directory, and `error`) comes on its event `restored`. Both verbs answer only
|
||||
`accepted`, `busy` or `error` (§554). No argument `local-name` or `from` is taken, and a
|
||||
catch-up is never text.
|
||||
- **Where a point lives.** The recovery module takes a point through the bus's stream snapshot under its own account
|
||||
(§554), one bus object of the kept data at a time (its stream, or the stream under its bucket or object store),
|
||||
with ADR 0235 §10's pacing and §11's bounds, and writes it with the point's record under a temporary name, renamed to its own name only once the point is
|
||||
whole (as ADR 0235 §2 writes the dump), in its own data directory, `points/<module>/<kept>/<form>-<fingerprint>`;
|
||||
at its start it removes every point left under a temporary name, so a point cut off by a restart, the bus away or
|
||||
its bound is never listed nor restored; each token escaped as a key's name is (§73) and a token that is
|
||||
empty, `.` or `..` refused; that name is the point's `snapshot`.
|
||||
- **Its copy.** That directory is an item of the recovery module's manifest `data`, backed up by the holder of
|
||||
`node-backup` on its node as every item is (ADR 0404), which stores the copy and nothing else. When
|
||||
`restore-kept` answers `missing`, the node agent calls it again with `from-backup: yes`: the recovery module asks
|
||||
the seat `node-backup`'s `restore` of its item for that directory, which brings it back beside its path as that
|
||||
verb always does, takes the point from there and restores it once its digest is checked (§567); when no
|
||||
repository snapshot holds it, `restored` says `failed` with `restore point missing`, and §300 holds the revert.
|
||||
- **The aside, and a restore that fails.** `restore-kept` sets the live kept data aside by taking a stream snapshot
|
||||
of each of its bus objects into `aside/<module>/<kept>/<at>` in the same directory, reads the aside back to check it (`at` the UTC time written
|
||||
`20261111T121200Z`, with `-2`, `-3` and on added when that name exists), only then removes the live objects, which needs no person's warrant, the data staying in the aside (the operator,
|
||||
13:06 CET, §554),
|
||||
restores the point under their names and sets each consumer to its recorded sequence. Each step is written first
|
||||
in the aside's record (`set aside`, `removed`, `restored`): when a step after the removal fails, `restore-kept`
|
||||
restores the aside under the live names and sets the consumers back before it answers `failed`, and at its start
|
||||
the recovery module does the same for every aside whose record ends at `removed`, so a failed or stopped restore
|
||||
never leaves the kept data gone. The aside's `id` is that directory's name, its `name` the bus name it was live
|
||||
under, its `size` its bytes on disk; nothing of an aside stays on the bus. `recovery_discard_aside` (§554) removes that directory
|
||||
and nothing else, an `id` holding `.`, `..` or `/` refused. A restore point is removed only as §551 says.
|
||||
|
||||
This completes §405 and §406. Why: the operator's answer of 12:55 CET (§554) puts the taking and restoring of a
|
||||
module's kept data on the live bus on a module of its own; the server restores a stream only where no stream of that
|
||||
name exists, so the live data is set aside as a snapshot before the point takes its name (develop questions BKP1,
|
||||
BKP3 and NATS4's argument names; review round 1, running and testing 1, seams and tools' notes). How it is checked:
|
||||
unit (mesh-recovery's contract), each verb's input and answer read, an input naming `from` refused; a node-engine's
|
||||
`restore-kept` with `aside-only` or `discard-aside` refused, naming them; whole (recovery
|
||||
module, backup holder, bus), a point is taken, its directory removed, brought back with `from-backup` and restored,
|
||||
the live data's aside listed with its size and the consumer at its recorded sequence; a point missing from both says
|
||||
`restore point missing`; a restore made to fail after the removal, and one stopped there and started again, leave
|
||||
the live data as before; a point cut off half way is removed at start and never listed.
|
||||
- §517 **The recovery module lists what it holds.** The seat `mesh-recovery`'s read verb `kept-points` (class `read`,
|
||||
§391's default callers) lists every restore point and aside in its directory with its module, kept data, form,
|
||||
fingerprint, size and `at`, and the act that runs, with since when and what it waits on (§554); its tools are `recovery_kept_points`, which reads the same, and
|
||||
`recovery_expire_points`, which runs §551's removal now (class `act`, §391's default callers, written in the
|
||||
hand-act log). The directory is the one place they are kept, so no copy can list less: the backup holder keeps no
|
||||
state of them (its `backed-up` lists the recovery module's item as any item, §302), and no `bus-points` state is
|
||||
made. Why: a module's tools read everything it holds (*Tools are how a module is spoken to*), and the list is the
|
||||
holder's own fact (ADR 0596 §2; develop question BKP2; review round 1, seams and tools 4). How it is checked: whole
|
||||
(recovery module), `kept-points` lists a point and an aside with their sizes, an aside is gone after
|
||||
`recovery_discard_aside`, and a restart lists the same.
|
||||
- §523 **A seat event whose subject has parts.** A seat definition's `subject` with `<part>` tokens (§397), such as
|
||||
`mesh-recovery`'s `ended.<module>.<kept>` and `node-backup`'s `item-started.<node>.<module>.<item>` (§554), is said on
|
||||
`mesh.seat.<seat>.event.` followed by the subject, each part filled in the definition's order: the seat
|
||||
generator's subject function for the event takes each part as a string in that order and fills each as one token,
|
||||
escaped as a key's name is (§73), refusing an empty part, `.` and `..`. A reader hears one module's events by the subject with
|
||||
that module's tokens and `>` after them, and every module's by `*` for each part. Such an event has no key of its
|
||||
own and is kept as its definition's `keep` says, or on EVENTS when it names no `keep` (§396). The holder's account
|
||||
publishes it as a seat's holder publishes its seat's events today; no grant changes. Why: §397 fixes the subject's
|
||||
words, not how a part is filled (develop question NATS4's wire). How it is checked: unit (seat generator), a subject
|
||||
of three parts made in order and an empty part refused; whole (recovery module), `ended` heard by a reader of one
|
||||
module's subject.
|
||||
|
||||
## The tool runner and its bundles
|
||||
|
||||
- §518 **A bundle's put and get sessions run on its channel to the tool runner.** A tools bundle reaches the tool runner
|
||||
only over the channel the tool runner starts it with (its standard input and output) and holds no bus account, so
|
||||
§345's put session and §490's get session run on that channel, as every `mesh/` call of a bundle does (ADR 0595
|
||||
§9); `<bundle call subject>` in §457, §489 and §490 names that call, and no bus subject. A value at or under the
|
||||
limit is put and got by one call, as today. Over it:
|
||||
- **Whose object.** A session's `name` names an object of the bundle's own module alone: the tool runner resolves
|
||||
it in that module's binding (its state's `<state>-objects` or its sent store), and refuses a name of another
|
||||
module's, naming it. A put's whole size is bounded by that store's cap, and a put whose chunks pass it is refused
|
||||
at the chunk that does, naming the store.
|
||||
- **Put.** The bundle sends each chunk as the call `mesh/objects.put-chunk` with `name` (the object), `call` (the
|
||||
bundle's id for this put), `seq` (from 1, one more each chunk), `last` (true on the last chunk alone) and `data`
|
||||
(the chunk's bytes in base64, at most half the bus's maximum payload before encoding, §457). The tool runner
|
||||
answers each with `{seq}` or `{error}`, the bundle sends the next only after a `{seq}`, and the answer to the last
|
||||
is the put's answer; a `seq` that is not one more than the last taken is answered `{error}` naming both, and a put
|
||||
not sealed within the call's bound is dropped (§345). The tool runner writes each chunk into the store as it takes it, through the
|
||||
object store's own chunking, holding no more than one chunk in memory, and seals the object on the last. A put dropped at its bound has its written chunks purged by the tool runner,
|
||||
which deletes the unsealed object; until then they count against the store's cap.
|
||||
- **Get.** The tool runner answers the get call `{chunked: <count>, size, digest, call}` (§490), and the bundle asks
|
||||
each chunk in order by the call `mesh/objects.get-chunk` with `call` and `seq`, answered `{seq, last, data}` or
|
||||
`{error}`; the bundle checks the whole against `digest`, and a get not completed within the call's bound fails,
|
||||
naming the object. The tool runner drops a get session it holds when the call's bound passes, or the bundle's
|
||||
channel closes, whichever comes first.
|
||||
- **The bound.** The call's bound counts once for the whole session, from its first chunk, never per chunk.
|
||||
|
||||
The headers `Mesh-Put-Seq` and `Mesh-Put-Last` of §457 are these fields `seq` and `last`. Nothing of a session is
|
||||
on the bus, so §489's grants are not made and the grants part grants nothing for it. A module's process, which
|
||||
holds a bus account of its own, writes and reads its objects on the bus through the object store package directly,
|
||||
with the store's own chunking, and runs no session. The capability word `put-sessions` (§488) names these two
|
||||
calls. This amends §345, §457, §489 and §490; in the get the bundle asks each chunk, since on that channel every
|
||||
`mesh/` call is the bundle's. Why: the operator's answer to develop question D-13, chunks in order, each
|
||||
acknowledged before the next, is kept whole on the one channel a bundle has (develop questions T-5 and T-7). How it
|
||||
is checked: whole (mesh-sdk, tool runner), a value of a hundred times the limit, into a fixture state whose
|
||||
`objects-max-mib` holds it, is put and got back whole by a bundle, its digest checked, a put naming another
|
||||
module's object is refused, and a chunk out of order fails the put naming both sequences; the grants part's table
|
||||
test grants no session subject.
|
||||
- §519 **A bundle's fingerprint is in the environment the node-engine composes.** The node-engine writes
|
||||
`MESH_FINGERPRINT`, the fingerprint of the want it applies for the bundle's module, into each tools bundle's
|
||||
composed environment, as it hands it to each process (§205); the tool runner starts each bundle with its composed
|
||||
environment whole, and reads no catalog entry and no want for it. A bundle started with no value says `own build
|
||||
unknown` (§424). This completes §424. Why: the want the node-engine applies is the one fact of which build a node
|
||||
runs (§205; develop question T-12). How it is checked: whole (node-engine, tool runner), a bundle reads the
|
||||
fingerprint of its module's want.
|
||||
|
||||
## The node-engine and the want
|
||||
|
||||
- §520 **`running` says a build's health, and the assigner reads health from it alone.** The node-engine seat's
|
||||
`running.<node>.<module>` gains `healthy` (true or false), the health of the build that runs as the node-engine's
|
||||
have holds it from the module's declared check (ADR 0240), and the node-engine says `running` again on the same
|
||||
subject each time `healthy` changes, and for every module its have holds at its own start, so the stream, kept last per subject, holds each build's newest health.
|
||||
`stopped` (§365, §404) is said only when the have no longer holds the module, never for an unhealthy build. A `running` with no
|
||||
`healthy`, said by a node-engine of before G, reads as healthy while it stands, as `running` meant on main. The
|
||||
assigner reads a provider's health for rule 5, the canary's for rules 3 and 4, and a put's end from these events
|
||||
alone, read as §556 has it, and reads no have: a
|
||||
provider of which nothing was heard counts as not healthy, so its consumers' new wants
|
||||
are held as rule 5 holds any, and nothing falls back to the have. `SeatsToPassOn` reads no `healthy` (§459): an
|
||||
unhealthy holder still holds its seat. The assigner's `reads` of the node-engine's have go. This amends §309 and
|
||||
§404. Why: the assigner reads no other module's state (ADR 0596 §2), and a stopped build and an unhealthy one are
|
||||
told apart (develop questions AS15 and AS16). How it is checked: unit (the node-engine seat's fixtures), `running`
|
||||
with `healthy` read; whole (assigner, node-engine), a provider turning unhealthy holds a consumer's new want and its
|
||||
recovery lets the want go, with the assigner's account holding no read of the have; whole (assigner, a
|
||||
node-engine of before G), its provider's `running` with no `healthy` holds no consumer's want; a restarted assigner
|
||||
holds nothing it did not hold before.
|
||||
- §524 **The node-engine checks a want's parts by their digests.** A composition's archive, process and container
|
||||
resources name a source and a digest, not an artifact, so the node-engine matches no resource to a `parts` key. A want
|
||||
with no `parts` (from an assigner of before G, or for a node whose record does not state `want-parts`, §525) is
|
||||
applied as today, its resources fetched by their sources and checked by their own digests. A want with `parts` is
|
||||
applied only when the digest of every resource it fetched is among the values of the want's `parts`, and
|
||||
every value of `parts` is the digest of a resource it fetched; otherwise it applies nothing of that module, its
|
||||
have says the phase `held` (one of the seat's phases, which gains none) with its detail opening `parts-differ:` and
|
||||
naming each digest that differs, and it says `wait-held` with the reason `parts-differ` (§541). `parts`
|
||||
stays keyed by `<module>:<artifact>` for people and
|
||||
the delivery record. This completes §379. Why: the digest is what is fetched and checked (develop question H-8); a
|
||||
hold is the phase `held` with its reason, as every hold of §300, §421 and §499 is, so the seat's phases stay as they
|
||||
are (develop question of 2026-11-11 on H-8's phase, batch 5). How
|
||||
it is checked: unit (node-engine), a want whose parts and fetched digests agree applies, and one with a fetched
|
||||
digest outside `parts` or a part not fetched applies nothing, naming it; whole (node-engine of G), a want written by
|
||||
an assigner of before G is applied.
|
||||
- §525 **A node-engine says it reads the want's new fields.** A node-engine of G states the capability `want-parts` in
|
||||
its node record's capabilities (ADR 0614 §4, ADR 0618 §2), and the assigner writes `version`, `parts` and `kept-on`
|
||||
(§526) into a node's want only while that node's record states it; that is §379's "once that node runs G's
|
||||
node-engine". Why: ADR 0614 §4 has each node-engine state what it reads (develop question H-7). How it is checked:
|
||||
unit (node-engine), the word stated; whole (assigner), a node whose record lacks it gets a want without the fields.
|
||||
- §526 **A want names the node that keeps a kept build.** For a kept build (§271) the assigner writes `kept-on`, the
|
||||
build node the builder's `build.ended` names as keeping it (§379), into the want beside `parts`; the node-engine
|
||||
fetches that build through the seat verb `kept-artifact` of that node, and fetches a want with no `kept-on` from the
|
||||
store. Nothing else names the keeping node, the builder's `builds` going (§421). This amends §271's "the builder's
|
||||
done key names it so with its digest". Why: ADR 0596 §2 (develop question H-9). How it is checked: whole (assigner,
|
||||
node-engine), a kept build's want names its node and is fetched from it.
|
||||
- §527 **The node-engine's own module is read from the catalog half.** The seat list answers seat definitions alone
|
||||
(ADR 0636), never who holds a seat. The node-engine's apply guard and its compose read their own module's name from
|
||||
the catalog half of their view: the module whose entry claims the seat `node-engine`, which §508 makes one per
|
||||
revision; the manifest validator, beside §508's refusal of a second claim, refuses a revision in which no module
|
||||
claims it, naming the seat. §364's, §365's and §508's "from the seat list" read so, the condition keeper's too. With
|
||||
no entry claiming the seat, which only a revision from before G shows, as at a revert of G, the node-engine's own
|
||||
module is the module of the catalog graph entry its own build's fingerprint names (§443, §205), so a revert of the
|
||||
node-engine applies. Why: §364 and §508 have the node-engine claim its seat
|
||||
as one module, so the claim names it (develop question H-15). How it is checked: unit (manifest validator), a
|
||||
revision with no claim of `node-engine` refused; whole (node-engine), its guard protects the module its catalog
|
||||
half names, and with a revision of before G the module its own fingerprint's entry names, and the code review finds no fixed name (§428).
|
||||
|
||||
## Findings and the condition keeper
|
||||
|
||||
- §529 **A part's finding state and stream are declared in its kept data's form.** A part's state `findings` and its
|
||||
finding stream (§383) are declared as ADR 0666 §4 has a part declare its kept data:
|
||||
- **A foundation part** (the manifest validator's list: ADR 0619 §1's parts as ADR 0666 §4 changes them, the runner
|
||||
from G's delivery and the checker until its removal) declares `findings` in its old `state` section and the
|
||||
stream `<module>-findings` in its old `streams` section, with the events `finding-written.*`, `finding-cleared.*`
|
||||
and `findings-stated.*`, 7 days and 16 MiB, its oldest dropped (§383), published on
|
||||
`mesh.stream.<module>.event.<event>.<node>` (ADR 0363 §2).
|
||||
- **Every other part** asks both in its `requires`: the state `findings` by the template `state`, keeping its local
|
||||
name and carrying its settings as options (ADR 0595 §11), and the stream by the template `events` under the local
|
||||
name `finding-events`, with `max-age-days` 7 and `max-mib` 16, which the template drops oldest when full (ADR 0595
|
||||
§3), its entry listing the three finding events in its field `events`, which the catalog graph entry carries
|
||||
(§365), published on its binding's subject, `mesh.bus.<module>.finding-events.<event>.<node>`.
|
||||
- **`mesh-healers`**, whose old `streams` declares `mesh-healers-findings` on main, moves it so in G's change, as
|
||||
ADR 0595 §11 moves a module G touches. Its old stream stays on the bus as an orphan that `orphans` lists, removed
|
||||
only by the operator's `drop` (ADR 0595 §4, §5); the grants part deletes the keeper's consumer on it once the
|
||||
healers' entry no longer declares it (§386), and the healers say `findings-stated` at their start on the new
|
||||
stream (§384), so the keeper loses no finding.
|
||||
|
||||
The manifest validator applies ADR 0595 §11's count of old sections to no part of its foundation list (ADR 0619
|
||||
§2), so the tool runner's and the checker's manifests are not refused for it, and accepts in a foundation part's
|
||||
old `streams` the finding stream in the one form above, as it accepts a stream on its list of streams kept until
|
||||
moved; every other old `streams` entry is judged as today. A part whose list entry is missing is a fault of the
|
||||
list, fixed in the validator (ADR 0619 §3). The validator refuses a manifest whose state `findings` has no finding
|
||||
stream in its part's form, or no `consumes` of `mesh-conditions.findings-asked` (§387). The writer (§461) is handed
|
||||
the subjects from the part's binding or its old stream, as it is handed every subject. The catalog graph entry
|
||||
carries the finding stream in either form (§365); the grants part makes the keeper's consumer of §386 on an
|
||||
old stream at the revision that declares it, and on a template stream when it hears the bus module's `created` for
|
||||
`finding-events` and grants each part publish on its own finding stream's subjects of its own node only, in either form; the
|
||||
keeper takes the observer and the node from either subject. This amends §383, §386 and §387, whose "into the
|
||||
manifest's `streams`" holds for a foundation part alone. Why: ADR 0666 §4 and ADR 0595 §11 put a part's kept data
|
||||
in the templates unless it is a foundation part, and ADR 0619 §2 keeps the foundation's old sections whole; the
|
||||
local name `finding-events` is the design's choice, since `findings` names the state and ADR 0595 §8 takes one
|
||||
name per module (develop questions on §387, both validator refusals). How it is checked: unit (manifest validator),
|
||||
a foundation part's old `findings` and finding stream accepted, a non-foundation part's old sections refused and its
|
||||
template form accepted; the catalog's validation at G's heads refuses none of §387's manifests; whole (keeper, a
|
||||
template observer and the tool runner), both forms' events raise a condition; whole (keeper, healers), a healers'
|
||||
finding raised before their move is still raised after it.
|
||||
- §540 **The keeper hands a failed head to the operator after two hours.** The condition keeper raises
|
||||
`pipeline-failed` (§33) with `owner` the commit's author, as the runner's finding names it, and two hours after the
|
||||
finding's `since` sets the condition's `owner` (a field of the condition, §685) to `operator-channel`, the seat the operator is asked through (a
|
||||
seat's name, §451), and says that change as it says any change of a condition; the runner's finding keeps the
|
||||
author. §451's `owner` and `waiting-on` gain a third form, `forge:<account>`, the commit's author as the forge
|
||||
names it. A `pipeline-failed` for a newer failing head is a finding of its own, its id naming that commit, so it is
|
||||
its new author's with two hours of its own. The keeper's tool `conditions_owner` sets a condition's `owner` now, of
|
||||
class `act` with §391's default callers, written in the hand-act log. The keeper times the two hours on its own conditions from `since`, so after its restart it times them from
|
||||
`since` again. Why: §33 has the keeper raise it as the author's, then the operator's (ADR 0505 §5, §6; develop
|
||||
question COND7). How it is checked: whole (keeper), a `pipeline-failed` is the operator's two hours after its
|
||||
`since`, and so after a restart of the keeper; unit (mesh-conditions' contract), `forge:<account>` read.
|
||||
- §541 **The keeper hears what a node runs and the healers' attempts as events.** The keeper's `reads` of
|
||||
`node-engine.have` and `mesh-healers.attempts` go (§434).
|
||||
- **What a node runs.** The keeper consumes the node-engine seat's `composed`, `running` (with `healthy`, §520, and
|
||||
`stopped`) and `wait-held` and `wait-ended` (§499), read as §556 has it, and compares them with the wants it hears
|
||||
from the assigner, to hold a module's findings while it is delivered (ADR 0564 §13), as it did from the haves.
|
||||
The node-engine says `wait-held` on `mesh.seat.node-engine.event.wait-held.<node>.<module>`, with `step: compose`
|
||||
and the reason, for each hold its have writes (§300's `restore point missing`, §421's `entry-missing`, §524's
|
||||
`parts-differ`, §499's kept build), and `wait-ended` on `.wait-ended.<node>.<module>` when the hold ends. The
|
||||
keeper raises no condition of its own from them: the watchman writes `delivery-waits` for a `wait-held` of the
|
||||
node-engine standing past composing's 30 minutes (§266), naming the node, the module and the reason, and it clears
|
||||
on `wait-ended`; a `composed` whose fingerprint is not the want's is covered by that same wait or by the put's
|
||||
wait (§498).
|
||||
- **The healers' attempts.** The seat `mesh-healers` says `attempt-changed` on
|
||||
`mesh.seat.mesh-healers.event.attempt-changed.<condition>.<attempt>` (§665), each token escaped as a key's name is (§73),
|
||||
carrying the attempt's value whole as its key in `attempts` holds it and `until`, the end of that key's
|
||||
lifetime, or `removed: true` when the healer ends the attempt; the seat's definition, in its contracts module,
|
||||
keeps it last per subject, 7 days and 16 MiB (§396). The healers say it again for every open attempt at their start. The
|
||||
keeper reads it as §556 has it, and drops an attempt at its `until` on its own clock.
|
||||
|
||||
Why: the keeper reads no other module's state (ADR 0596 §2; develop question COND8). How it is checked: whole
|
||||
(keeper, healers, node-engine), an attempt said is shown on its condition and dropped at its `until` or its
|
||||
removal, a node-engine's hold is heard from `wait-held` and past 30 minutes raises `delivery-waits`, a restarted keeper
|
||||
holds the attempts it held, and the keeper's manifest declares no `reads`.
|
||||
|
||||
## Views of the catalog half, per watched branch
|
||||
|
||||
- §521 **An owner's view event names its state, and the key names the branch.** An event an owner says for a per-branch
|
||||
state (`catalog-revision` for `catalog-revisions`) names the state as the owner's manifest names it,
|
||||
`catalog-revisions`, and the key, `<branch>.<revision>` (§343); mesh-sdk's `go/views` reader and process reader
|
||||
route it to the view `<owner>.<state>.<branch>` (§479) by the key's first token, the branch escaped as a key's name
|
||||
is (§73), which the subject's last token repeats. No event names a state `<state>.<branch>`; mesh-sdk's code at
|
||||
eed96854, which names it so, follows this line. Why: a state is named once, as its owner declares it, and the key
|
||||
already carries the branch (develop question on §479 and §495). How it is checked: unit (`go/views`), one
|
||||
subscription of a per-branch event routes two branches' events to their two views.
|
||||
- §530 **`revision` says `none yet` in a field.** The seat `mesh-catalog`'s `revision` for a watched branch with no
|
||||
revision yet answers `{none-yet: true}`, with no revision and no `error`; mesh-sdk's client reads `none-yet` as a
|
||||
ready, empty view (§495) and never reads an error's text for it; `views.NoneYet` is the client's name for that
|
||||
answer. Why: an answer is told by its fields, never its words (develop question on §495). How it is checked: unit
|
||||
(mesh-catalog's contract), the answer read; unit (client), `none-yet` gives a ready, empty view.
|
||||
- §532 **Every reader of the catalog half follows every watched branch, and adopts a branch registered while it runs.**
|
||||
Every part that reads the catalog half per watched branch (among them the bus module, the status view, the
|
||||
condition keeper, the tool runner, the assigner, the grants part and the runner) follows one view per watched
|
||||
branch: the branches the registrations name, read by `registrations` at its start and followed by
|
||||
`registration-changed`, and before the first registrations the branches the gate's `source` answers (§507); no part
|
||||
names a branch in its code, the view's `main` included.
|
||||
- **Adopted after the start.** mesh-sdk's views reader adopts a view after its start and drops one: on
|
||||
`registration-changed` adding a watched branch the part adopts `<owner>.<state>.<branch>`, which takes its first
|
||||
view at once and then follows; on one removing a branch the part drops the view and its row of
|
||||
`<module>_first_views`.
|
||||
- **One subscription per event.** The reader subscribes once per event, `<event>.>`
|
||||
(`mesh.seat.mesh-catalog.event.catalog-revision.>`), and routes each event to its branch's view by §521, so two
|
||||
branches never overwrite each other's handler; `meshcatalogclient` wires its views on that one reader, and the
|
||||
runner's own wiring of `catalog-revision.>` goes for it.
|
||||
- **The bus module across branches.** The bus module plans a module's kept data from the newest revision of each
|
||||
watched branch that lists the module: each local name once, from the first of the module's watched branches, in
|
||||
its registration's order, whose revision lists it. A registration's watched branches are a list in that order: the
|
||||
branch `register` names first (the repository's default branch, a placement's default, ADR 0666 §6), then each
|
||||
branch in the order `watch` added it; `unwatch` removes one and keeps the others' order; another branch asking that local name with other settings is
|
||||
said as the bus module's `drifted`, naming the branch (ADR 0595 §6), and plans nothing.
|
||||
|
||||
This amends §358, §365, §421, §433 and §507. Why: no part fixes a branch (§7, ADR 0666 §5), and a registration may
|
||||
add a watched branch while a part runs (develop questions NATS3, VW1 and COND6, and the client's lost per-branch
|
||||
events). How it is checked: unit (`go/views`), a view adopted after the start takes its first view and follows,
|
||||
and two branches' events reach their own views; whole (keeper, bus module), a branch registered while they run is
|
||||
followed with no restart; whole (bus module), a feature branch asking a local name with other settings than the
|
||||
default branch's is said `drifted` while the default branch's are planned; the code review finds no branch's name
|
||||
in any part.
|
||||
|
||||
## The bus module's plans
|
||||
|
||||
- §533 **A module of the new form.** "A module of the new form" (§59, §344) is a module that is no foundation part.
|
||||
The bus module tells it by its catalog graph entry declaring no old `objects` section `sent-objects`: a foundation
|
||||
part whose entry claims a seat whose definition has a verb or an event, or declares an event of its own, declares
|
||||
it (§59), and the manifest validator refuses such a foundation part's manifest without it, naming it. Why: the bus
|
||||
module reads no list of foundation parts, which is the validator's alone (ADR 0619 §3; develop question on §59 and
|
||||
§344). How it is checked: unit (manifest validator), a foundation part saying an event without `sent-objects`
|
||||
refused; whole (bus module), a module of the new form gets a sent store and a foundation part none.
|
||||
- §537 **A definition's included parts are resolved before they are counted.** The bus module counts a seat
|
||||
definition's verbs and events after resolving the parts it includes: it reads each definition that includes parts
|
||||
by the seat list's answer for that one seat, which resolves them (ADR 0636), and plans a sent store (§344) for the
|
||||
seat's holders when the resolved definition has a verb or an event. Including parts alone counts for nothing.
|
||||
Why: an included part may hold only shared bodies (develop question on §344). How it is checked: whole (bus module),
|
||||
a seat whose verbs come only from an included part gives its holder a sent store, and one including only bodies
|
||||
gives none.
|
||||
- §538 **The bus module plans again when the bus-state entries change.** The bus module compares, for each module,
|
||||
the bus-state entries the catalog graph entry carries with those it last planned for that module, kept in its old
|
||||
`state` section `plans` (the bus module is a foundation part), one key `<module>` holding the entries as planned
|
||||
and their digest, written after each plan and deleted when the module is in no watched branch's newest revision, its
|
||||
entries then orphans that `orphans` lists; its tool `nats_plans` reads it. It plans again only when they differ; it never compares the manifest's digest (§506), which changes with
|
||||
any field. Why: what it plans is those entries alone (develop question on §433 with §506). How it is checked: whole
|
||||
(bus module), a manifest changed outside `requires` plans nothing, and a changed option plans the difference.
|
||||
|
||||
## The runner, the checker and mesh-sdk's helpers
|
||||
|
||||
- §528 **The checker built from G's heads asks no group build.** From G's delivery the pipeline is the check and the
|
||||
run on request the try (§66), so the checker built from G's heads makes no group check: its group build through
|
||||
tagged versions (its check's group path, `judge.go`'s, and `tagwanted.go`) goes with `tag-wanted` (§373), nothing replaces it in
|
||||
the checker, and a group's builds against its package members' digests are the runner's (§373, §465). The
|
||||
checker's old Node form goes with it: it no longer packs a group member's package for a Node fetch (ADR 0500 §5),
|
||||
for any group, since a group's builds against its members' heads are the runner's (ADR 0573 §4). The checker's
|
||||
manifest of G's heads consumes neither package registry's `published` and its asks stream carries no `tag-wanted`;
|
||||
its `go.mod` requires nothing that no code of it imports; and the checker's seat definition in mesh-delivery's contracts (§645)
|
||||
defines no `tag-wanted` holder event, which goes as §321 retires `tag-wanted`. Until G's
|
||||
delivery the running checker makes G's own check with main's code (ADR 0667 §2). Why: §66 and §373 (develop
|
||||
questions CK10 and the checker developer's three after it, batch 5). How it is checked: the code review finds no
|
||||
group build and no Node packing in the checker of G's heads; unit (manifest validator), the checker's manifest of
|
||||
G's heads accepted without the two `consumes`; unit (mesh-contracts), the checker's seat definition holds no
|
||||
`tag-wanted`.
|
||||
- §534 **The wait's `step` in the runner's body.** The runner's wait body keeps `step` as the pipeline step a wait inside
|
||||
a pipeline is in (`lint`, `unit`, `integration`, `composition`, `review`, `approval`), and gains the value
|
||||
`pipeline`, the acting step of §203, for a wait of the entries the runner holds for its new version (§266,
|
||||
`runner_waits`); `clock-from` stands beside it (§498). The watchman tells them apart by the value: `pipeline` and
|
||||
every value §498 lists are acting or delivery waits, timed by their bound (§269); a pipeline step's value is a
|
||||
step's wait on a person, a build node or the revision view (§73), timed as §75 has it, a wait on a person owing no
|
||||
progress (ADR 0666 §24). This completes §498 for the runner. Why: one field, told apart by its value (develop
|
||||
question on §498). How it is checked: unit (runner's contract), both kinds read; whole (watchman), a runner's
|
||||
`pipeline` wait raises `acting-part-late` past its 30 minutes and an `approval` wait raises no `pipeline-stands`.
|
||||
- §535 **A missing build seat and a seat with no holder, each read where it shows.** The runner reads that no build
|
||||
seat serves an artifact's language from the catalog half of its revision view: no entry whose `acts`, the acting
|
||||
rules of the seats it claims, hold a rule `build` with `on` naming that language (§323); the pipeline then ends
|
||||
failed at once, naming the language and the missing build seat. It reads that a seat has no holder from the step
|
||||
itself: an ask on that seat's work queue whose `step-started` is not heard within five minutes ends the pipeline
|
||||
failed, naming the seat (§32). It reads neither from the seat list, which answers definitions alone (§527), nor
|
||||
from any holding. Why: §32 already ends a pipeline whose step nothing starts, and the catalog half is the runner's
|
||||
one view (develop question Q-RN7). How it is checked: whole (runner), a fixture language with no build seat ends at
|
||||
once, naming it; a seat whose holder is stopped ends within five minutes, naming the seat.
|
||||
- §539 **The runner's tools are named `runner_`.** The runner's module, `mesh-delivery-pipeline`, names its tools with
|
||||
the prefix `runner_`, so the design's `runner_catch_up`, `runner_waits`, `runner_release` and `runner_health`
|
||||
stand, and its first-view tool is `runner_first_views`; `<module>_first_views` (§205) carries the prefix the
|
||||
module's tools carry. Why: the design names the runner's tools so throughout (develop question RN1). How it is
|
||||
checked: the runner's manifest, and `mesh_search` with "runner first views" finds `runner_first_views`.
|
||||
- §531 **The kept-call form in Go.** §460's form is mesh-sdk's `busretry.DoKept`, which takes the ask's key, `open` (of
|
||||
the type `busretry.Open`), an optional `replaced` and the function that writes the journal, and a process's call
|
||||
through it is `process.Conn.CallKeptWhileOpen`, as mesh-sdk 19fb1dce has them. Why: a helper's API is the design's
|
||||
(develop question R-5). How it is checked: §460's.
|
||||
- §536 **`SeatsToPassOn`'s types.** §459's function in mesh-sdk's `catalogue` takes `holdings`, `assignments` (a list
|
||||
of `Assignment` with `Node`, `Module` and `Branch`), `heard` (a function of a node and a module answering a `Heard`
|
||||
with `Fingerprint` and `Stopped`, and whether anything was heard) and `entries` (an `EntryReader` with
|
||||
`ByFingerprint` and `OnBranch`), as mesh-sdk e0f9356b has them. Why: a helper's API is the design's (develop
|
||||
question R-4). How it is checked: §459's.
|
||||
|
||||
## The operator's words
|
||||
|
||||
- §522 **The words the teller says.** The teller says, through `notify` on every channel (§514), in the operator's
|
||||
words:
|
||||
- `widens`, one line per entry of the pipeline's end (§335): "Widening: `<module>` `<commit7>` on `<branch>` widens
|
||||
`<kind>` `<name>`.", `<kind>` and `<name>` the entry's.
|
||||
- `recovery-merged` (§81): "Recovery merged: `<module>` `<commit7>` on `<branch>`, named by `<caller>`: `<why>`."
|
||||
- a `forge-made` record (§170): "Merged at the forge, not through the mesh: `<repository>` pull request #`<n>` at
|
||||
`<commit7>` on `<branch>`; delivered like any merge."
|
||||
|
||||
`<commit7>` is the commit's first seven hexadecimal characters. This completes §514. Why: the operator, 2026-11-11
|
||||
12:11 CET at lead-47's terminal, approved the words as the design writer proposed them. How it is checked: unit
|
||||
(teller), each of the three said in these words with its fields filled; whole (teller, router), as §514.
|
||||
|
||||
- §542 **An Approve given before G is that pull request's approval step.** The operator, 2026-11-11 12:33 CET at
|
||||
lead-47's terminal: approval is a step whose status each module's registration sets, never a gate of its own, so
|
||||
an Approve given before G counts, once G is delivered, as that pull request's `approval` step and nothing else is
|
||||
special. At G's switch the gate says, for each `merges` key the old bridge had not performed, `approval-carried` on
|
||||
`mesh.seat.mesh-delivery.event.approval-carried.<repository>.<pull>` (`repository`, `pull`, `commit` the head the
|
||||
Approve named, `approver` the operator, `at`), declared in the seat's definition with `keep` 90 days (§396). The gate
|
||||
keeps its `merges` keys, read no more, until §97's removal, so a revert's gate of before G finds them. The runner
|
||||
hears the event through a durable consumer made to start from the stream's first message, so a runner started
|
||||
after the switch hears every one, and keeps each in its old `state` section `pipelines` as
|
||||
`carried-approval.<repository>.<pull>`, holding the commit. It takes it as the result `passed`, caller the
|
||||
operator, only of the `approval` step of the delivering pipeline whose source revision takes in that pull
|
||||
request's merge, as `take-in.taken`'s entries name it (each entry carrying the repository and the pull request of a
|
||||
`merged` message, §493, §509), and only when the pull request's head merged is that commit; never in a run on
|
||||
request and never in another pipeline at the same commit; then it deletes the key, and deletes it unused when the
|
||||
seat `git` says the pull request `pull-closed`. Where the module's policy makes `approval` `required`, a pull request whose head
|
||||
moved after the Approve asks a new approval, as any step whose input changed does (an `approval` step is never
|
||||
carried, ADR 0666 §8); where it is `optional` or `bypass`, the carried Approve holds nothing. The merge itself is
|
||||
any merge (`merge-pull`, ADR 0666 §11): the bridge performs no carried act, the gate says no `wait-held` with
|
||||
`step: merge`, and the watchman writes no `merge-waits`. Every `wait-held` the runner says for an `approval` step
|
||||
has its `wait-ended`, said when the step ends in any of §24's outcomes (`passed`, `declined`, `superseded`,
|
||||
`cancelled`, `not asked`) or its pipeline is superseded or cancelled, so no approval wait stands without an end.
|
||||
This replaces §510 and §608, and `merge` leaves §498's list. Why: the operator's answer ("you're mixing in step and
|
||||
approval gates, this should be configurable"); the event, the key and the deletion are the design's (round 3,
|
||||
health and events' R7). How it is checked: whole (gate, runner), a carried Approve of a head passes the `approval` step
|
||||
of the pipeline taking in that pull request's merge, with the operator as caller, and no run on request at that
|
||||
commit; a runner started after the switch takes it all the same; with `approval` `required`, a head moved after the Approve asks a new
|
||||
approval; a pull request closed deletes its carried key; every `approval` wait-held in the whole test has its
|
||||
`wait-ended`; the code review finds no `merge-waits`.
|
||||
|
||||
## Round 3 of the review of §480 to §515
|
||||
|
||||
- §543 **A build seat's holder refuses a kind its asker may not ask.** Callers stay per accepted kind (§338), and the
|
||||
builder joins them as the one caller, beside the checker until §483's removal, of the kind `build`, as §601 has it
|
||||
ask under its name. The grants part grants each asker publish on `mesh.seat.<seat>.accept.<queue>.<asker>` (§600)
|
||||
for every queue of the seat on which a kind it is a caller of is taken, and the holder, which takes the asker from
|
||||
the subject (§481), refuses an ask whose body names a kind that asker is not a caller of, answering `error` naming
|
||||
the kind and the asker, and ends it with `step-ended` `failed`. On main's subject `accept.<queue>`, which names no asker and is granted to
|
||||
the checker and the builder alone (§600), the holder takes only the kinds `build` and `run` and refuses any other,
|
||||
`step` among them, as above; the builder's `run` there is taken until the checker's removal (§97), after which
|
||||
main's subject goes (§483). This amends §338 and §600. Why: §600 puts the queue
|
||||
in the subject and the kind in the body, so a subject's grant alone cannot hold a caller to its kinds (round 3,
|
||||
security and upgrade, seams and tools: R1). How it is checked: unit (Go build module), a `step` published from the
|
||||
indexer's account is answered `error` naming `step` and `indexer`; unit (grants part), each asker's grants are the
|
||||
queues of its kinds; unit (Go, Node and image build modules), a `step` on main's subject is answered `error`.
|
||||
- §544 **Named asks have a worker of their own.** For each queue the grants part makes, beside main's worker
|
||||
`SEAT_<SEAT>_<QUEUE>_worker` filtered to main's `mesh.seat.<seat>.accept.<queue>` alone, a second worker
|
||||
`SEAT_<SEAT>_<QUEUE>_named_worker` filtered to `mesh.seat.<seat>.accept.<queue>.*`; the two filters do not overlap,
|
||||
so the work-queue stream takes both. Only a holder of G pulls the named worker, and a holder of before G knows only
|
||||
main's, so no filter is ever switched and a holder of before G that comes back from away (a laptop) after the grants
|
||||
part runs takes no named ask. This replaces §601's "until every node's holder of a build seat is of G the grants
|
||||
part's worker keeps main's one filter", and with it the edge from the seats' holders to the grants part, which no
|
||||
manifest fact gives: the order is the asker's `invokes` of `<seat>.accept.<queue>` (kind 6, from the seat's holders
|
||||
to the asker, §601), and the grants part comes before every asker as the acting part of the step `grant` on
|
||||
`every` (§203): each entry of a revision waits for the grants part's new version to act for it (§266), so no asker
|
||||
of G runs before a grants part of G has made its named worker and its grant. A holder of G pulls main's worker
|
||||
alone until its membership names the named worker, which the grants part writes as it binds every consumer
|
||||
(§386), and says meanwhile, in its journal and its read tool, `named worker not bound`. A named ask waits on its worker while no holder of G
|
||||
runs, said by `build-capacity-waits` after 60 minutes, which corrects §483's "waits on the work queue, whose
|
||||
consumer of before G takes only main's subject". At a revert, the grants part of G takes its view of the revert's catalog revision before
|
||||
it is itself reverted, since it acts for every entry of it (§203); at the first revision in which no entry `invokes`
|
||||
any `<seat>.accept.<queue>`, it purges the subjects `accept.<queue>.*` of the stream and deletes the named workers,
|
||||
within its granting bound of 10 minutes (§266), saying each in its journal. Where that did not run (the grants part
|
||||
stopped first), its tool `grants_named_purge`, of class `act` with §391's default callers and written in the
|
||||
hand-act log, does it by hand; an ask is transport,
|
||||
not kept data (as §59 has a sent object). Why: one consumer per filter, and a filter that never moves cannot be
|
||||
pulled by a holder that does not read it (round 3, R2, the security reviewer's form). How it is checked: whole
|
||||
(grants part, Go build module), a holder of before G started after the grants part of G takes main's asks and no
|
||||
named one; a revert leaves no named worker and no named ask; a holder of G before its binding pulls main's worker
|
||||
and says `named worker not bound`.
|
||||
- §545 **The tool runner's artifact declares the words it states.** The artifact entry of the tool runner's manifest
|
||||
takes `states`, the capability words (§488) it states; the manifest validator refuses a package artifact's `calls`
|
||||
word (§605) that no artifact of the revision `states`, naming the word, and the delivery plan's edge of §605 runs to
|
||||
the module whose artifact `states` that word, so no manifest names the tool runner's module. A bundle is started by
|
||||
the tool runner on its channel and ends with it, so a restart of the tool runner restarts its bundles, and each
|
||||
reads the stated words at its own start; no event is heard and nothing polls. This amends §605's "each time the
|
||||
tool runner of its node starts". Why: the words are stated only at run time, and the plan needs them in a manifest
|
||||
(round 3, seams and tools: R3). How it is checked: unit (manifest validator), a `calls` word nothing `states`
|
||||
refused; unit (tool runner), the words it states are exactly its manifest's `states`; unit (delivery plan), the edge to the module stating the word.
|
||||
- §546 **When the builder forgets a node's toolchain.** The builder deletes its `toolchains-heard` key
|
||||
`<node>.<language>` (§604) when the node-engine seat's `running.<node>.<module>` says `stopped` (§365) for the
|
||||
module whose catalog graph entry claims the build seat of that language (its `acts` rule `build` with `on` naming
|
||||
it, §535), heard through its consumer of that seat's `running`, read as §556 has it; never on a placement, which
|
||||
is the seat `mesh-module-assigner`'s state. The state is an old `state` section of the builder, a foundation part,
|
||||
one key per node and language, about 12 keys of about 0.3 KiB each, capped at 1 MiB, so it adds no objects store
|
||||
to §607's count (§58 plans one only for a state of the template); `builder_toolchains` reads it and
|
||||
`builder_toolchains_drop`, of class `act`, taking its default (§610), deletes one key,
|
||||
with why, written in the hand-act log of the seat `mesh-hand-acts`; a
|
||||
holder says the key again at its start and on each change (§492). This amends §604's "when the node leaves the
|
||||
catalog's placements". Why: the builder reads no other module's state (ADR 0596 §2; round 3, seams and tools: R4).
|
||||
How it is checked: whole (builder), a build module stopped on a node deletes that node's key, and the drop tool
|
||||
deletes one key and logs it.
|
||||
- §547 **The sum of the bus's caps is read against its limit.** Measured on 2026-11-11 against an embedded bus
|
||||
(nats-server 2.11.17): an account's storage limit reserves each stream's maximum size when the stream is made (a
|
||||
second stream of 64 MiB under an account limit of 100 MiB is refused, "insufficient storage resources"), and a
|
||||
server's own limit does not. So each `<state>-objects` is planned with 16 MiB (about 2.4 GiB for 150 states), a
|
||||
state needing more naming `objects-max-mib`, and no later measure is waited for. The bus module reads the account's
|
||||
and the server's storage limits at its start and at each plan, sums the cap of every entry it plans (states, objects
|
||||
stores, sent stores, streams, logs) and of every old section the catalog graph's entries declare, and shows the sum and
|
||||
both limits in its `list` and its read tool. A plan whose sum would pass the account's limit is refused, naming the
|
||||
sum and the limit, said as its `refused` (ADR 0595 §6): the plan refused is the one that adds or raises a cap, and
|
||||
the caps already made stay as they are. With no account limit (unlimited) nothing is refused and the sum is shown
|
||||
against the server's limit. This amends §607's 64 MiB and its measure in G's check. Why: the limit is a
|
||||
fact of the server, read rather than written in the design, and a sum over it fails loudly (round 3, running and
|
||||
testing: R5). How it is checked: whole (bus module), a fixture bus with a small account limit refuses the plan
|
||||
that would pass it, naming the sum, and keeps the caps made; with no account limit nothing is refused; the sum and
|
||||
the limits are in `list`.
|
||||
- §548 **The forge module writes `seen` only from a pipeline's steps.** §487's `seen.<commit>.<module>` is written only
|
||||
from a `step-ended` that carries a pipeline's `commit` and `fingerprint`, which a pipeline's step carries; the
|
||||
`step-ended` of any other ask (§602) carries `asker`, `ask`, `node`, `outcome` and `at` and neither, so the forge
|
||||
module writes nothing from it and names no asker. This amends §487.
|
||||
Why: since §602 every ask says `step-ended`, and a builder's build is no run of a commit (round 3, health and
|
||||
events: R6). How it is checked: unit (forge module), a builder's failed build writes no `seen`; whole (watchman), a
|
||||
`stuck` raised for an ask clears on its late `step-ended`.
|
||||
- §549 **Smaller corrections from round 3.** At its start the runner appends an `unlogged` entry (§606) only after
|
||||
reading back its `step-log`'s last entry of that pipeline and step, and skips it when that entry is already
|
||||
logged, so a stop between the append and the clear appends nothing twice. `step-progress` and `ask-taken` (§603)
|
||||
name no `keep` and go on EVENTS, kept 7 days (§396). `runner_unlogged_append` is of class `act` and takes §391's
|
||||
default callers. §58 is planned with the cap §607 and §547 give. How it is checked: whole (runner), a runner
|
||||
stopped between the append and the clear logs the entry once; the rest as the lines they correct.
|
||||
- §550 **A publication's pipeline keeps the version.** The runner's pipeline value gains `version`, the version
|
||||
`package-published` names, for a pipeline the runner started from a publication or from `outdated` (§334), and
|
||||
none for any other; `show` answers it. This completes §334's "the version in the pipeline's state". Why: the
|
||||
runner's contract had no field for it (develop's note on §334). How it is checked: unit (runner's contract), the
|
||||
field read; whole (runner), a publish's pipeline shows its version.
|
||||
|
||||
## The operator's answers of 12:41 CET
|
||||
|
||||
- §551 **A restore point is removed after 90 days, never the newest.** The operator, 2026-11-11 12:41 CET at lead-47's
|
||||
terminal. The recovery module removes a restore point's directory (§516) 90 days after its `at`, as long as delivery
|
||||
records are kept, unless it is the newest point of its module and kept data, which it never removes; it checks at
|
||||
its start, once a day and at each `snapshot-kept`, on its own clock and its own directory, as one of its acts, never
|
||||
beside a `restore-kept` (§554). Each removal is written in the hand-act log of the seat `mesh-hand-acts` and said as
|
||||
`mesh-recovery`'s event `point-removed` on `mesh.seat.mesh-recovery.event.point-removed.<module>.<kept>` (`module`,
|
||||
`kept`, `form`, `fingerprint`, `snapshot`, `at`), kept 90 days (§396). The backup holder's copies go by its
|
||||
repository's own rotation (ADR 0235 §4). Data set aside is removed only by `recovery_discard_aside` (§554). This completes
|
||||
§516 and answers issue 1921's note on the retention of restore points. Why: the operator's answer; the event, the
|
||||
checks and their order are the design's. How it is checked: whole (recovery module), a point older than 90 days is
|
||||
removed and said, the newest point of a kept data older than 90 days stays, and a removal due during a restore runs
|
||||
after it.
|
||||
- §552 **The checker may drop its own asks until its removal.** The operator, 2026-11-11 12:41 CET at lead-47's
|
||||
terminal: the build seats' verb `drop` (§338) gains the checker as a caller until its entry is removed with its
|
||||
buckets and bus account (ADR 0667 §12), as `run` has it, so the validator accepts the checker's manifest's
|
||||
`invokes` of `seat:node-build-go.drop` and `seat:node-build-node.drop`. This amends §338. How it is checked: unit
|
||||
(manifest validator), the checker's manifest accepted; whole (grants part), the checker's drop granted and another
|
||||
module's refused.
|
||||
- §553 **A step status left empty is `optional`.** The operator, 2026-11-11 12:41 CET at lead-47's terminal: a step
|
||||
whose status a registration's policy (module-level `policy.steps`, or an artifact's) leaves empty is read as
|
||||
`optional`: it runs, and a failure is shown and holds nothing (§23); `register` and `policy` accept such a policy
|
||||
and refuse nothing for it, and the runner, `registrations` and `show` say the step `optional`. This completes §23.
|
||||
How it is checked: unit (runner), a step with no status runs and its failure holds no want; unit (mesh-catalog's
|
||||
contract), a registration with an empty status accepted.
|
||||
|
||||
## Questions an earlier line answers
|
||||
|
||||
These write nothing new; develop reads the line named.
|
||||
|
||||
- The tool runner's fallback of local commit 1015b38 for a foundation part's bundle goes: §407 binds that bundle to
|
||||
its old `objects` section, and a binding that is missing fails loudly (ADR 0595 §1) (Q-T5, T-11).
|
||||
- The re-read tool's callers of `seatlist.Part` are §204's for every part, the assigner's own list going (AS19).
|
||||
- The bus module's cap of a state's objects store is §513's as §607 and §547 amend it, and the binding's `objects`
|
||||
and `sent-objects` are §513's (NATS1, §399).
|
||||
- The contracts' `Hold` is §194's view, the newest assignment half and the catalog half it names; the candidate's
|
||||
whole-half reader is §195's and §441's, read by a run on request's steps, `grants-for` and the indexer's candidate
|
||||
pass (contracts).
|
||||
- The view's module value carries the entry's `manifest-digest` (§506) and computes none (VW1).
|
||||
- `store-filling` is raised past 80 % and cleared below 70 % (§181).
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0656"
|
||||
- "ADR 0603"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0584"
|
||||
- "ADR 0564"
|
||||
- "ADR 0536"
|
||||
- "ADR 0525"
|
||||
- "ADR 0436"
|
||||
- "ADR 0363"
|
||||
---
|
||||
|
||||
# 47, milestone G: the review of §480 to §515, rounds 2 and 3
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n"; this file
|
||||
takes §600 on, since §516 on are those of batch 4's file G-12.
|
||||
|
||||
*These lines answer what the four reviewers by architectural domain found in round 2 of
|
||||
[G-11](G-11-develop-answers-review.md) (§480 to §515), the last round. Each is the design's choice with its reason.*
|
||||
|
||||
- §600 **A build seat's asks: subjects, workers and grants.** (§543, §544) A build seat (`node-build-go`, `node-build-node`,
|
||||
`node-build-image`) keeps its three queues of main, `delivery`, `check` and `recheck` (ADR 0525 §4, ADR 0536 §4), on
|
||||
its one work-queue stream. An ask is published on `mesh.seat.<seat>.accept.<queue>.<asker>`, `<queue>` one of the
|
||||
three, `<asker>` the asker's name; the ask's kind (`step`, `run`, a build) stays in its body, as on main. The
|
||||
stream takes, per queue, main's `mesh.seat.<seat>.accept.<queue>` and `mesh.seat.<seat>.accept.<queue>.*`, which
|
||||
overlap no other queue's. Its workers stay the grants part's, one per queue (`SEAT_<SEAT>_<QUEUE>_worker`, as on
|
||||
main), each filtered to both of its queue's subjects, so one consumer per queue takes an ask in either form and
|
||||
every node's holder pulls it, as ADR 0436 §6 has one consumer. The grants part grants publish on
|
||||
`accept.<queue>.<asker>` to that asker's account alone, each asker by the queues §338 gives it, and on main's
|
||||
`accept.<queue>` to the checker's and the builder's accounts alone, until §483's removal. This amends §481's
|
||||
subject and its "the seat's stream takes `accept.>`". Why: one stream refuses overlapping subjects and a work
|
||||
queue has one consumer per filter (review round 2, security and upgrade 1 and 3, running and testing 1). How it is
|
||||
checked: unit (grants part), each queue's worker filters both subjects and each asker's grant is its own name;
|
||||
whole (grants part, Go build module), an ask under `runner` is taken and answered, and the runner's account
|
||||
publishing on main's `accept.check` is refused.
|
||||
- §601 **The order of G's rollout for the build seats.** (§544) The holders of the three build seats are delivered first,
|
||||
then the grants part, whose workers then filter both forms (§600), then every asker of G (the runner, the indexer,
|
||||
the assigner, the builder) and the watchman. Until every node's holder of a build seat is of G the grants part's
|
||||
worker keeps main's one filter, so a holder of before G never takes a named ask, whose key it would write in main's
|
||||
form. Each order is a served-verb edge (kind 6) the delivery plan derives: an asker names each queue it publishes
|
||||
to in its manifest's `invokes` as `<seat>.accept.<queue>`, which the manifest validator checks against the seat's
|
||||
definition, and the edge runs from that seat's holders to the grants part and from the grants part to the asker;
|
||||
the watchman's `consumes` of the build seats' `step-progress` (§496) gives the edge from those holders to it, as an
|
||||
event G adds. A holder is counted delivered once `version-delivered` (§376) says every present machine placing it
|
||||
runs it. A revert runs the same edges backward (ADR 0656 §4). Until the watchman hears a holder's `step-progress`,
|
||||
it times that holder's ask from its `ask-taken` (§602), and a body of before G with no `step` or `clock-from`
|
||||
(§498) is timed from its own `at` with no step's own finding. This amends §483's "whose consumer of before G takes
|
||||
only main's subject" and its edge's source. Why: one consumer is shared by every node's holder, so the filter moves
|
||||
only once every holder reads both forms (review round 2, security and upgrade 2 and 4, seams and tools 2, health
|
||||
and events 2). How it is checked: unit (delivery plan), the edges from `invokes` and `consumes` are derived and G's
|
||||
plan names them; whole (grants part, Go build module, runner), a rollout with one holder of before G keeps main's
|
||||
filter, and the runner's ask is answered once every holder is of G.
|
||||
- §602 **An ask outside a pipeline has a start and an end.** A build seat's holder says `ask-taken` (`asker`, `ask`,
|
||||
`node`, `at`) on `mesh.seat.<seat>.event.ask-taken.<node>` when it takes any ask, and `step-ended` when it ends any
|
||||
ask, whatever its asker (§24's event, the asker in its body); a reader of `step-ended` takes only its own asks by
|
||||
their ids. The watchman times every ask from `ask-taken` and its `step-progress` (§496), and ends the clock on
|
||||
`step-ended`. A runner's step that stands raises `pipeline-stands` (§75); any other ask that stands raises
|
||||
`stuck`, naming the seat, the asker, the ask and the node, as main's mirror does. This completes §496. Why: only the
|
||||
runner says `step-started` (review round 2, health and events 1). How it is checked: whole (watchman, Go build
|
||||
module), a builder's build whose holder stops raises `stuck` within five minutes and one that ends raises nothing.
|
||||
- §603 **Where `step-progress` and `ask-taken` go.** (§549) Both are seat events and go on the stream that carries the seat's
|
||||
events, at that stream's own age; no stream is added and no event's age changes. `step-progress` and `ask-taken`
|
||||
are declared in the three build seats' definitions in `mesh-delivery-contracts`, and `step-progress` in
|
||||
`mesh-wants`' definition for a composition step. This amends §496's "kept on the seat's stream for a day". Why:
|
||||
a build seat's own stream is its asks' work queue (review round 2, running and testing 4, seams and tools' note).
|
||||
How it is checked: unit (definitions), each event declared.
|
||||
- §604 **The builder keeps the toolchains it heard.** (§546) The builder writes each `toolchain-reported` (§492) in a state of
|
||||
its own, `toolchains-heard`, one key `<node>.<language>` holding the version, the recipe, the digest and when it
|
||||
was said, and agrees the version from those keys, read back at its start, so a restart needs no node to report
|
||||
again. Its manifest `consumes` the Go and node build seats' `toolchain-reported` (§702); its tool `builder_toolchains` lists the
|
||||
keys; a key is deleted when the node leaves the catalog's placements (§365's reason). Why: a durable consumer does
|
||||
not deliver an acknowledged event again (review round 2, seams and tools 1). How it is checked: whole (builder), a
|
||||
restarted builder agrees the same version with no new report.
|
||||
- §605 **The order after G's tool runner is derived from the package a build resolved.** (§545) A package's artifact in a
|
||||
manifest takes `calls`, the capability words (§488) its code calls of the tool runner; mesh-sdk's Go and npm
|
||||
packages name `ledger-upkeep-calls`, `sent-objects-calls` and `put-sessions` from G. For each module whose build
|
||||
resolved a version of such a package (§466's `resolved`), the delivery plan derives a served-word edge (kind 6) to
|
||||
the tool runner stating those words on each node, so no manifest names the tool runner's module. mesh-sdk's
|
||||
bundle reads the stated words again each time the tool runner of its node starts. §494's release reads `calls`.
|
||||
This amends §488's `needs`. Why: the bundle makes a session whenever a value is large, so only the package knows
|
||||
(review round 2, seams and tools 3, running and testing's note). How it is checked: unit (delivery plan), a module
|
||||
built on G's mesh-sdk is ordered after the tool runner on each node; unit (manifest validator), `calls` read.
|
||||
- §606 **A step's end and its unlogged entry are one write.** (§549) The runner writes a step's end in `pipelines` with the
|
||||
entry as `unlogged` in the same compare-and-set, appends it to `step-log`, then clears it; at its start it appends
|
||||
every `unlogged` entry of its pipelines, in order. It reads the log's fill again at each step's end, at its start,
|
||||
and once a minute while the log is `full`, so the entries the bus ages out say `room` with no step ending. Its tool
|
||||
`runner_unlogged` lists the unlogged entries and `runner_unlogged_append` appends them by hand, written in the
|
||||
hand-act log. This amends §486. Why: a stop between the two writes lost the entry, and only the bus's age frees a
|
||||
full log (review round 2, running and testing 2, health and events 3, seams and tools' note). How it is checked:
|
||||
whole (runner), a runner stopped after a step's end appends its entry at start; a full log whose oldest entries age
|
||||
out says `room` with no step ending.
|
||||
- §607 **A state's objects store has the state's size.** (§547) Each `<state>-objects` (§58) is planned with the state
|
||||
template's size, 64 MiB, unless the state's `requires` names `objects-max-mib`; the sent stores keep §477's 256
|
||||
MiB. An estimated 150 states across about 120 modules give about 9.4 GiB of caps. Whether the bus counts a cap
|
||||
against its storage limit before anything is written is measured in G's check (the bus module's whole test reads
|
||||
the account's reserved storage after its plan); if it does, the plan refuses caps whose sum passes the limit,
|
||||
naming the sum, as §495 has it for sent stores. A write refused at a cap names that store, `<state>-objects` of
|
||||
its owner. This amends §513 and leaves §58's "reserves nothing" to that measurement. Why: §58 names the template's
|
||||
size (review round 2, running and testing 3). How it is checked: unit (bus module's plan), each objects store's cap;
|
||||
whole (bus module), the reserved storage read after the plan.
|
||||
- §608 **A carried merge that waits is a timed wait.** (§542) A merge act carried by the switch (§510) waiting for the first
|
||||
registrations is said by the gate as `wait-held` with `step: merge` (added to §498's list), naming the pull request
|
||||
and the head the Approve named, bounded at 60 minutes; past it the watchman writes `merge-waits`, which the
|
||||
condition keeper raises and `wait-ended` clears. It merges only the head the Approve named: a pull request moved
|
||||
since is refused, said as the delivery's `transition` with the why `head moved`. This amends §510. Why: an Approve
|
||||
the operator gave must not wait unseen (review round 2, health and events 4, security and upgrade's note). How it is
|
||||
checked: whole (gate, watchman), a carried merge waiting 60 minutes raises `merge-waits`; a moved head is refused.
|
||||
- §609 **Smaller corrections from round 2.** An entry taken in before G carries as `manifest-digest` the digest its
|
||||
module value held (§506). §508's check adds a revision moving the `node-engine` claim to another module, accepted
|
||||
and said in the catalog's `catalog-revision`. `watchman_views` (§501) answers from the keeper's `view-waits` alone,
|
||||
so a ready view is not listed and the watchman calls no part's tool. §485's reconnect case is a whole test of
|
||||
mesh-sdk, and `observe` must not call `write`, since the writer runs one act at a time. A node-engine of before G says
|
||||
no wait for a kept build; that is accepted, `kept-build-waits` naming its version (§499). The kept-data row of
|
||||
`step-log` reads an old `logs` section (§470). How it is checked: as the lines it corrects.
|
||||
@@ -0,0 +1,218 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0259"
|
||||
- "ADR 0672"
|
||||
- "ADR 0436"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0660"
|
||||
- "ADR 0656"
|
||||
- "ADR 0636"
|
||||
- "ADR 0626"
|
||||
- "ADR 0619"
|
||||
- "ADR 0618"
|
||||
- "ADR 0614"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0564"
|
||||
- "ADR 0505"
|
||||
- "ADR 0404"
|
||||
- "ADR 0363"
|
||||
- "ADR 0240"
|
||||
- "ADR 0235"
|
||||
---
|
||||
|
||||
# 47, milestone G: the recovery module, and the review of §516 to §553
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§554 and §555 are the operator's answer of 2026-11-11 at 12:55 CET at lead-47's terminal. §556 to §558 answer the
|
||||
four reviewers' round 1 of [G-12](G-12-develop-answers-fourth.md) (§516 to §553); the rest of that round's findings
|
||||
are written into the lines of G-12 they name.*
|
||||
|
||||
## The recovery module
|
||||
|
||||
- §554 **The recovery module takes, sets aside and restores kept data on the live bus.** The operator,
|
||||
2026-11-11 12:55 and 13:02 CET at lead-47's terminal: a module of its own, on a seat of its own, with an account of
|
||||
its own, owns snapshotting, setting aside and restoring a module's kept data on the live bus, each act in the
|
||||
hand-act log; the bus module keeps no restore permission but a protected snapshot's restore in place (§681, O-7), the snapshot account stays
|
||||
limited to the nightly dump (ADR 0626 §2), and the backup holder only stores copies. ADR 0672 decides the account,
|
||||
its grants and its callers; this line is written to it.
|
||||
- **What it alone does.** It alone takes a restore point before a module moves across a `form` change, and alone
|
||||
restores into the live bus. The bus module's snapshot before its own `recreate` or `drop` (ADR 0595 §5, ADR 0626
|
||||
§4) and the nightly dump (ADR 0626 §2) stay theirs. The restore ADR 0667 §12 tests of the snapshot `drop` takes is
|
||||
the recovery module's `recovery_restore` naming that snapshot, which it reads through the seat `mesh-broker`'s
|
||||
verb `snapshot-get`, or has the keeper restore in place by `snapshot-restore` when it is protected (§681).
|
||||
- **Its seat, module and node.** The seat `mesh-recovery`, held once for the mesh, is defined in mesh-contracts
|
||||
beside `mesh-broker`, and the module `mesh-recovery`, in mesh-catalog, claims it; its glossary word is the
|
||||
*recovery module*. It runs on the node of the holder of `mesh-broker`, read from the catalog half and the seat
|
||||
`mesh-module-assigner`'s placements, never a fixed name: G's seed writes its placement there. It moves as any
|
||||
module that holds data, its points and asides holding no move (§562). Its data directory, its manifest's `data`
|
||||
item `points`, is capped by its setting `points-cap` and measured by the disk readings (ADR 0571 §5): past its fill
|
||||
mark it writes the finding `points-filling` (§561), and an act that would pass the cap ends `failed`, naming it,
|
||||
before it removes anything; a `snapshot-kept` that ends so holds the move as a failed backup does (§296, §564). Its `findings` state and finding stream are declared as §529's other parts declare
|
||||
them, so it waits on the bus module for them and is no foundation part (ADR 0619 §1).
|
||||
- **Its verbs answer at once; the result is the event.** `snapshot-kept` and `restore-kept` answer `accepted` with
|
||||
the act's `act` id, `busy` with the act that runs (it runs one act at a time and keeps no queue), or `error`, and
|
||||
nothing more; the point comes on `ended`, the restore on `restored`. A caller told `busy` asks again when it hears
|
||||
the ending event of the act it was named (§560). The module writes each accepted act in its directory before it answers; at its start an act
|
||||
accepted and not ended is ended `failed` with `stopped`, so its caller asks again.
|
||||
- **Its events.** On `mesh.seat.mesh-recovery.event.` and kept 90 days (§396): `ended`,
|
||||
`restore-started`, `restored`, `set-aside`, `aside-discarded` and `point-removed`, each with the parts
|
||||
`<module>.<kept>` and kept last per subject, and `expiry-ended`, which has no parts and is not (§691), their fields as §560 gives them. The node agent wakes only on the ending
|
||||
event of its own act, and its wait for `ended` or `restored` is bounded at 90 minutes from its first ask (§405,
|
||||
§560), past which its have says the wait and the watchman's `delivery-waits` raises it.
|
||||
- **Who calls it.** `snapshot-kept` and `restore-kept` are of class `act` and take its default (§610, the operator's
|
||||
answer of about 16:10 CET: no verb keeps a named list): the bus proves who called, every act is logged with it,
|
||||
and the identity of who asked is the check (the operator, 13:06 CET); no want is read for it, since the operator,
|
||||
2026-11-11 13:10 CET: "if something asks for a restore, you should do it; he'll have his reasons".
|
||||
`kept-points` takes the default of a read (§391). The operator, 2026-11-11 13:06 CET at lead-47's terminal: setting
|
||||
aside is a move, not a removal, so deleting the live objects once their aside is whole and checked (§516) needs
|
||||
no person's warrant, the data staying in the aside until the operator discards it; setting aside without restoring is
|
||||
the operator's hand tool `recovery_aside`, and discarding an aside the hand tool `recovery_discard_aside`, both of
|
||||
class `act`, taking its default (§610), written in the hand-act log first, each a verb of the seat (§656); `restore-kept` refuses
|
||||
`aside-only` and `discard-aside`, naming them (§516). The hand tools are checked by the identity of who asked: each takes its class's default (§610), the bus proves the
|
||||
calling account and the verb logs it with the `asker` the tool runner proved (§682), with no warrant asked per act. Each act is written in the hand-act log of the seat `mesh-hand-acts` first, with the caller the
|
||||
bus proved and `why`; an act whose log write fails does nothing and answers `error`, naming the log (as §480).
|
||||
- **The operator's hand tools.** `recovery_take` (take a point of one module and kept data now), `recovery_restore`
|
||||
(restore one point) and `recovery_retry` (ask again the last failed act of a module and kept data), each of class
|
||||
`act`, taking its default (§610), written in the hand-act log, each a verb of the seat (§656); they are checked by identity, as above.
|
||||
§566 adds `recovery_restore_aside` and `recovery_cancel`.
|
||||
- **Its account.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal (the knowledge base's review of
|
||||
ADR 0672, R3): the recovery module is one process with a bus account of its own (ADR 0436 §1, as ADR 0626 §1 has
|
||||
the bus module's keeper), which holds the seat `mesh-recovery` and answers its verbs itself; each verb is one its
|
||||
caller names (`by-caller`, ADR 0259 §3), so the bus proves who calls, each taking its class's default (§610): a
|
||||
node-engine's account for `snapshot-kept` and `restore-kept`, and the tool runner's account, which the sessions and
|
||||
the operator reach the bus as, for its hand acts, are the callers it serves in practice. Its tools bundle, carried by the tool runner, calls those verbs and holds no grant of its own. ADR 0672
|
||||
§4 records that this changes ADR 0436 §3 and §5 for this process alone. The account is composed by the grants part
|
||||
from the seat's definition and the process's `bus` declaration: the bus's stream
|
||||
snapshot and restore subjects with their chunk subjects, a stream's deletion and a durable consumer's creation,
|
||||
only for the bus names of a module's kept data; the subjects its seat says and its hand-act log write; its
|
||||
consumers of `node-backup`'s `item-started` and `item-ended`, filtered to those two events, each with the ordered
|
||||
consumer §556 needs, from the process's `consumes`; its calls of `node-backup`'s `restore`, which takes its class's default with no own-item limit (§610, §555), and
|
||||
of `mesh-broker`'s `snapshot-get` and `snapshot-restore` with `snapshot-get`'s chunk subjects, and the hearing
|
||||
of `snapshot-restored` from its `consumes` (§681); and nothing else. The bus names granted are those of every kept data
|
||||
of every catalog graph entry the catalog half keeps (§443) and of every entry a restore point names (an old
|
||||
section's name before a form change, a template's binding after), read by the grants part through
|
||||
`kept-points` (§565); no stream of the mesh's own (`CONTROL`, `NODES`, `EVENTS`, `DEAD_LETTER_NOTICES`, `DEAD_LETTERS`,
|
||||
`PROV_APPLIED`, `CHECKS_READY`, `CHECKS_OTHER`, every `SEAT_` stream and every `KV_` bucket of a seat) is ever
|
||||
granted. Restricted streams (ADR 0436) are among the names, as ADR 0672 records.
|
||||
- **Bus names.** The recovery module turns a module and a kept data into bus names from the catalog graph entry of
|
||||
the fingerprint it acts at, read by `mesh-catalog`'s `entry` (§443) in its `invokes` (an edge of kind 6): a
|
||||
template entry's name is `<module>_<local>` (ADR 0595 §1), an old section's its declared name; a point records the
|
||||
names it took, and a restore uses those.
|
||||
- **Never beside the nightly dump.** A `snapshot-kept` may run while the dump runs, both paced as ADR 0235 §10 has
|
||||
it, so the server has at most two snapshots at once. No `restore-kept` starts while the dump runs, and no dump of
|
||||
the bus starts while a restore runs: the holder of `node-backup` of G says `item-started.<node>.<module>.<item>`
|
||||
and `item-ended.<node>.<module>.<item>` for every item, kept last per subject, 7 days, and puts off the bus's item
|
||||
while a `restore-started` of the recovery module has no `restored`, the dump giving way when both start at once
|
||||
(§563); the recovery module learns the bus module and
|
||||
its node from the seat list's definition of `mesh-broker` and the catalog half's claim of it, never a fixed name.
|
||||
Both read these streams as §556 has it, so a restart mid-dump or mid-restore misses neither. A blocked act waits,
|
||||
neither refused nor failing a want: past 60 minutes the recovery module writes `recovery-waits` and the backup
|
||||
holder `backup-waits`, each naming the item or the act it waits on, and `kept-points` lists the act that runs
|
||||
with since when and what it waits on. The recovery module consumes the item events that G's backup holder adds,
|
||||
an edge of kind 6 from that holder to it, so a backup holder of before G, which says none, is never relied on.
|
||||
- **Its health.** Its declared health check names the tool `recovery_kept_points` (§657), and fails only while an aside's record ends at
|
||||
`removed` (§516) and no running act holds it, so a restore under way never fails it; it then also writes the
|
||||
finding `aside-unrestored` (§561), which `recovery_restore_aside` (§566) clears.
|
||||
- **Its order.** The node-engine's `invokes` of its verbs gives the edge of kind 6 from the recovery module to the
|
||||
node-engine, so §298's first edge reads "the new version of the recovery module" (§555). At a revert of G the
|
||||
edge runs backward, as the order does when something goes away (ADR 0666 §14): the node-engine goes back only after every module it restores there said
|
||||
`restored` and runs (§298), and the recovery module, new in G, goes only after the node-engine, so it stays while
|
||||
any restore runs; its data item stays on its node, with its copies.
|
||||
- **Its tools** are named `recovery_`.
|
||||
|
||||
Why: the operator's answers; the node, cap, events, answer on acceptance, dump order, health and edges are the
|
||||
design's (review rounds 1 and 2; ADR 0672). How it is checked: test 36 with the real backup holder and the recovery
|
||||
module; unit (grants part), the recovery module's account holds exactly the grants above, a call outside its names
|
||||
refused, both a point's old-section names and a template's binding granted, and the bus module's and the snapshot
|
||||
account's hold no restore; whole (recovery module), a hand act's log naming the account the bus proved and the asker, a
|
||||
`busy` answer then the act on the other's `ended`, a second form change not woken by the first's `ended`, a dump
|
||||
asked during a restore starting after `restored`, a restart during the dump starting no restore, an act whose
|
||||
hand-act log write fails doing nothing, an unrestored aside failing the health check, a node-engine's `restore-kept` with `aside-only` or `discard-aside`
|
||||
refused, and the operator's `recovery_discard_aside` removing the aside and writing the log.
|
||||
- §555 **What moves to the recovery module.** §296 asks `mesh-recovery`'s `snapshot-kept` and waits for its `ended`,
|
||||
never the seat `node-backup`; §297 asks its `restore-kept` and waits for its `restored`; §302's `backed-up` lists
|
||||
the recovery module's data item as any item, and the restore points and asides are listed by `kept-points` (§517),
|
||||
an aside discarded by the hand tool `recovery_discard_aside` (§554); §303 and test 36 name the recovery module where they named the backup
|
||||
holder and the bus module; §337's and §405's callers `holder:node-backup@same-node` go with the two verbs of
|
||||
`mesh-broker`, which gains only §681's `snapshot-get` and `snapshot-restore`; the seat `node-backup` gains nothing for kept data on the bus: its `aside` and
|
||||
`discard-aside`, the arguments `kept`, `form` and `fingerprint` of its `now` and `restore`, and its events
|
||||
`ended.<node>.<module>.<kept>` and `restored.<node>.<module>.<kept>` go from its definition in mesh-contracts, and
|
||||
it gains only `item-started` and `item-ended` with their `<item>` token, its `restore` taking its class's default
|
||||
with no own-item limit (§610); §450's `aside` answer is `recovery_aside`'s;
|
||||
§406's catch-up is recorded in the point as before. Why: one owner of the acts (§554). How it is checked: unit
|
||||
(definitions), `node-backup` and `mesh-broker` hold none of the moved verbs, `mesh-broker` only §681's two; test 36.
|
||||
|
||||
## The review of §516 to §553, round 1
|
||||
|
||||
- §556 **A part that follows a stream kept last per subject reads it whole at each start.** A durable consumer goes on
|
||||
from its acknowledged position after a restart, so a part that acts on what it heard from a stream kept last per
|
||||
subject, and keeps no state of it, reads at each of its starts every subject's last message through an ordered
|
||||
consumer it makes for that one read, then follows its durable consumer from its acknowledged position, an event
|
||||
older by `at` than what that read gave changing nothing. Until the read ends the part acts on none of those events
|
||||
and its `<module>_first_views` names the stream `waiting`. So read: the node-engine seat's `running`, `composed`,
|
||||
`wait-held` and `wait-ended` (the last two kept on the stream `running` is kept on, last per subject, a node's
|
||||
module's newer one by `at` standing), by the assigner (§520), the condition keeper (§541), the builder (§546), the
|
||||
node-engine and the grants part (§199), and the watchman for `delivery-waits` (§541); `mesh-healers'`
|
||||
`attempt-changed` by the keeper (§541); `node-backup`'s `item-started` and `item-ended` by the recovery module, and the recovery module's `restore-started` and `restored` by the backup holder (§554). The
|
||||
grants part grants each such reader, beside its durable consumer, the creation of an ordered consumer on that
|
||||
stream with no durable name (the bus's consumer-create subject for that stream), from the reader's `consumes`. This amends
|
||||
§199's "each part's durable consumer starts from the last message per subject". Why: what a part heard must not
|
||||
depend on when it last restarted (review round 1, health and events 2, with running and testing 3). How it is
|
||||
checked: whole (assigner, keeper), a restarted assigner holds no want it did not hold before, and a restarted
|
||||
keeper holds the healers' attempts it held.
|
||||
- §557 **A whole test's bounds are settings.** Every bound a whole test waits on (§535's five minutes, §534's 30
|
||||
minutes, §544's 60 minutes, §540's two hours, §551's 90 days, §554's 90 minutes) is a setting of the part that
|
||||
times it, read at its start, its default the design's value, so a whole test sets it in seconds and never waits on
|
||||
real time. The bus module's and the recovery module's whole cases in these lines are cases of each module's one
|
||||
whole test against the real bus, within the test budget. Why: a whole test that waits hours runs never (review
|
||||
round 1, running and testing's notes). How it is checked: the code review; each whole test of these lines runs in
|
||||
under a minute.
|
||||
- §558 **A field of a served verb's answer is an edge of kind 6.** Kind 6 (§374) orders a module after a version that
|
||||
serves a verb or a word it uses, and a verb's presence alone says nothing of a field its answer gained, so a
|
||||
consumer relying on a new field of a served answer could run before the provider's new version and fall back in
|
||||
silence (as the grants part's first view relied on `binding` in `mesh-broker`'s `list` before design 25's §9). An `invokes` entry therefore
|
||||
takes a long form, `{verb, fields}`: `verb` as today (`seat:<seat>.<verb>`), `fields` the names of its answer's
|
||||
fields the consumer relies on. The manifest validator refuses a field the seat's definition does not give that
|
||||
verb's answer, naming it. The delivery plan derives the edge of kind 6 from the holders of that seat to the
|
||||
consumer, held until the holder runs a build whose catalog graph entry's seat definition (§194) gives the verb's
|
||||
answer every named field; §374's entry of kind 6 records the verb and its fields, and `outdated` (§375) says it
|
||||
behind when the delivered version no longer gives them. Both forms stay: the short string where no field is relied on, the long form
|
||||
where one is. On main mesh-sdk's manifest reader reads `invokes` as a list of strings, so the long form is read
|
||||
first by §494's second reader release, merged and delivered before G's merge: its manifest reader takes either
|
||||
form, and the running checker, the indexer and the grants part, which read manifests through it, are rebuilt with
|
||||
it by the release's publish (kind 1), as for `max-mib`. That publish rebuilds every module importing mesh-sdk's
|
||||
`catalogue`, the set the release rebuilds already, so the form adds no rebuild. The consumers of G that name fields:
|
||||
every reader of `mesh-catalog`'s `revision` through
|
||||
mesh-sdk's client (the bus module, the status view, the condition keeper, the tool runner, the assigner, the grants
|
||||
part and the runner), with `none-yet` (§530); the status view, `mesh-catalog`'s `entry` with `manifest-digest`
|
||||
(§506). The grants part names none: design 25 *bindings-first-view* §9 composes a binding from the fields `list` answered before G, so it relies on no
|
||||
new field of `list`, and naming `binding` would close the circle of §569. The recovery module reads the manifest whole the entry carries (§443), a field of before G. Any other part of G relying on a field G adds to a served answer names it so in G's change, which the
|
||||
code review checks. Why: the module graph orders a consumer after what it relies on, and a field is part of the contract
|
||||
(lead-47's question from the operator's "the module graph is for this", 2026-11-11). How it is checked: unit
|
||||
(manifest validator), a field the definition lacks refused; unit (delivery plan), a consumer naming a field
|
||||
ordered after a holder whose definition gives it, and not before; unit (the release's reader), both forms read.
|
||||
- §559 **A bundle's finding writer publishes and hears through two runtime calls.** A bundle, in Go or TypeScript,
|
||||
holds no bus connection, so mesh-sdk's finding writer (§461, §503) in a bundle uses the runtime channel's existing
|
||||
calls, each gaining one argument: `mesh/publish` takes `ack: true`, and the tool runner then publishes the event on
|
||||
the subject the module's membership names for it (its old stream's `mesh.stream.<module>.event.<event>.<node>` or
|
||||
its template binding's, §529; ADR 0363 §8) and answers only once the bus acknowledged it, with its sequence, or
|
||||
`{error}`; `mesh/subscribe` takes `deliver: new`, and the tool runner makes an ordered consumer on the stream that
|
||||
carries the pattern, starting at the next message, and delivers each message to the bundle as a subscription
|
||||
delivers today. The writer says each finding event with `ack: true` (§383's one minute of resending counting from
|
||||
an unanswered or `{error}` publish) and makes its consumer of `findings-asked` with `deliver: new` before its start
|
||||
statement (§485). The tool runner states the capability word `finding-calls` (§488, §545) once it answers both,
|
||||
and the bundle's writer fails at once, naming the word and the node, where it is not stated. Why: one writer in both
|
||||
languages over the one channel a bundle has, as §518 (develop question of 13:08 CET on §461). How it is checked:
|
||||
unit (tool runner), a publish with `ack` answered with its sequence and a refused one with `{error}`, a `deliver:
|
||||
new` subscription given no message older than it; whole (mesh-sdk, TypeScript and Go bundles), a finding written
|
||||
and an ask answered through a tool runner of G; unit (mesh-sdk, both languages), the writer fails at once, naming
|
||||
`finding-calls` and the node, against a tool runner that does not state it.
|
||||
@@ -0,0 +1,637 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0673"
|
||||
- "ADR 0672"
|
||||
- "ADR 0668"
|
||||
- "ADR 0618"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0664"
|
||||
- "ADR 0626"
|
||||
- "ADR 0619"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0573"
|
||||
- "ADR 0564"
|
||||
- "ADR 0539"
|
||||
- "ADR 0500"
|
||||
- "ADR 0429"
|
||||
- "ADR 0404"
|
||||
- "ADR 0283"
|
||||
- "ADR 0259"
|
||||
- "ADR 0235"
|
||||
---
|
||||
|
||||
# 47, milestone G: develop's fifth batch, and round 3's notes on §516 to §559
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§560 to §586 answer develop's fifth batch (lead-47's input of 2026-11-11), the reviewers' round-3 notes on §516 to
|
||||
§559, the knowledge base's review of ADR 0672 (the recovery module), and round 1 of this batch's own review, written
|
||||
into the lines it names. Seven carry the operator's answers of 2026-11-11 at lead-47's terminal, each line saying
|
||||
so: §562 (the module's move), §563 (a put-off dump), §568 (the builder's retry), §570 (refused asks), §575 (a failed
|
||||
build's words), §584 (the forge and image registry in the foundation) and §554's account. The rest are of internal
|
||||
form only. A note that corrects a line of G-12 or G-14 is written in that line; here only what is new.*
|
||||
|
||||
## The recovery module
|
||||
|
||||
- §560 **Every event of the recovery module says its act, and its caller wakes on its own act.** (§554, §516, §551)
|
||||
Every event of the seat `mesh-recovery` carries `act`, the id `snapshot-kept` or `restore-kept` answered with
|
||||
`accepted`, or a hand tool's own act id (§566), and `at`, the UTC time it was said in RFC 3339. Each event's other
|
||||
fields:
|
||||
- `ended.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint`, `snapshot`, `outcome` (`done` or `failed`),
|
||||
`error` when failed, and on `done` the point whole (§516).
|
||||
- `restore-started.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint` and `snapshot`, said before the
|
||||
restore reads the dump's item events and before any aside is taken (§563).
|
||||
- `restored.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint`, `snapshot`, `outcome`, `error`, `aside` and
|
||||
`missing` (§516).
|
||||
- `set-aside.<module>.<kept>` and `aside-discarded.<module>.<kept>`: the aside's `id`, `name` and `size`.
|
||||
- `point-removed.<module>.<kept>` as §551 has it; and `expiry-ended`, a subject without parts, with `removed`, the
|
||||
number of points the run removed, 0 included.
|
||||
- **Every act ends with an event under its `act`.** `snapshot-kept` and `recovery_take` end with `ended`;
|
||||
`restore-kept`, `recovery_restore` and `recovery_restore_aside` (§566) with `restored`; `recovery_aside` with
|
||||
`set-aside`; `recovery_discard_aside` with `aside-discarded`; a run of §551's removal (at the start, once a day,
|
||||
at a `snapshot-kept` or by `recovery_expire_points`) with `expiry-ended`, also when it removes nothing;
|
||||
`recovery_retry` with the ending event of the act it asks again, under an `act` of its own; `recovery_cancel`
|
||||
(§566) with the cancelled act's ending event, `outcome` `failed` and `error` `cancelled`.
|
||||
- **An act a restart cut off** (accepted and written in the directory, never ended, §554) is ended at the module's
|
||||
start, after the start's repair of asides (§516, §564), with its own ending event under its `act`, `outcome`
|
||||
`failed` and `error` `stopped`.
|
||||
- **A restore runs** from its `restore-started` until a `restored` with the same `act`; the backup holder reads it
|
||||
so (§554, §563).
|
||||
- **The caller wakes on its own act.** The node agent told `accepted` wakes only on an `ended` or a `restored`
|
||||
whose `act` is the one it was given; a `form` and a `fingerprint` alone wake nothing, since a retried act's old
|
||||
`failed` carries the same ones. Told `busy` with the running act's `act`, it wakes on that act's ending event
|
||||
and asks again, and its new answer gives the `act` it waits on next.
|
||||
- **The 90-minute bound** of the node agent's wait (§554) counts from its first ask for that point or restore,
|
||||
every `busy` and every ask again within it. Past it, the node agent's have says the wait and the watchman's
|
||||
`delivery-waits` raises it.
|
||||
- **How the node agent hears and keeps it.** The node-engine's manifest `consumes` `mesh-recovery`'s `ended` and
|
||||
`restored`, read as §556 has it. Before it asks, the node agent writes in its have the want's wait with `ask-at`
|
||||
(its first ask's time); once answered `accepted`, it writes the `act` there too. After a restart it reads that
|
||||
have: an `act` whose ending event the read of §556 already gives is taken as heard, and one not yet ended is
|
||||
waited on, asked nothing again; a wait with no `act` yet is asked again within its bound.
|
||||
- **What the node agent does with each ending.**
|
||||
|
||||
| outcome, `error` | what the node agent does |
|
||||
|---|---|
|
||||
| `done` | goes on with the move or the revert |
|
||||
| `failed`, `stopped` (a restart or a move cut the act off, §562) | asks again, within the same bound |
|
||||
| `failed`, `cancelled` (§566) | asks nothing again; holds the move, the reason `cancelled` with the hand act's `why` |
|
||||
| `failed`, `cap` (§564) | holds the move, the reason the error's own words |
|
||||
| `failed`, `restore point missing` or `restore point damaged` (§516, §567) | holds the revert as §300 has it, the reason the error's own words |
|
||||
| `failed`, any other error | holds the move, the reason the error's own words |
|
||||
|
||||
A hold is the phase `held` with that reason, said as `wait-held` (§541), and ends on a new want or the
|
||||
operator's hand act (`recovery_retry`, then a new ask of the node agent on that act's ending).
|
||||
|
||||
This replaces §554's list of events. Why: an event a caller cannot tie to its own call wakes it on another's
|
||||
failure, and an act whose end is never said holds its caller unseen (round 3, health and events, items 1 and 2;
|
||||
running and testing, item 3). How it is checked: unit (mesh-recovery's contract), each event's fields read; whole
|
||||
(recovery module), a retried act's caller not woken by the first act's `failed`, an expiry run that removes nothing
|
||||
says `expiry-ended` with `removed` 0, an act cut off by a restart ends `failed` with `stopped`; unit (node agent),
|
||||
`busy`, then the other act's ending event, then an ask again within one bound, the bound passed after repeated
|
||||
`busy` answers, and each row of the table; whole (node-engine, recovery module), a node-engine restarted during a
|
||||
restore takes that restore's `restored` and asks nothing again.
|
||||
- §561 **The recovery module's and the backup holder's findings, each with its scope, id and clearing.** (§554) Each
|
||||
is written by mesh-sdk's finding writer (§461) as the key `<scope>.<id>.<kind>` (ADR 0564 §2), each token escaped
|
||||
as a key's name is (§73), and said on the part's finding stream (§383, §529):
|
||||
- `points-filling`: the recovery module's, scope `mesh-recovery`, id the node it runs on. It is written when the
|
||||
directory of its data item `points` passes the fill mark of its cap (§564), which the recovery module measures
|
||||
itself at its start, after each act and after each expiry run, naming the size, the cap and the five largest points and asides. It is cleared when a
|
||||
measure finds it below the mark.
|
||||
- `recovery-waits`: the recovery module's, scope the module whose act waits, id the act's `act`. It is written
|
||||
60 minutes after the act began waiting on the dump of the bus's item (§563), naming the item and since when. It
|
||||
is cleared when the act starts its work, or ends.
|
||||
- `backup-waits`: the backup holder's, scope its node, id the item that waits (`<module>.<item>`, one escaped
|
||||
token). It is written 60 minutes (its setting `put-off-bound`, §557) after the item was put off (§563), naming the restore's `act`. It is cleared
|
||||
when the item starts again.
|
||||
- `aside-unrestored`: the recovery module's, scope the module, id the aside's `id`. It is written at an act's end
|
||||
and at the module's start for each aside whose record ends at `removed` and that no running act holds, which
|
||||
the health check also fails on (§554). It is cleared when that
|
||||
aside's record ends at `restored` or the aside is discarded.
|
||||
|
||||
The recovery module and the backup holder of G are observers on the condition keeper's list of sources (§387,
|
||||
§388), written into it in G's change. Why: a finding no one can clear stays open for ever, and the four findings
|
||||
of §554 named no scope, id or clearing (round 3, health and events, item 4). How it is checked: whole (recovery
|
||||
module, backup holder, the keeper), one whole test writes and then clears each: the points directory filled past
|
||||
the mark, then an aside discarded; an act held by a dump past the bound (a setting, §557), then started; a dump
|
||||
held past it, then started; an aside left at `removed` with no running act, then restored by
|
||||
`recovery_restore_aside`.
|
||||
- §562 **The recovery module moves as any module that holds data; its points and asides hold no move.** (§554) The
|
||||
operator, 2026-11-11 about 14:15 CET at lead-47's terminal: the recovery module may be moved off its node at any
|
||||
time, and the restore points and asides in its directory do not hold the move. A placement that moves it is made as
|
||||
for any module that holds data. The assigner asks for the move a person must allow before it writes the want (ADR
|
||||
0539 §14). The old node's want is written as removed, and its data item stays on that node's disk, with its copies
|
||||
in that node's repository, until the operator answers (ADR 0429 §6). On its new node the recovery module starts
|
||||
with an empty directory: `kept-points` lists nothing of the old node's, and a `restore-kept` of a point left there
|
||||
answers `missing`. Its `from-backup: yes` then asks the new node's backup holder, which holds no copy of it, so
|
||||
`restored` says `failed` with `restore point missing` and §300 holds the revert. The placement verb asks the
|
||||
recovery module nothing, and §554 no longer refuses a move while a point younger than 90 days is listed.
|
||||
- **An act a move cuts off.** When its node's want is written as removed, the old instance takes no new call
|
||||
(answering `error` naming the move) and, before it stops, ends its running act through its failure path (§516):
|
||||
a restore past its removal puts the aside back under the live names and sets the consumers back; then it ends
|
||||
every accepted act, the running one included, with its ending event, `outcome` `failed` and `error` `stopped`.
|
||||
- **A stop it could not finish** (the old node gone or killed): the new instance, at its start, reads the seat's
|
||||
stream as §556 has it, and ends `failed` with `stopped` every act whose `restore-started` or acceptance (its
|
||||
`ended` or `restored` missing) the stream holds with no ending event, and writes `aside-unrestored` for each such
|
||||
restore, naming the old node, since its aside is on the old node's disk.
|
||||
- **Who asks again.** The node agent and the backup holder hear those endings (§560, §563); the node agent asks
|
||||
again the new holder, which it finds from the catalog half's claims of `mesh-recovery`, and the backup holder
|
||||
starts a put-off item again on a `stopped` `restored`.
|
||||
|
||||
Why: the operator's answer (the knowledge base's review of ADR 0672, R6); the ending of a cut-off act is the
|
||||
design's (round 1 of this batch's review, running and testing R2, health and events H1). How it is checked: unit
|
||||
(assigner), a placement that moves the holder of `mesh-recovery` while points are listed is made, asked as ADR
|
||||
0539 §14 has it; whole (recovery module), on an empty directory a restore of an unknown point says `restore point
|
||||
missing`; a move during a restore after its removal leaves the live data as before and ends the act `stopped`; a
|
||||
new instance whose old one was killed during a restore ends that act `stopped` and writes `aside-unrestored`; a
|
||||
put-off dump starts again after it.
|
||||
- §563 **When a dump and a restore start at one moment, the dump gives way.** (§554) Each side says its start before
|
||||
it reads the other's:
|
||||
- **The backup holder** of G, for the item of the holder of `mesh-broker` (read from the seat list's definition
|
||||
and the catalog half's claim, §554), says `item-started`, then reads `mesh-recovery`'s `restore-started` and
|
||||
`restored`, last per subject, once more. When a restore runs (§560), it ends the item at once with `item-ended`,
|
||||
`outcome` `put-off` and `restore` naming that act, having read nothing of the bus, and starts the item again when
|
||||
it hears `restored` with that `act`.
|
||||
- **The recovery module** says `restore-started`, then reads the item events of the bus's item. While an
|
||||
`item-started` has no `item-ended`, it waits, and takes no aside.
|
||||
- **Said means acknowledged.** Each side reads the other's only once the bus acknowledged its own start event on
|
||||
its stream.
|
||||
|
||||
Since each side says its start before it reads, at least one side sees the other, and the backup holder always
|
||||
gives way, so the two never overlap. A dump that has run since before the restore began is waited for. The backup
|
||||
holder ends every item of its own that it said started and never ended, at its start, after its read of §556, with
|
||||
`item-ended`, `outcome` `failed` and `error` `stopped`, so no restore waits on a dump that no longer runs. Every
|
||||
`item-started` and `item-ended` carries `at`, and `item-ended` its `outcome` (`done`, `failed` or `put-off`).
|
||||
- **A put-off dump is that night's backup.** The operator, 2026-11-11 about 14:05 CET at lead-47's terminal: an
|
||||
item ended `put-off` is neither a failed night nor a missed one (ADR 0235 §13, §21 hold): the item runs when
|
||||
`restored` is heard, and the night is the outcome of that run. The item's last good backup ages as before, so
|
||||
`backup-stale` is raised only by its own age rule, never for the put-off itself. `backed-up` (§302) lists a
|
||||
put-off item with the restore's `act` and since when; `backup-waits` names it after 60 minutes (§561).
|
||||
|
||||
Why: §554 left open which side gives way when both start at once (round 3, running and testing, item 1); how a
|
||||
put-off item counts is the operator's answer (the knowledge base's review of ADR 0672, R5). How it is checked:
|
||||
unit (backup holder and recovery module against a fake bus), both orders of "say, then read" in both sides, each
|
||||
ending with no overlap; whole (backup holder, recovery module, the real bus), a dump and a restore asked at one
|
||||
moment never overlap and the item runs after `restored`; a put-off item raises no `backup-stale` and is not counted failed; a backup holder
|
||||
restarted during an item ends it `failed` with `stopped`, and a waiting restore then starts.
|
||||
- §564 **The cap is a setting, an act that would pass it is refused before it removes anything, and a failed act
|
||||
leaves no half-made point or useless aside.** (§554, §557)
|
||||
- **The settings.** The cap of the data item `points` is the recovery module's setting `points-cap`, 10 GiB unless
|
||||
set, and its fill mark the setting `points-filling-mark`, 80 % unless set, both read at its start, as §557 has a
|
||||
bound.
|
||||
- **Before it writes.** Before it writes anything, `snapshot-kept`, `recovery_take` and the aside of
|
||||
`restore-kept` add up the bytes the bus states for each bus object they take. An act that would pass the cap
|
||||
ends `failed` with `error` `cap`, naming the cap, that size and the directory's fill, before any aside is taken
|
||||
and so before any live object is removed. A `snapshot-kept` that ends so holds the move as a failed backup does
|
||||
(§296). A point or an aside that passes the cap while it is taken (the stream grew) ends its act the same way.
|
||||
- **Brought back.** A point brought back from the backup (`from-backup: yes`, §516) is not counted against the
|
||||
cap: it lies beside its path only while its restore runs, and is removed when the restore ends.
|
||||
- **On a failure.** An act that ends `failed` removes its point under a temporary name (§516) then, not only at
|
||||
the next start. An aside whose record ends at `set aside` (the live objects were never removed) is removed when
|
||||
its restore fails or stops before the removal, and at the start, since the live data was never touched.
|
||||
|
||||
Why: the cap is a bound a whole test sets (§557), and a restore refused for size after the removal would leave the
|
||||
module's data only in an aside (round 3, running and testing, item 2 and its notes). How it is checked: whole
|
||||
(recovery module, with small settings), a snapshot passing the cap ends `failed` and holds the move; a restore whose
|
||||
aside would pass the cap is refused with the live data untouched; `points-filling` written past the mark (§561); a
|
||||
failed point leaves no temporary name; an aside left at `set aside` by a stopped restore is removed at the start.
|
||||
- §565 **The grants part reads the restore points' names through `kept-points`, and composes without them until it
|
||||
answers.** (§554) The bus names of the recovery module's account include those a restore point names (§554). The
|
||||
grants part reads them by its `invokes` of `seat:mesh-recovery.kept-points`, a kept call (ADR 0668 §1). It asks
|
||||
again on every `ended` with `outcome` `done`, every `point-removed` and every `expiry-ended` of `mesh-recovery`,
|
||||
which it consumes (ADR 0668 §4), so a point's names are granted before any restore of it. Until `kept-points`
|
||||
first answers after a start, the grants part keeps the names it last granted from it, kept in its state
|
||||
`memberships` with the account, and adds the catalog half's kept entries (§443); it takes no name away before
|
||||
that answer, and says the wait in `grants_first_views` (§556). The edges between the two (kind 5, the grants part has acted for the recovery module's account; kind 6,
|
||||
the recovery module serves `kept-points`) are a cycle of delivery edges, delivered as one step (§49). Why: the
|
||||
account must follow the points without the grants part reading another part's state (ADR 0596; round 3, seams and
|
||||
tools' notes; running and testing R3). How it is checked: unit (grants part), a restarted grants part with
|
||||
`kept-points` unanswered keeps a point's names it granted before, and a point's old-section names are added after
|
||||
an `ended` and taken away after its `point-removed`.
|
||||
- §566 **Two more hand tools: put an aside back, and cancel a waiting act.** (§554, §516) Each is of class `act`,
|
||||
taking its default (§610), checked by the identity of who asked (§554), written first in the hand-act
|
||||
log of the seat `mesh-hand-acts` with the caller the bus proved and `why`, and an act of its own that runs one at a
|
||||
time (ADR 0672):
|
||||
- `recovery_restore_aside` (`module`, `kept`, `id`, `why`) restores an aside under the live names it was taken from,
|
||||
for an aside whose record ends at `removed` that no running act holds (the case `aside-unrestored` names, §561).
|
||||
It refuses, naming them, while live objects of those names exist (an aside taken by `recovery_aside` is put
|
||||
back by this tool too, the refusal naming `recovery_aside`'s aside when the names are live), and it ends with
|
||||
`restored`.
|
||||
- `recovery_cancel` (`act`, `why`) ends an accepted act that has not begun its work (one waiting on the dump, §563)
|
||||
with its ending event, `outcome` `failed` and `error` `cancelled`. It refuses an act that has begun, naming its
|
||||
step, since a restore cut off after its removal is put back only by its own failure path (§516).
|
||||
|
||||
Why: a module's tools recover what a person may need to recover (*Tools are how a module is spoken to*; round 3,
|
||||
seams and tools' notes). How it is checked: whole (recovery module), an aside left at `removed` put back by
|
||||
`recovery_restore_aside` and refused while the live names exist; a waiting act cancelled and a running one refused.
|
||||
- §567 **A restore point is checked by the digest of its bytes before it is restored.** (§516) A point's record
|
||||
carries `digest`, the `sha256:` digest of each bus object's snapshot file, written when the point is renamed whole
|
||||
(§516). The same digests are kept on the bus, apart from the bytes: in the recovery module's own state `digests`
|
||||
(its kept data, by the template `state`), one key per point, its `snapshot` escaped as a key's name is (§73),
|
||||
written before `ended` is said and deleted when the point is removed (§551); about 0.3 KiB a key, capped at 4 MiB.
|
||||
`kept-points` lists the digests, and `ended` carries them with the point. Before a restore takes any aside, it
|
||||
computes the digest of the bytes it is about to restore, whether from its own directory or brought back by the
|
||||
backup holder (`from-backup: yes`), and compares it with the key in `digests`; the record in the directory is never
|
||||
the reference. The state holds digests only and lists nothing, so §517's "no `bus-points` state" stands for the
|
||||
list. A mismatch ends the act `failed` with `restore point
|
||||
damaged`, naming the point and the bus object, and §300 holds the revert. Why: a point brought back from the backup
|
||||
was restored unchecked (round 3, security and upgrade's notes). How it is checked: whole (recovery module, backup
|
||||
holder), a point whose file and record were both altered after it was taken is refused before any aside is taken.
|
||||
|
||||
## The builder
|
||||
|
||||
- §568 **A transport fault in the builder's own read ends no build key, and a key's error says which kind of fault it
|
||||
is.** (ADR 0664 §1 to §3, ADR 0668 §1 to §3, ADR 0673 §1 to §4; issue 1612, met again 2026-11-11)
|
||||
- **The builder's own reads.** The builder's reads of the build seats' state `steps` (its own step keys, §467,
|
||||
§481) are kept calls (ADR 0668 §1, §2). A read that meets a transport fault, of ADR 0664 §1's classes or the
|
||||
read's own deadline, changes nothing the builder holds and writes no build key. The builder's watch of `steps`
|
||||
takes up again from its last revision once the bus answers, and a read it made at its start is made again then.
|
||||
Past five minutes it writes `call-unanswered` (ADR 0668 §3). A build key is written `failed` only from a step
|
||||
key's end (ADR 0664 §3).
|
||||
- **The words of a failed key.** A step that ended failed because its third run met a transport fault (ADR 0664
|
||||
§2) gives the build key an `error` that opens `transport fault:`, then the class (`no responders`, `name lookup`,
|
||||
`connection` or `deadline`), the step's seat and node, and the cause of its last run in the build module's words.
|
||||
Every other failure keeps its own opening (`not stored:`, `not pinned:`, `version taken:`, `refused kind:` or the
|
||||
build's own words) and never opens `transport fault:`. The builder's read tools show the error whole, so a reader
|
||||
tells a passing cause from a real one without the journal.
|
||||
- **What builds such a key again.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal, decided by the
|
||||
record of the builder's retry, ADR 0673 §1 to §3, which changes ADR 0664 §3: a key whose error opens `transport
|
||||
fault:` is built again, once, as a new revision of the key (ADR 0429 §18), on the first event after its failure
|
||||
that says its cause passed. Two events say so (ADR 0673 §2): the node-engine seat's `running.<node>.<module>`
|
||||
saying `running` for a module whose catalog graph entry claims the build seat the failed step ran on (§546), on
|
||||
any node; and the node-engine seat's `manifest-reads-changed` of the node the failed step ran on with `why`
|
||||
`returned` (ADR 0618 §4, ADR 0668 §4; §590), which it says when that node is heard again after being away. One
|
||||
saying `why` `started` builds nothing again. The builder hears both through its consumers of the node-engine seat, read as §556 has it.
|
||||
It builds a key again at most once per event heard, and only while the key's newest revision is that failure, so
|
||||
one event starts one revision. A revision that fails again on a transport fault is said as every failed key is,
|
||||
its error opening `transport fault:` again, and waits for the next such event. The builder's manifest `consumes`
|
||||
both events.
|
||||
- **Three automatic builds, then the operator is asked.** The operator, 2026-11-11 about 14:55 CET at lead-47's
|
||||
terminal: the builder counts, in the key's newest revision, the revisions events started since the last one a
|
||||
person started (`event-builds`). After three that failed, the next such event starts nothing: the builder asks
|
||||
the operator once, through the seat `operator-channel`'s ask (level `approve`, options *Build once more* and
|
||||
*Leave it*, `about` the key so a newer ask replaces it, ADR 0666 §12), naming the module, the fingerprint and
|
||||
the last error whole. *Build once more* builds one revision, counted as a person's, so the count starts again;
|
||||
*Leave it*, a decline or the ask's expiry leaves the key failed until `builder_build_again` or a new
|
||||
fingerprint. The failure stays said throughout. A key failed for any other cause
|
||||
is never built again on these events. `builder_build_again` (ADR 0668 §5) and the builder's next start build it
|
||||
again too, as before. The builder's read tool of a key, `builder_build` (§589), names the event that built its
|
||||
newest revision again, `event-builds`, and the open ask.
|
||||
|
||||
Why: a read the builder made met `no responders` and failed a key, which then stayed failed for about 45 minutes
|
||||
(issue 1612, 2026-11-11), against ADR 0664 §3 (a key is not written failed while its step runs on); the retry on an
|
||||
event is the operator's answer. How it is checked: unit (builder), a `no responders` read of `steps` leaves the key
|
||||
`building` and the next revision ends it; a third-run transport fault's key error opens `transport fault: no
|
||||
responders`, and a compile error's does not; a `running` of a holder of that build seat builds the key again once,
|
||||
and a second `running` while the new revision builds starts nothing; a node's `manifest-reads-changed` with `why`
|
||||
`returned` builds again a key failed on that node, and one with `why` `started` does not; a key failed on a compile error is not built again on either event; after three failed
|
||||
event-started revisions the fourth event asks the operator once and builds nothing, and *Build once more* builds
|
||||
one.
|
||||
|
||||
## The order of a change
|
||||
|
||||
- §569 **A cycle with a build edge is refused, whatever its other edges, and a grant composed from a first view is an
|
||||
edge.** (§49, §558, §374) The indexer's cycle rule (§49) reads every edge of the six kinds in one graph per watched
|
||||
branch. A cycle whose edges are all delivery edges (kinds 2, 4, 5 and 6) is delivered as one step. A cycle with at
|
||||
least one build edge (kind 1 or 3) is refused, whatever its other edges, at the check and at take-in, naming every
|
||||
artifact on it in order with each edge's kind and the manifest word it came from: no build on such a cycle can
|
||||
start before a delivery that waits for that build (ADR 0666 §14). Two kinds of edge enter that graph:
|
||||
- **A seat verb a build calls.** When a build module's step calls a seat verb while it builds (the image build
|
||||
module calling `container-image-registry`'s `mirror` for a base image), its manifest names it in `invokes`, and
|
||||
the indexer derives an edge of kind 3 from that seat's holders to every image artifact the build module builds.
|
||||
The edge is met by the holder that runs when the build starts, whatever its version: it orders a build after a
|
||||
running holder, never after the holder's new version. So the holder's own artifacts are built through its
|
||||
running version, and the edge makes no cycle through them.
|
||||
- **A grant from a first view.** A binding the grants part writes for a holder from its first view of a served
|
||||
answer (design 25 *bindings-first-view* §4 and §9)
|
||||
is an edge of kind 5 from the grants part to that holder. Where the grants part names a field of that answer
|
||||
(§558), an edge of kind 6 runs from the answer's seat holder to the grants part. Its new version must run, so this
|
||||
edge is met only by that version.
|
||||
|
||||
The circle of 2026-11-11 was such a cycle: the bus module's image waits, by kind 3, for the registry holder's
|
||||
delivery; the registry holder waits, by kind 5, for its binding `mirrors` from the grants part; and the grants part
|
||||
waited, by kind 6 on `mesh-broker`'s `list` with `binding`, for the bus module's new version: the kind-3 edge waited
|
||||
for a registry that served `mirror`, which needed its binding, which needed the new bus module. It is refused at its
|
||||
check, naming the three artifacts. Design 25 *bindings-first-view* §9 removes the kind-6 edge: the grants part
|
||||
composes a binding from the fields `list` already answers, so it relies on no new field and names none (§558).
|
||||
G's delivery then puts the grants part before the bus module's build. Why: the circle mixed a build edge and a
|
||||
delivery edge, and no rule saw it (issue 1966, notes 203 and 204; the operator, 2026-11-11 13:30 CET, on design
|
||||
25's §9). How it is checked: unit (indexer's graph), a cycle of kinds 3, 5 and 6 is refused naming its three
|
||||
artifacts and their kinds; a cycle of kinds 5 and 6 alone is one step; a build module's `invokes` of `mirror` gives
|
||||
a kind-3 edge to its image artifacts; the image registry's own image, built through its running holder, is not
|
||||
refused.
|
||||
|
||||
## Asks to the operator
|
||||
|
||||
- §570 **An ask the router refuses is heard by its asker, and no asker asks for longer than its level allows.**
|
||||
(ADR 0259 §3, §4) The kind behind it is an asker left waiting on an ask the router never took (issue 1610; met
|
||||
again 2026-11-11, when the bus module's three `nats_recreate` asks were refused at take-in and their tool kept
|
||||
saying the operator had not answered).
|
||||
- **The router says the refusal.** The router (the holder of `operator-channel`) says every ask it refuses at
|
||||
take-in, for each cause ADR 0259 §4 gives as it holds, on that asker's
|
||||
`mesh.seat.operator-channel.event.decided.<asker>`. The event carries `outcome` `refused`, the ask's id (the warrant's `ask`), why
|
||||
in the router's words (its `words`) and `at`, as an ask's end without a warrant is said (ADR 0259 §3).
|
||||
- **Every asker ends its request on it.** The asker client, mesh-contracts' `operatorchannelclient` (§638), gives its caller `refused` with `why` as soon as
|
||||
it hears it. An asker's tool or act that waits on that ask answers `refused` with why, never "not answered yet";
|
||||
the bus module's `nats_recreate` and `nats_drop` are among them.
|
||||
- **The lifetime is held where every ask is made.** `operatorchannelclient` changes in G's mesh-contracts release, which
|
||||
every asker is built against (§47). That client, through which every asker publishes
|
||||
an ask, refuses before it publishes an ask whose expiry passes the bound of its highest option level (`approve`
|
||||
24 hours, `destroy` 10 minutes, ADR 0259 §4), naming the level and the bound. The bus module's keeper asks for
|
||||
its level's bound in place of its fixed 12 hours (`AskLasts`).
|
||||
- **The operator sees a refused ask.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal: the router
|
||||
keeps each ask it refuses at take-in in its bucket `asks` under `<asker>.<id>` as a closed record, with `outcome`
|
||||
`refused`, `why` in the router's words and `at`, for 30 days like every closed record (ADR 0259 §3). It writes
|
||||
the record only by a create: when `<asker>.<id>` holds a record already (an open ask, its tickets with it), the
|
||||
refusal writes nothing there and is said on `decided` alone. The operator, 2026-11-11 about 15:15 CET ("just make
|
||||
it configurable"): the router keeps at most `refused-kept` refused records per asker, a setting of the router,
|
||||
20 unless set, removing the oldest first, each also gone after its 30 days, each removal written in its journal. The seat's
|
||||
`asks` lists it with that outcome and words, as an ask that ended.
|
||||
|
||||
Why: ADR 0259 §4 refuses such an ask on the decided subject and bounds a destroy option at 10 minutes, and neither
|
||||
reached the asker; the list is the operator's answer. How it is checked: unit (`operatorchannelclient`), a table
|
||||
over the levels in which an expiry at the bound is published and one past it is refused before it is published,
|
||||
and a `refused` that ends the request with why; the code review refuses an ask built outside the client; whole
|
||||
(router, bus module), a refused ask is said on `decided`, a refusal of an id that is open leaves the open record and its tickets as they
|
||||
were, a twenty-first refusal of one asker removes its oldest, `asks` lists it `refused` with the router's words, and
|
||||
`nats_recreate` answers `refused` with those words.
|
||||
|
||||
## A module's packages
|
||||
|
||||
- §571 **A mesh package's entry in a lock file is written by the build, never by hand.** (ADR 0573 §2, §4; §47,
|
||||
§478, §504) A module's `package.json` names a mesh package (one whose name §504 maps to a catalog artifact) with the
|
||||
range `*`, or with the oldest version it accepts (ADR 0573 §2). Its committed `package-lock.json` may hold any entry
|
||||
for that package, or none.
|
||||
- **What the build writes.** The Node and TypeScript build module removes every entry of a mesh package from the
|
||||
workspace's lock before it installs. It then writes the entry from the version the planner read and the
|
||||
integrity the package registry gives for that version (ADR 0573 §2). In a delivery group's check where a member
|
||||
publishes the package, it writes the entry from that member's head, packed for the check, with the packed
|
||||
tarball's integrity (ADR 0573 §4, ADR 0500 §4). Every other entry is installed as the lock names it and checked
|
||||
against its integrity (*Every dependency comes by the version its lock file names*).
|
||||
- **Before its first publication.** A package of which no version is published yet (`npm-package-registry`'s
|
||||
`versions` answers none, as for the packages G itself publishes first) is reached only in a group's check, from
|
||||
its member's head. Outside a group, the edge of kind 1 (§47) builds the dependent after the package's first
|
||||
publication, which G's delivery orders.
|
||||
- **The check's lock rule** never refuses a mesh package's entry, nor its absence.
|
||||
- **The reuse key** of a build takes the versions it wrote for mesh packages, as the fingerprint takes them (ADR
|
||||
0573 §2), so a build is never reused across two of them.
|
||||
|
||||
Why: ADR 0573 §2 has the build write a mesh package's version into the workspace, and a lock made by hand holds
|
||||
the integrity of a locally packed tarball, which the published one never matches (develop question of the forge
|
||||
module's developer, 2026-11-11). How it is checked: unit (Node build module), a lock naming a local tarball's
|
||||
integrity for a mesh package is built with the registry's version and integrity; a lock without the entry is built
|
||||
alike; a non-mesh entry with a wrong integrity is refused; whole (G's group check), the forge module's tools are
|
||||
built against the head of the member that publishes the package, packed.
|
||||
|
||||
## The builder's forms
|
||||
|
||||
- §572 **The builder's process answers a package's name, and its tools ask it.** (§478, §504) The mapping from a
|
||||
package's name to its module and artifact is the builder process's own, from its view of the catalog half (§348).
|
||||
The seat `mesh-builds`' `reserved`, given `name`, is answered by the builder's process from that view. The
|
||||
builder's tools bundle holds no catalog view: `builder_versions`, asked by a name, calls `reserved` (or, for a
|
||||
module, `reserved` with `module`) through its `invokes` and answers what the process answers. Why: one mapping
|
||||
and one view, in the part that keeps them (ADR 0596 §2; develop question BL7's name part). How it is checked: unit
|
||||
(builder), `reserved` with an npm name and with a Go module path answered from the view; whole (builder),
|
||||
`builder_versions` by name answers the same as `reserved`.
|
||||
- §573 **The build seat's ask carries `version`.** (§370, §458) The ask of each build seat (`node-build-go`,
|
||||
`node-build-node` and the image build seat) gains the field `version`: the version `MAJOR.MINOR.PATCH` the builder
|
||||
reserved for the build (§369), or empty for a build that reserves none (§371, §373). The field is part of the build
|
||||
seats' contract package, beside the ask's other fields. The builder fills it when it asks, and each build module
|
||||
reads it and passes it as `MESH_VERSION` (§370), passing none when it is empty, so the build stamps none (§458). A
|
||||
build module of before G ignores the field and stamps nothing. Why: §370 named no field (develop question of the
|
||||
builder's developer on CT5). How it is checked: unit (build seats' contract), `version` read and an empty one
|
||||
read as none; whole (Go and TypeScript build modules), a build asked with a version carries it.
|
||||
- §574 **`toolchain-agreed` carries `digests` as a list.** (§472, §495) The event's field `digests` is a list, each
|
||||
item `{digest, nodes}`: the `sha256:` digest of the toolchain program and the nodes that reported it at the agreed
|
||||
version, sorted by digest, with each item's `nodes` sorted. It replaces the field `digest`, which the event no
|
||||
longer carries; a kept event of before G that carries `digest` alone is read as
|
||||
one item with no `nodes`. Why: §472 names one digest per group of nodes, and a list keeps two digests of one version apart
|
||||
(develop question of the contracts developer). How it is checked: unit (mesh-builds' contract), the list read, and
|
||||
an event of before G carrying `digest` alone read as one item with no `nodes`, so a reader of G refuses none
|
||||
and nothing waits for the next agreement.
|
||||
|
||||
## What a node-engine says of a build
|
||||
|
||||
- §575 **A want held for its build says how the build stands.** (§524) The operator, 2026-11-11 about 14:05 CET at
|
||||
lead-47's terminal: the words "the build of <module> at <fingerprint> is not done" are the assigner's, the reason
|
||||
of its hold under its first rule (no want for a fingerprint whose build is not done, ADR 0429 §6). That reason now
|
||||
says how the build stands, from the builder's `build.ended` the assigner already consumes and from the build it
|
||||
waits on: a failed build reads `the build of <module> at <fingerprint> failed: <the error>`, the error whole as
|
||||
`build.ended` carries it, `transport fault:` included (§568); a build that runs reads `the build of <module> at
|
||||
<fingerprint> is being built`. The assigner says it in its wait for that module, which the watchman's
|
||||
`delivery-waits` names; the node-engine reads no build state for it (ADR 0596). "Is not done" is said no more.
|
||||
Why: the operator's answer; a failed build said as not done left the operator and the watchers waiting for a
|
||||
build that had ended (round 1 of this batch's review, seams and tools T1, placed the words in the assigner). How
|
||||
it is checked: unit (assigner), a module whose build ended failed is held saying `failed:` with the error, and one
|
||||
whose build runs saying `is being built`.
|
||||
|
||||
## The tool runner, and kept data at run time
|
||||
|
||||
- §576 **A bundle reads its tool runner's capability words through one call.** (§488, §545, §559) The tool runner
|
||||
answers the runtime call `mesh/capabilities` with `{words}`, the capability words it states (§545). mesh-sdk's
|
||||
bundle, in Go and in TypeScript, makes that call once, at its first call that needs a word, and keeps the answer
|
||||
for as long as it runs. A bundle lives no longer than the tool runner that started it (its channel is that tool
|
||||
runner's standard input and output), so after a restart of the tool runner the bundle is started again and reads
|
||||
the words again. A tool runner of before G answers `mesh/capabilities` as an unknown call, which the bundle reads as
|
||||
no word stated, so a call of a family G adds fails at once, naming its word and the node (§488). A bundle learns
|
||||
its node from `MESH_NODE`, which the tool runner sets in the environment of every bundle it starts, read alike in Go
|
||||
and in TypeScript. Why: §488 named no channel (develop question of the mesh-sdk developer). How it is checked: unit
|
||||
(tool runner), `mesh/capabilities` answers the stated words; unit (mesh-sdk, both languages), the words read once,
|
||||
an unknown-call answer read as none, and the node read from `MESH_NODE`.
|
||||
- §577 **A part reaches the kept data its `requires` declares through its binding, never by an old name.** (§529;
|
||||
ADR 0595 §1, §9; ADR 0619 §2)
|
||||
- **A bundle** reaches it only through the tool runner's `mesh/` calls by its local name (ADR 0595 §9), which the
|
||||
tool runner resolves in the module's membership binding.
|
||||
- **A process with an account of its own** reaches it by its binding: mesh-sdk's process package gains `Binding`,
|
||||
which takes a local name and gives the entry's bus names (bucket, stream and subjects, object stores). It asks the
|
||||
seat `mesh-broker`'s `show` for the module and the local name at the process's start, as a kept call (ADR 0668 §1)
|
||||
that the process's `bus` names in `invokes`. It asks again when it hears `mesh-broker`'s `recreated` or `dropped`
|
||||
for that entry, which the process consumes. Until `show` answers, the process acts on nothing that needs the entry
|
||||
and its health says it waits, naming the entry (ADR 0283).
|
||||
- **The old name** (mesh-sdk's opening of an essential bucket by its declared name) is for a foundation part's old
|
||||
`state` section alone (ADR 0619 §2). A part that declares an entry in `requires` opens it by `Binding`; the forge
|
||||
bridge's `performed` is among them, moved so in G's change.
|
||||
- **The validator's rule for the finding words** (§529) accepts the old stream `<module>-findings` only for a part
|
||||
on the foundation list (ADR 0619 §3 as ADR 0666 §4 changes it). It refuses that stream for every other part, whose
|
||||
finding stream it requires in the template form: `finding-events`, by the template `events`, its entry listing
|
||||
`finding-written.*`, `finding-cleared.*` and `findings-stated.*`.
|
||||
|
||||
Why: a part's kept data is the bus module's to name and the part's to ask for (ADR 0595 §1, §7), and §529 left the
|
||||
run-time path and the validator's row open (develop question of the watchman's developer). How it is checked: unit
|
||||
(mesh-sdk's process package), `Binding` answered from `show`, asked again on `recreated`, and waiting while
|
||||
unanswered; unit (forge bridge), `performed` opened by its binding; unit (manifest validator), a foundation part's
|
||||
old finding stream accepted, another part's refused and its template form accepted.
|
||||
|
||||
## The indexer's forms
|
||||
|
||||
- §578 **`manifest-digest` carries its prefix, and the key does not.** (§506, §445) The catalog graph entry's field
|
||||
`manifest-digest` is `sha256:` followed by the 64 hex digits, as every digest a record names. The module's value
|
||||
and §445's `manifests` key stay the 64 hex digits as on main, since a key's name takes no `:` (§73), and mesh-sdk's
|
||||
catalog client gives the bare digits as today. The indexer writes all three from the one digest it computed at take-in.
|
||||
Why: one digest in two forms, the field's as the records write a digest and the key's as a key's name allows
|
||||
(develop question of the indexer's developer). How it is checked: unit (indexer), the entry's field after its
|
||||
prefix equals the key's name and the module's value.
|
||||
- §579 **The indexer keeps the records pinning each revision, and removes a revision only on an event.** (§153,
|
||||
§178, §216)
|
||||
- **Where.** The indexer, a foundation part, keeps its old `state` section `pins`: one key per catalog graph
|
||||
revision, `<branch>.<revision>` (the watched branch and the revision's id, each escaped as a key's name is, §73),
|
||||
whose value is the list of delivery record ids that pin it. A list of ids is kept rather than a count, so a
|
||||
`pinned` heard twice pins once. At about 20 revisions pinned a day for 90 days, about 1,800 keys of about 0.2 KiB
|
||||
each, it is capped at 4 MiB (an estimate).
|
||||
- **Applying `pinned` and `freed`.** The indexer adds or removes a record's id by compare-and-set, and only then
|
||||
acknowledges the event on its durable consumer of `mesh-delivery`'s events (§153). That consumer, when first
|
||||
made, starts at the stream's first message. A `freed` said while it was stopped is applied when it comes back,
|
||||
and one applied twice changes nothing. A `pinned` of a revision the indexer no longer holds writes its key with
|
||||
the record's id and `missing: true`, and the finding `pin-missing` (scope the indexer, id the revision) naming
|
||||
the record, which `indexer_unpin` clears.
|
||||
- **The gate states its pins.** At each start the gate says `pinned` again for every revision each record it keeps
|
||||
pins, then once `pins-stated` (`at`), so pins said before the indexer's consumer existed, or older than the
|
||||
stream keeps, are heard again; the set form absorbs the repeats.
|
||||
- **Current.** A revision is current while it is the newest revision the indexer took in on its watched branch,
|
||||
whichever watched branch that is, or the newest revision of an open candidate (its `candidates.<group>`). It stops
|
||||
being current when a newer revision of its branch is taken in, or when the candidate's `heads-replaced` is heard
|
||||
(its heads replaced, or its pull request closed) or the candidate is taken in.
|
||||
- **Removal.** When a revision's list is empty and it is not current, the indexer removes its revision object and
|
||||
its key, and its entry objects as §179 has it, but only while its consumer of `mesh-delivery`'s events is caught
|
||||
up (none of its messages pending), so a `pinned` said before is applied first, and only once it has applied a
|
||||
`pins-stated` said after its state `pins` was made. It judges on applying `freed`, on a revision ceasing to be
|
||||
current, on being caught up, and at its start after both of these hold, when it judges every kept revision, so a
|
||||
stop between a take-in and a removal keeps nothing for ever. Never on a timer.
|
||||
- **Inputs.** A catalog graph revision names no input revision. "The input revisions it names" (§178) are an
|
||||
assignment graph revision's catalog revisions, which a delivery record pins itself (§153), so the indexer keeps
|
||||
each by its own list alone.
|
||||
- **The assigner** keeps its pins of assignment graph revisions in the same form and by the same rules: its old
|
||||
`state` section `pins` (it is a foundation part, ADR 0619 §1), one key per assignment graph revision, capped at
|
||||
4 MiB; a revision is current while it is the newest the assigner wrote, or the newest it wrote for an open
|
||||
candidate; its tools are `assigner_pins` and `assigner_unpin`, as the indexer's below.
|
||||
- **Its tools.** `indexer_pins` reads each kept revision with the records that pin it, whether it is current and
|
||||
why, and the store's fill against its cap. `indexer_consumer` reads each of the indexer's durable consumers: its
|
||||
stream, name, acknowledged sequence, the messages pending, and the last event applied with its `at`. Both are
|
||||
of class `read`. `indexer_unpin` (`revision`, `record`, `why`; class `act`, taking its default (§610),
|
||||
written in the hand-act log first) removes one record's id from a revision's list, for a `freed` that was lost,
|
||||
and the removal rule then applies.
|
||||
|
||||
Why: §153 and §178 said what is kept, not where or how it is counted (develop questions IX17 and IX18). How it is
|
||||
checked: unit (indexer), a revision pinned twice by one record and freed once is removed; a `freed` applied twice
|
||||
changes nothing; a revision of a second watched branch kept while current; a candidate's revision removed on
|
||||
`heads-replaced`; a revision that stops being current while a `pinned` of it is pending is kept; a `pinned` of a
|
||||
removed revision writes `pin-missing`; `indexer_unpin` frees a revision; whole (indexer, gate), a `freed` said
|
||||
while the indexer was stopped is applied after its start; a `pinned` said before the indexer of G first started is
|
||||
heard through the gate's restatement; an indexer stopped between a take-in and a removal removes the revision at
|
||||
its start.
|
||||
|
||||
## Observers with more than one bundle, and the forge module's forms
|
||||
|
||||
- §580 **Each finding kind belongs to one bundle, and a statement covers only its own kinds.** (§383, §384, §485;
|
||||
the writer's own kind `finding-refused` and the statement's `bundle`: §707)
|
||||
A module whose bundles write one state `findings` (the forge module has four: its TypeScript tools, its Go package
|
||||
registry, its forge and its agent token) gives each finding kind to exactly one bundle. That bundle's mesh-sdk
|
||||
writer is made with the kinds it owns, and refuses a `write` or a `clear` of any other kind, naming it. Each bundle
|
||||
judges and states its own kinds from its own truth (§384); no bundle reads the whole state to state another's
|
||||
findings. Its `findings-stated` carries `kinds`, the kinds it states, and the writer's statement deletes only keys of
|
||||
those kinds. The keeper sets its view of that observer, node and set of kinds from the statement, and clears only
|
||||
conditions of those kinds that the statement does not name. A statement with no `kinds` (a writer of a part with
|
||||
one bundle) covers every kind, as before. Why: a statement that deletes every key it does not state would clear
|
||||
another bundle's findings (develop question of the forge module's developer, GIT14). How it is checked: unit
|
||||
(writer, both languages), a statement with `kinds` deletes no key of another kind, and a write of another kind is
|
||||
refused; whole (keeper), two bundles' statements on one node each keep the other's conditions open.
|
||||
- §581 **A `seen` key's 30 days are judged on each step end, with no timer.** (§487) The forge module writes `at` in
|
||||
each `seen` key's value, the time of the `step-ended` that wrote it. On each `step-ended` it handles, and at its
|
||||
start, it deletes the `seen` keys whose `at` is more than 30 days old, at most 100 for each event, the oldest first,
|
||||
so the bus keeps no key time-to-live and no template option is added; its tool `git_seen_expire` (class `act`,
|
||||
taking its default (§610), written in the hand-act log) runs the same removal now. The forge module being a foundation part
|
||||
(§584), its state `failed-steps` is in its old `state` section, and a bundle reaches it through the tool runner's
|
||||
state call by its name, as every old section is reached. Why: §487's 30 days needed
|
||||
a trigger, and an event the module already handles is one (develop question of the forge module's developer). How
|
||||
it is checked: unit (forge module), a `seen` key older than 30 days is deleted at the next `step-ended`, and a
|
||||
younger one is kept.
|
||||
- §582 **`reserved` by name answers a released version from the catalog.** (§381, §478, §369) Given `name` and
|
||||
`version` with no reservation of that version (§369 deletes a reservation once its version is released), the
|
||||
builder finds the module from the name (§572) and asks the seat `mesh-catalog`'s `versions` for that module, as a
|
||||
kept call in its `invokes`. When the catalog holds that version released, `reserved` answers `released` with the
|
||||
artifacts and the digests the release recorded, and the registry holder answers its publish as held when the
|
||||
digest is the release's, storing nothing (§381). A version neither reserved nor released, or a name no entry
|
||||
carries, is answered `error`, naming which, and the holder refuses the publish. Why: the holder knows only the name,
|
||||
and the module is the builder's own mapping (develop question of the forge module's developer). How it is checked:
|
||||
unit (builder), `reserved` for a released version answers `released` with its digests, and an unknown version
|
||||
`error`; whole (registry holder, builder), a publish of a released version at its digest stores nothing.
|
||||
- §583 **A manifest's first `version` is §368's.** The `version` G writes into the forge module's and the image
|
||||
registry module's manifests is the `MAJOR.MINOR` of the version §372 fills for each, read through
|
||||
`builder_versions`, and `0.1` for a module with none published (§368); their old values (`"1"` and `"2"`) carry
|
||||
nothing over. Why: §368 answers it (develop questions GIT15 and MAN1). How it is checked: as §368.
|
||||
- §584 **The forge module and the image registry module are foundation parts.** (§6, §529, §569; ADR 0619 §1 to §3)
|
||||
The operator, 2026-11-11 about 14:30 CET at lead-47's terminal: ADR 0619 §1 is followed as written. The bus
|
||||
module's build calls both before the bus module can answer, the forge for its source and the image registry's
|
||||
`mirror` for its base image, so both are foundation parts.
|
||||
- **The list.** The manifest validator's list (§6, ADR 0619 §3) gains the holder of the seat `git` (the forge
|
||||
module, `gitea`) and the holder of `container-image-registry` (the image registry module, `distribution`) in G's
|
||||
change.
|
||||
- **Their kept data.** Each keeps its kept data in its old sections (ADR 0619 §2): its state `findings` and its
|
||||
finding stream `<module>-findings` as §529 has a foundation part declare them, and every other state in its old
|
||||
`state` section, the forge module's `failed-steps` among them (§581). The image registry module's `holds`,
|
||||
`windows` and `mirrors` go back from their template entries to its old `state` section under the names they had
|
||||
there. The operator, 2026-11-11 about 15:15 CET ("whatever must be done to not end up in a cycle"): at its first
|
||||
start, before it serves anything, the image registry of G copies every key of its template `holds`, `windows` and
|
||||
`mirrors` into its old sections, from its own data alone, and says in its journal and its read tool how many keys
|
||||
it copied of each; a key already in the old section stays. This needs no other part's answer and adds no edge,
|
||||
so it can close no cycle; holding its delivery until the template state is empty could wait for ever, since the
|
||||
running registry keeps writing it. The template entries, no longer declared, then become orphans that `orphans`
|
||||
lists, removed only by the operator's `drop` (ADR 0595 §4, §5), as §529 has it for the healers' old stream. Their
|
||||
other template entries become old sections, empty, nothing copied (§612).
|
||||
- **The delivery order.** The running image registry, of its build before G, serves `mirror` only once its bindings
|
||||
are composed, and both the bus module's new build and the image registry's own new build ask it for their base
|
||||
images. So the grants part with design 25 *bindings-first-view* §9 is delivered first, alone, and composes the
|
||||
running registry's three bindings from `list`'s fields. Then
|
||||
the image registry's and the bus module's builds of G are built through the running registry's `mirror` (edges of
|
||||
kind 3, §569), and delivered by the edges of §49. Once the image registry of G runs, its state is in the old
|
||||
sections, which the grants part creates as it creates every foundation part's, and no binding of a first view is
|
||||
needed for it.
|
||||
|
||||
Why: the operator's answer; the list and the order follow ADR 0619 §1 and §3 and §569. How it is checked: unit
|
||||
(manifest validator), both manifests of G's heads accepted with their old sections, and a template entry in either
|
||||
refused; the catalog's validation at G's heads refuses neither; whole (image registry), a first start with keys in
|
||||
its template `mirrors` copies them, says how many, and then serves them; the delivery plan of G puts the grants part's
|
||||
delivery before both builds.
|
||||
- §585 **What the forge module reads from `step-ended`, and the form of its `failed-steps`.** (§39, §475, §487, §581)
|
||||
The forge module's readings where no line spoke, made design lines:
|
||||
- **The fingerprint** of a step is the body's `fingerprint`, the build-input fingerprint, which the runner fills
|
||||
in every `step-ended`. No field of the step's `result` is read for it. A `step-ended` without it writes the
|
||||
forge module's finding `step-unread` (scope the module the step is of, id the run), naming the run, the module and
|
||||
the step, cleared by a later `step-ended` of that step with its fingerprint, so a failure that could not be
|
||||
recorded is seen.
|
||||
- **A failure** is a `step-ended` whose `outcome` is `failed` and whose `status` is `required`. An `optional` or
|
||||
`bypass` step's failure, and a `could not run`, record no failure, as ADR 0666 §11 refuses only a failed
|
||||
`required` step.
|
||||
- **A rerun's pass** clears a failure when the body says `rerun: true` and its `by`, the caller the runtime proved
|
||||
as the runner's verb `rerun` names its callers, is `operator` or `seat:mesh-healers`; any other `by` clears
|
||||
nothing (§39).
|
||||
- **The watched branches** are those of each registration whose git origin is a repository the forge holds, per
|
||||
repository, read through `mesh-catalog`'s `registrations` and kept current on `registration-changed`.
|
||||
- **A `seen` key** `seen.<commit>.<module>` holds `{v, commit, module, fingerprints, at}`: `v` the form's version
|
||||
(1), `fingerprints` every build-input fingerprint the runs named for that commit and module, and `at` as §581
|
||||
has it. Both key forms of `failed-steps` are the forge module's own, defined in its own package, since no other
|
||||
part reads that state.
|
||||
- **The fill** of `failed-steps` that its read tool shows is read through the bundle's state call
|
||||
`mesh/state.info`, against the state's cap of 64 MiB (§623).
|
||||
|
||||
Why: the forge module's developer read these where no line spoke (develop, batch 5); each follows §39, §475, §487
|
||||
and ADR 0666 §11. How it is checked: unit (forge module), a `required` failure recorded and an `optional` one not;
|
||||
a rerun pass by `operator` clears and one by another caller does not; a `seen` value read in its form, and both
|
||||
key forms; a `step-ended` without `fingerprint` writes `step-unread`.
|
||||
- §586 **The client of `reserved` is the seat `mesh-builds`' contract package.** (§381, §478, §582) The forge
|
||||
module's package registry and the image registry module both ask `reserved`. The call is the seat `mesh-builds`'
|
||||
contract, so its client lives where ADR 0573 §1 puts a seat's contract: the package the seat's holder's
|
||||
repository publishes, mesh-delivery's contracts package, which defines `mesh-builds` (§52). That package gains a
|
||||
`mesh-builds` client with `reserved`, its input and its answers (`reserved`, `released`, `error`), and both
|
||||
registry holders use it in place of their own copies. It is not put in mesh-sdk, which keeps no seat-specific
|
||||
verb (ADR 0573 §1). Why: code two modules share has one home, and for a seat's verb that home is the seat's
|
||||
contract package (develop question of the forge module's developer). How it is checked: unit (mesh-delivery's
|
||||
contracts), the client's input and each answer read; the code review finds no second copy in either holder.
|
||||
@@ -0,0 +1,216 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0673"
|
||||
- "ADR 0672"
|
||||
- "ADR 0668"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0664"
|
||||
- "ADR 0656"
|
||||
- "ADR 0618"
|
||||
- "ADR 0615"
|
||||
- "ADR 0614"
|
||||
- "ADR 0597"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0441"
|
||||
- "ADR 0429"
|
||||
---
|
||||
|
||||
# 47, milestone G: develop's sixth batch
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§587 to §594 answer develop's sixth batch (lead-47's input of 2026-11-11, met in the join and develop after §560 to
|
||||
§586). Each is of internal form only, within what the records and the lines it cites decide; §588 carries the operator's
|
||||
answer of 2026-11-11, between 15:14 and 15:42 CET. A line of another file that one of them corrects is corrected in that line (§169, §170,
|
||||
§171, §177, §226, §404, §462, §513, §568, and the event table of G-4); here only what is new.*
|
||||
|
||||
## The ledger's upkeep calls
|
||||
|
||||
- §587 **`mesh/ledger.fill` answers the history's fill, and `mesh/ledger.remove` has a request and an answer of its
|
||||
own.** (§408, §462, §488, §155; ADR 0597 §1, §8, §10, §13) Both calls are in mesh-sdk's `go/buscalls`, as the
|
||||
constants `LedgerFill` (`mesh/ledger.fill`) and `LedgerRemove` (`mesh/ledger.remove`), under the capability word
|
||||
`ledger-upkeep-calls` (§488), served by the tool runner for the calling module's own ledgers alone, as every call of
|
||||
the package is.
|
||||
- **The fill.** The request is `LedgerRequest`, `{ledger}`, the ledger's local name in `requires`. The answer is
|
||||
`buscalls.Fill` of the ledger's history alone: its messages, its bytes and the ledger's `cap` in bytes. The
|
||||
history holds every operation and each instance's snapshot subject (ADR 0597 §1, §8), and the ledger's cap is
|
||||
its history's (design 25's template row for a ledger, its state and index buckets being states), so neither the
|
||||
fullest of the parts nor their sum is answered. A full state bucket is said by `apply`'s `state_behind` (ADR 0597
|
||||
§6), not by this call. The gate's `filling`, `full` and `room` (§181) and `gate_records` (§159) judge this answer
|
||||
against the 768 MiB of §155.
|
||||
- **The removal.** The request is `LedgerRemoveRequest`, `{ledger, instance}`. The tool runner removes the
|
||||
instance's operations and its snapshot from the history (by the instance's subjects), its key in the state bucket
|
||||
and its keys in every declared index, and answers `LedgerRemoved`, `{instance, operations, bytes}`: the number of
|
||||
operations removed and the bytes the history held less after the removal. An instance the ledger does not hold
|
||||
answers `operations` 0 and `bytes` 0, so a removal said twice changes nothing. `Applied` gains no field: a
|
||||
removal is no operation of the history and has no author. Only the owner's bundle calls it, through its tool
|
||||
runner (ADR 0597 §13 leaves any other caller to the design, and the design names none); the gate calls it for a
|
||||
record past its retention (§154).
|
||||
|
||||
This amends §462's "as `buscalls.Fill`" and its removal, which named no shape. Why: one cap, the history's, is
|
||||
the ledger's, and a removal is not an operation (develop questions on §462 and §408, join of the tool runner). How
|
||||
it is checked: unit (`buscalls`), each request and answer read; unit (tool runner), a fill answers the history's
|
||||
bytes against the cap with snapshots counted, a removal answers the operations it removed, and a removal of an
|
||||
unknown instance answers 0; whole (gate), a record past its retention is removed and the next fill is lower.
|
||||
|
||||
## The builder's publishes and its reads
|
||||
|
||||
- §588 **A reserved version left unpublished is published again, by the builder on an event or by a person's tool.**
|
||||
(§52, §369, §381, §590, §610, §633; ADR 0673 §2; *Tools are how a module is spoken to*) A publish sends the
|
||||
artifacts the build stored, by their digests, to the holder of the registry seat of their kind, under the same
|
||||
version, and builds nothing, so it takes no build capacity.
|
||||
- **By itself, on an event.** The operator, 2026-11-11 between 15:14 and 15:42 CET at lead-47's terminal, decided by
|
||||
ADR 0674 (the builder's publish again), which extends ADR 0673 to publishes: the builder
|
||||
publishes again, by itself, every reserved version `version-unreleased` names (§369), on the node-engine seat's
|
||||
`running` of a module whose catalog graph entry claims the registry seat of the missing artifacts' kind, on any
|
||||
node, and on the node-engine seat's `manifest-reads-changed` with `why` `returned` of the node that holder runs on
|
||||
(ADR 0673 §2, §590); one with `why` `started`, or with none, publishes nothing. No timer. The builder's manifest
|
||||
`consumes` both events (it does for ADR 0673 already).
|
||||
- **Each event once.** The builder acts on a message of those subjects at most once, by its stream sequence: it
|
||||
keeps, in its old `state` section `acted`, one key per subject holding the last sequence it acted on, so the read
|
||||
of §556 at its start acts on each subject's last message only when that is newer than the kept one, and a message
|
||||
read again after a restart starts nothing. The keys are one per subject of those two events, at most one per
|
||||
module and node, about 0.1 KiB each; the section is capped at 1 MiB.
|
||||
- **One queue per holder, kept on the bus.** Each registry seat holder has one queue of versions to publish, sent
|
||||
one at a time, each bounded by §369's 10 minutes; a version already queued or being published is not queued
|
||||
again. The queues are kept in the builder's old `state` section `publish-queue`, one key per queued version,
|
||||
`<holder module>.<module>.<version>` (each token escaped as a key's name is, §73), holding its artifacts' digests,
|
||||
when it was queued and whether its publish has begun; a key is deleted when its outcome is said. At its start the
|
||||
builder reads the section whole and takes up every queued version again, a begun one sent anew (a publish of the
|
||||
same digests under the same version, which the holder takes as it takes §52's), so a restart loses none. The
|
||||
section is capped at 4 MiB (about 0.5 KiB a key); a version that would pass it is not queued, said in
|
||||
`version-unreleased` as `queue full`, and waits for the next event or the tool. One event queues at most
|
||||
`republish-max-versions` versions (a setting, §633, 50 unless set), at most one per `MAJOR.MINOR` of each module
|
||||
(§369's key), the newest keys first; the rest wait for the next event or the tool. `builder_publish_queue` (class
|
||||
`read`) lists the queued versions per holder, in order, with when each was queued and whether it has begun, and
|
||||
the `acted` keys with their sequences.
|
||||
- **An artifact no store holds.** It is named once in `version-unreleased` as not stored, and is not tried again on
|
||||
events: its build key is built again by `builder_build_again`, which reserves anew.
|
||||
- **The failure still said.** A publish that fails again keeps `version-unreleased` with the holder's words and
|
||||
waits for the next such event.
|
||||
- **By a person's tool.** `builder_publish_again` (class `act`, §610, written in the hand-act log) takes `module`
|
||||
and `version`, each optional, and `why`: with both, that reserved version's missing artifacts (§52); with `module`
|
||||
alone, each reserved version of that module whose artifacts are not all published, at most one per
|
||||
`MAJOR.MINOR`; with neither, every version `version-unreleased` names, one module after another, the newest key
|
||||
first. It queues as the events do and answers at once with the versions it queued; each outcome is said as
|
||||
`package-published`, or left in `version-unreleased` with the holder's words.
|
||||
- **Before develop.** These lines rest on ADR 0674, accepted before G's builder is built from them.
|
||||
|
||||
Why: the operator's answer; §369 gave the tool one version, and a recovery of a registry holder away for hours
|
||||
left many (issue 1966 note 206); the queue, the sequence, the cap and the unstored artifact are the design's
|
||||
(review: health and events 5, running and testing 4 and 7). How it is checked: unit (builder), a version left
|
||||
unreleased is published again on the holder's `running` and on its node's `returned`, not on `started` or no
|
||||
`why`, once per message: a restart reading the same last message starts nothing; a version queued is not queued
|
||||
twice; one event queues at most the setting's number; an unstored artifact is named once and not tried on the next
|
||||
event; whole (builder, a fake registry holder away and back), every unreleased version published with no hand act,
|
||||
one at a time; a restart with three versions queued publishes the three; a fourth past the cap is said `queue full`;
|
||||
one call with neither argument publishes three modules' versions.
|
||||
- §589 **`builder_build` answers a module's build keys whole.** (§348, §568, §575; *one-question-one-tool*) The
|
||||
builder's tool `builder_build` (class `read`) takes `module` and, optional, `fingerprint`. With `fingerprint`, it
|
||||
answers that key whole; with `module` alone, the module's build keys newest first, at most 20, each with its
|
||||
`fingerprint` whole (the build-input fingerprint as the catalog graph entry carries it, never cut), `state`
|
||||
(`queued`, `building`, `done` or `failed`), the key's revision and since when, the step and its node, the error
|
||||
whole, `event-builds` and the open ask (§568), and `newest: <branch>` for each watched branch whose newest catalog
|
||||
graph entry of the module names that fingerprint, read from the builder's own view of the catalog half (§348). A
|
||||
module none of whose keys the newest entry names says so, naming the entry's fingerprint. Beside it,
|
||||
`builder_builds_since` gains `module`, which narrows its answer to that module's builds, and `builder_stuck`
|
||||
answers each key's fingerprint whole. Which node wants which fingerprint is the assigner's to answer, through the
|
||||
seat `mesh-wants`, never the builder's (ADR 0596); the words of a want's hold (§575) may shorten a fingerprint for
|
||||
reading, while every field named `fingerprint` in a tool's answer is whole. Why: no read answered a module's
|
||||
wanted build whole in one call (lead-47, issue 1966). How it is checked: unit (builder), `builder_build` by module
|
||||
answers its keys with whole fingerprints and marks the one the newest entry names; `builder_builds_since` with
|
||||
`module` answers that module's builds alone.
|
||||
|
||||
## A node's capabilities, said once
|
||||
|
||||
- §590 **One event says a node's capabilities: `manifest-reads-changed`.** (§177, §226, §404; ADR 0618 §3, §4, ADR
|
||||
0673 §2, ADR 0614 §4) The node-engine seat's event `manifest-reads-changed`, on
|
||||
`mesh.seat.node-engine.event.manifest-reads-changed.<node>`, published only by that node's node-engine (ADR 0363
|
||||
§2), is the one event of a node's stated capabilities. It carries, as the seat's definition already has it,
|
||||
`node`, `manifest-reads` (every capability the engine states now, whole), `at`, and `why`: `started` when the
|
||||
engine started or what it states changed, `returned` when its node is heard again after being away (ADR 0673 §2).
|
||||
G adds no event `capabilities-changed`: §177, §226, §404 and G-4's event table name `manifest-reads-changed` in
|
||||
its place.
|
||||
- **The assigner** consumes it, asks the seat's `manifest-reads` of that node again (ADR 0618 §3, §4), and makes a
|
||||
new assignment graph revision only when the capabilities answered differ from those its newest revision placed
|
||||
on, so a `returned` that changes nothing makes no revision.
|
||||
- **The builder** consumes it as ADR 0618 §4 and §568 have it, and builds a key again only on `returned`.
|
||||
- **An older node-engine's event.** A `manifest-reads-changed` with no `why`, from a node-engine of before G, is
|
||||
read as `started`, so it builds and publishes nothing again.
|
||||
|
||||
Why: two events of one fact, one decided (ADR 0618 §4) and one the design's (§226), are a second source of the
|
||||
same fact, and ADR 0673 §2 builds on the decided one's `why` (lead-47, on ADR 0673). How it is checked: unit
|
||||
(assigner), a `started` with new capabilities makes a revision and a `returned` with the same ones makes none;
|
||||
the code review finds no `capabilities-changed` in any module or seat definition at G's heads.
|
||||
|
||||
## The bus's object stores
|
||||
|
||||
- §591 **Which cap governs which object store.** (§58, §477, §513, §547, §607) The bus module plans:
|
||||
- **each `<state>-objects`** with 16 MiB, unless the state's `requires` names `objects-max-mib` (§547, which
|
||||
replaced §607's 64 MiB, which replaced §513's 256 MiB);
|
||||
- **each `<module>_sent-objects`** with 256 MiB (§477, kept by §607);
|
||||
- **a foundation part's old `objects` section** with the `max-mib` it declares, `sent-objects` 256 (§477).
|
||||
|
||||
No store is planned with 1024 MiB; the bus module's earlier commit that did is changed to these. Every one of
|
||||
them counts in §547's sum against the account's limit. Why: three lines in sequence named three caps for one
|
||||
store (develop question NATS1, join of the bus module). How it is checked: unit (bus module's plan), a table of the
|
||||
three kinds with their caps, and a state naming `objects-max-mib` planned at that size.
|
||||
|
||||
## The gate from G's delivery
|
||||
|
||||
- §592 **The gate's merge path from G's delivery, whole.** (§97, §150, §151, §156, §162, §164, §166, §167, §169,
|
||||
§542; ADR 0666 §10, §11, ADR 0596) The gate makes no merge and hands none to the forge:
|
||||
- **Who merges.** A session merges through the seat `git`'s `merge-pull` (ADR 0666 §11). The gate calls no
|
||||
`merge-pull`, its manifest `invokes` none, and it says no `owed` act `merge`: `owed`'s acts are the delivery's
|
||||
view, a comment and the note's line (§167). The merge as an owed act is not built.
|
||||
- **How the gate learns a merge.** From the indexer's `take-in.taken`, one record per source revision of a
|
||||
watched branch the indexer takes in, a group's members in one (§151). The gate does not consume the forge
|
||||
bridge's `merged` and `pushed` messages on its stream `forge` (§509); those are the indexer's. It consumes from
|
||||
that stream only `owed.ended` (§164), through a durable consumer its manifest's `consumes` declares and its
|
||||
binding names, so the gate's code names no module: the forge bridge's module name leaves the gate's code.
|
||||
- **A group.** Its members land one by one (*Groups*), its end the runner's `group-ended` (§431); §166's order of a
|
||||
group's merges ends at G's delivery.
|
||||
- **What was owed at the switch.** Each `owed` key the old bridge had not performed becomes an act (§169). Each
|
||||
`merges` key it had not performed is said once as `approval-carried` (§542) and becomes no act. The gate writes
|
||||
`merges` no more (§156) and reads it no more, and keeps it until §97's removal (§542).
|
||||
- **A carried approval nobody uses is seen.** For each `approval-carried` it says, the gate writes the finding
|
||||
`approval-carried-open` (scope the gate, id `<repository>.<pull>`, each escaped as a key's name is, §73), naming the
|
||||
head the Approve named, and clears it when it hears the seat `git`'s `pull-merged` or `pull-closed` of that pull
|
||||
request, which its manifest `consumes`.
|
||||
- **The approval.** No Approve is asked per merge (§150); an `approval` step's answer is the runner's (ADR 0666
|
||||
§10, §542).
|
||||
|
||||
This replaces the merge parts of §169 and §171, and §170 from G's delivery. Why: the join kept the old hand-off
|
||||
(`merges`, the bridge's messages, the bridge named in code) beside the owed acts, and the lines were spread over
|
||||
four places (join of the gate). How it is checked: unit (gate's manifest), no `invokes` of `merge-pull` and no
|
||||
`consumes` of `merged` or `pushed`; the code review finds no module name of the forge bridge in the gate's code
|
||||
and no act `merge`; whole (gate, bridge), a merge made through `merge-pull` opens one record from `take-in.taken`,
|
||||
and a `merges` key at the switch is said `approval-carried` and performed by nobody, its `approval-carried-open`
|
||||
raised and then cleared by that pull request's `pull-merged`.
|
||||
- §593 **The gate hears neither the teller's answers nor the view's reach.** (§150, §431, §512) The gate's reads of
|
||||
the teller's `answered` and the status view's `reached`, and its calls of `teller_answers` and `view_reach`, served
|
||||
the Approve per merge, which goes at G's delivery (§150, §431): the gate's `asks` go with it. So the gate's
|
||||
manifest declares neither event in `consumes` nor either tool in `invokes`, and its code calls neither; the commit
|
||||
that added those calls is undone. `view_reach` stays the status view's own read tool, answered from the catalog
|
||||
half (§196, test 61). Why: §431's "by their events" named the readers that stay, and the gate is not one of them
|
||||
(join of the gate). How it is checked: unit (gate's manifest), neither event nor tool named.
|
||||
|
||||
## Readers of a module's manifest
|
||||
|
||||
- §594 **The readers take a module's manifest from its catalog graph entry.** (§196, §421, §443, §445; ADR 0656 §6,
|
||||
ADR 0615 §1) The holders of `mesh-config`, `mesh-provisioner` and `mesh-self-check`, and the claude-code module,
|
||||
read a module's manifest from the catalog graph entry that carries it whole (§443), taken through
|
||||
`meshcatalogclient`'s view of the catalog half, which follows each watched branch's newest `catalog-revision` and
|
||||
asks `revision` again on a gap (§196). None reads a manifest by digest from the indexer, its state `manifests` or
|
||||
its tools: `manifests` is kept only for node-engines from before G (§445). Why: test 61's "no call of the indexer
|
||||
by name" holds for every reader of which modules exist, and the entry carries the manifest (join of these readers,
|
||||
RDR1 to RDR4). How it is checked: test 61, a row for each of the four readers; the code review finds no read of
|
||||
`manifests` in them.
|
||||
@@ -0,0 +1,598 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0673"
|
||||
- "ADR 0672"
|
||||
- "ADR 0668"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0660"
|
||||
- "ADR 0656"
|
||||
- "ADR 0626"
|
||||
- "ADR 0619"
|
||||
- "ADR 0618"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0579"
|
||||
- "ADR 0573"
|
||||
- "ADR 0448"
|
||||
- "ADR 0429"
|
||||
- "ADR 0363"
|
||||
- "ADR 0286"
|
||||
- "ADR 0283"
|
||||
- "ADR 0259"
|
||||
---
|
||||
|
||||
# 47, milestone G: develop's seventh batch, the operator's answers of 15:14 to 15:42 CET, and the refreshed build maps
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§610 to §676 answer lead-47's seventh batch of 2026-11-11: the operator's answers of 2026-11-11 between 15:14 and
|
||||
15:42 CET at lead-47's terminal (§610 to §613, and §588 in G-16), the design questions of the six refreshed build
|
||||
maps, and develop's D-1 to D-15. Each other line is of internal form only, within what the records and the lines it
|
||||
cites decide. A line of another file that one of these corrects is corrected in that line (§15, §30, §391, §394,
|
||||
§423, §462, §515, §528, §570, §584, §585, §588, design 34 every-event §11, §19, §35, and design 46 7-approval-mark §22); here only what is new. A question one line answers
|
||||
for several build maps is answered once, and the maps' names for it are given in brackets.*
|
||||
|
||||
## The operator's answers
|
||||
|
||||
- §610 **No verb keeps a list of named callers: every verb takes its class's default.** (§391, §392, §394; ADR 0286;
|
||||
the records this ends are named below) The operator, 2026-11-11 about 15:40 CET at lead-47's terminal: "the operator
|
||||
and agents may do everything, we might make it more strict and start asking for additional checks for agent stuff,
|
||||
modules can get as many permissions as they want"; and about 16:10 CET: "we can always lock it down later, but we
|
||||
most definitely don't want stupid things like 'named lists'".
|
||||
- **The defaults.** A verb's definition names its class, and its callers are its class's default, which the
|
||||
seat-definition reader applies: `read` takes `invokers`, `persons` and `sessions`; `act`, `warrant` and `accept`
|
||||
take `operator`, `sessions` and `invokers`. `invokers` is every module whose manifest names the verb in `invokes`
|
||||
(§391). A module tool's callers are its class's default alike.
|
||||
- **No list.** A seat definition and a manifest name no `callers`: the seat-definition reader and the manifest
|
||||
validator refuse the field, naming the verb or tool, and every `callers` G's heads carry goes in G's change. So
|
||||
`git`'s `tag` (§370), the build seats' `run` (§229), `node-backup`'s `now`, `restore`, `aside` and `discard-aside`
|
||||
(§405, §555), `restore` taking any module's item (no own-item limit), `mesh-recovery`'s `snapshot-kept` and
|
||||
`restore-kept` (§554, §659), `mesh-broker`'s `snapshot-get` and `snapshot-restore` (§681), the node-engine's eleven verbs
|
||||
(§393) and its `first-views` and `first-view-again` (§422), `operator-channel`'s `ask-again` (§394), the indexer's `indexer_unpin` (§579) and every hand tool of G
|
||||
take their class's default. §392's prefixed forms and `holder:<seat>@same-node` (§336) are no longer written in
|
||||
a definition; a part that must tell callers apart reads the account the bus proved (`by-caller`, ADR 0259 §3).
|
||||
- **What stays.** Who may publish an event or a step's result stays its owner's alone (ADR 0363 §2, ADR 0666
|
||||
§21), and a verb's own judgement of its input stays (`restore-kept` refusing `aside-only`, §516; `merge-pull`
|
||||
counting a rerun only by the operator or the healers, ADR 0666 §11, §585).
|
||||
- **The approval mark stays** (ADR 0286), so a `warrant` verb, or any verb a manifest marks, runs only on the
|
||||
operator's Approve of that one call, whoever calls (§677).
|
||||
|
||||
This amends §391, §392, §393 and §394, and every line naming `callers`. Why: the operator's answers. How it is
|
||||
checked: unit (seat-definition reader, manifest validator), a definition or manifest naming `callers` refused, and
|
||||
a table row per class giving its default; whole (backup holder, recovery module), a `restore` from a session and
|
||||
from the recovery module for another module's item taken, and a `snapshot-kept` from a session taken.
|
||||
- §611 **Five parts move only their findings to templates in G.** (§529, §577) The operator, 2026-11-11 between 15:14
|
||||
and 15:42 CET at lead-47's terminal: in G only the state `findings` and the finding stream of the teller, the
|
||||
status view, the watchman, enrolment and the forge bridge move to `requires` templates (§529). Their other old
|
||||
sections stay as they are, keys and names untouched: the watchman's `presence` and `stuck`, the teller's `answers`,
|
||||
`asked` and `ends`, the view's `answers` and `reach`, enrolment's `enrolments`, and the forge bridge's `heads`,
|
||||
`inputs` and stream `forge`. The manifest validator accepts these parts' other old sections until a later line
|
||||
moves them; §577's rule for the finding words stands. [mesh-delivery DQ-4.] Why: the operator's answer. How it is
|
||||
checked: unit (manifest validator), each of the five manifests of G's heads accepted with its old sections and its
|
||||
finding templates.
|
||||
- §612 **The forge module's and the image registry's other template entries become old sections, empty.** (§584) The
|
||||
operator, 2026-11-11 between 15:14 and 15:42 CET at lead-47's terminal: the forge module's `incoming` (objects,
|
||||
2048 MiB), `transfers` and `idle`, and the image registry module's `incoming` (objects, 8192 MiB), `transfers`,
|
||||
`idle`, `findings` and stream `events`, move to old sections under the same names, each with the cap it declares
|
||||
today, and start empty: nothing is copied. They hold uploads in flight, which their callers ask again, and
|
||||
findings, which are stated again at the start (§384). The template entries, no longer declared, become orphans that
|
||||
`orphans` lists, removed only by the operator's `drop` (ADR 0595 §4, §5), as §584 has it for `holds`, `windows`
|
||||
and `mirrors`, which alone are copied. [mesh-catalog Q-F2.] Why: the operator's answer. How it is checked: unit
|
||||
(manifest validator), both manifests of G's heads hold no template entry; whole (image registry), a first start
|
||||
copies only `holds`, `windows` and `mirrors`.
|
||||
- §613 **The gate's kept data, state by state.** (§15, §156, §172, §431, §542, §584) The operator, 2026-11-11
|
||||
between 15:14 and 15:42 CET at lead-47's terminal:
|
||||
- `pulls` and `asks` go with nothing copied (§172, §431): G's gate declares neither, and they are orphans that
|
||||
`orphans` lists, removed only by the operator's `drop` (ADR 0595 §4).
|
||||
- `merges` and `source` stay in the old `state` section (§542, §15).
|
||||
- `deliveries`, `groups`, `owed` and `conditions` move to `requires` entries of the template `state`, under the same
|
||||
names. The old sections' keys stay, read no more, so a revert's gate of before G finds them, until the operator's
|
||||
`drop`.
|
||||
- **Copied at every start that finds the old section changed.** The gate of G keeps, in a template state of its
|
||||
own `copies`, one key per state with the old section's last revision it copied and when. At each start, before it
|
||||
serves anything, for each of the four whose old section's last revision is newer than that key, it copies every
|
||||
key changed since: a key whose write in the old section is newer than its template key's replaces it, a key
|
||||
deleted in the old section after its template key's write is deleted in the template, and an older one is left.
|
||||
A deletion G's gate makes itself in an old section (below) moves that state's mark in `copies` to the
|
||||
deletion's revision in the same step, so the next start copies only what another gate wrote after it and never
|
||||
deletes G's ended records from the template.
|
||||
It then writes the mark, and says in its journal and in `gate_records` how many keys it copied and deleted of
|
||||
each. So forward, a revert (whose gate of before G writes the old sections), and forward again carry every change
|
||||
made while reverted, and nothing older overwrites a newer write.
|
||||
- **What G ended, a revert does not take up.** When G's gate ends a delivery, a group or an owed act, it deletes
|
||||
that key in the old section, the ended record staying in its template; so a revert's gate of before G finds only
|
||||
what G left open, and never acts again on what G ended.
|
||||
|
||||
[mesh-catalog Q-G1.] This corrects §15's "the gate's other kept data stay in ADR 0595's templates". Why: the
|
||||
operator's answer; the copy at every start is the design's (review: security and upgrade 5, running and testing 2).
|
||||
How it is checked: unit (manifest validator), the gate's manifest of G's heads declares `merges` and `source` old
|
||||
and the five templates; whole (gate), forward, then a revert's write and deletion in the old sections, then
|
||||
forward: the template holds the reverted gate's write, the key it deleted is gone, and a key written in the
|
||||
template after the revert is kept; a delivery G ended is not in the old section the reverted gate reads; a delivery ended, then a restart of G's
|
||||
gate: its ended record is still in the template.
|
||||
## Reading the catalog half, every part alike
|
||||
|
||||
- §614 **How a part reads the catalog half per watched branch, once for every part.** (§196, §199, §443, §459, §479,
|
||||
§507, §521, §530, §532, §556; ADR 0656 §6, ADR 0596) Every part that reads the catalog half (a bundle or a process)
|
||||
reads it so, and no part builds its own:
|
||||
- **The reader.** Through `meshcatalogclient`, the client of the seat `mesh-catalog` in mesh-delivery's contracts
|
||||
(ADR 0573 §1), wired on the one `go/views` reader the part already has: the bundle reader for a bundle, the process
|
||||
reader (`views.ProcessReader`) for a process. Each watched branch is a view of that reader named
|
||||
`mesh-catalog.catalog-revisions.<branch>` (§479), routed by §521, and the part's pass reads the views through that
|
||||
reader alone.
|
||||
- **The branches.** `meshcatalogclient` follows the branches itself, as mesh-delivery's code shared by every
|
||||
reader of the seat: the registrations' watched branches, read by `registrations` at the start and followed by
|
||||
`registration-changed` (§532); before the first registrations, the branches the gate's `source` answers, read at
|
||||
the start and followed by the gate's `source-moved` (§507), which it stops following at the first
|
||||
`registration-changed`. The gate's `source` given no branch answers `{branches}`, the branch of each
|
||||
`source.<branch>` key it keeps, and given a branch that branch's source revision. mesh-sdk's `go/views` adopts and
|
||||
drops the views (§532) and holds no seat's verb. The forge module, in TypeScript, follows the branches the same
|
||||
way through the TypeScript form of `meshcatalogclient`, beside the seat's generated TypeScript contract in
|
||||
mesh-delivery's contracts, on mesh-sdk's TypeScript view follower (§196).
|
||||
- **The manifest entries every such part declares.** In `consumes`: `mesh-catalog.catalog-revision`,
|
||||
`mesh-catalog.registration-changed` and `mesh-delivery.source-moved`. In `invokes`:
|
||||
`seat:mesh-catalog.revision` (with the field `none-yet`, §617), `seat:mesh-catalog.registrations`,
|
||||
`{verb: seat:mesh-delivery.source, fields: [branches]}` in the long form (§558), and `seat:mesh-catalog.entry` for a part that reads an entry by its fingerprint.
|
||||
- **An older build's entry.** A part that resolves an entry by fingerprint (§459's reader, the node-engine's want,
|
||||
§421) keeps beside its views the entries by fingerprint it asked of `mesh-catalog.entry`. When it hears a
|
||||
`running` or a `wanted` naming a fingerprint that no branch's newest revision lists and it does not hold, it asks
|
||||
`entry` once, as a kept call (ADR 0668 §1), and keeps the answer; it removes one when no `running` or `wanted` it
|
||||
holds names that fingerprint any more. The reader it passes to a pure function (§459) answers from the views and
|
||||
those entries alone, and reads no bus. Each kept entry is `asked`, `answered` or `not-found`:
|
||||
- **asked**: the reader answers it as pending, a state apart from none; the part's health says it waits on that
|
||||
fingerprint, and the node-engine's have says the phase `held` with its detail opening `entry-asked:` (§421's
|
||||
`entry-missing` is said only once `entry` answered not found);
|
||||
- **answered**: the answer starts a new pass at once;
|
||||
- **not-found**: the part writes the finding `entry-not-found` (scope the part, id the fingerprint), cleared when
|
||||
nothing it holds names that fingerprint or a later ask answers it.
|
||||
|
||||
`<module>_first_views` lists these entries with their state and since when, and the part's tool
|
||||
`<module>_entry_again` (`fingerprint`; class `act`) asks one again now; the node-engine's are its seat's verbs
|
||||
(§683).
|
||||
- **A branch at `none yet`.** A view whose `revision` answered `none-yet` (§530) is ready and empty: the reader's
|
||||
answer for that branch lists no entry.
|
||||
- **The first pass.** A part acts on nothing until every branch it follows has its first view, `none-yet`
|
||||
counting as one; until then `<module>_first_views` says each branch `waiting` and its health says it waits (§200,
|
||||
§556). After its first views it acts on what it holds, and a view behind is read again by §196.
|
||||
- **The node-engine follows no branch.** It reads the catalog half only by the fingerprints its wants name
|
||||
(§421), through the entries above, and declares none of the branch entries; it never waits on a branch's view, the
|
||||
registrations or the gate's `source`, so it composes the gate (and every part) before any of them answers, and no
|
||||
cycle runs through it at G's delivery. It keeps those entries on its node and serves their rows and ask again as
|
||||
its seat's verbs (§683). The grants part composes a want's account before its first views (§684).
|
||||
- **A dropped branch.** When `go/views` drops a branch's view (§532), it clears, through the part's mesh-sdk writer (§639: a views reader writes its findings through the part's writer, never a writer of its own),
|
||||
every finding that view raised (`first-view-unanswered`, `view-waits`, `view-unsaid`, `view-too-large`), says the
|
||||
drop and each cleared finding in the part's log, and removes the view's row of `<module>_first_views` [D-12].
|
||||
|
||||
This is the one form for every reader of which modules exist and of the catalog graph's entries: the tool runner
|
||||
(its owner of the assigner's views, §616, and its branches, [mesh-tools Q-R12]), the grants part [D-5], the
|
||||
readers of §594, the node-engine (§421) and the Go build module (§615); and it answers mesh-sdk's question on
|
||||
§532. Why: the same question came from five build maps, each about to build its own form (develop's D-5). How it
|
||||
is checked: unit (`meshcatalogclient`), a branch added by `registration-changed` and one by `source-moved` before
|
||||
the first registrations are followed, an entry asked once on a `running` of an unlisted fingerprint and dropped
|
||||
when nothing names it, a pending entry read apart from none and a not-found one raising `entry-not-found`, a
|
||||
`none-yet` branch read as empty; unit (gate), `source` with no branch lists the branches; whole (grants part, tool
|
||||
runner), a part started with one branch at `none-yet` acts once every first view is in; a dropped branch's findings
|
||||
are cleared; whole (node-engine), a want composed with no branch view and no `source` answered.
|
||||
- §615 **The tool runner's and the Go build module's manifests come from the entry too.** (§594, §614, §443, §445,
|
||||
test 61) The mesh MCP server of the tool runner takes each module's manifest from its catalog graph entry, through §614's
|
||||
reader; its first view of the indexer's `manifests` and its `consumes` of `mesh-delivery-indexer.views.manifests`
|
||||
go [mesh-tools Q-R2]. The Go build module's compose ask takes each manifest whole from the entry its fingerprint
|
||||
names, by `seat:mesh-catalog.entry` (§506's `manifest-digest` naming the entry's), and its `invokes` of
|
||||
`mesh-delivery-indexer.indexer_read_manifests` goes [mesh-catalog Q-B1]. Both join §594's readers. Why: test 61, and
|
||||
`manifests` is kept only for node-engines of before G (§445). How it is checked: test 61, a row each.
|
||||
- §616 **The assigner's views are named by the seat, in the code and in the manifest.** (§428, §7) The tool runner's
|
||||
code reads the module that holds `mesh-module-assigner` from the catalog half's claims of that seat (§614) and
|
||||
names the views `<that module>.assignments` and `<that module>.seat-holders` from it, never by a name fixed in code.
|
||||
Its manifest's `consumes` names them by the seat, marked so as `invokes` marks a seat:
|
||||
`seat:mesh-module-assigner.views.assignments` and `seat:mesh-module-assigner.views.seat-holders`, which the grants
|
||||
part resolves to the seat's holder from the catalog half as it resolves a seat's events, so a new holder is
|
||||
granted with no change of the manifest. The seat `mesh-module-assigner`'s definition declares its states
|
||||
`assignments` and `seat-holders` as views (ADR 0571 §6), and the manifest validator refuses a seat-named view the
|
||||
definition does not declare, naming it. The edge is of kind 6 from the seat's holders to the consumer, held until a
|
||||
holder runs a build whose seat definition declares the view, as §558 derives it for a field. mesh-sdk's manifest
|
||||
reader reads the `seat:` form in `consumes`, first in §494's second reader release (§621), which the parts reading
|
||||
manifests take before G's merge. Design 46
|
||||
7-approval-mark §19's holder of `operator-channel` is read from that view so found. [mesh-tools Q-R3, Q-R17.] Why:
|
||||
§428 removes the name from code, and the catalog half says who claims a seat (review: seams and tools' note). How
|
||||
it is checked: unit (tool runner), a fixture catalog half naming another holder of the seat gives views of that
|
||||
name; unit (grants part), a seat-named view granted for the seat's new holder; unit (manifest validator), a
|
||||
seat-named view the definition lacks refused; the code review finds no assigner
|
||||
name in the tool runner's code or manifest.
|
||||
- §617 **The long form of an `invokes` entry stands beside `*`.** (§558, §545) A manifest's `invokes` may hold `*`
|
||||
and long-form entries together: the tool runner's is `*`, then `{verb: "seat:mesh-catalog.revision", fields:
|
||||
["none-yet"]}` and `seat:mesh-catalog.entry` with no fields named [mesh-tools Q-R4, Q-R18]. The manifest validator
|
||||
accepts a long-form entry beside `*`, and each named verb gives its edge of kind 6 (§558). In mesh-sdk's Go form,
|
||||
`Invokes` stays the list of verbs, each long-form entry's verb among them, and `InvokeFields`, beside it, maps a verb
|
||||
to the fields it names, so none of `Invokes`' readers changes [mesh-sdk 4]. Why: `*` lets a part call any verb, and a
|
||||
long-form entry is how it says it relies on a verb or a field, which `*` cannot say. How it is checked: unit
|
||||
(manifest reader), `*` with two long-form entries read into `Invokes` and `InvokeFields`; unit (manifest
|
||||
validator), the tool runner's manifest accepted and its two edges derived.
|
||||
|
||||
## The tool runner
|
||||
|
||||
- §618 **The tool runner's own fingerprint reaches no bundle.** (§205, §519; design 46 7-approval-mark §5, §19)
|
||||
`MESH_FINGERPRINT` is one of the tool runner's own words, which `BundleEnv` hands no bundle: a bundle reads only the
|
||||
fingerprint the node-engine composed into its environment (§519). A bundle whose environment carries none has no
|
||||
served entry the tool runner can read, so every marked call on that runtime is refused, as design 46 7-approval-mark
|
||||
§5 and §19 have it. [mesh-tools Q-R5, extended.] Why: a bundle given the tool runner's own value is judged against
|
||||
the tool runner's entry. How it is checked: unit (tool runner), a bundle composed with none sees none and its marked
|
||||
call is refused; one composed with a fingerprint sees its own.
|
||||
- §619 **A foundation part's bundle reaches its old `objects` sections through its membership.** (§407, §518) The grants
|
||||
part issues each old `objects` section of a foundation part in the membership, as it issues its old `state`
|
||||
sections: mesh-sdk's membership gains `Objects`, beside `State`, mapping each section's name to its object store.
|
||||
The tool runner resolves a put or get session's `name` (§518) of a foundation part's bundle in that field, and of any
|
||||
other part in its state's objects store and its sent store, as §518 has them. [mesh-tools Q-R6.] Why: §407 binds
|
||||
that bundle to its old `objects` section and no membership field carried it. How it is checked: unit (membership),
|
||||
the field read; whole (tool runner), a foundation part's bundle puts and gets a value over the limit in its old
|
||||
section.
|
||||
- §620 **A sent object over the limit is read by a get session.** (§59, §518) The tool runner's `mesh/objects.sent-get`,
|
||||
asked for another module's sent object larger than the bus's per-message limit, answers `{chunked: true, size}` and
|
||||
sends the object through §518's get session on the bundle's channel, read from the sender's sent store under the
|
||||
standing grant §59 gives the caller. [mesh-tools Q-R7.] Why: a bundle calling a verb whose reply is larger had no
|
||||
way to read it. How it is checked: whole (tool runner), a reply's object of 20 MiB read whole by a bundle.
|
||||
- §621 **What the second reader release reads, and how it is released.** (§494, §545, §605, §508, §554) §494's release
|
||||
reads, beside `calls` (§605) and the long form (§617), the tool runner's artifact word `states` [mesh-tools Q-R8,
|
||||
mesh-sdk 3] and `by-caller` on a verb, which the seat definition's format gains for `snapshot-kept` and
|
||||
`restore-kept` (§554) [mesh-contracts DQ-1] and every verb G gives the form (§681, §682), and the `seat:` form
|
||||
of a seat-named view in `consumes` (§616). Since it also carries §508's validator rule, which is the indexer's,
|
||||
the release is a delivery group of mesh-sdk (the reader's words) and mesh-delivery (the rule), checked and delivered
|
||||
together [mesh-sdk 5]. Why: the running reader refuses an unknown word, and the rule is mesh-delivery's code. How
|
||||
it is checked: unit (the release's reader), `states` and a verb's `by-caller` read; the group's one check.
|
||||
- §622 **Where the capability words and `mesh/capabilities` are named.** (§462, §488, §576) mesh-sdk names each
|
||||
capability word as a constant beside the calls of its family: the bus-state words (`bus-state-calls`,
|
||||
`ledger-calls`, `ledger-upkeep-calls`, `sent-objects-calls`) in `go/buscalls`, and `put-sessions`,
|
||||
`finding-calls` and `seat-settle-delay` with `mesh/publish` in `go/stdio.go` and `src/stdio`; the call
|
||||
`mesh/capabilities` and its answer `{words}` are named there too, since `go/buscalls` holds only bus-state calls.
|
||||
Which calls each word covers stays the tool runner's (§462). mesh-sdk's Go package names all four words its calls
|
||||
need in `calls` (§605), the npm package `finding-calls` alone, since its code makes no session, ledger or sent-object
|
||||
call. [mesh-tools Q-R9; mesh-sdk 1, 2, 11.] This amends §462's "mesh-sdk gains no capability list": it gains the
|
||||
words, not the families. Why: the bundle fails naming a word (§488), so it must know it. How it is checked: unit
|
||||
(mesh-sdk), each call's word; unit (tool runner), its families use mesh-sdk's constants.
|
||||
- §623 **The forge module's fill is read through `mesh/state.info`.** (§585) A bundle's state call `mesh/state.info`
|
||||
answers a state's bucket against its caps, so the forge module's read tool shows `failed-steps`' fill from it, not
|
||||
counted from the keys. §585's fill bullet is corrected so. [mesh-tools Q-R11.] Why: the call exists. How it is
|
||||
checked: unit (forge module), the fill read from the call.
|
||||
- §624 **The tool runner's manifest `version` is §368's.** (§368, §583) The tool runner's manifest names `0.1`, the
|
||||
`MAJOR.MINOR` of §368, as every manifest; design 34 every-event §11's "Its `version` stays `2`" is corrected.
|
||||
[mesh-tools Q-R13.] Why: §368 is the later line and holds for every manifest. How it is checked: as §368.
|
||||
- §625 **The act's build of a marked wait is the tool runner's own `MESH_FINGERPRINT`.** (§205; design 34 every-event
|
||||
§35) The build an act of a marked wait names is the tool runner's own fingerprint, read from its `MESH_FINGERPRINT` at
|
||||
its start (§205), the fingerprint the node-engine's `running` says of it; the tool runner consumes nothing more for
|
||||
it. Design 34 every-event §35 is corrected so. [mesh-tools Q-R14.] Why: the node-engine hands each process its own
|
||||
build (§205). How it is checked: unit (tool runner), the act carries the fingerprint of its environment.
|
||||
- §626 **A module ends G with no more tests than it starts with.** (§419, test budget) §419's count starts, for each
|
||||
module, at the commit of its repository's main from which G's branch of that repository starts (its merge base with
|
||||
that branch), and the check compares the module's count at G's heads against it. This holds for the tool runner's
|
||||
work of design 34 every-event and design 46 7-approval-mark: the new tables and the whole test of design 46
|
||||
7-approval-mark §22 and design 34 every-event §19 replace existing tests, and every test they delete counts. A test
|
||||
moved with its code (§655) counts in the module it moves to, as no new test there, and as deleted in the module it
|
||||
left. [mesh-tools Q-R16.] Why: §419 is milestone G's rule and both parts ship in G's release. How it is checked: the
|
||||
check counts each module's tests against its count at that commit.
|
||||
## The node-engine and the node agent
|
||||
|
||||
- §627 **The node-engine is a foundation part.** (§6, §529, §577; ADR 0619 §1 to §3) The node-engine composes every
|
||||
part, the bus module included, before the bus module can answer, so it is a foundation part as ADR 0619 §1 defines
|
||||
one, as §584 has it for the forge module and the image registry. The manifest validator's list gains the holder of
|
||||
the seat `node-engine` in G's change; its kept data stays in its old sections, its old stream `mesh-host-findings`
|
||||
among them, and it opens them by their declared names (§577's old-name path), not by `Binding`. [the node-engine's map, RQ-1.]
|
||||
Why: ADR 0619 §1. How it is checked: unit (manifest validator), the node-engine's manifest of G's heads accepted with
|
||||
its old stream.
|
||||
- §628 **G removes the node-engine's reads of the indexer and the builder alone.** (§421, §195, §435) In G the
|
||||
node-engine's reads of the indexer's and the builder's states go, as §421 names them. Its reads of the assigner's
|
||||
`assignment` and `candidates`, the provisioner's `provisions` and the vault's `signers` and `node-logins` stay in G
|
||||
(§459's input among them), and `compose-candidate` keeps its reads of `candidates`, `manifests` and `verdicts` until
|
||||
it goes with the checker (§435). §421's "holds no read of another module's state" is checked for the indexer's and
|
||||
the builder's states. [the node-engine's map, RQ-2, RQ-3.] Why: G moves the reads §421 names; the others need verbs no line of G
|
||||
gives, and they are reads of another module's state that ADR 0596 §2 ends when their owners move. How it is checked:
|
||||
the node-engine's account holds no read of the indexer's or the builder's states.
|
||||
- §629 **The have's wait and the node-engine's `wait-held`.** (§423, §498, §499, §541, §560) A node-engine's
|
||||
`wait-held` and `wait-ended` carry the fields of the seat `mesh-wants`' wait (§498, §512: `owner`, `step`, `node`,
|
||||
`on`, `bound`, `since`, `words`) and `reason`; `node` is the node waited on (the build node for a kept build), and
|
||||
the subject's `<node>` the node that says it [mesh-contracts DQ-3]. The have's value gains `wait`, one such wait with
|
||||
`ask-at` and `act` (§560), present while the have waits; `waits`, the composition digest of the phase
|
||||
`waiting-yes`, stays as it is [the node-engine's map, RQ-4, mesh-contracts DQ-4]. This corrects §423's "with no new field". Why:
|
||||
§499 and §560 need the wait in the have. How it is checked: unit (node-engine seat's contract), `wait` read with
|
||||
both fields; whole (node-engine), a kept-build wait says it.
|
||||
- §630 **At a revert the node agent reads the point from `kept-points`.** (§516, §560) At a revert the node agent asks
|
||||
`seat:mesh-recovery.kept-points` for the module and kept data, takes the point of the form and fingerprint it reverts
|
||||
from, and passes its `snapshot` and its catch-up to `restore-kept`; its manifest names the verb in `invokes`.
|
||||
[the node-engine's map, RQ-5.] Why: `ended` is kept last per subject, so a later point replaces it. How it is checked: unit (node
|
||||
agent), a revert after two points restores the one of its form.
|
||||
- §631 **A manifest's `catch-up` is a tool and its arguments.** (§451, §516) A manifest declares `catch-up` as `{tool,
|
||||
arguments}`, mesh-sdk's reader takes it whole (a string of before G read as `{tool, arguments: {}}`), and the node
|
||||
agent passes it to `snapshot-kept` unchanged. [the node-engine's map, RQ-6.] Why: `snapshot-kept` takes both. How it is checked:
|
||||
unit (reader), both forms read.
|
||||
- §632 **The node agent's `why`.** (§516) It gives `snapshot-kept` the `why` "form change of `<module>`'s `<kept>` to
|
||||
`<fingerprint>`" and `restore-kept` "revert of `<module>`'s `<kept>` from `<fingerprint>`", which the hand-act log
|
||||
keeps. [the node-engine's map, RQ-7.] How it is checked: unit (node agent), both words.
|
||||
- §633 **§557's bounds are `config` keys.** (§557) Each bound §557 makes a setting is a key of the part's manifest
|
||||
`config`, read at its start through mesh-sdk's `stdio.Config`, its default the design's value; the node-engine's are
|
||||
`recovery-wait-minutes` (90) and `view-waits-minutes` (5) [the node-engine's map, RQ-8], the condition keeper's two hours of §540
|
||||
`owner-handover-minutes` (120) [D-7], and every other part's is named `<what>-<unit>` alike. The gate's own settings
|
||||
stay where they are. Why: one form for every part's bound. How it is checked: each whole test sets its bounds in
|
||||
its fixture's `config`.
|
||||
- §634 **The node-engine's own name, everywhere it is used, is §527's.** (§428, §527) Its discovery, its finding
|
||||
stream's subject and observer, and the vault's login names read the node-engine's own module from §527, as its
|
||||
guard, compose and bootstrap do. [the node-engine's map, RQ-9.] How it is checked: the code review finds no fixed name.
|
||||
- §635 **The node-engine invokes `withheld`; it gains no `uses`.** (§555, §558; design 46 7-approval-mark §6) Its
|
||||
manifest names `seat:mesh-grants.withheld` in `invokes`, which gives the served-verb edge, and gains no `uses`.
|
||||
[the node-engine's map, RQ-10.] How it is checked: unit (manifest validator), the edge derived.
|
||||
- §636 **The private network module's name is its own manifest's.** (§428) `overlay.Name` stays the `module` the private network package's (`overlay`)
|
||||
computed manifest names itself; mesh-networking removes the fixed `mesh-provisioner` alone, read from the seat
|
||||
list's holder of `mesh-provisioner`. [the node-engine's map, RQ-11.] How it is checked: the code review.
|
||||
- §637 **Withdrawn (the operator, 2026-11-12; A-320).** **Test 36 reads the builds of before G through the registry seat.** (§405, §555, §610) Test 36's run, in the
|
||||
check the running checker makes of G (ADR 0667 §2), fetches the builds of before G and the recovery module by their
|
||||
fingerprints through the seat `oci-artifact-registry`, read-only, under the checker's account, its manifest naming
|
||||
the fetch in `invokes`, granted by §610's default. [the node-engine's map, RQ-12.] How it is checked: test 36 runs in that check.
|
||||
|
||||
## mesh-sdk's shared parts
|
||||
|
||||
- §638 **The asker client is mesh-contracts' `operatorchannelclient`.** (§570, §586; ADR 0573 §1) The client §570
|
||||
names is `operatorchannelclient`, of the seat `operator-channel`'s contracts in mesh-contracts, which already
|
||||
bounds the expiry; `Take` answers `Refused` with the router's words. mesh-sdk builds nothing for it. The fields
|
||||
§570 names are the contract's: on `decided`, the warrant's `ask` (the ask's id) and `words` (the why); on the record,
|
||||
`state` `refused` and `answer`, and the record gains `at`. §570 is corrected so. [mesh-sdk 6, mesh-contracts DQ-9,
|
||||
DQ-8.] Why: a seat's client lives in its contracts package (ADR 0573 §1). How it is checked: unit
|
||||
(`operatorchannelclient`), a refusal answered and an expiry past its level's bound refused.
|
||||
- §639 **A views reader writes its findings through the part's writer.** (§461, §485, §580) `go/views`' findings
|
||||
(`view-waits`, `first-view-unanswered`, `view-unsaid`, `view-too-large`) are written through the part's mesh-sdk
|
||||
writer as kinds of their own (§580), and the writer's statement covers them beside `observe`'s. [mesh-sdk 7.] How it
|
||||
is checked: unit (writer), a statement keeps the reader's open findings.
|
||||
- §640 **The finding writer reads the generated types; it keeps no copy of the layout.** (§439, §452) mesh-sdk's
|
||||
writer, in Go and TypeScript, defines the small interface it needs of a finding's body (its key's parts, its
|
||||
`since`, its kind) and takes the types the seat generator emits for `findings` through it; it holds no
|
||||
`FindingBody`, `FindingCleared`, `FindingsStated` or `FindingsAsked` of its own, so there is one layout and nothing
|
||||
to keep equal. [mesh-sdk 8.] Why: a copy held equal by a test in another repository is not checked on a change of
|
||||
mesh-sdk alone, and the TypeScript copy by none (review: running and testing 6). How it is checked: unit (writer,
|
||||
both languages), a generated body written and stated; the code review finds no copy.
|
||||
- §641 **A stream kept last per subject: where it is read and how it is told.** (§396, §556) mesh-sdk's
|
||||
`HearSeatEvents` makes §556's read at the start with an ordered consumer, then follows the durable, and names the
|
||||
stream `waiting` in `<module>_first_views` until the read ends [mesh-sdk 9]. The grants part tells such a stream by
|
||||
the seat definition's `keep` of that event, and grants its ordered consumer from the reader's `consumes`
|
||||
[mesh-delivery DQ-7]. `wait-held` and `wait-ended` of the node-engine each have their own stream (§396), with
|
||||
`running`'s keep (no age limit, last per subject) [mesh-contracts DQ-2]; §556's "on the stream `running` is kept
|
||||
on" reads so. How it is checked: unit (mesh-sdk), the start read then the durable; unit (grants part), the ordered
|
||||
consumer granted from `keep`.
|
||||
- §642 **A binding's `objects` is a state's field, and the sent store an entry.** (§399, §513) In mesh-sdk's
|
||||
membership, `objects` is a field of a state's binding, and the sent store an entry of its own, local name
|
||||
`sent-objects`, as §399 has it; §513's "fields of `membership.BusBinding`" reads so. [mesh-sdk 10.] How it is
|
||||
checked: unit (membership).
|
||||
- §643 **Put and get sessions are Go only in G.** (§518, §57) A TypeScript bundle's value over the per-message limit
|
||||
fails at once, naming the limit, in G. [mesh-sdk 12.] How it is checked: unit (mesh-sdk TypeScript), the refusal.
|
||||
- §644 **`Hearing` is told which events are defined.** (§474) `catalogue.Hearing` takes, beside the manifests, a
|
||||
function the grants part passes, answering whether an event is defined by a seat definition of the seat list or is
|
||||
one of mesh-sdk's finding events. [mesh-sdk 13.] How it is checked: unit (`Hearing`).
|
||||
|
||||
## mesh-delivery's parts
|
||||
|
||||
- §645 **The checker's seat definition is in mesh-delivery's contracts.** (§528) §528's "the checker's seat
|
||||
definition in mesh-contracts" is corrected to mesh-delivery's contracts module, where `tag-wanted` is removed.
|
||||
[mesh-delivery DQ-1, mesh-contracts DQ-13.]
|
||||
- §646 **The runner's bodies name `fingerprint`.** (§585) The runner's `step-ended`, pipeline and wait bodies name the
|
||||
build-input fingerprint `fingerprint`, not `fp`, as §585 reads it. [mesh-delivery DQ-2.] How it is checked: unit
|
||||
(runner's contract).
|
||||
- §647 **The runner times an ask from `ask-taken`.** (§535, §602) §535's five minutes run from the ask until the build
|
||||
seat holder's `ask-taken` (§602), which the runner consumes; `step-started` is the runner's own. [mesh-delivery
|
||||
DQ-3.] How it is checked: whole (runner), an ask no holder takes ends its pipeline after the setting.
|
||||
- §648 **A `take-in.taken` entry carries the pull request.** (§476, §542) An entry is `{module, commit, fingerprint,
|
||||
repository, pull}`, the last two from a `merged` message and absent for a `pushed` one. [mesh-delivery DQ-5.] How it
|
||||
is checked: unit (indexer's contract).
|
||||
- §649 **`ParseStepKey` answers the asker.** (§484) `buildstep.ParseStepKey(key)` answers `(asker, node, id, err)`,
|
||||
`asker` empty for main's form. [mesh-delivery DQ-8.] How it is checked: unit, both forms.
|
||||
- §650 **The `mesh-builds` client's name.** (§586) It is `contracts/go/clients/meshbuildsclient`, with `Reserved`.
|
||||
[mesh-delivery DQ-9.]
|
||||
- §651 **The checker's first tier of the change's own acting parts is not built.** (§528, §66, §279 to §290) The
|
||||
checker of G's heads never makes G's own check (ADR 0667 §2), so the first tier of §279 to §290 (CK6 to CK8) is not
|
||||
built in the checker; the runner's steps check a change's acting parts from G's delivery. [mesh-delivery DQ-10.]
|
||||
- §652 **`pins-stated` is kept as `pinned` is.** (§579) The seat `mesh-delivery`'s definition gives `pins-stated` the
|
||||
`keep` of `pinned`, so the indexer's consumer from the stream's first message hears it. [mesh-delivery DQ-11.]
|
||||
- §653 **The teller's reads of the gate's `asks` go.** (§431, §512, §593) The teller's manifest drops its `reads` of
|
||||
`mesh-delivery-gate.asks`, since the Approve per merge goes. [mesh-delivery DQ-12.]
|
||||
|
||||
## mesh-catalog's parts
|
||||
|
||||
- §654 **The readers of the forge module's old events and tools move to the seat `git`.** (§310, §311, §313, §431;
|
||||
ADR 0659 §1) [mesh-catalog Q-F1.]
|
||||
- **mesh-config** consumes `git`'s `pull-merged` and `pull-closed` in place of `gitea.pull.merged`,
|
||||
`gitea.pull.closed` and `gitea.forge.pull-request.closed`, and invokes `seat:git.pull` in place of
|
||||
`gitea.gitea_get_pull_request`; **the records module** consumes `git.pull-merged`.
|
||||
- **The order.** Main's forge module says its own `pull.merged`, not the seat's events, so each consumer of
|
||||
`git.pull-merged` or `pull-closed` is ordered after the forge module's version that says them, by the edge of kind
|
||||
6 its `consumes` gives (§558); its durable consumer is made from the stream's first message, so no merge said
|
||||
before it began is missed, and no catch-up through `git.pull` is needed.
|
||||
- **mesh-issues** invokes `seat:git.pull` and `seat:git.contains`, and reads a pull request's steps from the
|
||||
runner, `seat:mesh-pipeline-runner.list` and `seat:mesh-pipeline-runner.show` in its `invokes`: `list` given the
|
||||
pull request's head commit answers that commit's pipelines, one per module of the repository it builds (G-6),
|
||||
and `show` of each answers it whole with each step's result, the review step's among them (ADR 0666 §22). For a
|
||||
repository of several modules mesh-issues shows one row per module's pipeline, each with its steps; a commit
|
||||
`list` answers with no pipeline is shown as "no run". The forge's own
|
||||
reviews are read nowhere: G keeps no review record (ADR 0659 §1). Its read of `seat:mesh-delivery.checks` and
|
||||
`gitea_pull_reviews` go.
|
||||
|
||||
How it is checked: unit (manifest validator), each manifest of G's heads names none of the old events or tools;
|
||||
unit (mesh-config, records), each acts on a `git.pull-merged` fixture; unit (mesh-issues), a commit with no
|
||||
pipeline shown as "no run", and one of a repository of two modules shown as two rows.
|
||||
- §655 **The take and restore of one stream is shared code in mesh-sdk.** (§554, §464; ADR 0672 §1) The code that
|
||||
takes and restores one stream's snapshot moves whole from the bus module's `bussnap` into mesh-sdk's `go/bussnap`,
|
||||
imported by the bus module (its snapshot before `recreate` and `drop`), by the recovery module (its points and
|
||||
restores), and by any part that takes the nightly dump's stream snapshots; no module imports another module's
|
||||
package, and no third copy is kept. The edge of kind 1 from mesh-sdk's package (§47) orders its publication before
|
||||
the bus module's and the recovery module's builds. [mesh-catalog Q-R1.] Why: code two modules share lives in
|
||||
mesh-sdk (§464's reason). How it is checked: the code review finds one copy.
|
||||
- §656 **The hand tools' acts are verbs of the seat `mesh-recovery`.** (§554, §566; ADR 0672 §7) Each hand tool's act
|
||||
is a verb of the seat `mesh-recovery`, `by-caller`, answered by the process: `take`, `restore`, `retry`, `aside`,
|
||||
`discard-aside`, `restore-aside`, `cancel` and `expire-points`, each of class `act` (§610), beside `snapshot-kept`,
|
||||
`restore-kept` and `kept-points`, all defined in mesh-contracts. The tools bundle's `recovery_*` tools call them,
|
||||
named in its `invokes`, and hold no grant of their own; the bus proves the tool runner's account, as ADR 0672 §7
|
||||
has the bundle call the seat's verbs. [mesh-catalog Q-R2.] Why: ADR 0672 §7 (review: seams and tools 2). How it is
|
||||
checked: unit (mesh-recovery's contract), each verb read; whole (recovery module), each hand tool acts through its
|
||||
verb. Each verb's fields and answers, and the `asker` each hand verb takes and logs beside the account the bus
|
||||
proved, are §682's.
|
||||
- §657 **The recovery module's health check is a tool.** (§554, §468, §561) The recovery module serves the read tool
|
||||
`recovery_kept_points`, answering what `kept-points` answers, its bundle naming `seat:mesh-recovery.kept-points` in
|
||||
`invokes`, and its declared health check names it. The check fails while an aside's record ends at `removed` and no
|
||||
running act holds it, as §554 has it, which `recovery_kept_points` answers. §554's "calls `kept-points`" is
|
||||
corrected so. [mesh-catalog Q-R3.] How it is checked: unit (manifest validator), the check names a served tool;
|
||||
whole (recovery module), an unrestored aside fails the check.
|
||||
- §658 **A module new in G starts with the tests its lines name.** (§419) A module new in G counts from none: its
|
||||
tests are the checks its design lines name (for the recovery module, its one whole test and the unit rows of its
|
||||
lines), within the per-module budget. [mesh-catalog Q-R4.]
|
||||
|
||||
## mesh-contracts' seat contracts
|
||||
|
||||
- §659 **`snapshot-kept` and `restore-kept` are of class `act`.** (§554, §610) Class `act`, taking its default, as
|
||||
every verb does (§610). [mesh-contracts DQ-5.]
|
||||
- §660 **A finding's key and value.** (§461, §437, §439) The state `findings` keeps the key `<scope>.<id>.<kind>` and the
|
||||
value `#/bodies/Finding`. [mesh-contracts DQ-7.]
|
||||
- §661 **`list` shows the caps.** (§547) The seat `mesh-broker`'s `list` answer gains `caps {sum-mib,
|
||||
account-limit-mib, server-limit-mib}`, an absent limit meaning unlimited. [mesh-contracts DQ-10.]
|
||||
- §662 **A subject function with parts can fail.** (§523) A generated subject function with `<part>` tokens answers
|
||||
`(string, error)` in Go and throws in TypeScript for an empty part, `.` or `..`. [mesh-contracts DQ-11.]
|
||||
- §663 **The condition keeper's sources are the bus module's `created` events, and it follows a moved stream.** (§515,
|
||||
§529, §611, §612) The keeper takes a source when it hears the seat `mesh-broker`'s `created` of a part's
|
||||
`finding-events` entry, named in its `consumes`, and a foundation part's old finding stream from the catalog half
|
||||
(§614); at its start it reads both by `list` and the catalog half. When a part's catalog graph entry no longer
|
||||
declares the stream the keeper follows for it (its findings moved to a template, §611, or to an old section, §612),
|
||||
the keeper drops that source (the grants part its consumer, §690), keeps the part's open conditions, and asks `findings-asked` on the
|
||||
new stream once it is a source; the part's statement there settles them (§384). §515 is corrected so.
|
||||
[mesh-contracts DQ-12; D-7's COND9.] Why: the grants part makes the consumers (§386), and no binding names one; a
|
||||
moved stream must neither raise twice nor leave a condition open (review: health and events 1). How it is
|
||||
checked: whole (keeper, bus module), a part declaring finding events is asked once its entry is `created`; a part
|
||||
whose findings move keeps its condition raised once and clears it from the new stream.
|
||||
- §664 **`backed-up` needs no contract change.** (§563) The backup holder's answer carries a put-off item's `act` and
|
||||
since when in its own fields. [mesh-contracts DQ-14.]
|
||||
- §665 **`attempt-changed` is keyed by condition and attempt.** (§541, §73) The subject is
|
||||
`mesh.seat.mesh-healers.event.attempt-changed.<condition>.<attempt>`, each token escaped as a key's name is (§73),
|
||||
kept last per subject, 7 days and 16 MiB (§541), so two attempts on one condition both stay. At about 0.5 KiB a
|
||||
message, 16 MiB holds about 30,000 attempts, far more than seven days have seen (an estimate). The stream discards
|
||||
nothing new: a publish past its cap fails, and the healers say it in their health and write the finding
|
||||
`attempt-unsaid` (scope the healers, id the condition), cleared by that subject's next publish taken. §541's
|
||||
subject is corrected so. [mesh-contracts DQ-15.] How it is checked: unit (healers' contract), two attempts of one
|
||||
condition kept; whole (healers), a full stream raises `attempt-unsaid`.
|
||||
## Met in develop
|
||||
|
||||
- §666 **`nats_snapshot_get`.** Replaced whole by §681 (G-18): the keeper serves the seat `mesh-broker`'s
|
||||
`snapshot-get` and `snapshot-restore`, the bundle lists by `nats_snapshots`, and a protected stream's snapshot is
|
||||
never sent (the operator, O-7). [D-1.]
|
||||
- §667 **Where a trusted change and the seed wait for their warrant.** (§3, §8; ADR 0666 §2, §12; ADR 0259 §6; ADR
|
||||
0286 §10) [D-2, D-3.]
|
||||
- **Kept.** The indexer keeps each change of `register`, `policy`, `watch` and `unwatch` awaiting its warrant in its
|
||||
old `state` section `proposals`, one key `<verb>.<module>` (`<verb>.<module>.<branch>` for `watch` and
|
||||
`unwatch`, each token escaped as a key's name is, §73), holding the change whole, its `digest` (`sha256:` of the
|
||||
change in RFC 8785's canonical form), the current ask's id, its caller and when it was asked; the seed's key is
|
||||
`seed`. Capped at 4 MiB.
|
||||
- **Asked.** Through `operatorchannelclient`, kind `ask`, level `approve`, its `about` the key, so a newer change of
|
||||
the same key replaces the open ask (ADR 0666 §12); the ask names every value the change moves (§3) and carries the
|
||||
change's `digest`, so the warrant approves those exact values (ADR 0286 §10).
|
||||
- **Heard.** The indexer's manifest `consumes` `operator-channel.decided`, on
|
||||
`mesh.seat.operator-channel.event.decided.<the indexer's module>`, through a durable consumer. A warrant is
|
||||
applied only when its ask's id is the key's current ask and its digest the key's, and when the router's own
|
||||
record of that ask, read under the indexer's module's name on §700's path, holds it with `Warrant.For` (ADR 0286 §26, §27, §36),
|
||||
never the decided event's body alone: it writes the version by
|
||||
compare-and-set and ends the key `applied`. A warrant of a replaced ask writes nothing, said in its journal. A
|
||||
Decline, an expiry or a refusal ends the key with that outcome; the seed's keeps `seed-waits` (§8).
|
||||
- **Ended keys stay readable.** An ended key keeps its outcome, its ask and when, until the next change of the same
|
||||
key replaces it.
|
||||
- **A restart.** At its start the indexer reads each open key and the router's record of its ask on §700's path: a decided one is applied or ended as above, an open one waited on, and one the router no longer holds ended
|
||||
`lost`, said.
|
||||
- **Its tools.** `indexer_proposals` (class `read`) lists the open and ended keys with their asks and outcomes;
|
||||
`indexer_proposal_drop` (`key`, `why`; class `act`, in the hand-act log) cancels the key's open ask and ends it
|
||||
`dropped`; `indexer_seed_again` raises the seed's ask again from its key (§8).
|
||||
|
||||
How it is checked: whole (indexer), a `policy` applied on its warrant after a restart while open; a decline ends
|
||||
the key `declined`, read by `indexer_proposals`; two `policy` changes of one module leave one ask, and the first
|
||||
ask's late warrant writes nothing; a dropped key's ask is cancelled.
|
||||
- §668 **`manifest-accepted`'s `problems`.** (§500; ADR 0579 §1) The event gains `problems`, a list of strings
|
||||
(§692): each problem the indexer's re-judgement finds in a manifest it keeps accepted because its digest is
|
||||
unchanged (ADR 0579 §1), the same items its verdict writes as warnings; an empty list when none. It is defined in
|
||||
the seat `mesh-catalog`'s definition in mesh-delivery's contracts, a row of its contracts change. [D-4.] How it is
|
||||
checked: unit (indexer), a manifest a stricter validator faults is accepted with its problems, and a mended one
|
||||
says none.
|
||||
- §669 **A have whose parts differ says the phase `held`.** (§524) §524 holds as written: the phase `held`, its detail
|
||||
opening `parts-differ:` and naming each digest that differs, and `wait-held` with the reason `parts-differ`; the
|
||||
build as made (H-8) stands. [D-6.]
|
||||
|
||||
## The builder's forms
|
||||
|
||||
- §670 **The key codec of the catalog half is a package of its own, which reaches no bus.** (§614) The codec of a
|
||||
catalog key's parts (`KeyPart`, `ParseKeyPart`) is mesh-delivery's contracts package
|
||||
`contracts/go/clients/meshcatalogkey`, which imports neither mesh-sdk's `views` nor the bus client;
|
||||
`meshcatalogclient` keeps the wiring of §614 and imports it. The builder's `builds` package imports
|
||||
`meshcatalogkey` alone, so it reaches no bus. [D-8.] Why: a package that only reads and writes keys needs no bus,
|
||||
and the builder's `builds` package is held to that (its test `TestThePackageReachesNoBus`, red on main too). How it
|
||||
is checked: that test, and one in `meshcatalogkey` that it imports no bus.
|
||||
- §671 **`build.ended` names the node that keeps a kept build in `kept-on`.** (§271, §379, §526) For a kept build the
|
||||
builder's `build.ended` carries `kept-on`, the build node that keeps it, once for the build, beside `made`, the name
|
||||
§526 gives the want's field; every artifact of the build is kept on that node, and each artifact in `made` keeps its
|
||||
digest as now. A build not kept carries no `kept-on`. [D-9.] How it is checked: unit (builder), a kept build's
|
||||
`build.ended` names its node, and the assigner writes it into the want (§526).
|
||||
- §672 **The builder's toolchain wait.** (§267, §276, §472, §557, §629, §633) The builder holds an entry while its
|
||||
fingerprint names a toolchain version of its language other than the one the builder's newest `toolchain-agreed`
|
||||
of that language names (§267).
|
||||
- **Its state.** Each wait is kept in the builder's old `state` section `waits`, one key `<module>.<fingerprint>`
|
||||
(each token escaped as a key's name is, §73), capped at 4 MiB, deleted when the wait ends.
|
||||
- **Its events.** It says `wait-held` and `wait-ended` on its own old `streams` section `waits`, on
|
||||
`mesh.stream.mesh-delivery-builder.event.wait-held.<module>.<fingerprint>` and `.wait-ended.<module>.<fingerprint>`,
|
||||
kept last per subject, 7 days, 16 MiB; the body the fields of §629's wait with `step` `build` (§702), `on`
|
||||
`toolchain:<language>@<version>`, `module` and `fingerprint`, and `wait-ended` its `how` (`ran` when a
|
||||
`toolchain-agreed` names that version, `released` by `builder_release`). At its start it says `wait-held` again
|
||||
for every key of `waits`. G-4's table reads so.
|
||||
- **Its bound.** 60 minutes, its setting `toolchain-wait-minutes`; past it the builder writes the finding
|
||||
`toolchain-waits` (scope `build`, id `<module>.<fingerprint>`, §702), cleared when the wait ends.
|
||||
- **Its tools.** `builder_waits` reads the state, and `builder_release` builds the entry with the running build
|
||||
module, written in the hand-act log (§276).
|
||||
|
||||
[D-10.] How it is checked: whole (builder, with small settings), an entry held until `toolchain-agreed` names its
|
||||
version, then built and ended `ran`; one released by hand ended `released`; one past its bound raises
|
||||
`toolchain-waits`; a restart says the open wait again.
|
||||
- §673 **`package-published`'s one `dependencies` list holds both kinds.** (§374, §466) The contract's `dependencies`
|
||||
in each artifact of `package-published` is §466's `resolved` and `toolchain` in one list: each item `{kind,
|
||||
part, version, digest}`, kind 1 for each `resolved` package (its part the package's artifact reference, §504) and
|
||||
one item of kind 3 for the toolchain (its part the agreed toolchain's part, its digest the building node's, §466).
|
||||
The indexer writes §374's entries of kinds 1 and 3 from the items of those kinds. [D-11.] How it is checked: unit
|
||||
(`mesh-builds`' contract), a list of both kinds read; whole (indexer), the release's kind-1 and kind-3 entries.
|
||||
- §674 **`<module>_first_views` says the part's own build.** (§205, §424, §519, §618) Both answers of a part's first
|
||||
views, the bundle reader's and the process's status tool's, gain `own-build` beside their list: the fingerprint the
|
||||
node-engine composed into the part's environment (§519), or `unknown` when it carries none (§618). A process writes
|
||||
it at its start at the key `own-build` of its state `first-views`, which its tools bundle's status tool reads.
|
||||
[D-13.] How it is checked: unit (`go/views`), both answers with a fingerprint and with none.
|
||||
|
||||
## The indexer's entries
|
||||
|
||||
- §675 **`entry` finds an entry by its fingerprint, and keeps one while a want or a running build names it.** (§443,
|
||||
§199, §556, §614) [D-14.]
|
||||
- **By fingerprint.** The indexer keeps, in its old `state` section `entry-index`, one key per build-input
|
||||
fingerprint, holding the digest of its entry's object (§179, §443); it writes the key when it first stores the
|
||||
entry and deletes it when it removes the object. `entry` reads the key and then the object. Capped at 8 MiB (an
|
||||
estimate: about 0.1 KiB a key for some tens of thousands of builds).
|
||||
- **Its callers.** `entry` is of class `read`, so it takes `read`'s default (§391, §610), its invokers among them
|
||||
(§614).
|
||||
- **Kept while named.** The indexer consumes the node-engine seat's `running` and `mesh-wants`' `wanted`, read whole
|
||||
at its start as §556 has a stream kept last per subject and then followed by a durable consumer, both in its
|
||||
`consumes`. It keeps in its old `state` section `named` one key per fingerprint with the set of
|
||||
`<node>.<module>` whose want or running build names it, written by compare-and-set, capped at 4 MiB. An entry
|
||||
object is removed only at the first revision after no kept revision lists it and its `named` set is empty; a
|
||||
`wanted` or `running` that moves on removes its `<node>.<module>` from the old fingerprint's set.
|
||||
- **A node that leaves.** A `running` stays on its stream for ever, so a member a `running` put in the set leaves it
|
||||
also when the want of that node and module is removed (`wanted` saying it removed: the node left or the module was
|
||||
unplaced), whatever that node last said running.
|
||||
- **Its tools.** `indexer_entries` (class `read`) reads a fingerprint's key, its object's digest and its `named` set;
|
||||
`indexer_named_drop` (`fingerprint`, `member`, `why`; class `act`, in the hand-act log) removes one member.
|
||||
|
||||
How it is checked: unit (indexer), an entry no revision lists is answered by `entry` while a want names it and
|
||||
removed after the want moves on; a member whose want is removed leaves the set; `indexer_named_drop` removes one;
|
||||
a restart reads `named` whole before it removes anything.
|
||||
- §676 **Each entry carries its artifacts' package names.** (§491, §478, §504, §412) At take-in the indexer reads each
|
||||
artifact's package name from the revision's own files at the artifact's path (the `module` of its `go.mod` for a Go
|
||||
package, the `name` of its `package.json` for an npm package, the manifest's image name for an image) and writes it
|
||||
into the entry's artifact as `package`, `{ecosystem, name}`. The manifest validator judges §491 from the entries'
|
||||
`package` fields, and the builder's mapping (§478, §504) reads them there. [D-15.] How it is checked: unit
|
||||
(indexer), a Go and an npm artifact's names read into their entries; unit (manifest validator), two entries
|
||||
carrying one name refused, naming both.
|
||||
@@ -0,0 +1,536 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0676"
|
||||
- "ADR 0674"
|
||||
- "ADR 0673"
|
||||
- "ADR 0672"
|
||||
- "ADR 0668"
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0660"
|
||||
- "ADR 0656"
|
||||
- "ADR 0626"
|
||||
- "ADR 0619"
|
||||
- "ADR 0618"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0579"
|
||||
- "ADR 0573"
|
||||
- "ADR 0448"
|
||||
- "ADR 0436"
|
||||
- "ADR 0429"
|
||||
- "ADR 0363"
|
||||
- "ADR 0286"
|
||||
- "ADR 0283"
|
||||
- "ADR 0259"
|
||||
---
|
||||
|
||||
# 47, milestone G: the fix round of batches 6 and 7
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§677 to §690 come from the fix rounds of the review by architectural domain of §587 to §676 (lead-47's inputs of
|
||||
2026-11-11): the approval mark once no verb names its callers (the operator's answer of about 16:10 CET, §610); the
|
||||
keeper's snapshots, with the operator's answer O-7 of 20:22 CET (§681); the recovery module's verbs, the entries the
|
||||
node-engine keeps and the grants part's first composition (§682 to §684); the gate before a marked verb's running
|
||||
(§677, the operator's O-8); develop's D-16 to D-49, of internal form only; and the operator's O-9 (§705, written whole in §706). The rounds' other fixes are written in the lines they correct.*
|
||||
|
||||
## The approval mark, with no named callers
|
||||
|
||||
- §677 **A marked verb a module's process serves is one call with a gate before its running, asked, checked and
|
||||
claimed by its holder alone.** (§610; ADR 0676 §1 to §7; ADR 0286 §6, §10 to §13, §24, §26 to §31, §35, §36,
|
||||
§38, §43; ADR 0259 §6) Once no verb names its callers (§610), a module's process may call a marked verb (ADR 0286 §1, §2)
|
||||
directly. Design 46 7-approval-mark's gate wraps only the handlers the tool runner serves (its §20); a marked verb a
|
||||
module's process serves is judged by that process, as ADR 0286 §43 has the controller judge its own verbs, whoever
|
||||
calls it. The operator, 2026-11-11 about 20:40 CET at lead-47's terminal (O-8): "the tool call should be the same,
|
||||
just with a 'gate' between the call and the execution"; so a module's call of a marked verb is one call:
|
||||
- **Only Go holds the gate, and every marked verb is `by-caller`.** The gate is mesh-sdk's Go seat server's; the
|
||||
manifest validator refuses a TypeScript process that serves a marked verb, and the seat generator makes every
|
||||
marked verb `by-caller` (§697).
|
||||
- **Refused before anything is kept.** A call whose arguments hold anything shaped like a secret or pass 16 KiB is
|
||||
refused at once, asking nothing and keeping nothing (ADR 0286 §12; ADR 0676 §1).
|
||||
- **The holder keeps the call and asks, under its own module's name.** Otherwise mesh-sdk's seat server makes the
|
||||
act as ADR 0286 §10 and §11 make one (the address as called, the holder's own node and build, the seat and verb,
|
||||
every argument, digested with `operatorchannelclient.ActDigest`), and finds or raises its ask through the key
|
||||
`act.<digest>`: it writes that key with the bus's create, naming a new ask, and raises the ask through
|
||||
`operatorchannelclient` under the holder's module's name at level `approve` (ADR 0286 §13, §15 to §23) only when
|
||||
the create succeeds; an instance whose create fails reads the key and takes that ask, so two instances raise one
|
||||
ask; a key whose ask has ended is replaced by compare-and-set on its revision. When the call says `wait: held`
|
||||
(below), the holder keeps it as a waiting call of its own, `call.<id>`, `<id>` 128 random bits, holding the call
|
||||
whole, the caller the bus proved and its ask (ADR 0286 §24; ADR 0676 §2), and answers at once `{asked: <ask id>, call: <id>}`.
|
||||
- **A second call of the same act.** Every call is kept as its own `call.<id>`, whoever makes it; one made while
|
||||
that act's ask is open takes the same ask (ADR 0286 §13, §23, §38; ADR 0676 §4) and its answer names that ask and its own
|
||||
call. On the Approve the oldest waiting call of the act runs; each other waiting call of the act is asked anew at once,
|
||||
as a new ask of that act (ADR 0286 §33: a second run takes a second approval), and its end comes when its own ask
|
||||
ends.
|
||||
- **On the Approve it runs that same call.** When the holder hears its own `operator-channel.decided` of the ask, it
|
||||
reads the router's own record of the ask under its module's name, on the one path §700 names, never the decided
|
||||
event's body nor the caller's word (ADR 0286 §6, §26, §36; ADR 0676 §3), holds the warrant to it with `Warrant.For`
|
||||
and `Option.Performs` against the waiting call (ADR 0259 §6; ADR 0286 §27, §28), claims it once (below), runs the
|
||||
waiting call, once, and
|
||||
records the run in the hand-act log of the seat `mesh-hand-acts` as a kept call until it is taken (ADR 0286 §35,
|
||||
ADR 0668 §1). The waiting call runs only within 30 minutes of the choice; heard later (the holder away meanwhile),
|
||||
it ends `expired` and runs nothing (ADR 0286 §29). After a Decline the same act is not asked again for 10
|
||||
minutes: a call of that act within them ends `declined` at once, with no ask (ADR 0286 §38; ADR 0676 §4).
|
||||
- **The caller hears the outcome on its own call's subject.** The holder says the call's end on its seat's event
|
||||
`gated-ended`, subject `mesh.seat.<seat>.event.gated-ended.<caller>.<call>`, `<caller>` the account the bus proved,
|
||||
escaped as a key's name is (§73): `{call, ask, act, verb, outcome, answer, error}`, `outcome` one of `ran` (with
|
||||
the verb's `answer`, and in its own field `approval` the line naming the approval, ADR 0286 §34), `declined`, `expired` and `refused` (with the router's or the check's words); a decline or
|
||||
an expiry runs nothing and says so (ADR 0676 §2). Kept last per subject, one day, and no argument is in it: a caller down for
|
||||
longer than a day loses that outcome, and reads the holder's `approvals` verb (below) for it. Every seat
|
||||
definition with a marked verb gains the event through the seat generator; the grants part grants hearing
|
||||
`gated-ended.<caller>.>` to that caller alone (ADR 0286 §6), from its `invokes` of the marked verb by name, as it composes a
|
||||
`by-caller` verb. mesh-sdk's seat client, in `go/seats` beside the seat server, sends `wait: held` on every call
|
||||
of a marked verb, waits as a kept wait (ADR 0668 §1) on its own `<call>` alone and reads it at its start, so a
|
||||
restart of either side loses no outcome; it makes no second call. A module may not mark its call of a marked
|
||||
verb safe to repeat (ADR 0668 §2): only the wait on `gated-ended` is kept. The calling module holds no warrant,
|
||||
and the holder asks once per call; `mesh-delivery.release` stays unmarked (ADR 0286 §46), so ADR 0286 §47's
|
||||
question is closed (ADR 0676 §7).
|
||||
- **A person through the tool runner keeps ADR 0286 §37's path for now.** An MCP call cannot be held for an ask's
|
||||
60 minutes (O-8; ADR 0676 §5); this "for now" is tracked by issue 417. A call that does not say `wait: held` (the tool runner sends none) is answered with ADR 0286
|
||||
§37's refusal naming the ask and keeps no call, its ask found and written through `act.<digest>` as above, so the
|
||||
same call made again after the Approve finds that ask, is checked as above, claimed and run. `wait` is no
|
||||
boundary: either path runs the act only on the operator's warrant for that act, claimed once, so a module that
|
||||
leaves it out only takes the person's path.
|
||||
- **The holder claims, once, in its own state.** The holder's template state `approvals` (a `requires` entry, 48
|
||||
hours to live, 64 MiB) keeps `call.<id>`, `act.<digest>` and `claimed.<ask id>`, the claim, written with the
|
||||
bus's create, which refuses a key that exists, so a warrant runs one call, on one instance or two (ADR 0286 §30,
|
||||
§31; ADR 0676 §3). At its start the holder reads every waiting call and acts on each ask already decided as above. A waiting
|
||||
call past its ask's 60 minutes with no decided heard raises the holder's finding `gate-unanswered` (scope the holder,
|
||||
id the call), cleared when its end is said. The manifest validator refuses a module that serves a marked verb from
|
||||
a process and declares no such entry. A state that cannot be read or written refuses every marked call of that
|
||||
holder, said as ADR 0286 §40 has it.
|
||||
- **Its read.** Every seat with a marked verb gains the verb `approvals` (class `read`, its default), served by the
|
||||
seat server: the waiting calls with their caller, ask and state, the `act.` keys and the claims, never an argument
|
||||
of a call it was not asked by that call's caller.
|
||||
- **A holder that does not check is never reached.** The grants part composes, for a module's process that serves
|
||||
a marked verb, the subscription of that verb's subjects only when the catalog graph entry of the build its want
|
||||
names (heard by `wanted`, §344) declares that `approvals` entry, which only a build of G's seat server passes the
|
||||
validator with, so a G holder is granted before it subscribes; otherwise it composes the subscribe deny design 46
|
||||
7-approval-mark §6 composes for a runner that does not state `approval-mark/1`, named in `withheld` with "its
|
||||
holder checks no warrant". So a holder of before G serves none of its marked verbs until its G version runs, and
|
||||
nothing runs unchecked. This changes ADR 0286 §7 and §8 for a process holder (ADR 0676 §6).
|
||||
|
||||
Why: the operator's answers O-6 and O-8; the holder alone can read the router's record of an ask made under its
|
||||
own name (review rounds 2 and 3). How it is checked, as cases of the seat server's one whole test (a fixture marked
|
||||
verb, its holder and a module's process): one call answered `asked` and run once on the Approve, its outcome heard
|
||||
on `gated-ended` by that caller and no other; a repeated identical call kept as a second call, asked anew after the
|
||||
first runs, each waking on its own `<call>`; two callers of one act, one ask, one run, the second asked anew; two
|
||||
instances of the holder raising one ask and one claim, a race of two at `claimed.<id>` running once; a decline and
|
||||
an expiry run nothing; a restart of the holder between the ask and the Approve runs the waiting call once; a warrant
|
||||
replayed runs nothing; a call within 10 minutes of a Decline ended `declined` unasked; a waiting call heard 31
|
||||
minutes after its choice ended `expired`; a secret-shaped argument refused unkept; a call with no `wait` refused with the ask and run
|
||||
on the same call made again; a decided event whose body was altered runs nothing, the holder reading the router's
|
||||
record; a warrant whose act is not the waiting call's runs nothing; the run found in the hand-act log; the `ran`
|
||||
end read with its `answer` and its `approval` field, and the `refused` end with the router's or the check's words;
|
||||
the outcome gone from `gated-ended` after one day; a caller holding a warrant of its own for an ask of its own still
|
||||
asked (ADR 0676 §7); unit (seat client), the module's call client never marks its call safe to repeat; unit
|
||||
(manifest validator), a module serving a marked verb from a process with no `approvals` entry refused; unit (grants
|
||||
part), a holder whose wanted entry declares no `approvals` composed with the deny and named in `withheld`, one that
|
||||
declares it granted before it runs.
|
||||
## Develop's D-16 to D-18
|
||||
|
||||
- §678 **The forms of a finding's `owner` and `waiting-on`.** (§451, §540, §580) Each is one of three forms, written as
|
||||
patterns in the finding layout's definition: a session `^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9-]*$` (who@node), a seat's
|
||||
name `^[a-z][a-z0-9-]*$`, or a commit's author `^forge:[A-Za-z0-9._-]+$`. mesh-sdk's finding writer (§580) refuses any
|
||||
other, naming the field; the contract's fixtures read each form. [D-16.] How it is checked: unit (writer), each form
|
||||
written and another refused.
|
||||
- §679 **`reserved`'s answers.** (§582, §586) `reserved` answers `{module, version, artifacts, released}`, `released` a
|
||||
boolean (as §530's `none-yet`), or `{error}`, a string; `module`, `version` and `artifacts` are required only without
|
||||
`error`. [D-17.] How it is checked: unit (`mesh-builds`' contract), the three answers read.
|
||||
- §680 **Where the seat generator and the build modules' conformance kit live.** (§464, §495) §495's "the seat
|
||||
generator is imported by no running module" reads "by no running module but through `steprun`'s regenerate and
|
||||
compare". The build modules' shared conformance kit is `sdk/steprun/conformance` in the delivery SDK, which
|
||||
mesh-catalog's build modules and their checks import. [D-18.] How it is checked: the code review.
|
||||
|
||||
## Round 2 of the review: the keeper's snapshots, the recovery module's verbs, the entries the node-engine keeps
|
||||
|
||||
- §681 **The keeper's snapshot is listed by a tool, sent by the keeper, and never sent when it holds a protected
|
||||
stream.** (§554, §567, §610; ADR 0675; ADR 0626 §1, §3, §4; ADR 0436 §3, §5; ADR 0672 §1, §8; ADR 0667 §12) This
|
||||
replaces §666 whole. [D-1; review rounds 2 and 3.]
|
||||
- **When it is taken.** The bus module's keeper, taking the snapshot before a `drop` or `recreate` (ADR 0595 §5),
|
||||
writes into that request's record in its state `requests` the snapshot's `path` (under its snapshots directory),
|
||||
`size`, `digest` (`sha256:` of its bytes) and `protected`, true when the entry has a stream ADR 0436 §3 lets no
|
||||
other account hear (the mail passwords' among them), before it touches the live data. A snapshot it cannot write
|
||||
whole and record so, or one that would pass the directory's cap `snapshots-cap` (a setting, 20 GiB unless set),
|
||||
makes the `drop` or `recreate` refuse, naming why, before anything is removed. Only the snapshots it recorded
|
||||
count against the cap; one of before G does not.
|
||||
- **Its fill.** Past 80 % of the cap the keeper writes the finding `snapshots-filling` (scope the bus module, id
|
||||
`snapshots`), naming the fill and the cap, and clears it once the fill is back under 80 %, each judged at every
|
||||
snapshot taken and removed and at its start.
|
||||
- **Who deletes it.** The keeper alone, a snapshot and its record kept `snapshot-keep-days` (90 unless set, ADR
|
||||
0667 §12) after its request: it removes those older at its start and at each new request, each removal written in
|
||||
its journal; nothing waits on a timer, so one may outlive its days until then, which `nats_snapshots` says as
|
||||
`expired`. By hand, the seat `mesh-broker`'s verb `snapshot-remove` (`request`, or `expired: true` for every
|
||||
expired one; `why`; class `act`, its default; `by-caller`), written in the hand-act log first, removes one now; a
|
||||
snapshot of before G it removes only by `request`.
|
||||
- **Listed.** The tools bundle's `nats_snapshots` (class `read`) reads the keeper's state `requests`, as ADR 0626
|
||||
§4 has the bundle read it, and lists each snapshot: its request, module, local name, stream, `size`, `digest`,
|
||||
`at`, `protected`, `expired` and `recorded`.
|
||||
- **Sent by the keeper.** The keeper's process serves the seat `mesh-broker`'s verb `snapshot-get`, `by-caller`,
|
||||
class `read`, taking its default (§610). It takes `request` and `call`, 128 random bits the caller chooses, and
|
||||
answers `{request, module, local, stream, size, digest, at, chunks}`, then sends the file in chunks of at most
|
||||
512 KiB on `mesh.seat.mesh-broker.chunk.snapshot-get.<call>.<n>`, each acknowledged by the caller on
|
||||
`mesh.seat.mesh-broker.ack.snapshot-get.<call>.<n>` within 30 seconds before the next, a missed one ending the
|
||||
send, said in its journal. The verb says so in its seat definition by the field `chunks: true`, a word of the
|
||||
definition format that §494's second reader release reads (§621, §697), costing no rebuild beyond that release's.
|
||||
From it the grants part composes the holder's publish of `mesh.seat.<seat>.chunk.<verb>.>` and subscribe of
|
||||
`.ack.<verb>.>`, and the reverse for an account that names the verb by name in `invokes`; a `*` entry grants no
|
||||
chunk subject. Since every such account may ask any snapshot it could hear by the read default, hearing another's
|
||||
chunks gains it nothing it could not read with the read default, and a protected snapshot's chunks are never
|
||||
published. A caller granted no chunk subjects (a session through the tool runner) is answered
|
||||
the fields alone, with `chunks` 0, and no file is sent. The verb answers `error`: `snapshot damaged`, naming the
|
||||
request, when the file's digest differs from the recorded one; `snapshot missing` when no file is there;
|
||||
`snapshot protected` when `protected` is true; and `snapshot taken before milestone G` for a request record that
|
||||
holds no `digest`. The read default widens who reads a dropped or recreated module's data beyond its owner (ADR
|
||||
0595's kept data reached only by its owner), for this snapshot alone (ADR 0675).
|
||||
- **A protected snapshot is restored in place.** The operator, 2026-11-11 20:22 CET at lead-47's terminal (O-7):
|
||||
the stream snapshot the keeper took before a `drop` or `recreate` of a protected stream is never handed out, and
|
||||
is restored only in place by the bus module on the recovery module's request; the recovery module's own asides
|
||||
of restricted streams stay as ADR 0672 §8 and §9 have them. The keeper's process serves `mesh-broker`'s verb
|
||||
`snapshot-restore`, `by-caller`, class `act`, taking its default (§610): it takes `request`, `verify` and `why`,
|
||||
answers at once `accepted` with an act id, `busy` or `error` (as §691's form), checks the file against its
|
||||
recorded digest, and with `verify` true says that and restores nothing; otherwise it refuses `names live` while a
|
||||
live object of the stream's name exists, before anything else, and restores the snapshot's stream under its own
|
||||
name, bounded at 60 minutes. It says the outcome on `mesh.seat.mesh-broker.event.snapshot-restored.<request>.<act>`
|
||||
(`{request, act, outcome, error}`, kept last per subject, 90 days), so a retried restore is told apart from an
|
||||
earlier one. A restore that fails, passes its bound or is cancelled removes the keeper's own partial stream (it
|
||||
held no live data, since `names live` came first) and then says `snapshot-restored` with `outcome` `failed`
|
||||
under that act, before the keeper answers anything more of it; one cut off by the keeper's restart is ended so at
|
||||
its start. `mesh-broker`'s verb `snapshot-restore-cancel` (`act`, `why`; class `act`, its default, `by-caller`)
|
||||
cancels a running restore so and answers `accepted`, or `error` when that act has ended. "Protected" means a
|
||||
stream ADR 0436 §3 restricts. The keeper holds a standing grant of the bus's
|
||||
stream restore subjects and their chunk subjects for the bus names of the streams ADR 0436 §3 restricts, and only
|
||||
those, and uses them only on a `snapshot-restore` call.
|
||||
- **Its use.** `recovery_restore` names a snapshot by `request` (§682). For one that is not protected the recovery
|
||||
module reads it through `snapshot-get` into `incoming/<request>` in its own data directory, refusing before the
|
||||
first chunk when `size` would pass `points-cap` (§554), checks the bytes against `digest` before it takes any aside
|
||||
(§567), restores it itself (§516), and deletes the file when the act ends, done or failed, and at its start any
|
||||
file left there. For a protected one it asks `snapshot-restore` with `verify` true, then sets the live data aside
|
||||
(§516), then asks `snapshot-restore`, and waits on `snapshot-restored` of that request and the act it was answered,
|
||||
bounded at 90 minutes, past the keeper's 60. It puts its aside back under the live names (§516) only on that
|
||||
event's `failed`. At its bound it first asks `snapshot-restore-cancel` and waits on the same event for the
|
||||
cancel's outcome; only when the keeper answers neither, it puts nothing back, ends `failed` with
|
||||
`snapshot-restore unanswered` and writes `aside-unrestored` (§561), which `recovery_restore_aside` (§566) clears,
|
||||
refused while the names are live. Its
|
||||
account (§554) hears `snapshot-restored`, from its `consumes`.
|
||||
- **A snapshot of before G.** A request record with no `path` or `digest` was written by a keeper of before G: it
|
||||
is listed with `recorded` false, `snapshot-get` and `snapshot-restore` refuse it as above, and the keeper of G
|
||||
neither sends nor deletes it by age: it stays where the keeper before G wrote it until `snapshot-remove` names it.
|
||||
- **Its record.** ADR 0675, which changes ADR 0626 §1 and §3, ADR 0436 §3 for the keeper's process, and ADR 0672
|
||||
§1, §8 (its read opened to the read default) and §10 (a protected snapshot restored under the stream's own name by
|
||||
the keeper), keeping ADR 0436 §3 and §5 for every other account.
|
||||
|
||||
Why: the operator's answers O-6 and O-7; the tools bundle runs as the tool runner and cannot read the keeper's
|
||||
directory (review rounds 2 and 3). How it is checked, as cases of the bus module's one whole test and the recovery
|
||||
module's one whole test, adding no test function to either (with small settings): a snapshot of three chunks sent
|
||||
and restored whole; a missed acknowledgement ending the send; `snapshot missing`; a file altered on disk refused
|
||||
`snapshot damaged`; a session through the tool runner answered `chunks` 0; a snapshot removed after
|
||||
`snapshot-keep-days` at the next request; `snapshots-filling` raised past 80 % and cleared under it; a protected
|
||||
snapshot refused by `snapshot-get` and restored in place by `snapshot-restore`, refused `names live` while its live
|
||||
stream exists, a retried restore woken by its own act; no chunk of a protected snapshot ever published; a restore
|
||||
stopped midway leaving no partial stream; the recovery module's bound cancelling first, then restoring the aside; a request record of before G listed `recorded` false and
|
||||
refused; a `drop` past `snapshots-cap` refused before it removes anything; unit (grants part), only the keeper
|
||||
publishes the chunk subjects, a `*` entry gets none, and the keeper alone holds the restore subjects of the
|
||||
restricted streams' names and of no other stream.
|
||||
- §682 **The seat `mesh-recovery`'s hand verbs: their fields, their answers, and who asked.** (§516, §554, §566, §656;
|
||||
ADR 0672 §4 to §6, §12; ADR 0386 §8) [D-19; review round 2, seams and tools 8.] Each hand verb is `by-caller`, of
|
||||
class `act` taking its default (§610), defined in mesh-contracts with fixtures, and takes, beside its own fields,
|
||||
`why` (required) and `asker`, the caller the tool runner proved and recorded for that call (ADR 0386 §8: who@node,
|
||||
or `operator`), which the tools bundle's `recovery_*` tool passes and which a module's process leaves out. `asker` is a stamp,
|
||||
no boundary (as ADR 0626 §4 has it): it is taken from any caller and logged beside the account the bus proved, so
|
||||
a false one is seen. The
|
||||
hand-act log's entry names both the account the bus proved and `asker`: the bus's is the boundary, `asker` names
|
||||
who asked, for the record (as ADR 0626 §4 has the stamp). Each answers at once, as `snapshot-kept` does (§554):
|
||||
one of `accepted` (the act's id), `busy` (the id of the act that runs) or `error` set alone (§691); the result
|
||||
comes on the act's ending event (§554, §560), under its `act` id.
|
||||
- `take` (`module`, `kept`, `fingerprint`): takes a point of that kept data at that fingerprint's form, as
|
||||
`snapshot-kept` does with no `catch-up`; ends with `ended`.
|
||||
- `restore` (`module`, `kept`, and exactly one of `snapshot`, a point's name, or `request`, a keeper's snapshot
|
||||
by its request, §681; `from-backup`, yes or no, only with `snapshot`): restores as `restore-kept` does; ends with
|
||||
`restored`.
|
||||
- `retry` (`module`, `kept`): asks again the last failed act of that module and kept data, with its fields;
|
||||
`error` `nothing to retry` when its last act did not fail; ends with that act's ending event.
|
||||
- `aside` (`module`, `kept`): sets the live kept data aside without restoring (§516); ends with `set-aside`.
|
||||
- `discard-aside` (`module`, `kept`, `id`): removes that aside's directory; ends with `aside-discarded`.
|
||||
- `expire-points` (no field of its own): runs §551's removal of points past their 90 days now; ends with
|
||||
`expiry-ended`.
|
||||
- `restore-aside` (`module`, `kept`, `id`) and `cancel` (`act`) as §566 has them; `cancel` answers in the same
|
||||
form (§691): `accepted` with the id of the act it cancelled, whose ending event follows under that id, or
|
||||
`error` naming the step the act has begun.
|
||||
- `restore` and `restore-kept` are two verbs, both kept: `restore-kept` is the node-engine's (§516), taking
|
||||
`form`, `fingerprint` and `snapshot` and refusing `aside-only` and `discard-aside`; `restore` is the hand verb
|
||||
above, taking a point by `snapshot` or a keeper's snapshot by `request`.
|
||||
|
||||
Why: the developer's D-19; the bundle calls on the tool runner's account, so the log would name the runner, not the
|
||||
asker (review round 2). How it is checked: unit (mesh-recovery's contract), each verb's fields and answers read
|
||||
from its fixtures, `restore` with both `snapshot` and `request` refused by the schema's `oneOf` (§701); `retry`
|
||||
with nothing failed answered `nothing to retry`; `cancel` of a begun act refused naming its step; whole (recovery module), a hand act's log
|
||||
entry names the tool runner's account and the asker.
|
||||
|
||||
- §683 **The node-engine keeps the entries and wants it composes from in its composition state.** (§421, §614,
|
||||
§627, §422, §693; ADR 0593 §6) [review rounds 2 and 3.] The node-engine, a foundation part (§627), composes the bus
|
||||
module and the indexer that serves `mesh-catalog.entry`, so it never relies on `entry` answering at a restart:
|
||||
- **Kept on the node.** Each entry it holds (§614, *An older build's entry*) and the wants of its last composition
|
||||
are kept in its composition state on the node (ADR 0593 §6), beside the composition, written whole before the
|
||||
composition that uses them; an entry is removed when no want or `running` it holds names its fingerprint, and a
|
||||
module's want when `wanted` says it `removed` (§693). At its start it reads them before it asks anything, so a
|
||||
restart composes the bus module and the indexer while both are down; only a fingerprint it holds no entry for is
|
||||
asked of `entry`.
|
||||
- **They give way to the bus.** The kept wants stand only until the seat `mesh-wants`' stream answers its read of
|
||||
§556; from then the stream's wants are the ones it composes from, so a revert then an upgrade never composes from
|
||||
a want older than the stream's. Until its first live answer its have says it composes from kept wants and
|
||||
entries, and a kept part it cannot read is said in its have and its log, that module composed as its running
|
||||
composition stands and its entry asked of `entry`.
|
||||
- **Its first start.** The node-engine of G names `seat:mesh-catalog.entry` in its `invokes` (§614), so by §558's
|
||||
kind 6 its G version runs on a node only after the indexer of G answers `entry`; the bus module and the indexer
|
||||
of G are composed by the node-engine of before G, as on main. A node-engine of G at its first start, or on a newly
|
||||
adopted node, holds nothing kept and asks `entry` of the indexer that runs.
|
||||
- **Its rows and its ask again, on its seat.** It serves no module tools (§422): its kept entries and kept wants
|
||||
are rows of the seat `node-engine`'s verb `first-views` (§698), and the seat gains the verb `entry-again`
|
||||
(`fingerprint`, `why`; class `act`, taking its default, §610), both in mesh-contracts with fixtures, in place of
|
||||
§614's `<module>_first_views` and `<module>_entry_again` for it.
|
||||
|
||||
Why: the node-engine composes before the bus answers (§627). How it is checked: whole (node-engine), a node-engine
|
||||
restarted with the bus module and the indexer stopped composes both from its composition state; a kept part made
|
||||
unreadable is said and asked again; a revert then an upgrade composes from the stream's wants once it answers;
|
||||
unit (the seat's contract), `first-views` rows and `entry-again` read from fixtures.
|
||||
- §684 **The grants part composes a want's account before any branch view.** (§614, §507, §558, §374) [review round
|
||||
2, seams and tools 1.] The grants part follows the branches as §614 has it, from the gate's `source` with no branch
|
||||
(named in its `invokes` in the long form, `{verb: seat:mesh-delivery.source, fields: [branches]}`, §558), so its
|
||||
full pass waits on the gate's G version (kind 6), while the gate's G version waits on the grants part composing its
|
||||
account (kind 5). So, until every branch it follows has its first view, the grants part keeps the user list it last
|
||||
wrote and composes, for a want whose account it holds none of or whose fingerprint changed, that one account from
|
||||
the want's catalog graph entry by fingerprint (§614, *An older build's entry*) and the assigner's views alone, as it
|
||||
composes any account, every marked subject in it denied while the marks are unread (design 46
|
||||
7-approval-mark §19); it writes that account into the user list it keeps, and says in its log and in
|
||||
`<module>_first_views` that it composed it before its first views. Its full pass, once the first views are in,
|
||||
composes every account again as always. So the gate's G version is granted before it answers, and no cycle stands.
|
||||
- **The indexer's own account.** `entry` is served only by the indexer of G, so the grants part of G names
|
||||
`seat:mesh-catalog.entry` in its `invokes` and, by §558's kind 6, runs only after the indexer of G answers;
|
||||
the indexer of G's account is composed by the grants part of before G, as on main, so no cycle runs through the
|
||||
indexer.
|
||||
|
||||
How it is checked: whole (grants part, gate), with the gate of before G running and a want of the gate's G
|
||||
version, the grants part composes that account before any branch view, and its full pass once `source` answers;
|
||||
whole (delivery plan), G's indexer is composed while the grants part of before G runs, and the grants part's G
|
||||
version is ordered after it.
|
||||
|
||||
|
||||
## Develop's D-20 to D-23
|
||||
|
||||
- §685 **A condition carries `owner` and `waiting-on`.** (§451, §540, §678) [D-20, mesh-catalog COND7.] The seat
|
||||
`mesh-conditions`' definition gives a condition, in its state `conditions` and in every condition event, the
|
||||
fields `owner` and `waiting-on`, both optional, beside `resolver`, which keeps its own meaning (who resolves it: `self`,
|
||||
`operator`, `agent` or `healer:<name>`). Each takes §678's three forms, refused otherwise, naming the field. The
|
||||
keeper sets both from the finding when it raises the condition, and changes them only as §540 says (the two hours'
|
||||
handover to `operator-channel`, and `conditions_owner`), saying each change as it says any change of a condition.
|
||||
A condition kept from before G carries neither and is read as having none; the keeper writes them into it at its
|
||||
next change.
|
||||
How it is checked: unit (mesh-conditions' contract), both fields read in the state and the events in each form;
|
||||
whole (keeper), a `pipeline-failed` raised with the author's `forge:` owner and changed to `operator-channel`.
|
||||
- §686 **The generated finding type carries its accessors; the writer takes it through an interface.** (§439, §640)
|
||||
[D-21.] The seat generator emits, beside each Go type generated from the finding layout of `mesh-conditions`'
|
||||
`bodies`, in the same generated package of mesh-contracts, methods on its pointer: one getter and one setter per
|
||||
field the writer reads or fills (`kind`, `scope`, `id`, `at`, `since`, `severity`, `summary`, `headline`, `source`,
|
||||
`subject`, `observer`). mesh-sdk's Go writer defines, in `go/views`, the interface `views.FindingBody` with those methods and takes a
|
||||
`views.FindingBody` and a constructor of one (`func() views.FindingBody`, the generated `New`); it uses no generics. In
|
||||
TypeScript the generated type is structural: the writer's interface names those fields as properties, set by
|
||||
assignment, and no setter is generated. How it is checked: unit (seat generator), the methods generated; unit
|
||||
(writer, both languages), a generated body written, its `observer`, `at` and `since` set by the writer.
|
||||
- §687 **mesh-sdk lays out the views reader's findings itself.** (§639, §686) [D-22.] The reader's four findings are
|
||||
laid out by mesh-sdk, through §686's constructor and setters, and the part supplies only the constructor: `kind`
|
||||
the finding's; `scope` the writer's own, as every finding it writes; `id` the view's name
|
||||
`<owner>.<state>.<branch>`, escaped as a key's name is (§73); `severity` `warning`, but `urgent` for
|
||||
`view-too-large`, which the part cannot read past; `source` `views`; `subject` `{scope: module, id: <the part's
|
||||
module>, machine: <its node>}`; `summary` and `headline` fixed texts naming the view and what it waits on. The
|
||||
part-supplied body function goes. How it is checked: unit (`go/views`, and the TypeScript follower), each of the
|
||||
four written with those fields.
|
||||
- §688 **The views reader's four kinds are always its writer's.** (§580, §639) [D-23.] The writer of a part that
|
||||
holds a views reader always owns `view-waits`, `first-view-unanswered`, `view-unsaid` and `view-too-large`, added
|
||||
to its stated kinds when `Kinds` is set, so its statement covers them (§580). A module whose bundles share one state
|
||||
`findings` holds a views reader in one bundle only (in G the forge module's TypeScript tools, §196), which the code
|
||||
review checks. How it is checked: unit (writer), a writer made with `Kinds` states the four with them.
|
||||
- §689 **The hand-written finding helpers go; mesh-sdk's writer is the one writer.** (§461, §503, §640, §660) [D-24,
|
||||
mesh-contracts K15.] With the state `findings` holding `#/bodies/Finding` under `<scope>.<id>.<kind>` (§660), the
|
||||
finding helpers of mesh-contracts' `meshconditionsclient` go, as its `findings` package does (§503): `CheckFinding`,
|
||||
`FindingFor`, `FindingWriter` and `FindingState`, `FindingVersion`, `Observation` (with its `Key` and `Finding`),
|
||||
`EncodeFinding`, `DecodeFinding`, `Says`, `FindingBucket`, `FindingsBucket`, `Raise`, `Clear` and `CallUnanswered`.
|
||||
`CheckFinding`'s rules (the key's parts, the kind, §678's forms) live only in mesh-sdk's writer, which refuses a
|
||||
finding breaking them before it writes, and which exports that check for the keeper as `views.CheckFinding(b views.FindingBody) error`, in `go/views`, returning the
|
||||
first rule broken, naming its field (§686; D-35). What concerns conditions
|
||||
stays: `Key`, `EventFor`, `ShowKeeper`, `SilenceAction`, `Order`, `SilencedAt`, `Escalated`, `ResolverHealer`, the
|
||||
scope, resolver and severity words, `KindCallUnanswered`, `CallUnansweredAfter` and the package `heard`. The
|
||||
importers move in G's change: every part that writes findings writes them through mesh-sdk's writer and the
|
||||
generated type (§686), its texts written into `summary` and `headline` where `Says` made them: in mesh-catalog
|
||||
`artifact-store-tools`, `claude-code`, `distribution` and `distribution-tools`, `package-registry`, `mesh-build-go`,
|
||||
`mesh-config`, `mesh-healers`, `mesh-provisioner`, `mesh-self-check`, `mesh-vault`, `mesh-watcher`, `object-store`
|
||||
and `nfs-server`; in mesh-delivery the assigner, the builder, the checker, the indexer and its tools, the teller,
|
||||
the status view and the watchman; in `mesh-host` the node-engine; in mesh-tools `node-tools/internal/firstview`. The
|
||||
keeper (`mesh-conditions`) reads the findings others write into the generated type and judges them with mesh-sdk's
|
||||
exported check. How it is checked: the code review finds none of the removed helpers at G's heads; unit (writer),
|
||||
a finding breaking each rule `CheckFinding` held refused before it is written.
|
||||
- §690 **Who drops the keeper's consumer of a moved finding stream, and the gate's `source` in mesh-contracts.**
|
||||
(§386, §611, §612, §614, §663, §684) [D-25, mesh-catalog COND9.]
|
||||
- **The grants part drops it.** The grants part, which makes the keeper's consumers (§386), deletes the keeper's
|
||||
durable consumer on a part's stream at its first composition from a catalog graph entry of that part that no
|
||||
longer declares the stream, and makes the consumer on the new stream from the same entry, as §386 does for a
|
||||
module that declares the events later. The keeper deletes no consumer: when it hears, through its catalog half
|
||||
reader (§614), the part's new entry, it stops reading the old source and keeps the part's open conditions.
|
||||
- **The keeper takes the new source.** For a template stream on the seat `mesh-broker`'s `created` of the part's
|
||||
new `finding-events` entry, for an old section's stream from the catalog half, as §663 has it; it binds the
|
||||
consumer the grants part makes as a kept call (ADR 0668 §1: a call made again on its schedule until it succeeds,
|
||||
kept in the keeper's state), so a consumer made after `created` is bound once it exists, the source said waiting
|
||||
in its log meanwhile; once bound it asks `findings-asked` there once (§663). Past 10 minutes unbound it writes the
|
||||
finding `consumer-unbound` (scope the keeper, id the part's module), cleared once bound.
|
||||
- **The message on a template stream.** A finding event on `mesh.bus.<module>.finding-events.<event>.<node>` is
|
||||
mesh-sdk's event envelope that every event publisher writes, its body under `body`; the keeper reads the module
|
||||
and the node from the subject's tokens and the event's name from `<event>`. [D-36.]
|
||||
- **The gate's `source` in the seat's definition.** The seat `mesh-delivery`'s definition in mesh-delivery's
|
||||
contracts (built there, not in mesh-contracts) gives
|
||||
`source` the optional input `branch` and two answers: with `branch`, that branch's source revision, as on main;
|
||||
without, `{branches}`, the list of the branch names of the `source.<branch>` keys the gate keeps (§614), the
|
||||
field `branches` that §684's long-form `invokes` names; with a fixture of each.
|
||||
|
||||
How it is checked: unit (grants part), an entry whose finding stream moved composes the old consumer's deletion and
|
||||
the new one's making; whole (keeper, grants part, bus module), a part whose findings move to a template is bound on
|
||||
the new stream after its consumer is made, its condition raised once, and `consumer-unbound` raised past 10
|
||||
minutes while unbound; unit (`mesh-delivery`'s contract), both
|
||||
answers of `source` read from their fixtures.
|
||||
|
||||
## The operator's O-8, and develop's D-26 to D-31
|
||||
|
||||
- §691 **The recovery module's contract, as the developer wrote it where no line spoke.** (§516, §554, §560, §567,
|
||||
§682, §396) [D-30, mesh-contracts K7 to K9.] Kept as lines: every verb of the seat `mesh-recovery` that acts
|
||||
(`snapshot-kept`, `restore-kept`, `cancel` and every hand verb of §682) answers one form, an object with exactly
|
||||
one of `accepted` (the act's id; for `cancel` the cancelled act's), `busy` (the id of the act that runs) or
|
||||
`error` (why) set; `cancel` answers no `cancelled` field [D-32];
|
||||
the seat's family is `mesh-recovery.events`, 7 days, for any event naming no `keep`; a point's `digests` is a list
|
||||
of `{name, digest}`, each a bus object of the point and the `sha256:` digest of its bytes (§567); `kept-points`'
|
||||
`running` is `{act, step, since, waits-on}`, `waits-on` present only while the act waits, and absent when no act
|
||||
runs. Corrected: `expiry-ended` has no parts, so it is kept 90 days and not last per subject (the definition
|
||||
validator refuses `last-per-subject` on an event without parts); §554 and §560 read so. How it is checked: unit
|
||||
(mesh-recovery's contract), each shape read from its fixtures and `expiry-ended`'s `keep` accepted.
|
||||
- §692 **`problems` are the manifest validator's strings.** (§668; ADR 0579 §1) [D-26, indexer IX28.] The manifest validator's problems are
|
||||
plain strings and gain no rule names: `manifest-accepted`'s `problems` is the list of those strings, the same the
|
||||
verdict writes as warnings, in the same order, and `{rule, words}` is not written. How it is checked: unit
|
||||
(indexer), a faulted manifest accepted with the validator's strings.
|
||||
- §693 **`wanted` says a want removed.** (§675) [D-27, indexer IX29.] The seat `mesh-wants`' event `wanted` gains
|
||||
`removed`, a boolean, true when the assigner removes the want of that node and module (the node left, or the module
|
||||
was unplaced); `fp` is then the fingerprint last wanted there, and the subject is the one that want was said on, so
|
||||
the stream kept last per subject keeps the removal as that want's last word. The assigner says it in the same
|
||||
write that removes the want. Every consumer of `wanted` reads `removed` in G's change: the indexer (§675) removes
|
||||
the member from `named`; the grants part (G-9 §344) composes no account for that want; every §614 reader removes
|
||||
that want from what names a kept entry; the node-engine (§683) removes its kept want. How it is checked: unit (assigner), an unplaced module's want said `removed`; unit
|
||||
(indexer), a `removed` empties that member from `named`; unit (grants part, each §614 reader, node-engine), each
|
||||
reads a `removed` so.
|
||||
- §694 **An image's package name is its module's and its artifact's.** (§676, §491) [D-28.] An image artifact's
|
||||
`package` is `{ecosystem: image, name: <module>/<artifact>}`, its module's name and its artifact's name from the
|
||||
manifest joined by `/`, unique as module names are; the indexer writes it at take-in, reading no file for it, and
|
||||
§676's "the manifest's image name" reads so. How it is checked: unit (indexer), two modules with an image artifact
|
||||
of one name carry two package names.
|
||||
- §695 **An old state section's cap is its `max-mib`.** (§469, §494, §588, §667, §672, §675) [D-29.] A cap the design
|
||||
gives an old `state` section is declared in that section's entry of the part's manifest as `max-mib`, the word §469
|
||||
added to the old section and §494's reader release reads: the indexer's `entry-index` 8, `named` 4 and `proposals`
|
||||
4, the builder's `publish-queue` 4, `acted` 1 and `waits` 4 (§672; its stream `waits` keeps its 16 MiB), each in G's change of that manifest; the grants part
|
||||
makes the bucket with it (§494). How it is checked: unit (manifest validator), each of those manifests at G's heads
|
||||
declares its caps; whole (grants part), a bucket made with its cap.
|
||||
- §696 **A pipeline wait's `on`.** (§534, §498, §266) [D-31, mesh-contracts CT14.] A wait with `step` `pipeline`
|
||||
has `on` the module of the runner's new version whose healthy run ends it (the holder of
|
||||
`mesh-pipeline-runner` the new revision names, read from the catalog half), and `words` names that version's
|
||||
fingerprint. How it is checked: unit (runner's contract), a `pipeline` wait read with its `on`.
|
||||
- §697 **`by-caller` reaches the mesh only after the reader release.** (§621, §494, §554) A seat definition that
|
||||
marks a verb `by-caller` (§554's `mesh-recovery`, §681's `mesh-broker` verbs, every verb §677 gates) is read only by
|
||||
§494's second reader release, merged and delivered before G's merge, whose publish (kind 1) rebuilds every reader
|
||||
of seat definitions; those definitions reach the mesh only in G's merge, so the order is the release's before G
|
||||
and needs no edge of the module graph. A reader of before the release that met one would refuse that definition,
|
||||
which blocks only new claims of the seat (ADR 0636 §3). How it is checked: G's check reads every definition with
|
||||
`by-caller` through the release's reader.
|
||||
|
||||
## The final fix: develop's D-33 to D-38
|
||||
|
||||
- §698 **The node-engine's `first-views` rows and `entry-again`'s answer.** (§683, §422) [D-33.] `first-views`
|
||||
answers `{first-views, entries, wants}`: `first-views` as today; `entries` a list of `{fingerprint, state, since}`,
|
||||
`state` one of `asked`, `answered` and `not-found`; `wants` a list of `{module, fingerprint, kept-since}`, the kept
|
||||
wants of §683. `entry-again` answers as `first-view-again` does: `asked` `{v, fingerprint, why, by, at}` and `read`
|
||||
`first-views`. How it is checked: unit (the seat's contract), both answers read from fixtures.
|
||||
- §699 **A state a seat offers as a view says so.** (§616; ADR 0571 §6) [D-34.] A state entry of a seat definition
|
||||
takes the optional field `view: true`; the seat `mesh-module-assigner`'s states `assignments` and `seat-holders`
|
||||
carry it. The definition format's schema and mesh-sdk's `seatdef` reader gain the field, read first by §494's
|
||||
second reader release (§621, §697); the manifest validator refuses a `seat:` view in `consumes` naming a state
|
||||
without it. How it is checked: unit (`seatdef`), `view` read and an unknown field still refused; unit (manifest
|
||||
validator), a view of a state without `view` refused.
|
||||
- §700 **A process reads the router's record of its asks from the bus, under its own name.** (§667, §677; ADR 0259
|
||||
§3; ADR 0286 §26) [D-37.] The one path: the read of the seat `operator-channel`'s state `asks` under
|
||||
`<module>.`, which the grants part composes for every user of the seat (ADR 0259 §3; design 46 7-approval-mark
|
||||
§7), on the process's own account; its value carries the ask's `warrant` (7-approval-mark §14). mesh-sdk's
|
||||
`operatorchannelclient.Client` is given, as its `Record`, a get of that key on the process's own bus connection
|
||||
(`go/process`), as the tool runner gives it the runtime's `mesh/seat.record`; `Where` and `Warrant.For` are the
|
||||
same. The indexer (§667, its *Heard* and *A restart*) and every holder of §677 read so; the verb `asks` stays as it
|
||||
answers. How it is checked: unit (`operatorchannelclient`), `Where` through a process's get; whole (indexer), a
|
||||
warrant applied from that read.
|
||||
- §701 **`resync`'s and `retire`'s classes; `restore`'s one of two.** (§610, §682) [D-38.] The seat
|
||||
`mesh-conditions`' `resync` is of class `act`, and the seat `mesh-vault`'s `retire` of class `warrant`, since its
|
||||
definition binds it to the operator's warrant on exactly its node, module and name; each takes its class's default.
|
||||
The seat `mesh-recovery`'s `restore` input holds `snapshot` and `request` under a `oneOf`, so the schema refuses
|
||||
both or neither, and the holder answers that refusal as `error`. How it is checked: unit (each seat's contract),
|
||||
the classes read; `restore` with both refused by the schema.
|
||||
|
||||
## The final fix: the builder's D-39 to D-44, and two fields
|
||||
|
||||
- §702 **The builder's toolchain wait, in the contract's words.** (§672, §629, §546, §604) [D-39 to D-44.]
|
||||
- **Its finding's scope** is `build` (a scope the finding layout has), id `<module>.<fingerprint>`; §672 reads so.
|
||||
- **Its step** is `build`, the contract's word; §672's `building` reads `build`.
|
||||
- **Its body is the contract's wait.** The seat `mesh-wants`' wait in mesh-delivery's contracts gains `owner`,
|
||||
`on` and `words` (§629, §512 name them), and `module`, `fingerprint` and `how` as optional fields; the builder
|
||||
says its `wait-held` and `wait-ended` with that type and keeps no body of its own.
|
||||
- **The build module of a language** is read through the builder's language mapping as it stands
|
||||
(`buildrun.BuildModuleOf`, from the seats' `acts` rules in the seat list); §546's words name that read.
|
||||
- **A superseded wait ends.** When the builder holds an entry of a newer fingerprint of the same module, it ends
|
||||
the older wait with `how` `superseded` and deletes its key.
|
||||
- **Two build seats report a toolchain.** §604's "the three build seats' `toolchain-reported`" reads "the Go and
|
||||
node build seats'": the image build seat's builds name no toolchain and it reports none.
|
||||
|
||||
How it is checked: unit (builder), a wait said with the contract's type, scope `build` and step `build`; a newer
|
||||
fingerprint ends the older wait `superseded`.
|
||||
- §703 **A stream kept last per subject says so in its manifest, and `running` says `stopped`.** (§695, §396, §365)
|
||||
A manifest's `streams` entry takes `last-per-subject` (yes or no, no unless set), as a seat event's `keep` does
|
||||
(§396), read by §494's second reader release beside `max-mib`; every old `streams` section the design keeps last
|
||||
per subject (the builder's `waits`, §672, among them) declares it in G's change. The node-engine seat's `running`
|
||||
body says a module no longer held by `stopped: true`, with no `fingerprint`, as §365 has it; the seat's definition
|
||||
gains the field. How it is checked: unit (manifest validator), `last-per-subject` read; unit (the seat's
|
||||
contract), a `running` with `stopped` read.
|
||||
- §704 **The indexer's seed, `unwatch`, its asks and its history, in the contract's words.** (§2, §8, §402, §667)
|
||||
[D-45 to D-49.]
|
||||
- **The seed's `steps`.** For each artifact of a module's entry the indexer asks `steps` of the build seat whose
|
||||
`acts` rule `build` has `on` matching that artifact's language or kind (§203, §324), read from the seat list as
|
||||
the builder's mapping reads it (§702), with `{module, artifact, fingerprint, fetch}`: the artifact's key in the
|
||||
entry, the entry's build-input fingerprint and the entry's `fetch` of its source, as the entry carries them.
|
||||
- **Its findings.** `call-unanswered` has scope `call`, id `<seat>.steps`, cleared when that seat answers;
|
||||
`seed-waits` has scope `seat`, id `mesh-catalog.seed`, cleared by the warrant and kept after a Decline or an
|
||||
expiry (§8).
|
||||
- **`unwatch`'s placements** are read through the seat `mesh-module-assigner`'s verb `placement`, named in the
|
||||
indexer's `invokes`: a placement names the branch when its `build` is a fingerprint listed by that branch's newest
|
||||
revision and by no other watched branch's.
|
||||
- **An ask's lifetime** is the router's approval maximum less ten minutes, as mesh-config's asks are.
|
||||
- **A branch named `candidate` or `entry`** is refused with the reason `reserved branch name`, which the seat
|
||||
`mesh-catalog`'s contract gains, not `unprotected branch`.
|
||||
- **`registration-history`** is declared with no age limit and a cap of 64 MiB (`max-mib`), discarding a new
|
||||
message when full rather than dropping an old one, so no version is lost unsaid: a registration change whose
|
||||
history write is refused fails, naming the stream, and past 80 % the indexer writes the finding
|
||||
`registration-history-filling` (scope `seat`, id `mesh-catalog.registration-history`), cleared under it.
|
||||
|
||||
How it is checked: unit (indexer), the seed's `steps` input per artifact; `unwatch` refused through a fixture
|
||||
`placement`; `candidate` refused with `reserved branch name`; a full history refusing a change and naming it.
|
||||
|
||||
## The operator's O-9: a delivery waits on another machine's network
|
||||
|
||||
- §705 **A machine whose network names an outside cause holds the delivery there as a wait, not a failure.** (ADR
|
||||
0677) The operator's answer O-9 of 2026-11-11 about 21:05 CET; the line is written whole in §706 (G-19), which
|
||||
replaces what stood here.
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0677"
|
||||
- "ADR 0668"
|
||||
- "ADR 0603"
|
||||
- "ADR 0596"
|
||||
- "ADR 0564"
|
||||
- "ADR 0539"
|
||||
- "ADR 0429"
|
||||
- "ADR 0241"
|
||||
---
|
||||
|
||||
# 47, milestone G: a delivery's wait on an outside cause, and the writer's own refusals
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§706 and §707 come from the review by architectural domain of lead-47's batch 8 (round 1, 2026-11-11 about 21:20
|
||||
CET). §706 writes the operator's O-9 whole, once, in place of §705: what counts as an outside cause, when it is
|
||||
judged, every way the wait ends, its event, its finding and its tools. §707 amends §580 for the finding writer's own
|
||||
refusals.*
|
||||
|
||||
## A delivery's wait on an outside cause
|
||||
|
||||
- §706 **A failing check on a machine whose network names an outside cause is a wait that ends by itself, and asks
|
||||
the operator after 30 minutes.** (ADR 0677 §1 to §4; ADR 0241 §11, §13, §16 to §21, §25 to §27; ADR 0564 §2, §3,
|
||||
§13; ADR 0596; ADR 0429 §6; ADR 0539 §18; ADR 0668 §1; ADR 0603 §11; §276, §461, §498, §512, §633, §700) The
|
||||
operator, 2026-11-11 about 21:05 CET at lead-47's terminal (O-9): a delivered module's own failing health check is
|
||||
the delivery's; while the machine's network names an outside cause, the delivery waits on that machine, says from
|
||||
the start what it waits on, judges again once the cause clears and goes on by itself, and after 30 minutes asks the
|
||||
operator to go on or count it as failed. Where the assigner judges a node's health after a want moves (the canary
|
||||
rule and its hold, ADR 0429 §6, ADR 0539 §18), for a module the delivery moved to a machine M:
|
||||
- **Where the causes are said.** The node-engine only states M's network in its health report (ADR 0241 §13). The
|
||||
watchman, holding `mesh-watchdogs`, raises the network findings from every machine's report together (ADR 0241
|
||||
§16 to §21, observed by it under ADR 0564 §2), and the condition keeper, holding `mesh-conditions`, holds them as
|
||||
conditions, never held while a module is delivered (ADR 0564 §13 (e)). The assigner hears them only through the
|
||||
seat `mesh-conditions`: its verb `conditions` and its events `condition-raised`, `condition-changed` and
|
||||
`condition-cleared` (ADR 0564 §3), named in its `invokes` and `consumes`; it never reads the watchman's `findings`
|
||||
nor the keeper's state (ADR 0596).
|
||||
- **What counts as an outside cause for M**, read from a condition's `key`, `raised` and subject (`machine`,
|
||||
`also`), the key being the only source of an owner (a finding has no owner field):
|
||||
- `machine.M.<owner>.rewritten`, or `machine.M.rewritten` (a file of no module), whose `<owner>`, the key's
|
||||
part between M and `rewritten`, is no module the delivery moved to M (ADR 0241 §17, §25; ADR 0677 §1);
|
||||
- `machine.<x>.unreachable` for another machine `<x>` whose `also` lists M, the machines whose failing parts
|
||||
point at `<x>` (ADR 0241 §19, §21, §25; ADR 0677 §1);
|
||||
- `machine.<x>.network` or `machine.<x>.silent` for another machine `<x>` whose `also` lists M: when `<x>` is
|
||||
down on the record by its own network or its silence, the watchman raises no `unreachable` for it and holds
|
||||
M's failures toward it under `<x>` (ADR 0241 §20, §21). The watchman (mesh-delivery's
|
||||
mesh-delivery-watchman) therefore lists in `also` of those two findings the machines whose failing parts point
|
||||
at `<x>`, as it lists them on `unreachable`; today it names them only in the `network` finding's summary and
|
||||
not at all on `silent`. A change of `also` rewrites the finding (ADR 0564 §2).
|
||||
|
||||
No other condition is one. A `machine.M.<owner>.rewritten` whose `<owner>` the delivery moved to M is that
|
||||
module's failure (ADR 0241 §26; ADR 0677 §3), and M's own `machine.M.network` holds M as a whole (below); neither
|
||||
takes this wait.
|
||||
- **When it is judged.** When a moved module's check fails on M, the assigner reads M's open conditions through
|
||||
`conditions` with `scope` `machine` and `machine` M, which answers each condition whose subject's `machine` is M
|
||||
or whose `also` lists M (ADR 0564 §3), as a kept call (ADR 0668 §1). It is read at each judging, never kept as a
|
||||
copy from the events, since the keeper says no event when only a condition's `also` changes (it says
|
||||
`condition-changed` for a severity, a resolver, a silence or its words). With an outside cause open, the move
|
||||
waits. With none open, the assigner settles: it holds its judgement for `outside-settle-seconds` (a setting,
|
||||
§633, 90 unless set) from the failing check, judging again on each `condition-raised` whose subject's `machine`
|
||||
is M or whose `also` lists M, since the node-engine states the failing check and the network in one report while
|
||||
the condition rises only after the watchman's two looks and the keeper's take-in; past it with still none, the
|
||||
failure is the delivery's, held as the canary rule has it. The settle says no `wait-held`; `assigner_waits` shows
|
||||
it as `settling` with its end.
|
||||
- **When the keeper does not answer.** While the read of `conditions` is unanswered, the move waits `on`
|
||||
`conditions-unread`, said and bounded as below, and is judged when the kept call is answered. A module that holds
|
||||
`mesh-conditions` itself, as the assignments name, is judged with no read: its failing check is its delivery's
|
||||
failure after the settle, since the keeper cannot answer for its own failing check.
|
||||
- **The wait.** The assigner holds that move as a wait, says `wait-held` on `mesh-wants` with `step` `outside` (a
|
||||
value added to §498's list, apart from a dependent's `step: delivery`), `node` M, `module`, `fingerprint`, `on`
|
||||
(`rewritten:<owner>`, `rewritten` for no owner, `unreachable:<x>`, `network:<x>`, `silent:<x>` or
|
||||
`conditions-unread`), `bound` (`outside-wait-minutes`, a setting, §633, 30 unless set) and `clock-from`, the
|
||||
wait's first start. When the wait stands on another cause, it says `wait-held` again with the new `on` and the
|
||||
same `clock-from`, so the bound is never restarted (ADR 0677 §2). It writes, through mesh-sdk's finding writer
|
||||
(§461), the finding `delivery-waits-outside` (scope `delivery`, id `<node>.<module>`), severity `warning`, its
|
||||
summary naming the module, M and the cause by its condition's key, its `needs` "Nothing for you to do.": a
|
||||
warning that needs nothing is not sent (design 45, the router's *When*), so the operator's one message is the ask
|
||||
at the bound. The watchman writes no `delivery-waits` for `step: outside`, so no second condition rises for it.
|
||||
- **Judged again.** A waiting move is judged again, reading M's conditions as above, on: the `condition-cleared`
|
||||
of its cause's key; each `condition-raised` or `condition-changed` whose subject's `machine` is M or whose
|
||||
`also` lists M; each change of that module's have on M; the kept call answered, for `conditions-unread`; and the
|
||||
assigner's start, for every wait it holds. A `condition-cleared` of a key no wait names is dropped. M leaving an
|
||||
`also` with nothing else changing says no event: it is heard at the next of these, or at the ask.
|
||||
- **How it ends**, each said on `wait-ended` with its `how`, the finding cleared at once:
|
||||
- `healthy`: the module runs healthy on M, the cause open or not; the delivery goes on.
|
||||
- `cleared`: judged again after its cause cleared, healthy; the delivery goes on.
|
||||
- another outside cause open at a judging: no end; the wait stands with the new `on` (above).
|
||||
- `own-failure`: judged again failing with no outside cause open, after the settle; it is the delivery's
|
||||
failure, and only then does the canary rule hold it (ADR 0677 §1).
|
||||
- `go-on` or `failed`: the operator's answer to the ask (below), or a release by hand with that `how`.
|
||||
- `replaced`: a newer want of that module on M replaces the waited one; its own move is judged afresh, and an
|
||||
open ask is ended.
|
||||
- **The bound and the ask.** Past `outside-wait-minutes` from `clock-from`, the assigner asks the operator through
|
||||
`operatorchannelclient`, `about` `<node>.<module>.<fingerprint>`, naming the cause and since when, with two
|
||||
options: Go on (the module counts healthy there by the operator's word, and the delivery goes on, `how` `go-on`)
|
||||
and Count as failed (held as the canary rule has it, `how` `failed`) (ADR 0677 §2). It reads the answer from the
|
||||
router's record of the ask under its own name, on §700's path, never from the decided event's body nor a
|
||||
caller's word. The wait's end ends the ask. An ask that ends unanswered is raised again at once while the wait
|
||||
stands. `how` `go-on` is recorded as such, so a Go on is never read as a passed health check.
|
||||
- **M's own network.** A machine whose own `machine.M.network` is open is held as a whole: no want moves to it
|
||||
until its `condition-cleared` is heard (ADR 0241 §27; ADR 0677 §3). The assigner keeps that set from its
|
||||
consumer of the three events and from a read of `conditions` with `scope` `machine` at its start; while that
|
||||
read is unanswered, wants move as if none were held, since the keeper is itself delivered by wants moving, and a
|
||||
failing check there meets the judging above.
|
||||
- **Its tools** (§276's row *delivery on an outside cause*): `assigner_waits` reads each outside wait, settle
|
||||
included, with its module, M, fingerprint, cause key, `on`, `clock-from`, `bound`, its ask's id and its state;
|
||||
`assigner_release` takes one with `how` `go-on`, `failed` or `rejudge` (judge it again now) and a `why`, class
|
||||
`act`, its default, and writes the hand-act log of the seat `mesh-hand-acts` first (ADR 0603 §11).
|
||||
|
||||
Why: the operator's answer O-9 and ADR 0677; the review's round 1 asked the mechanism once and whole: the watchman
|
||||
holds a machine's failures toward a machine down on the record under that machine (ADR 0241 §20), so the
|
||||
commonest cause, a hub silent or its own network open, is a `network` or `silent` finding and needs M in its
|
||||
`also`; the keeper says no event for `also` alone, so the read at each judging is the truth, and the settle covers
|
||||
a condition rising after the check it explains. How it is checked, as cases of the assigner's whole test (a
|
||||
fixture keeper answering `conditions` and saying its three events): an `unreachable` whose `also` lists M turns a
|
||||
failing check into a wait said at once with `on` `unreachable:<x>`, `bound` and `clock-from`, and ends `cleared`
|
||||
on its clearing; one whose `also` does not list M leaves the failure the delivery's; a hub silent and a hub whose
|
||||
own network is open, each listing M, each end in a wait; an `unreachable` cleared as the hub's `network` rises
|
||||
keeps the wait with `on` `network:<x>` and the same `clock-from`; a condition raised 30 s after the failing check
|
||||
turns it into a wait within the settle, and none within 90 s is held as the delivery's failure; a wait judged
|
||||
again failing with no cause open ends `own-failure`; the keeper silent waits `on`
|
||||
`conditions-unread` and is judged when it answers; the keeper's own module failing is its failure with no read;
|
||||
the module turning healthy with the cause open ends `healthy`; a newer want ends `replaced` and its ask; a restart
|
||||
of the assigner judges each wait again; past the bound the ask, Go on ending `go-on`, Count as failed ending
|
||||
`failed`, an unanswered ask raised again; a `rewritten` naming a moved module fails at once; M's own `network`
|
||||
moves no want to M; `assigner_release` with each `how`, each in the hand-act log; the finding
|
||||
`delivery-waits-outside` written as a warning that needs nothing and cleared at the end, and no `delivery-waits`
|
||||
for it. Whole (watchman): a machine silent, and one whose own network is open, while two machines fail toward it,
|
||||
each list both in `also`. Unit (grants part): the assigner granted `conditions` and the three events, and no read
|
||||
of the watchman's `findings`.
|
||||
|
||||
## The writer's own refusals, in every bundle
|
||||
|
||||
- §707 **`finding-refused` is the one kind every bundle's writer owns, within its own bundle's key part.** (§383,
|
||||
§384, §437, §461, §580; design 73 knowledge §1, §1a) §580 gives each finding kind to one bundle. One kind is the
|
||||
writer's own: mesh-sdk's finding writer refuses a key it cannot compose (a scope outside the seat
|
||||
`mesh-conditions`' set, an empty id, a kind or `ref` holding a dot, as design 73 knowledge §1 has it) and writes in
|
||||
its stead, in the observer's own state `findings`, the finding of kind `finding-refused` (design 73 knowledge §1a),
|
||||
keyed `module.<observer>.<bundle>.<asked-key-escaped>.finding-refused`, `<bundle>` the bundle's artifact `name` in
|
||||
the manifest's `build`, and the asked key escaped as a key's name is (§73). A module with one bundle has that
|
||||
bundle's name there too, so the form has no exception. Every bundle's writer owns that kind within its own key
|
||||
part only: it writes and clears only `finding-refused` keys of its own bundle. `findings-stated` (§384, §437)
|
||||
gains `bundle`, of the same form, beside `kinds`; a statement of a bundle, with `kinds` or without, covers the
|
||||
`finding-refused` keys of that bundle and never another's, and the keeper clears from it only the
|
||||
`finding-refused` conditions of that bundle it does not name. Every bundle of a module writes the one state
|
||||
`findings` under the module's one account, so the bus grant cannot tell bundles apart: mesh-sdk's writer, made
|
||||
with its bundle's name, is what keeps each to its own key part. Why: a refusal is that bundle's own fault, so no
|
||||
other bundle's statement may clear it (the review of design 73, HE-W2R2-2; review round 1 of batch 8). How it is
|
||||
checked: unit (writer, both languages), a refused key written as `finding-refused` under its bundle's key part,
|
||||
escaped; a statement of bundle A leaves bundle B's `finding-refused` key; a writer refusing to write or clear
|
||||
another bundle's `finding-refused`; unit (contracts), `findings-stated`'s `bundle` read; whole (keeper), two
|
||||
bundles' statements on one node each keep the other's `finding-refused` condition open.
|
||||
@@ -0,0 +1,361 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0667"
|
||||
- "ADR 0666"
|
||||
- "ADR 0664"
|
||||
- "ADR 0661"
|
||||
- "ADR 0659"
|
||||
- "ADR 0656"
|
||||
- "ADR 0644"
|
||||
- "ADR 0619"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0593"
|
||||
- "ADR 0573"
|
||||
- "ADR 0567"
|
||||
- "ADR 0564"
|
||||
- "ADR 0548"
|
||||
- "ADR 0525"
|
||||
- "ADR 0505"
|
||||
- "ADR 0448"
|
||||
- "ADR 0363"
|
||||
- "ADR 0283"
|
||||
- "ADR 0240"
|
||||
---
|
||||
|
||||
# 47, milestone G: pipelines per module: any size, the pipeline makes the check, who may say what, health, recovery, tools, kept data, how G itself is delivered, the choices under ADR 0666 and ADR 0667
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
### Any size
|
||||
|
||||
- §57 **A value over the limit is an object.** mesh-sdk's own small object store package (ADR 0595 §9) puts a value over
|
||||
the bus's per-value limit in the object store beside its state and keeps the object's name in the key; a verb's reply or an event over the
|
||||
per-message limit carries the object's name (ADR 0666 §16). A replace writes the new object and sets the key by
|
||||
compare-and-set; the object it replaced stays, as one whose key was deleted does, listed as `key gone` since no key
|
||||
names it, and only the drop tool removes either, on a person's word: the mesh removes kept data only through `drop`
|
||||
and `recreate`, each on a person's word (ADR 0595 §4; ADR 0666 §16).
|
||||
- §58 **One object store per state, granted to its owner.** (§513, §549) Each state asked by the template `state` gets its object store
|
||||
beside it, `<state>-objects`, a template form of ADR 0595 that the bus module provides and the grants part grants to
|
||||
the state's owner alone (ADR 0666 §16); a foundation part's large values go in an old `objects` section of its own.
|
||||
Its size is the template's (ADR 0595 §3). An object store holds no space until a value is written to it, so the many
|
||||
it adds reserve nothing; the few values over the limit (a delivery record, a catalog graph revision list, a test's
|
||||
output) are estimated, as a guess, at under 1 GiB across the mesh.
|
||||
- §59 **An object sent is readable by the modules it is for.** (§533) A reply's or an event's object is written to the sender's
|
||||
**sent store**, `<module>_sent-objects`: the bus module plans it, with no manifest field, for every module of the new
|
||||
form that serves a verb or emits an event, and a foundation part declares it as an old `objects` section
|
||||
`sent-objects` (ADR 0619 §2). Its cap is the objects template's (ADR 0595 §3), 256 MiB (§477), and it keeps an object 7 days, after
|
||||
which the bus expires it. That age is transport, not kept data: an object sent exists only to carry one reply or one
|
||||
event, read within its call's bound or its consumer's lag, so its expiry is not a removal of kept data that ADR 0595
|
||||
§4 holds to a person's word (the design's choice; 7 days is a guess at the longest a consumer lags, read from the
|
||||
fill tools once G runs). A full sent store is the bus module's `full` and its condition (ADR 0595 §6), and the sender
|
||||
then refuses the large reply or event loudly, naming the store. The grants part derives from the catalog graph (§442), never from a manifest, a standing grant to read them: the
|
||||
callers its seat definition names for the verbs it serves, and the consumers whose `consumes` declare its events;
|
||||
no other module may read them (ADR 0666 §16). The grant follows the catalog graph, so it is written at each `catalog-revision` that changes it,
|
||||
never per object. A consumer's reader of
|
||||
the form is delivered before its producer writes it, an edge of kind 4.
|
||||
- §60 **Its tools come with the package.** The package serves each module that imports it `<module>_state_objects` (the
|
||||
objects of each state, with their size, the key that names each or `key gone`, and the fill against its cap),
|
||||
`<module>_state_objects_drop` (drop the objects whose key is gone, on a person's word as ADR 0595 §4 removes kept
|
||||
data), `<module>_sent_objects` (each sent object, its size, when it was sent and to which verb or event, its age
|
||||
against the store's and the fill against its cap) and `<module>_sent_objects_drop` (drop sent objects older than an
|
||||
age a person names); each drop names who asked and is recorded in the hand-act log.
|
||||
- §61 **Its cost.** Only the modules that import the package are built with it, so no other module is rebuilt for it: a
|
||||
package published before its users build (ADR 0573 §2, ADR 0667 §3).
|
||||
- §62 **How it is checked.** Whole (mesh-sdk, against the real bus): values of one, ten and a hundred times the per-value
|
||||
limit stored, sent as a reply and as an event, and read back whole by a caller and a consumer the manifests declare,
|
||||
and refused to a module that declares neither; a replaced large value leaves its old object listed `key gone` beside
|
||||
the new one, and a key deleted leaves its object listed `key gone`, each removed by the drop tool and by nothing else;
|
||||
a sent object is readable within its 7
|
||||
days and expired after; each drop without who asked is refused. Tests 69 and 90.
|
||||
|
||||
### The pipeline makes the check, and the checker goes
|
||||
|
||||
- §63 **Every step runs on parts built from the change.** Every pipeline step that runs a change's code runs on parts built
|
||||
from the change's own heads, in the build seat's sandbox, with no network and no bus account (ADR 0666 §17, ADR 0448
|
||||
§2, §4): the composition step composes each machine's declaration with the composer built from the heads and refuses
|
||||
a change in which two modules own one path; the build seats' steps run lint and tests. A step whose serving part the
|
||||
change alters runs from the change's build of it, built within 20 minutes or ending `waiting for a build node` or
|
||||
`did not build`, the first tier of *The check on the change's own acting parts* made the runner's.
|
||||
- §64 **The run on request.** The runner's verb `run` takes one or more members, each a module at a named commit, runs them
|
||||
through their pipelines and delivers nothing, replacing the checker's try (ADR 0666 §18). Its callers are the
|
||||
sessions, as the agents' bus identities the tool runner proves, and the operator, and nothing else, since a run of a
|
||||
group builds every member and its dependents on the build capacity (§373). The runner says `run-started`, the indexer builds
|
||||
the run's candidate from it, and the candidate's composition step writes no want (ADR 0656 §9); the candidate is
|
||||
dropped on that run's `pipeline-ended`. A step that could not run ends `could not run`, never as passed. A
|
||||
composition step for a node that sleeps passes it with a warning (ADR 0593 §9).
|
||||
- §65 **The runner is an acting part in the checker's place.** The acting rule's step `check` becomes the runner's step
|
||||
`pipeline` (*What ADR 0656 leaves to this design*): its own change runs on its own new version, a failing new runner
|
||||
holds while the old one runs pipelines (ADR 0656 §4), and the pipelines in flight when its holder is replaced are
|
||||
taken up again from its kept state (ADR 0666 §19).
|
||||
- §66 **What the checker did, and where it goes.**
|
||||
|
||||
| The checker's | From G's delivery |
|
||||
|---|---|
|
||||
| the merge check, one status `mesh/check` | the pipeline, its result in `pipeline-ended`, the runner's `show` and the delivery record; no status on the forge |
|
||||
| the try | the run on request |
|
||||
| the composition check | the composition step, the assigner's |
|
||||
| the kept verdicts and the carry | the runner's `kept`, which starts empty, and its carry (ADR 0548 §5); the checker's verdicts go with its buckets |
|
||||
| the checking program: the manifest validator at the heads, the cycle rule, the composer, the rule names | the indexer's validation before take-in; the indexer's cycle rule; the composition step; `widens` in the pipeline's end |
|
||||
| the `run` ask, published by the checker's account | published by the runner's account and the indexer's (its validation), each with step keys under its own name |
|
||||
| its buckets and bus account | removed with the gate's forge merge token after the first pipeline after G's delivery has delivered a change end to end, on the operator's one word, the buckets through `drop` after its snapshot, whose restore is tested first (ADR 0667 §12) |
|
||||
|
||||
- §67 **How it is checked.** Whole (runner, assigner): a run on request of `feature/1966-x` delivers nothing, and a call of
|
||||
`run` from another account is refused; a composition step refuses two modules owning one path; a step whose part
|
||||
cannot be heard ends `could not run`; a node asleep passes with a warning; a runner stopped mid-step takes its
|
||||
pipelines up again. Live: once the checker is removed no module claims the seat `mesh-delivery-checker`. Tests 64,
|
||||
84 and 88.
|
||||
|
||||
### Who may say what
|
||||
|
||||
- §68 **Callers per verb, in the seat's definition.** Each seat definition names, per verb, the callers allowed to call it,
|
||||
in its field `callers` (holder roles, modules, the operator, the healers), which the grants part reads as its one
|
||||
source of caller limits; the runner's verbs are the first (ADR 0666 §20). A verb whose definition names no `callers`
|
||||
takes the default of its class (§391), so no verb is closed for lack of a list. It replaces the holder's
|
||||
trusted config value `callers` of the seat `git` and the package seats (design 51), which no holder set for most
|
||||
verbs. A format its running reader cannot read, so its reader is delivered first (ADR 0667 §4).
|
||||
- §69 **A step's result from its holder only.** The runner takes a step's result only from the bus account of that step's
|
||||
seat holder, naming the commit and the build-input fingerprint; a result from another account, or naming another
|
||||
commit or fingerprint, is refused and said (ADR 0666 §21).
|
||||
- §70 **A review gates, once it can.** (§482) A code review's result is taken through the verb `review` from the reviewer's bus
|
||||
account, and in G refused from no one for authorship, recording the caller the bus proved (§482); a review is to gate where a policy says `required` once a record binds the accounts (ADR 0666 §22, which
|
||||
changes ADR 0659 §2). Until a record binds each forge account to its bus identity no policy may say it (*A module's
|
||||
registration*; issue 1966, note 157).
|
||||
- §71 **A widening is said, never refused.** A change that alters any part serving a step (the runner, step detection, the
|
||||
lint rule, a build seat's test runner, the composer), or widens an acting rule, a grant or a credential, says so in
|
||||
its `pipeline-ended`, its field `widens` naming each. The running runner computes it, the serving parts from the seat list by fingerprint and the grants through the seat `mesh-grants`' verb `grants-for` (§441), from the sources of the change
|
||||
against those of the watched branch, never the runner built from the change, so a change cannot hide its own
|
||||
widening; nothing refuses it for that (ADR 0666 §23, the operator, 2026-11-10). The teller passes `widens` on to every
|
||||
channel, as it does `recovery-merged`. This is what replaces ADR 0656 §8's naming in the Approve ask.
|
||||
- §72 **How it is checked.** (§482) Whole (runner, grants part): a forged step result is refused and a review records its proven caller; a
|
||||
caller not in a verb's `callers`, and any caller but the holder roles and the operator of a verb with no `callers`,
|
||||
are refused by the bus; a change widening a grant, and one altering the Go build module's test runner, each name it in
|
||||
`widens`, computed by the running runner, and are delivered. Tests 85 and 86.
|
||||
|
||||
### Health and going back
|
||||
|
||||
- §73 **The runner's events.** Each on `mesh.seat.mesh-pipeline-runner.event.<event>.<module>.<branch>.<commit or group>`,
|
||||
a branch or group name escaped as a key's name is, declared in the seat's definition and published by the runner's
|
||||
account alone (ADR 0666 §24, ADR 0363 §2):
|
||||
|
||||
| Event | Says |
|
||||
|---|---|
|
||||
| `pipeline-started`, `pipeline-ended` | a pipeline's start, and its end with every step's result, its test counts, the source revision and `widens` |
|
||||
| `group-ended` | a group's one pipeline's end, on which alone its wants are written |
|
||||
| `run-started` | a run on request's start, with its members at their commits |
|
||||
| `step-started`, `step-ended` | each step's start and end: the step, its status in the policy, its outcome, its build-input fingerprint, its test counts, for a carried result what it was carried from, and for a rerun that it is one and the caller the runtime proved; a bypassed step too |
|
||||
| `wait-held`, `wait-ended` | a step's wait: on a person (approval, code review), on a build node, on the revision view |
|
||||
|
||||
- §74 **What a session hears after its merge.** Between its `merge-pull` and the delivery, a session waits with `mesh_wait`
|
||||
on the runner's `pipeline-started` and `pipeline-ended` of its module, branch and commit, and then on the status
|
||||
view's `delivered` of the source revision; a failed head reaches it as `pipeline-failed` (*What holds a delivery*).
|
||||
- §75 **Nothing stands still unseen.** (§496) The watchman times each step's owed progress from `wait-held` and `step-started`,
|
||||
never by run length; a step or a watched branch's head standing still five minutes is its finding `pipeline-stands`,
|
||||
raised by the condition keeper and cleared on the step's end, or for a head on its `pipeline-started` (ADR 0564 §2,
|
||||
§4). A step waiting on a person is said as waiting on that person (ADR 0283), never as a stall; a group waiting for
|
||||
its members, and a merge behind a pending revision, are governed by their own bounds (ADR 0666 §24). At its start the
|
||||
watchman seeds its open waits from the runner's `stands`, as it seeds `take-in-waits` from `indexer_pending`.
|
||||
- §76 **The runner's own health.** Its manifest declares its health check, run outside its process (ADR 0240; its tool
|
||||
`runner_health`, §468): the runner
|
||||
answers `show`, and its durable consumers of `step-ended`, `take-in.taken`, `package-published`, `toolchain-agreed`
|
||||
and `decided` are
|
||||
each less than five minutes behind; a failed check names which.
|
||||
- §77 **The runner waits for its view.** With no revision view it says so in `<module>_first_views` and raises `view-waits`
|
||||
after five minutes, as every part does (ADR 0656 §6).
|
||||
- §78 **Going back is one act.** A failing new version holds while the old keeps running (ADR 0656 §4); a return to an
|
||||
earlier build is one value in the placement, the module's `build` in its placement key, set by a placement proposal
|
||||
(ADR 0666 §24).
|
||||
- §79 **How it is checked.** Whole (runner, watchman): a step whose worker dies raises `pipeline-stands` within five minutes;
|
||||
a head with no pipeline raises it and its `pipeline-started` clears it; a step waiting on the operator raises nothing;
|
||||
a runner restarted mid-step takes its pipelines up again; its health check fails naming a consumer behind. Whole
|
||||
(assigner): a placement's `build` delivers the earlier build. Tests 76, 77 and 88.
|
||||
|
||||
### A recovery fix is an ordinary merge
|
||||
|
||||
- §80 **No recovery merge verb.** A fix that recovers the mesh is merged like any change on a watched branch, by a
|
||||
session's `merge-pull`, and runs its pipeline; no trusted recovery merge verb is built (ADR 0666 §26). The shape this
|
||||
design sketched for one (a verb `merge` of the seat `mesh-delivery` with `recovery-callers`) is dropped.
|
||||
- §81 **Named a recovery, said everywhere.** Its author names it a recovery by the seat `mesh-delivery`'s verb `recovery`
|
||||
(the commit and why), never by a pull request's text: the gate marks the delivery record `recovery` with the caller
|
||||
the runtime proves, and says `recovery-merged`, which the teller passes on to every channel (ADR 0666 §26). A widening
|
||||
it carries is said in its `pipeline-ended` like any other (*Who may say what*).
|
||||
- §82 **How it is checked.** Whole (gate): a merge named a recovery is marked in its record and `recovery-merged` is said;
|
||||
a recovery named by a description is not. Test 92.
|
||||
|
||||
### The tools
|
||||
|
||||
§83 Every new state has a tool that reads it and every new act a tool that starts it by hand (ADR 0666 §25), against the
|
||||
four lines a module's tools answer; every read tool of a store also answers its fill against its cap:
|
||||
|
||||
| Part (seat) | Read everything it holds | Do by hand what it does itself | Recover without help |
|
||||
|---|---|---|---|
|
||||
| the runner (`mesh-pipeline-runner`) | `show` (one pipeline whole, its steps, step log and outputs), `list` (the pipelines of a module, a branch or a commit), `stands` (what stands still, since when), `why-carried` (where a step's result was carried from), `kept` (the kept results by digest) | `start` (a delivering pipeline for a watched branch's head), `run` (the run on request), `rerun` (one step again) | `accept` (a failed `optional` step, with why), `cancel` (a pipeline, dropping its asks), `ask-again` (a declined `approval`) |
|
||||
| the indexer (`mesh-catalog`) | `registrations` (with each one's history); `indexer_edges` (an artifact's edges per watched branch), `indexer_order` (the order of a change, its cycles named), `indexer_catalog_difference` (two revisions); `indexer_pending` (a group with its missing members) | `register`, `policy`, `watch`, `unwatch` (each on the operator's Approve) | `indexer_seed_again` (the first registrations' ask raised again), `indexer_take_in_pending` (a group taken in as gathered), `indexer_drop_pending` (dropped) |
|
||||
| the gate (`mesh-delivery`) | `source` with a branch (`gate_source`: every branch's source revision) | — | `gate_source_again` (say `source-moved` again for a branch) |
|
||||
| the forge module (`git`) | `git_failed_steps` (the failed `required` steps it holds, by module and fingerprint, with the run that failed each) | — | `git_failed_steps_drop` (drop one, with why) |
|
||||
| the grants part (`mesh-grants`) | `grants_callers` (the callers each verb admits, read from the seat definitions) | — | `grants_why_refused` (for an account and a verb, the grant it lacks and the definition's line that leaves it out) |
|
||||
| the build seats | `steps` (the steps detected for an artifact) | `steps` with `again` (detect again) | `drop` (ADR 0664 §4) |
|
||||
| the builder (`mesh-builds`) | `builder_reserved` (each module's reserved version, §369); the released versions are the catalog's `versions` | `builder_publish_again` (publish the missing artifacts under the reserved version) | — |
|
||||
| the assigner (`mesh-wants`) | `assigner_why_not` (why a module is not on a machine yet: the pipeline it waits for, its hold, the placement, the node's last `running`) | — | `assigner_release` |
|
||||
| the watchman | `watchman_views` (how far behind each part's view is, from each part's `view-waits` and first-view tools) | — | each part's `<module>_first_view_again` |
|
||||
| the operator-channel | `asks` (with each asker's asks held back) | — | `ask-again` (offer a held-back ask now) |
|
||||
| every module that imports mesh-sdk's small object store package | `<module>_state_objects`, `<module>_sent_objects` | — | `<module>_state_objects_drop`, `<module>_sent_objects_drop` |
|
||||
|
||||
§84 Every recover tool writes its act, who and why in the hand-act log of the seat `mesh-hand-acts`. How it is checked:
|
||||
each tool answers its whole-module test's case, every store's read answers its fill, and `mesh_describe` reads each
|
||||
(test 93).
|
||||
|
||||
### The runner's kept data, and the build capacity
|
||||
|
||||
§85 Each kept form names its section or template, its retention and its cap; a state template fixes its own size (ADR 0595
|
||||
§3), up to 64 MiB, and an old `state` section names its cap in `max-mib` (§469); the step log is an old `logs` section (§470). The numbers are estimates, to be read from the fill tools once G runs: an estimated 20 merges a day
|
||||
(as *The delivery record*), each starting a pipeline for about 3 modules of its repository whose fingerprint it changes,
|
||||
and an estimated 2 mesh-sdk publishes a day, each rebuilding about 120 artifacts and starting about 120 pipelines: about
|
||||
300 pipelines a day, of about 6 steps each.
|
||||
|
||||
| Part | Kept data | Section or template | Retention | Cap and growth |
|
||||
|---|---|---|---|---|
|
||||
| the runner | `pipelines`: one key per pipeline, keyed by watched branch, source revision and module, its steps, statuses, outcomes, fingerprints, test counts and end count | old `state` section | a pipeline that ended passed on a watched branch, 90 days after its end; any other (failed, superseded, cancelled, a run on request's), 7 days after its end; always while it is its branch's newest for its module; deleted by the runner (§330) | 256 MiB; about 300 pipelines a day of about 4 KiB is at most about 105 MiB at 90 days, under half the cap |
|
||||
| the runner | `step-log`: one entry per step's start and end, its output, its pipeline and the `kept` digests it wrote or carried, by name | old `logs` section (§470) | by its age, 100 days, ten days past the longest retention it ages (§330) | 128 MiB; about 3,600 entries a day of about 0.2 KiB is about 70 MiB at 100 days |
|
||||
| the runner | `step-outputs`: a lint or test step's output | old `objects` section | 7 days | 1 GiB; about 1,200 steps run a day (the rest carried) of about 50 KiB is about 420 MiB at 7 days |
|
||||
| the runner | `kept`: a package's passing result per level, by digest, with `last-read`, written by each carry that reads it | old `state` section | 30 days after `last-read` | 128 MiB; a publish changes the digest of about 1,000 importing packages at two levels, so about 4,000 entries a day of about 0.3 KiB, about 36 MiB at 30 days |
|
||||
| the indexer | `versions`: one key per module and version, its artifacts' digests, the source revision and its edge entries (§349, §374) | old `state` section | a module's newest 100 versions, never its highest of a `MAJOR.MINOR`; the indexer deletes the older at each release | 64 MiB; about 120 modules of 100 versions at about 3 KiB is about 35 MiB; at the cap it records no release and says `full`, as *A store filling is said before it is full* |
|
||||
| the indexer | `build-edges`: the newest build's edge entries per module and watched branch other than the first | old `state` section | replaced by the next build; deleted when the branch is unwatched | under 1 MiB |
|
||||
| the builder | `reserved`: one key per module and `MAJOR.MINOR` (§369) | old `state` section | until its `version-released`, a release of another `MAJOR.MINOR` of the module, or `builder_reserved_drop` | a few KiB; at the cap the builder reserves nothing and says why |
|
||||
| the assigner | `written`: the last source revision and end count per module and watched branch (§352) | old `state` section | until the branch is unwatched | a few KiB |
|
||||
| the indexer and the assigner | `inputs`: a `run` ask's input by write time and sha256 (§354) | old `state` section | until the ask's `step-ended`, at most a day | 64 MiB; an estimated 300 asks a day of under 100 KiB each is under 30 MiB; at the cap the asker refuses the ask loudly, naming the store |
|
||||
| the runner and the assigner | `findings` (§355), read by their own read tools alone; the keeper hears the findings as events (§383) | old `state` section | an entry deleted when its finding clears | a few KiB |
|
||||
| every observer (§387) | the stream `<module>-findings`: `finding-written`, `finding-cleared`, `findings-stated` (§383) | old `streams` section for a foundation part, the module's `streams` otherwise | 7 days, its oldest dropped when full; the next statement covers a lost one (§384) | 16 MiB; an estimate of a few hundred findings a day of about 1 KiB is under 2 MiB a week |
|
||||
| the condition keeper | `findings-views`: one key per observer and node, its last stated set, pending keys and open asks (§388) | the form of its `conditions` | deleted when the module leaves the catalog or the node (§365) | under 1 MiB; about 30 observers on four machines |
|
||||
| the watchman, the keeper | `machine-away`, `machine-back` (§389); `findings-asked` (§390) | the seats' definitions | 90 days; 7 days | 16 MiB each |
|
||||
| the runner | `aged`: three step-log sequences (§330) | old `state` section | never deleted | bytes |
|
||||
| the teller | `widens-said` (§335) | the template `state` | until the branch is unwatched | a few KiB; the template's |
|
||||
| each build module | `detected-steps` (§326, §414) | the template `state` | one key per artifact, dropped by `steps` with `drop` | under 1 MiB; the template's |
|
||||
| the indexer | `registrations` and its stream `registration-history` | old `state` and `streams` sections | the life of the mesh, never derived again | 64 MiB (*A module's registration*) |
|
||||
| the indexer | catalog graph revisions, per watched branch | objects in the old form (ADR 0619 §2) | while a record pins it or it is its branch's current | 512 MiB, as *Two graphs, one source of truth*; a second watched branch on a few modules adds a few MiB |
|
||||
| the gate | `source`: one key per watched branch | its old `state` section, as today | 64 revisions of each key; a branch's key deleted when `registration-changed` says the last registration watching it unwatched it (`unwatch`) | a few KiB |
|
||||
| the forge module | `failed-steps`: failed `required` steps by module and fingerprint, with the commits each was seen at, and the `seen` keys of §487 | state | an entry deleted once none of its commits is a watched branch's head or the head an open pull request names, judged on each `branch-moved` and each pull request's close | the template's 64 MiB; an estimated 50 failed steps a day of about 0.5 KiB, most gone within days, is under 1 MiB; the `seen` keys under 3 MiB (§487) |
|
||||
| every module that imports the object store package | `<state>-objects` beside each state | objects | an object no key names, its key deleted or its value replaced, until `<module>_state_objects_drop` on a person's word (ADR 0595 §4, ADR 0666 §16) | the template's; nothing reserved until written |
|
||||
| every sender of the new form, and a foundation part by its old `objects` section | `<module>_sent-objects` (`sent-objects`) | objects, planned by the bus module with no manifest field | 7 days, transport (*Any size*) | the objects template's cap; full is the bus module's `full` |
|
||||
| the grants part, for each seat event a definition keeps | `SEAT_<seat>_<event>`, among them `SEAT_mesh-pipeline-runner_*`, `SEAT_<build seat>_step-ended`, `SEAT_git_branch-moved`, `SEAT_mesh-builds_package-published` and `SEAT_mesh-catalog_registration-changed` | a stream the grants part makes with its durable consumers | the seat definition's age (`keep`), 90 days unless the definition names less | the seat definition's cap (`keep`) |
|
||||
|
||||
§86 **The build capacity.** A pipeline runs a module's steps once per commit on a watched branch, carrying what did not
|
||||
change, where the check ran before the merge and carried after each trunk move; a group runs one pipeline per watched
|
||||
branch; a second watched branch runs the steps of the modules it carries once more. A mesh-sdk publish asks about 120
|
||||
lint steps and the test steps of the importing packages, about 240 more, the rest carried; a newer publish of the same
|
||||
package supersedes the older one's rebuild pipelines, so two publishes close together run the rebuilds once. The first
|
||||
pipelines after G's delivery run every module's lint and tests once, since `kept` starts empty (ADR 0666 §21): about
|
||||
120 modules, one round of the build capacity, taken in the `check` stream's order; from then they run only what
|
||||
changed. Before G's delivery the operator is told the first round's estimated length, read from the checker's last
|
||||
full check of every module (its verdicts' run times). A step queued on `check` behind the build capacity is said
|
||||
`wait-held` on a build node and is owed no progress while queued; the watchman raises one finding per build seat,
|
||||
`build-capacity-waits`, when that seat's oldest queued step has waited 60 minutes, cleared when it starts. A pipeline
|
||||
whose commit is named a recovery has its steps asked on the build seats' stream `delivery`, ahead of the first round. The steps go on the build seats' stream `check`,
|
||||
after `delivery` and before `recheck`, within each node's build capacity (ADR 0525 §4, §5), and a run on request on the
|
||||
same stream. How it is checked: live, the build nodes' time per day in the week after G, read from the build modules'
|
||||
step keys and the builder's build log, against the checker's last week (test 99).
|
||||
|
||||
### How milestone G itself is checked and delivered
|
||||
|
||||
- §87 **Under the clauses as they stand.** Milestone G is checked and delivered under ADR 0656 §5 and the clauses it changed
|
||||
as they hold before its delivery: the acting parts (the indexer, the build modules, the assigner, the grants part,
|
||||
the node-engine, and the checker, which makes G's own check) act from the milestone's heads, each first in its step,
|
||||
in the order of their ordering edges; the trunk-only want, the checker and the Approve per merge as ADR 0661 leaves it
|
||||
govern this delivery, no exemption is asked and no delivery goes ahead of it (ADR 0667 §2, ADR 0644 §1).
|
||||
- §88 **Its other parts as any module.** mesh-sdk's small object store package is a package published before the modules
|
||||
that use it build; the manifest validator moves with the indexer; the runner is placed like any module and acts only
|
||||
from the next change on; mesh-delivery's `contracts` and `sdk` move under its `modules/` folder (ADR 0667 §3).
|
||||
- §89 **Readers first** (ADR 0667 §4): an edge of kind 4 each, which the delivery plan derives: the indexer's manifest
|
||||
validator for the seat definitions' new fields (`callers`, `acts`, `subject`, `keep`) before any definition using
|
||||
them; the manifest validator for the manifests' new words (`version`, §368; a `*` emitter in `consumes`, §386; the
|
||||
finding stream an observer declares, §383, §387) and the grants part that makes a `*` entry's durable consumers
|
||||
(§386), before the keeper's or any observer's manifest using them; on each node the node-engine that reads `form`
|
||||
and `catch-up` from the catalog graph before any manifest writing them. The manifest validator the running checker asks for G's own check is the indexer's, so the reading
|
||||
of these words goes in the reader release of §340, whether or not that validator already takes one of them.
|
||||
- §90 **The stream maker first** (ADR 0667 §5): the grants part, which makes each kept seat stream and its durable
|
||||
consumers, before the parts that hear a kept stream.
|
||||
- §91 **The bus module first** (ADR 0667 §6): the bus module, with `<state>-objects`, the sent store and the grant of each
|
||||
state's object store to its owner alone, before the modules requiring them, the grants part writing that grant from
|
||||
the milestone's heads.
|
||||
- §92 **A bus restart is said first** (ADR 0667 §7): a restart of the bus its new version needs is said to the operator
|
||||
before it starts.
|
||||
- §93 **A gap is built, not worked around** (ADR 0667 §8): an order the delivery plan cannot derive from the edges is
|
||||
built in the delivery system within G, never a step done by hand.
|
||||
- §94 **A node-engine of the version before applies what it can read** (ADR 0667 §9): it applies every want whose
|
||||
manifests use no field it does not read, and a want that uses one waits, by the reader edge above, for the new
|
||||
node-engine on that node.
|
||||
- §95 **The new assigner and the new merge switch on the first registrations** (§507) (ADR 0667 §10). The new assigner, which
|
||||
writes a want only from a passed pipeline, is delivered before the forge module's new `merge-pull`, which asks no
|
||||
Approve, and before the runner: an edge of kind 4 from `mesh-delivery-assigner` to each, declared in their manifests'
|
||||
`needs` since nothing else says it (the design's choice: without it a merge made with no Approve could reach the old
|
||||
assigner, which writes wants from the trunk with no pipeline). Both switch on one fact, the first registrations
|
||||
written, said as `registration-changed`: until it the new assigner keeps the trunk-only want and the new
|
||||
`merge-pull` refuses every merge, naming the missing registrations.
|
||||
- §96 **Until then, the operator merges a fix at the forge** (ADR 0667 §11). Until that fact an ordinary change waits for
|
||||
it, and a fix, an indexer fix included, is merged by the operator's own hand at the forge (ADR 0661 §3) and
|
||||
delivered under the trunk-only want still in force; `indexer_seed_again` asks the first registrations again. Before
|
||||
the milestone's delivery every merge goes as ADR 0661 has it.
|
||||
- §97 **What is removed, and when** (ADR 0667 §12). The checker's buckets and bus account and the gate's forge merge token
|
||||
are removed only after the first pipeline after G's delivery has delivered a change end to end (test 63), the runner
|
||||
then making every check in the checker's place (ADR 0666 §19), and only on the operator's word, asked once for all
|
||||
three. The checker then leaves the manifest validator's list of foundation parts (ADR 0666 §4) and is no longer
|
||||
placed, so no running module declares its buckets and they
|
||||
are orphans `orphans` lists; they go through ADR 0595's `drop` on that word, after the stream snapshot `drop` takes
|
||||
(ADR 0595 §4, §5, §10), kept 90 days as the delivery records are; a restore from that snapshot is tested before the
|
||||
word is asked. The runner is on that list from G's delivery.
|
||||
- §98 **Told to the operator before it starts**: G's own merges are asked as ADR 0661 has them (ADR 0667 §2, §11); the one
|
||||
ask of the seed registrations; the bus's restart (ADR 0667 §7); and later the one word for the checker's removal
|
||||
(ADR 0667 §12).
|
||||
- §99 **How it is checked.** Tests 94 to 97, and 100 for the assigner's edge.
|
||||
|
||||
### Choices this design makes under ADR 0666 and ADR 0667
|
||||
|
||||
| Choice | Why |
|
||||
|---|---|
|
||||
| §100 The registrations in the indexer's old `state` section with their history on an old stream, capped at 64 MiB, kept for the life of the mesh | the indexer is a foundation part (ADR 0619 §1 to §3, ADR 0666 §4); a registration keeps every version with who and on whose Approve, and nothing derives it again |
|
||||
| §101 The runner a foundation part, its kept data in old sections, on the manifest validator's list | the bus module's own delivery waits on its pipeline (ADR 0666 §4) |
|
||||
| §102 The want's compare-and-set keyed per module and watched branch | source revisions are kept per watched branch, so two branches' revisions have no order; comparing across them would let `main` refuse `beta`'s want (ADR 0666 §9) |
|
||||
| §103 A second start of a pipeline for the same source revision and module, on `take-in.taken` and `package-published`, writes nothing, by the pipeline key's compare-and-set | the runner starts on both events (ADR 0666 §13, §15); the key it already keeps judges a start in one place, so neither producer knows of the other |
|
||||
| §104 A refused new indexer revision holds later merges on its branch until a revert lands | the validation runs after the merge and before take-in (ADR 0666 §6), so the refused revision has landed; taking later merges in past it would deliver them on a graph its own validator refused |
|
||||
| §105 Each check at its row's level, every requirement R1 to R18 keeping its named test | the record's rows name the levels; the test budget and one check per release bound what runs |
|
||||
| §106 The approval asked as the operator-channel's accepted kind `ask` on its work queue | the seat has no verb `ask`: an ask is its accepted kind, by the asker's own name (design 46 §10) |
|
||||
| §107 The gate no foundation part, its `source` in the old section it already has | the bus module's delivery waits on no act of the gate's; only the key's layout changes |
|
||||
| §108 The verbs `register`, `policy`, `watch`, `unwatch`, `registrations` and the event `registration-changed` on `mesh-catalog` | one verb per trusted act, each its own ask naming what moves; readers follow the event (ADR 0596) |
|
||||
| §109 The seed of the first registrations proposed as one ask, with the statuses the check judged until then and approval `bypass`, raised again by `indexer_seed_again` | registering is a trusted act (ADR 0666 §3), and one ask for the whole seed is how placement was adopted (ADR 0567); a declined or expired seed must not leave the pipelines stopped with no way on |
|
||||
| §110 Code review refused as `required` until forge accounts are bound to bus identities | a review is refused from its author (ADR 0666 §22), which cannot be told until then |
|
||||
| §111 The seat `git`'s read verb `branch` and event `branch-moved`, kept on its seat stream | registering reads a branch's protection through the seat `git` (ADR 0666 §3); a branch moves by a merge or an admitted push; a stream keeps a move said while the bridge is down |
|
||||
| §112 The watchman's finding `pipeline-stands` for a step or a head still five minutes, cleared by the step's end or the head's `pipeline-started` | the record's five minutes (ADR 0666 §24), one finding for both, timed from the runner's events |
|
||||
| §113 The runner's finding `pipeline-failed`, the author's for two hours, then the operator's | ADR 0666 §24 with ADR 0505 §5, §6 |
|
||||
| §114 A module's watched branch and its `build` in the placement key, set by a proposal | a placement names the branch and a return is one value in it (ADR 0666 §6, §24); every placement change goes by a proposal (ADR 0567) |
|
||||
| §115 The gate's `source.<branch>`, its verb `source` and event `source-moved` | one source revision per watched branch (ADR 0666 §6), read through the seat (G's prepare, the indexer's watch of the gate's `source`) |
|
||||
| §116 The build seats' verb `steps` and accepted kind `step` on `check`; the assigner's kind `step` on `mesh-wants`; `step-ended` with the node in its subject | steps are asked on the serving seat's work queue (ADR 0666 §7); the `check` stream already orders after `delivery`; a node's account says only its own node's ends (ADR 0363 §2) |
|
||||
| §117 The outcomes, with `accepted`, `superseded` and `cancelled`; an approval expired or refused ends `declined`, replaced `superseded` | every end a step can have is said as itself (ADR 0666 §10) |
|
||||
| §118 `approval` asked last, its `about` the module and the branch | the operator approves a commit the mesh already judged, the point ADR 0661 §3 kept in one place; one ask per module and branch, a newer commit's replacing it |
|
||||
| §119 A superseded pipeline's asks dropped by `drop`, and a pipeline superseded only when its module's fingerprint changed | a cancelled pipeline must leave no ask on a stream (ADR 0664 §4; G's prepare); a module the commit did not change has nothing newer to run (ADR 0666 §9) |
|
||||
| §120 A group's wants only on `group-ended` | a group is delivered whole or not at all (ADR 0666 §9; research 086, R5) |
|
||||
| §121 The six kinds derived from existing words, `needs` per artifact only where nothing says it | one form of each fact (ADR 0656 §6); the catalog entry's `needs` already exists from G |
|
||||
| §122 The cycle rule in the indexer's graph | the graph is the indexer's, and the checking program goes with the checker |
|
||||
| §123 The builder's seat `mesh-builds` and its event `package-published` | the record has the builder say the publication as an event of its seat (ADR 0666 §15); its waits move only when a later change gives the seat its verbs |
|
||||
| §124 Each state's object store `<state>-objects` through mesh-sdk's small package, sent objects readable by a standing grant derived from the catalog graph, and the package's two tools | one object store per state, granted to its owner; the sender's declared callers and consumers read what it sends (ADR 0666 §16); only its users import it, so nothing else is rebuilt |
|
||||
| §125 The `run` ask published by the runner's account and the indexer's; the verb `run` called by the sessions and the operator only | the steps and the validation run code from the change, in the sandbox only, and each asker reads only its own step keys (ADR 0363 §2); a run publishes tagged packages, so nothing else may ask one |
|
||||
| §126 The checker's step `check` becomes `pipeline`; `kept` starts empty | the runner is an acting part in the checker's place (ADR 0666 §19); a step's result is taken only from its seat holder (ADR 0666 §21), so no verdict of the checker's is taken in, at the cost of one round of every module's tests |
|
||||
| §127 The field `callers` in a seat definition replaces the holders' trusted `callers`, a missing field taking the default of the verb's `class` (§391) | the record makes the definition the grants part's one source (ADR 0666 §20); a field left out must open nothing |
|
||||
| §128 `widens` in `pipeline-ended`, computed by the running runner over every part serving a step, passed on by the teller | a widening is said and nothing refuses it (ADR 0666 §23, §26); a change must not compute its own |
|
||||
| §129 `merge-pull` judged from the forge module's own `failed-steps`, heard from `step-ended`, any run at the head's fingerprint counting unless a later rerun of that step passed, the protection read again on every `branch-moved` | the record has it judge from the events it consumes, never by a call, so a head merges while the runner is down (ADR 0666 §11, ADR 0596) |
|
||||
| §130 The new assigner before the new `merge-pull` and the runner, by `needs`, both switching on the first `registration-changed` | a merge with no Approve must never reach an assigner that writes wants without a pipeline (ADR 0667 §10) |
|
||||
| §131 The forge bridge's finding `branch-unprotected` | a move of a branch whose protection was removed starts no delivering pipeline and is said (ADR 0666 §6) |
|
||||
| §132 The verb `recovery` of `mesh-delivery` | a recovery is named by its author as a fact the bus keeps with who said it, never read from text (ADR 0666 §26, research 086 R10) |
|
||||
| §133 No forge status for a pipeline | the step results live on the bus (research 086 R10), shown by the runner and the delivery record; a status would be a second copy outside it |
|
||||
| §134 The runner's `pipelines` kept 90 days after a passed end on a watched branch, any other 7 days, its step log 100 days by age, its outputs 7, keyed by source revision | a retention the runner judges from its own facts, never from the gate's records (ADR 0596); the delivery record keeps the step events itself (§34), so nothing it traces needs the runner's key; one nothing delivered has no trace to serve after a week; about 300 pipelines a day must fit its caps, so they are 256 MiB and 128 MiB |
|
||||
| §135 The forge module's `failed-steps` kept until no head it was seen at is a watched branch's head or an open pull request's | an entry judges only a head that can still be merged; once neither names it, nothing can be refused by it |
|
||||
| §136 The gate's `source.<branch>` deleted when the branch's last watch goes | a branch no registration watches makes no revision, so its key would name nothing |
|
||||
| §137 The sent store kept 7 days by age, with no person's word | an object sent carries one reply or event and is transport, not kept data (ADR 0595 §4); its readers fetch it within a call's bound or a consumer's lag |
|
||||
| §138 A replaced object listed `key gone`, as a deleted key's is | no key names it, and one listing lets the drop tool take both on a person's word, as ADR 0595 §4 and ADR 0666 §16 have it |
|
||||
| §139 The checker's removal after the first end-to-end pipeline, on one word, its buckets through `drop`, the snapshot kept 90 days | ADR 0667 §12 asks one word for all three; 90 days is the delivery records' retention, the longest a trace reads back |
|
||||
| §140 The pipeline steps on the `check` stream after `delivery` | a delivery's build is never held by a pipeline, as a check never held one (ADR 0525 §4) |
|
||||
@@ -0,0 +1,690 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0667"
|
||||
---
|
||||
# 47, milestone G — the lead's answers to develop's questions
|
||||
|
||||
The operator, 2026-11-11 about 21:30 CET: a develop question is tested against [the principles](../../../00-META/principles.md)
|
||||
and answered by the lead in one line, without a design batch or a review round for a detail. Each answer here is one line,
|
||||
names the principle that settles it, and is cited as "design 47 G A-n".
|
||||
|
||||
- **A-1** (§686, §640, §687, §689; principle 7, principle 3) `views.FindingBody`'s accessors follow Go's idiom, `Kind()` and
|
||||
`SetKind(v)` per field; the subject is one pair, `Subject()` and `SetSubject(...)`, with its fields as plain values in the
|
||||
contract struct's order; the writer is given the generated contract's constructors for `finding-cleared`, `findings-stated`
|
||||
and `findings-asked` when it is made, never plain maps, so a contract mismatch fails at compile time; `Observed.Findings`
|
||||
and `Pending` hold `FindingBody` values.
|
||||
- **A-2** (§655; principle 7, the rule that code shared between modules lives in mesh-sdk) `nats-tools/internal/busarchive`
|
||||
moves whole with `bussnap` into mesh-sdk as its own package `go/busarchive`, its tests with it; every other importer
|
||||
(verify, the nightly dump) imports it from there, and no copy stays in mesh-catalog.
|
||||
- **A-3** (§686, §640, §687; principle 7; replaces A-1) The generated contract types stay as they are, plain structs whose
|
||||
fields every importer already reads; nothing is generated for the writer and no field is renamed. mesh-sdk's writer is
|
||||
generic over the contract's body types (`Writer[F, C, S, A any]`, one type parameter per body) and works on their wire
|
||||
form: it marshals a body, reads and sets `kind`, `scope`, `id`, `subject`, `at` and `since` by their contract names,
|
||||
and runs `views.CheckFinding` on that form. A body whose wire form lacks a field the contract names fails loudly at its
|
||||
first write (principle 3) and in the writer's unit test, which runs against the real generated types of
|
||||
`mesh-conditions`.
|
||||
- **A-4** (§556; principle 7, and who may do what is no question) The grants part grants a reader of a stream read whole
|
||||
every subject an ordered consumer of that stream uses: its creation, its next message, its info and the deletion of
|
||||
its own consumer.
|
||||
- **A-5** (§614, D-5; principle 7) The branch follower of §614 (registrations, the gate's `source`, adopt and drop of
|
||||
views, entries by fingerprint kept asked, answered or not found) is built once, in `meshcatalogclient`, and every
|
||||
part named in §614 uses it; none builds its own.
|
||||
- **A-6** (§693; principle 7) The grants part keeps reading the assigner's `wants` view and consumes no `wanted`: a want
|
||||
the assigner removes leaves that view, and the grants part composes no account for it. The `removed` field of §693 is
|
||||
added to the seat `mesh-wants` for its other readers.
|
||||
- **A-7** (GR10, §276, §266, §269; principles 1 and 7) The grants part holds the entries of a revision whose accounts its
|
||||
new version has not yet composed; it keeps the hold in its own state on the bus; `clock-from` is the start of its new
|
||||
version's first pass, which it knows itself, so it hears no `build.ended`; the bound is §266's granting bound of 10
|
||||
minutes, a constant of the part.
|
||||
- **A-8** (§613; principle 7) The template state takes the local name; the gate opens each old section once at its start
|
||||
by the section's bus name, its own data, and copies a key only when the template lacks that key. No clock or
|
||||
sequence is compared. Once every key is copied the old section is left to its retirement (§613).
|
||||
- **A-9** (CT15, §476, §493, §530; principle 8) The `bodies` types are named `TakeInTaken`, `TakeInPending`,
|
||||
`TakeInMerge`, `TakeInMember` and `TakeInEntry`, as built in mesh-delivery f38e45fd..1c09de8d.
|
||||
- **A-10** (§276, §498; principles 1 and 7, and who may do what is no question) A holder says a seat event through one
|
||||
mesh-sdk call, `Conn.EmitSeat(seat, event, body)`, publishing on `mesh.seat.<seat>.event.<event>`; the grants part
|
||||
grants every holder publish of its held seats' event subjects.
|
||||
- **A-11** (GR10; principle 7) A wait's `version` is the holder's own running fingerprint, as the indexer names it.
|
||||
- **A-12** (GR10, §276; principle 4) `grants_release` ends the granting wait: the held entries go on, the wait is said
|
||||
ended with `how` `released`, and no `acting-part-late` is raised for it.
|
||||
- **A-13** (§461, §439, §383; principle 7) mesh-sdk's writer forms its three own subjects (`finding-written`, `finding-cleared`, `findings-stated`) itself, in §383's form
|
||||
`mesh.stream.<module>.event.<event>.<node>`, from its module and node, through one function
|
||||
`views.FindingSubjects(module, node)`; a module passes no subjects. It hears `findings-asked` on the seat's subject `mesh.seat.mesh-conditions.event.findings-asked` (§387, §390, A-17), never on the part's own stream.
|
||||
- **A-14** (§580; principles 7 and 8) A finding that told several findings of one kind on one id apart by a `ref` puts that
|
||||
part into its id (`<id>.<ref>`, each escaped as §73 escapes a name); a kind stays a fixed word of its bundle's list. The
|
||||
key's scope and id and the subject's scope and id are set equal, as before.
|
||||
- **A-15** (§614; principle 3) In the branch follower an `entry` the holder refuses is `not-found`; a transport error leaves
|
||||
it `asked` and is asked again; each part writes `entry-not-found` from the follower's changes. As built in mesh-delivery
|
||||
ab616fa6.
|
||||
- **A-16** (§461, §485, §503; principle 7, the rule that shared code lives in mesh-sdk) A stdio module's writer is made by
|
||||
one mesh-sdk constructor, `views.NewStdioWriter`, generic as the writer is,, which fills `Publish` from the module's own event
|
||||
publish and `Listen` from its subscription; no module writes that adapter. The tool runner's own finding writer
|
||||
(mesh-tools' first views) hands in `Publish` and `Listen` from its bus connection, in its one place.
|
||||
- **A-17** (§461, A-16; ADR 0613 §5, principle 7; corrected) `findings-asked` is always the seat `mesh-conditions`' event,
|
||||
but a seat's name and contract are mesh-contracts' (ADR 0613 §5), so mesh-sdk names no seat: the writer takes the
|
||||
subject it hears in one config field `Asked`, which each module fills from the generated `mesh-conditions` contract's
|
||||
own constant (`SubjectFindingsAsked`), in Go and TypeScript alike.
|
||||
- **A-18** (§587, §305; principle 7) What G retires leaves no trace: the gate's record type drops the `remove` operation
|
||||
and `OpRemove` (§587: a removal is no operation), and its `table`, `recheck` and `close` go with their handlers and
|
||||
tools (§305). `checks` goes too, as §431 retires it with the merge states; the check is the runner's now (G-2).
|
||||
- **A-19** (§641, §556; principles 1, 7 and 2) `HearSeatEvents` learns that a stream is kept last per subject from the
|
||||
stream's own info on the bus, with no new parameter; its ordered read at the start filters on the same subjects as its
|
||||
durable; the read's messages go to the same handler as the durable's, so one path handles both; it names the read
|
||||
`waiting` and then `read` through one callback option it calls, which the module wires to its first views, so
|
||||
`process` imports nothing of `views`.
|
||||
- **A-20** (§613, replaces A-8's way of opening the old section; principles 7 and 2, and no data is lost) The gate keeps
|
||||
its kept data with the fewest parts that lose none: where the template `state` entry can take the old section's
|
||||
existing bucket as its own, it does, and nothing is copied; where it cannot, the old section stays declared in
|
||||
`state` under the local name `<name>-old` bound to its existing bucket, read through `stdio.State` like any section,
|
||||
copied once by A-8's rule (a key only where the template lacks it), and retired with §613's other old sections.
|
||||
The developer says which of the two the runtime allows, with the line of code that shows it.
|
||||
- **A-21** (§518, §345, §457, §643; principles 7 and 3) Each put-chunk and each get call carries `objects`, the store's
|
||||
local name, as today's put does. The chunk is a constant of `buscalls`, 512 KiB, which fits under the bus's default
|
||||
maximum payload of 1 MiB whatever the bus is set to; a value larger than one chunk goes by session, a smaller one in
|
||||
one call as today, so there is one threshold.
|
||||
- **A-22** (§686, §689, A-14; principle 7) As built in mesh-catalog 9677f200 and b78e90c0: a pending finding's bound is the
|
||||
module's own check interval; a field the contract's `Finding` lacks (`resolved`, and its words in every module, the watchman's included) is dropped, the keeper saying a clear in its own words; an id is escaped as a
|
||||
name, and a `ref` goes into the id only where two findings of one kind share an id; old keys are cleared by the
|
||||
writer's first statement. mesh-sdk keeps no old `views.Finding` or `FindingWriter` beside the generic writer.
|
||||
- **A-23** (§81, §132; principle 7) The gate's `recovery` marks the delivery record through its ordinary update (the record type's `trace` set-fields operation), with one
|
||||
field `recovery` holding `by` (the proved caller), `why` and `commit`; no new operation. Then it says `recovery-merged`.
|
||||
- **A-24** (§431, §172, §592, §158; principles 7 and 4) Once the merge states go, `deliveries`, `show` and `groups` answer
|
||||
the delivery records as the gate keeps them (G-3 *The delivery record*), filtered by their arguments, with no line
|
||||
form of their own; `stalled` answers the records whose open wait is past its bound, with that wait. The pull-request
|
||||
handlers and their effects stay only where G-3 §158 names what the gate owes the forge, rebuilt on the records; the
|
||||
rest goes with the state table.
|
||||
- **A-25** (§518, A-21; principle 3) A session put loses nothing a one-call put keeps: its first chunk carries the put's
|
||||
`description`.
|
||||
- **A-26** (§687, §689, A-22; principle 7) A part raises no follower's finding itself: every follower is adopted through
|
||||
its reader (`Adopt`), and the reader raises and clears the follower's four findings through the module's writer, so
|
||||
the pipeline runner, the indexer and the assigner build none of their own.
|
||||
- **A-27** (A-16, A-28, A-29; principle 7; corrected) The tool runner is no process module, so it keeps its own
|
||||
consumer and its four followers as they are and builds no `views.ProcessReader`. It writes the findings it wrote
|
||||
before (`first-view-unanswered` and the others of its old code, the same kinds) through one `views.HeldWriter` over
|
||||
its own connection: a small state adapter over the connection's state calls, `Publish` and `Listen` from the
|
||||
connection, in its one place (A-16); it writes and clears each follower's finding as that follower's status says, as
|
||||
A-28 has for a single ask. Its JetStream comes from the connection as `bindings.go` already makes it.
|
||||
- **A-28** (A-26, A-22, §687; principle 7) A part's own follower (mesh-config, mesh-vault) is followed through a
|
||||
`views.Reader` holding the module's writer and adopted, as A-26. A single ask that is no follower (mesh-build-go's
|
||||
manifest ask) writes and clears its `first-view-unanswered` finding itself through
|
||||
`Writer.Write` and `Writer.Clear` with the contract's `Finding`; mesh-sdk exports no helper for it.
|
||||
- **A-29** (§686, A-3; principle 7, shared code lives in mesh-sdk) A part whose findings are raised as they happen, not
|
||||
judged afresh, uses one mesh-sdk form of the writer that observes from its own state: it seeds what it holds from the
|
||||
state's keys before it starts, keeps them under its own lock, and its `Observe` answers them, so no part writes that
|
||||
adapter (the watchman's copy moves there; the assigner, builder and indexer use it).
|
||||
- **A-30** (§383, §529, §686; principle 3; corrected) Every module whose writer publishes declares its finding events
|
||||
as §529 says: a foundation part (the validator's list) in its old `streams` section as `<module>-findings`; every
|
||||
other part in its `requires` by the template `events` as `finding-events`, its entry listing `finding-written.*`,
|
||||
`finding-cleared.*` and `findings-stated.*`. None publishes an event it does not declare, and no other part declares
|
||||
`<module>-findings`.
|
||||
- **A-31** (A-14, A-22; principles 3 and 7) A module whose old finding keys end in no kind it owns (gitea's package
|
||||
registry, distribution) deletes those old keys once at its start, in its own code, so no stale finding stays; the
|
||||
writer's rule is unchanged.
|
||||
- **A-32** (A-29; principle 7) A part that keeps its open findings in memory (artifact-store-tools, mesh-config,
|
||||
distribution, gitea's package registry) uses the held writer of A-29, so a restart neither drops nor rewrites them.
|
||||
- **A-33** (§151, §592, A-9; principle 7) The gate opens one record per `take-in.taken`: its key is the taken body's
|
||||
`branch` and `source` (`<branch>.<source>`, each escaped as a name); its members are the taken body's `entries`
|
||||
(`repository`, `pull`), a group's members in one record; its heads are the commits the take-in names for them.
|
||||
- **A-34** (§291, §15, §592; principle 1) `source.<branch>` moves on `take-in.taken`: the gate writes the taken body's
|
||||
`source` for its `branch`, then says `source-moved`; nothing else moves it.
|
||||
- **A-35** (§167, §158, §168; principle 7) The record's changes make the same owed acts the delivery's transitions make
|
||||
today (the delivery's view, a comment, the note's line), re-keyed on the record; each act said is kept in the record's
|
||||
`acts` with when it was said, and ended there on `owed.ended`, so `stalled` reads the unanswered ones.
|
||||
`approval-carried` and `approval-carried-open` are design lines (§542, §592), not left out: they are built, with the
|
||||
`pull-merged` and `pull-closed` consumes that clear the finding.
|
||||
- **A-36** (§622, §643, A-21, A-22; principle 7) As built in mesh-sdk a59ccad8: the Go package's `calls` are the four
|
||||
words its calls need (`ledger-upkeep-calls`, `sent-objects-calls`, `put-sessions`, `finding-calls`), the npm package's
|
||||
`finding-calls` alone; the npm package makes no session, so its `state.put` refuses a value over one chunk, naming
|
||||
it. A-22 holds for TypeScript too: the TS follower keeps no plain-field `Finding` of its own beside the writer.
|
||||
- **A-37** (A-16, A-29, A-32; principles 7 and 3) The held writer has a stdio form, `views.NewStdioHeldWriter`, filling
|
||||
`Publish` and `Listen` as A-16's does, so no module writes the adapter; and the held writer holds every finding
|
||||
written through it, its `WriteWire` included, so a reader's findings are in its statement too.
|
||||
- **A-38** (A-29, A-32; principles 4 and 7) The held writer can be read: `HeldWriter.Held()` answers the findings it holds,
|
||||
so a part that diffs or looks up its open findings (artifact-store-tools, mesh-config) reads them there and keeps no
|
||||
set of its own.
|
||||
- **A-39** (§158, §161 to §167, A-35; principle 7) Which record change makes which act, as the transitions did: every
|
||||
change of a record (a step's outcome applied, the record ended) says a line on the head commit's note
|
||||
(`<at> record <key>: <what changed> (<why>)`, one line) and replaces each member pull request's delivery view with
|
||||
what `show` answers for the record; a comment is said only when the record ends failed or held, naming why. The gate's
|
||||
own findings (`approval-carried-open` first) go through one held writer over its own `findings` state (A-29, A-37).
|
||||
- **A-40** (A-29, A-37; principle 7) The held writer seeds what it holds from its state at `Start`, not when it is
|
||||
made, so every module makes it at once, as it makes the plain writer, and no stdio module waits for `Served` in an
|
||||
adapter of its own; a `Write` before `Start` is held and written at `Start`.
|
||||
- **A-41** (§492, §604, §546; principle 1) A part reads another module's data only by asking it, so the checker and the
|
||||
indexer stop reading the build modules' `toolchains` buckets: they take a toolchain from the builder's own read verb
|
||||
or event where one serves it today. Where none does, the restored read stays for G, built as mesh-delivery 7a3c312a
|
||||
has it, and the lead notes it for the next milestone; no new verb is added in G for it.
|
||||
- **A-42** (§592; principle 8) The gate's `approval-carried-open` has scope `delivery`, a scope the mesh already uses
|
||||
for the delivery's findings, and id `<repository>.<pull>`.
|
||||
- **A-43** (A-26, A-28, A-16; principle 7) As built in mesh-catalog d03ffa80 and mesh-tools 4bfae38a: an owner's plain
|
||||
`<owner>.views.<state>` family is followed by `Reader.Follow`, the follower's `Ask` set after (`Adopt` is for a seat's
|
||||
events); and the tool runner's held writer is given its JetStream, the writer's own defaults publishing and
|
||||
listening, so `bus.Conn` gains no publish method.
|
||||
- **A-44** (§681, §691; principle 7) `snapshot-remove` answers the one acting form of §691 (exactly one of `accepted` with
|
||||
the act's id, `busy` with the running act's id, or `error` with why), and records its caller as `by` from the proved
|
||||
caller (`by-caller`), as every acting verb does. mesh-sdk's seat definition reader gains the verb field `chunks`
|
||||
(§681) in the same change, with the bootstrap copies of `mesh-broker` laid again byte for byte.
|
||||
- **A-45** (§681, ADR 0675; principle 7 and the standing rule that a process answers no call, mesh-sdk 1171/AC3) The bus
|
||||
module answers the seat `mesh-broker`'s verbs through its tools bundle, as every bundle answers a seat verb
|
||||
(`SeatAnswer`, the proved caller from the runtime), and sends a snapshot's chunks and hears their acks from there
|
||||
(`SeatPublish`, its subscription); the keeper's process does the work that needs the bus and the files (taking,
|
||||
restoring, removing), handed over and answered through the bus module's own `requests` record, as the bundle hands
|
||||
the keeper its requests today. Where §681 says "the keeper's process serves", read "the bus module serves".
|
||||
- **A-46** (§368, §372, §411; principle 7) The `version` G's change writes into each manifest is read once now from the
|
||||
same source §372 fills from: the highest `published` version over the module's artifacts in the builder's
|
||||
`versions` (`builder_versions`), as its MAJOR.MINOR (mesh-sdk `0.1`), and `0.1` for a module with none published.
|
||||
The developer reads it through `builder_versions` and writes it into every manifest of every repository in the
|
||||
group, one commit per repository, listing the values in the commit.
|
||||
- **A-47** (§421, §683, §698, ADR 0593 §6; principle 7) The node-engine reads by fingerprint only (§421), so it follows
|
||||
no branch: it uses the shared follower's entry cache alone (`Entries`, A-5, A-15) over its own `entry` calls on its
|
||||
link session. It keeps the entries and wants of §683 in its `composed-<node>` bucket, keys `entry.<fingerprint>` (the
|
||||
entry as `entry` answers it) and `want.<module>` (the want as `wanted` says it), beside the composition. Its other
|
||||
users of `compose/frombus` keep that reader until their own lines move them; only the node-engine's own reads of the
|
||||
indexer's and builder's states go in this change.
|
||||
- **A-48** (§706, §276; principle 7, one way per part) The assigner's outside waits: a state `waits`, declared in the
|
||||
same form as the assigner's other states, key `<node>.<module>.<fingerprint>`, read by `assigner_waits` and written by
|
||||
the process and by `assigner_release`; the ask goes the way the assigner asks everything, through its state `asks`
|
||||
as a new kind `outside` (key `outside.<node>.<module>.<fingerprint>`, options go on and count as failed), its answer
|
||||
read as the others are; `assigner_release` with `how` releases an outside wait and without it ends a put as today;
|
||||
a want held for M's own network says `RuleNotHeld` with words naming the open `machine.<M>.network`. As the
|
||||
developer proposed on mesh-delivery feat/47-G-outside5.
|
||||
- **A-49** (§518, §576; principle 7) A put or get session's bound is the call's bound the runner already uses,
|
||||
`launch.CallTimeout` (30 seconds), counted from its first chunk; a session also ends when its bundle's channel closes
|
||||
(the child's exit), as §518 says. The runner answers `mesh/capabilities` with `{words}` (§576), so a bundle's
|
||||
capability check passes.
|
||||
- **A-50** (§421, §379, §466; principle 7) The node-engine makes each artifact's reference from the want's part digest
|
||||
in the form the store is already addressed by, `artifact-store://<module>/<artifact>/blobs/<digest>`, and the rest
|
||||
of the resolved manifest (languages, launchers, template texts) from the entry and the parts fetched by digest; its
|
||||
read of the builder's done key goes.
|
||||
- **A-51** (§388; principle 7) The grants part moves to the writer only the four kinds §388 names (`view-waits`,
|
||||
`first-view-unanswered`, `first-view-empty`, `binding-unlisted`), through the held writer of a process (A-29, A-40)
|
||||
with the foundation stream form of §529; its other conditions stay as they are.
|
||||
- **A-52** (§200, §388, §614, §684, A-26; principle 7) The grants part's `view-waits` is the one its reader raises for the
|
||||
catalog half views the branch follower adopts (A-5): the part waits on the catalog half of the revision it composes
|
||||
for, through those views, and writes no `view-waits` of its own.
|
||||
- **A-53** (§681, ADR 0675; principles 3 and 7) A snapshot's record never ages out before its file: the bus module's
|
||||
`requests` state keeps its records 91 days (one more than `snapshot-keep-days`), and the keeper removes a snapshot's
|
||||
record together with its file at expiry, so no file is left without a record and no record without its file.
|
||||
- **A-54** (§584, ADR 0363; principle 8) Distribution's old-section stream takes the old form's name beside
|
||||
`<module>-findings`: `distribution-events`, never the mesh's own `events`.
|
||||
- **A-55** (§672, ADR 0348 §5; principle 7) The builder's `toolchain-wait-minutes` reaches it the way the mesh fills a
|
||||
part's settings, as mesh-conditions reads its own (the runtime's settings read at start, the manifest marking it
|
||||
`runtime`), never through the process's environment.
|
||||
- **A-56** (ADR 0573 §2, §4; principle 5, everything ships the same way) The locks check treats every `@novox/mesh-*`
|
||||
package as it treats the SDK's, since the builder builds a module against the latest published version of each mesh
|
||||
package as it does for Go; the order between the contracts' publish and gitea's build is the delivery plan's.
|
||||
- **A-57** (§466, §52, §334, §349, §371; principle 7) BL1 is the whole `package-published` emission, built after BL5's
|
||||
watched-branch half: the builder says it per artifact when it hears the registry seat's `published`, carrying
|
||||
`resolved` and `toolchain` from the step's result (`dependencies`, §673), `branch` the watched branch of the catalog
|
||||
revision whose entry the build came from, and `source` that revision, both kept on the build's reservation from the
|
||||
candidate it built.
|
||||
- **A-58** (§540, §685, §8; principle 8, one word per thing) An owner change is said as `condition-changed` with a new
|
||||
`change` value `owner`, added to the seat `mesh-conditions`' enum in mesh-contracts; `words` stays for a change of the
|
||||
text. `seed-waits` waits on the operator through the finding's own `waiting-on`, which the indexer sets and the keeper
|
||||
copies, with no rule of the keeper's for that kind.
|
||||
- **A-59** (§681, A-45; principle 7, and who may do what is no question) The bus module's snapshots directory is made
|
||||
readable by its tools bundle's account in its manifest (a group read, nothing more), so the bundle reads a
|
||||
snapshot's file and sends its chunks as A-45 says; the keeper still writes and removes the files, and a protected
|
||||
snapshot is still never handed out, by the bundle's own check.
|
||||
- **A-60** (§681, A-45; principle 7) The chunk subjects of a `chunks: true` verb are granted to the principal that
|
||||
answers the verb: for the bus module, the tool runner's principal for its tools bundle (A-45); the restore subjects of
|
||||
restricted streams to the keeper's process account, standing (ADR 0675 §2).
|
||||
- **A-61** (§693, §683, §556, §113's form; principles 1 and 7) The assigner says `wanted` for every want it writes and in
|
||||
the same write that removes one, on `mesh.seat.mesh-wants.event.wanted.<node>.<module>` (each escaped as a name, §73),
|
||||
the seat's stream kept last per subject, so each want's last word stands; `revision` is the assignment graph revision
|
||||
of the pass that writes or removes it, and `source` that pass's source revision. The seat's definition gains the
|
||||
subject's two tokens and its keep-last setting.
|
||||
- **A-62** (§704, §402; principles 7 and 8) The seat `mesh-module-assigner` gains the verb `placement`, class `read`:
|
||||
input `{module}`, answer the module's placements as its `assignments` rows hold them (`{placements: [{branch,
|
||||
nodes}]}`); the assigner serves it. An `unwatch` of a branch a placement names is refused with the new reason
|
||||
`placed`, naming the placement. §675's *Kept while named* and §700's read of the router's record are the indexer's
|
||||
in G, built with these rows.
|
||||
- **A-63** (§706; principle 7) As built in mesh-delivery 76fce4bd and 640191d4: "a module the delivery moved to M" is
|
||||
one whose want on M names a fingerprint its have there does not yet run healthy; a wait's `clock-from` is the
|
||||
failing check's time, also when it first rests on `conditions-unread`; while the keeper has not answered, a first
|
||||
failing check holds as the settle and becomes `conditions-unread` only after it, and a standing wait keeps its cause
|
||||
while a new read is out.
|
||||
- **A-64** (§681, A-45; principles 7 and 8) As built in mesh-catalog 47aaee12: a chunk's body is `{n, of, data}`; a
|
||||
restore's act id is the id of the request the bundle hands the keeper; `verify` answers `accepted` with that id and
|
||||
says no event; an unprotected snapshot's restore refuses nothing further; each chunk is acknowledged by a request,
|
||||
since the runtime answers only calls with a reply. One change: `snapshots-filling` takes an existing scope word,
|
||||
scope `module`, id `nats.snapshots`.
|
||||
- **A-65** (§387, §529; principle 1, a module's data lives on the bus) mesh-knowledge's findings go through the writer
|
||||
like every observer's: it gains the `finding-events` stream and a writer over the `findings` state it already
|
||||
requires, and its findings no longer live in its database.
|
||||
- **A-66** (§704; principles 7 and 3) The seed's `steps` call names the artifact by the contract's key
|
||||
`<module>.<artifact>`, `module` beside it; its `fetch` is composed from the entry (repository, commit, path, the
|
||||
artifact's paths, and `packages` and `beside` where the entry carries them); the time since a seat first failed to
|
||||
answer is kept in the indexer's own state, so a restart keeps it, and the finding is written at the next seed attempt
|
||||
past five minutes.
|
||||
- **A-67** (§292 and G-5's *wants follow the assignment graph*; principle 7) (c): the design has the assigner write each
|
||||
want from an assignment graph revision it keeps, so the pass keeps that revision first (assigngraph.Compute and Keep
|
||||
per catalog revision taken in), and `wanted`'s `revision` and `source` are that kept revision's and its catalog's.
|
||||
No stand-in value.
|
||||
- **A-68** (§375, A-66; principles 1 and 7) The indexer keeps what `outdated` compares against in its own state, as it
|
||||
hears the events: `toolchain.<language>` the newest `toolchain-agreed` per language, and `delivered.<module>` the
|
||||
newest `version-delivered` per module, in the same own state as A-66's keys; no new state, no field of `VersionEntry`,
|
||||
no scan of the streams at a call.
|
||||
- **A-69** (§349; principles 3 and 7) The artifacts published so far at a version not yet released are kept in the
|
||||
indexer's own state under `pending.<module>.<version>`, beside A-66's keys, and removed when the version is released.
|
||||
- **A-70** (§572, A-45; the rule that a process answers no call) The builder's tools bundle serves `reserved` and
|
||||
`builder_versions`, following the catalog half's view itself as other bundles follow views; the builder's process
|
||||
serves no call.
|
||||
- **A-73** (§588, §369; principle 7) To publish again, the builder takes each artifact's kind and package name from the
|
||||
catalog entry of the reserved commit, and sends the registry seat's `publish` with the digest and no parts: the
|
||||
holder takes the bytes from the artifact store, addressed by digest (A-50). "Not stored" is the holder's refusal;
|
||||
the entry then ends failed, said with its reason.
|
||||
- **A-74** (§700; principle 1, no module names another) The indexer reads the router's `asks` through the seat
|
||||
`operator-channel`: its manifest `uses` that seat, and the bucket is formed from the seat's holder on the seat list
|
||||
and the contract's `StateAsks`, never a module's name in code; the teller's and the node-engine's `messenger_asks`
|
||||
constant goes the same way in their own change.
|
||||
- **A-75** (§654, §610; principle 7, and who may do what is no question) mesh-issues shows the pipeline rows where it
|
||||
already shows a delivery: its `DeliveryLine` gains `pipelines`, one row per module with its steps, `no run` where the
|
||||
runner's list answers none. A runner verb's old callers list does not refuse a module: from G no verb names its
|
||||
callers (§610), so the class's default holds.
|
||||
- **A-76** (§588, §568, A-73; principle 7) A reservation keeps the fingerprint of the catalog entry it was made from
|
||||
(`versions.Source` gains it), so publish again reads that entry; the registry seat for an artifact is found from its
|
||||
package ecosystem as the builder's seat hearing already maps it. A transport fault's class (`no responders`, `name
|
||||
lookup`, `connection`, `deadline`) is the one the existing retake reads from the call's error (ADR 0664, ADR 0668).
|
||||
- **A-77** (§373; principles 1 and 7) A wait's bound is a bound key with its own time to live in the waiting part's own
|
||||
state, as the condition keeper times its two hours (COND7): its expiry is heard as the bus's event and ends the wait
|
||||
then, failing the step naming the member; no timer in the process and no looking only at the next event.
|
||||
- **A-78** (§374, §375, §377, §354; principles 1 and 7) For `outdated` and its inputs, the indexer keeps what it hears:
|
||||
(a) `acted.<acting part>.<dependent>` in its own state, the newest `wait-ended` (ran or released) with the build it
|
||||
acted for, compared with the dependent's current build (its newest entry's fingerprint); (b) what a delivered
|
||||
version serves is read from that version's catalog entry (its manifest's `serves` and words); (c) a derived edge is
|
||||
marked by a field `filled: true` the contract's `VersionEdge` gains, the machines that place a module come from the
|
||||
assigner's `placement` (A-62), and each node's last `running` from the node-engine's stream read whole at start
|
||||
(§556); (d) an `inputs` entry is also keyed by its run ask's id, so the build seat's `step-ended` of that ask finds
|
||||
and deletes it.
|
||||
- **A-79** (§558, §614, §684, §196, §444; principle 7) As built in mesh-delivery 6d483af2: the gate's `source` is named
|
||||
in the short `invokes` form (the manifest's `invokes` is a list of names); the catalog half's rows go into the part's
|
||||
existing `grants_first_views`; while the catalog half is unread the part keeps what the bus loaded and only adds,
|
||||
which is §684's way past the cycle, so no registered branch holds nothing. The part's remaining reads of the
|
||||
indexer's `manifests`, `modules` and `verdicts` go too (§196): the catalog half answers them.
|
||||
- **A-80** (A-66, A-68, A-69, §375, §73; principles 7 and 8) What the indexer keeps as heard (`steps-since`, `toolchain`,
|
||||
`delivered`, `pending`, `acted`) lives in one state of its own, `heard`, not in `own-build`, which keeps its one key;
|
||||
every key part is escaped as a name (§73), so a dotted module name parses; a kind-3 edge compares its artifact's
|
||||
language with `toolchain.<language>`.
|
||||
- **A-81** (§588, §369; principle 7) As built in mesh-delivery f12883b2: the publish-again queue key is
|
||||
`<holder>.<module>.<version>`; the holders and their nodes are what the builder hears on `running` (read whole at its
|
||||
start, §556, so a restart rebuilds them); `running` means not stopped, with a fingerprint; a not-stored artifact is
|
||||
remembered on the reservation (`Unstored`); the tool's request rides a reasks mode `republish`; a message the state
|
||||
cannot take is not acknowledged, as the other parts do.
|
||||
- **A-82** (§7, §428, §704's seed; principle 1, no module names another) The builder finds a build module the way the
|
||||
indexer's seed does (mesh-delivery d73aeb02): the build seat whose acting rule `build` has an `on` matching the
|
||||
artifact's language or `kind:image`, and that seat's holder on the seat list. `Task.Module` and every lane key are
|
||||
that holder's name as the seat list gives it; a plan's artifact, the toolchain sources, `UnpublishedModules` and
|
||||
`builder_stuck` all derive from it, and `builder_stuck` reads each build seat's steps through the seat (`<seat>.steps`)
|
||||
rather than a named module's state. The floor's sources are the holders of the seats `git` (tags) and `mesh-catalog`
|
||||
(requirements) on the seat list.
|
||||
- **A-83** (§374, A-78, A-80; principles 1 and 7) `BuildEdges.fp` is the fingerprint of the branch's catalog entry at
|
||||
the revision `source` names, which the indexer holds itself; no field is added to `package-published`. For a build
|
||||
of another watched branch, kind 1 compares with that branch's newest build, kept as heard in `heard` as
|
||||
`branch-build.<module>.<branch>` (the digest `package-published` carries), not with a released digest.
|
||||
- **A-84** (§194 and its candidate's-view line, §348, §473; principle 1) The builder follows no candidate: only a run's
|
||||
steps read a candidate's view, so the builder learns a candidate's build from the run's build step ask, which names
|
||||
each entry by fingerprint; it reads each entry through `mesh-catalog.entry` (repository and commit are the member's
|
||||
head) and needs no pull request number, a build being per fingerprint. Its read of the indexer's `candidates` view
|
||||
goes.
|
||||
- **A-85** (§291, ADR 0666 §6, §9, §614, A-83; principle 7) The indexer keeps a catalog revision per watched branch, as
|
||||
it keeps the trunk's: objects `branch.<branch>.<n>` (the branch escaped as a name) beside `trunk.<n>`, the trunk being
|
||||
the first watched branch's; the branch follower's per-branch `revision` reads them, and `BuildEdges.fp` is the entry's
|
||||
fingerprint in the revision whose `source` the build names.
|
||||
- **A-86** (§354, §375; principle 7) `KeepInput` is called by the indexer's own `input` verb, when a run on request's asker
|
||||
hands an input; the indexer asks no `run` itself. A kind-3 edge's language is its catalog edge's target artifact's
|
||||
language, else the one language the entry's artifacts share, as built in mesh-delivery 652285f0.
|
||||
- **A-87** (§568, §589, A-70, A-76; principle 7) As built in mesh-delivery bd228d51: a fault with no node says `no node`;
|
||||
`FaultClass` maps `buildstep.PassingCause`'s words onto A-76's four classes. `builder_build`'s `newest: <branch>` is
|
||||
read by the tools bundle following the catalog half itself, as A-70 has it for `reserved`.
|
||||
- **A-88** (§610, the main file's item 85; principle 7) mesh-networking's A-N1 (callers named in its seven seat
|
||||
definitions) is not brought in: from G no verb names its callers (§610), which supersedes item 85's reading.
|
||||
- **A-89** (§37, §700, A-74; principle 7, one way for every asker) The runner reads its approval ask's warrant from the
|
||||
router's record of its ask, through the seat `operator-channel` as A-74 has it, never from the decided event's body;
|
||||
the decided event only wakes it.
|
||||
- **A-90** (§13, §14, §291, ADR 0666 §6, A-85; principle 7) A non-first branch's revision is made by the same take-in as
|
||||
the trunk's, taken per watched branch: `sourceFollow` takes `source.<branch>` for every watched branch, and the
|
||||
source path (`ApplySource`, the catch-up, the heads' take-in) runs with the branch as its argument, writing that
|
||||
branch's revision (corrected by A-93).
|
||||
- **A-91** (§26, §37, §700, Q-RN8; principle 7) The router's record value carries its `warrant` as §700 says: the
|
||||
operator-channel contract's `AsksByAskerIDValue` gains the field. A step the module's policy does not name is no step
|
||||
of its pipeline, so `Status` has no open question for it. The runner places a pipeline's steps when it starts it and
|
||||
asks each build seat for its step, as its asking half; that half is built in G.
|
||||
- **A-92** (§560, A-77, §627; principle 7, and who may do what is no question) The node-engine's bound keys live in a new
|
||||
state `bounds` of the seat `node-engine`'s definition, declared in its old section as a foundation part's states are
|
||||
(§627), key `<what>.<id>` with its own time to live; the node-engine's account holds it as it holds its other states.
|
||||
- **A-93** (§13, §14, §291, ADR 0666 §6, A-85, A-90; principle 7; corrects A-90's trunk) A non-first branch's take-in
|
||||
is a pure computation from that branch's merged messages straight into its revision (`KeepBranchEntries`): each
|
||||
module's fingerprint, inputs and graph node at the branch's heads, computed as the trunk's are, with no modules view
|
||||
or bucket of its own; the trunk's `modules`, `manifests` and graph stay the trunk's alone. The trunk stays the
|
||||
mesh-wide branch `main` (ADR 0666's default watched branch), named once in the indexer; a registration's own first
|
||||
branch decides only which of its branches it delivers from.
|
||||
- **A-94** (§379, §525, §526; principle 1, a part asks for another's data or hears its events) The assigner hears the
|
||||
builder's `build.ended` event (§379 names it) and reads no builder state such as `build-log`. A want for a trunk
|
||||
revision's entry takes `version` and its parts' digests from the `version-released` whose `source` is that revision;
|
||||
a build `build.ended` marks kept (its keeping node) is a kept build, its parts and `kept-on` from that event; a build
|
||||
of another watched branch is the one whose `build.ended` names that branch, its parts from that event.
|
||||
- **A-95** (§421, §683, §698, A-47; principle 7) As built in the node-engine's commits 7703162c and 97d3df3e: the entry key is
|
||||
`entry.<digits>` (a key takes no colon); `want.<module>` holds the want with its own `since`; an entry another node
|
||||
runs is learned from `running`; the refusal "no entry of fingerprint" is `not-found`; `first-view-again` refuses in
|
||||
words, the node-engine following no view. Still owed in G: a restart composes the bus module and the indexer from the
|
||||
kept entries and wants while both are down, without waiting for every input (§683's purpose), and the
|
||||
`entry-not-found` finding (§614).
|
||||
- **A-96** (§379, G-4's event table, §529, A-67; principles 1 and 7) The builder says `build.ended` itself, as a module
|
||||
event on its own stream: a foundation part's old `streams` entry `mesh-delivery-builder-builds`, 7 days, kept last per
|
||||
subject `build.ended.<module>.<fingerprint>` (each escaped), its body the build log entry's `module`, `fingerprint`,
|
||||
`made` (artifact → digest), `kept-on` and `branch`; the assigner `consumes` `mesh-delivery-builder.build.ended`. A
|
||||
want's source revision is the one its pass's kept assignment graph revision was computed from (A-67), matched to
|
||||
`version-released.source`; `parts` stay keyed `<module>:<artifact>`, the contract's comment corrected to say so.
|
||||
- **A-97** (§230, §194, §473, A-84, A-91; principle 7) A candidate's build is asked by the runner as a pipeline step of
|
||||
its own: the runner's asking half asks the seat `mesh-builds` a `build` step naming each entry by fingerprint, and the
|
||||
builder takes that ask (its one entry point for a candidate) and answers the step's end; `StepRun`'s steps stay lint,
|
||||
unit and integration on the build seats. With the candidate coming that way, the builder's watch of the gate's
|
||||
`source` for candidate base merges goes. A kind-3 dependency's `part` is the entry's kind-3 edge, as built in
|
||||
2dc51626.
|
||||
- **A-98** (§297; principles 3 and 7) The node agent gains one act, `Env.Stop`: it stops a module's units and containers
|
||||
and removes nothing, so a revert's data survives; the revert stops the newer build with it, asks `restore-kept`, and
|
||||
then `Apply` starts the reverted build. `Remove` keeps its meaning.
|
||||
- **A-99** (§230, A-91, A-97; principle 7) `build` is a step every pipeline has, placed first and required whatever its
|
||||
policy names, so the runner's step enum gains `build`; each module's pipeline asks the build of its own module's
|
||||
entry only, and a group's members are built by their own pipelines.
|
||||
- **A-100** (§379, A-96; principle 7) As built in mesh-delivery a5e1a5e5 and 5a352482: `build.ended` is said for a done
|
||||
build only; its `branch` is empty for the trunk; its old-section stream keeps 7 days with no keep-last setting (an
|
||||
old `streams` entry has none), which A-96's "kept last per subject" gives up.
|
||||
- **A-101** (§532, §521, A-74, A-89; principles 1, 3 and 7, shared code in mesh-sdk) mesh-sdk's `stdio.TopicMatches`
|
||||
understands the bus's own `>`, so an adopted follower's `<seat>.<event>.>` hears every event, not the first view only.
|
||||
The operator-channel asker (the seat holder found on the seat list, the ask published, the router's record read for
|
||||
the warrant, `decided` heard) is one mesh-sdk helper that the builder, the teller, the indexer and the runner use; no
|
||||
module keeps its own copy. A step's own bound is a key with its own time to live too (A-77), not a timer.
|
||||
- **A-102** (§22, §24, A-97; principle 7) A part that puts work on a seat's accepted kind names it in `invokes` as
|
||||
`seat:<seat>.<kind>` (the runner: `seat:mesh-builds.step` and each build seat's `check.step`), and the grants part
|
||||
composes the publish of that accept subject from it, as it composes a verb's call.
|
||||
- **A-103** (§532, §614; principle 7, shared code in the follower) When two watched branches list one module, the
|
||||
module's own registration decides: its first branch is the one read. The `Entry` and `Ready` helpers over the branch
|
||||
follower, the `<module>_entry_again` tool, the `entry-not-found` finding and the entries rows of
|
||||
`<module>_first_views` are built once, in `meshcatalogclient` and mesh-sdk's views, and every reader uses them.
|
||||
- **A-104** (§22, §70, §482, §25, §416, §535, §647, A-70, A-77, A-68; principle 7) The runner's write verbs (`review`,
|
||||
`rerun`, `accept`, `cancel`) are served by its tools bundle, which writes the state `pipelines` by compare-and-set and
|
||||
says `step-ended`; an optional step still waiting does not hold a pipeline's end, only a required one does. A step
|
||||
ask's kept results are the `kept` entries whose `package` is the artifact's package (from the entry); its
|
||||
`toolchain` is the runner's `toolchain.<language>` kept as heard from `toolchain-agreed`. An unanswered ask is read
|
||||
from the build seat holder's `ask-taken` as §647 says (not the seat list), its five minutes a key with its own time to
|
||||
live in a state `bounds` of the runner.
|
||||
- **A-105** (A-104; principle 7) The runner keeps what it hears of `toolchain-agreed` in a state of its own,
|
||||
`toolchains` (history 1), key `<language>`, value its language, version and digests; a step ask's `toolchain` is read
|
||||
there.
|
||||
- **A-106** (§25, §416, §535, §647, A-104; principle 7) As built on the runner's recover branch (239a6693, e8b69509,
|
||||
2d9c6591): a kept result counts for an artifact when its package is the artifact's or lies beneath it; the ask's
|
||||
toolchain carries language and version; only the three build seats' asks are bounded by `ask-taken`; the five
|
||||
minutes are a constant; an expired step ends `could not run` and the others run on. The runner's write verbs wait on
|
||||
the operator's answer whether a process may answer calls (then the process writes `pipelines` by compare-and-set
|
||||
itself); the runner gains a whole-module integration test against the bus in G.
|
||||
- **A-107** (the operator, 2026-11-11 about 23:45 CET: yes, a process may answer calls; principles 1 and 7; withdraws
|
||||
A-45, A-59, A-60's bundle principal, A-70, A-87's bundle part and A-104's bundle part) mesh-sdk's rule that a process
|
||||
answers no call (issue 1171, AC3) is removed: a process answers its seat's verbs directly over the bus, with the
|
||||
proved caller, through one mesh-sdk call. The part that holds the data answers for it: the bus module's keeper serves
|
||||
`mesh-broker`'s verbs and sends the chunks itself; the builder's process serves `reserved` and `builder_build`; the
|
||||
runner's process serves `review`, `rerun`, `accept` and `cancel`, writing `pipelines` by compare-and-set; the chunk
|
||||
subjects go to the process that answers.
|
||||
- **A-108** (§630, §214, §406, §516; principle 7) The catch-up is the node agent's: after the restore and the `Apply` of
|
||||
the reverted build, it calls the point's recorded `{tool, arguments}` as that module's tool on its own node through
|
||||
the tool runner, as `holder:node-engine` is granted (§406); `restore-kept` carries the snapshot only. §630's "passes
|
||||
its catch-up to restore-kept" reads "runs its catch-up after it".
|
||||
- **A-109** (A-107, §681, ADR 0675; principles 6 and 7) A process that answers its seat's verbs declares them in its
|
||||
manifest's process section as `serves` (mesh-sdk's `ProcessBus` takes the field, its old "a process serves nothing"
|
||||
gone), and the grants part composes the process account's subscribe of `mesh.seat.<seat>.call.<verb>.*` and
|
||||
`.tool.<verb>` from it, plus, for a `chunks: true` verb, the publish of `.chunk.<verb>.>` and the subscribe of
|
||||
`.ack.<verb>.*.*`. A snapshot's chunks are a transfer, published on the core bus with no stream; any caller of
|
||||
`snapshot-get` receives the chunks (who may hear them is no question), a protected snapshot never.
|
||||
- **A-110** (A-109; principle 7) A process's `serves` is a list of `seat:<seat>.<verb>` strings, the form of `invokes`
|
||||
beside it; whether a verb sends chunks is read from its seat definition (`chunks: true`), never declared again.
|
||||
- **A-111** (§22, §70, §482, A-104, A-107; principle 7) As built in mesh-delivery 210abfc8 and bb51e59a: a rerun's ask id
|
||||
carries its time; a rerun carries the kept results and toolchain; `rerun` of `approval` is refused naming
|
||||
`ask-again`; `cancel` drops the asks' bounds and leaves what the build seats already took; the hand act's verb is
|
||||
`mesh-pipeline-runner.<verb>`; a review ends every running pipeline at that commit and fingerprint waiting on one. A
|
||||
review given after a pipeline ended passed is refused, as built.
|
||||
- **A-112** (§633, A-71; principle 7, one way per kind of part) A process reads its settings as the builder does
|
||||
(A-71): a file its manifest declares, its content `${config:<key>}`, read at its start; so the node-engine reads
|
||||
`recovery-wait-minutes` from such a file, default 90 when it is missing. A bundle keeps `stdio.Config`.
|
||||
- **A-113** (§297, §560, §541; principle 3, failure is loud and leaves nothing broken) A revert whose restore fails
|
||||
after `Env.Stop` starts the newer build again and holds the move, saying why; a module is never left stopped. A form
|
||||
change whose entries are not yet answered waits for them, never moves without its restore point. The hold says
|
||||
`wait-held` (§541) as every wait does.
|
||||
- **A-114** (§693, A-61; principle 7) The seat `mesh-wants`' `wanted` stream keeps the last message per subject with no
|
||||
age limit (`max-age-days: 0`): it is each want's last word, read whole at a start.
|
||||
- **A-115** (A-107, A-109, A-110; principle 7; corrects both) `serves` is a field of a process itself, beside its
|
||||
`bus`, not inside it: the grants part adds the serve subjects to whichever account that process runs on, the
|
||||
module's own when it declares no `bus`, so a process keeps hearing what it heard. Only a seat's verbs move to the
|
||||
process: a module tool such as `builder_build` stays its tools bundle's (A-87).
|
||||
- **A-116** (§636, §616; principle 7) A read of a seat holder's state names the seat, as §616 does for views:
|
||||
`reads` takes `seat:<seat>.<state>` (route-proxy: `seat:mesh-provisioner.plans`); the grants part resolves it to the
|
||||
holder on record as it resolves a seat's views, and the module finds the holder through mesh-sdk's seat-list reader
|
||||
(the holder of the seat), naming no module in code; the tests that held code to a fixed name hold it to the seat form.
|
||||
- **A-117** (§683, §614, §560, A-95; principle 7) At a start, until its assignments, haves and resolved values are read,
|
||||
the node-engine's pass composes only the modules whose kept want and kept entry it holds, a kept want standing for
|
||||
their placement, and leaves every other module as it runs; the full pass follows once the inputs are read. It writes
|
||||
`entry-not-found` itself through its own writer (kind added, scope `module`, id the fingerprint, escaped). The seat
|
||||
`mesh-recovery`'s `ended` and `restored` events gain `why` (the hand act's words for a cancel), regenerated, which the
|
||||
recovery module fills and the node-engine's hold says.
|
||||
- **A-118** (A-77, A-101, §167's checker that goes with G; principle 7) The shared `Asker` takes optional bound keys;
|
||||
the builder arms them in its `bounds` state. The checker, which the runner replaces and which goes after G, keeps its
|
||||
step bound as today and gains no `bounds` state.
|
||||
- **A-119** (§7, §616, A-82, A-116; principles 1 and 7) The builder's floor takes its requirements from the catalog
|
||||
half it already follows (each entry's dependencies), so `indexer_requirements` goes; it is woken by the catalog
|
||||
revisions it already hears, so the forge stream's consume goes; the assignments view is named in the seat form of
|
||||
§616 (`consumes` `seat:mesh-module-assigner.views.assignments`, `reads` `seat:mesh-module-assigner.assignments`, A-116),
|
||||
its first view through the seat's verb `assignments`. No module is named.
|
||||
- **A-120** (A-117, A-95; principles 2 and 7) The early pass takes the node's own record from the statement the
|
||||
node-engine makes of itself (its tunnel key, sealing key and capabilities), held in memory and built before the
|
||||
composer's watch starts, without the bus; no copy of the record is kept in the composition state.
|
||||
- **A-121** (§554; principles 7 and 8) An old section's kept data is named as the bus composer of mesh-sdk names it
|
||||
when it makes it: a state `KV_` and its bucket name (module and name joined by `_`), a log its log stream name
|
||||
(`LOG_`, module, `_`, name), a stream its declared name as is, an objects section `OBJ_` and module and name joined
|
||||
by `_`. The recovery module calls those mesh-sdk names; it writes no name of its own.
|
||||
- **A-122** (A-116, §324; principle 7) The node-build-image seat's `build` act takes the bound the other build seats
|
||||
take, `60m`.
|
||||
- **A-123** (§682, §554; principle 2) `aside` takes its bus names from the catalog entry of the fingerprint the
|
||||
node-engine's placement runs for the module: what is set aside is the data that runs.
|
||||
- **A-124** (§682; principle 7) The seat's aside names every bus object of the kept data: `Aside.name` becomes the list
|
||||
`names` in the mesh-recovery contract, regenerated; no names joined into one string.
|
||||
- **A-125** (§655) The restore of one stream moves into mesh-sdk's bussnap beside its snapshot, and the bus module
|
||||
imports it from there; the recovery module's builder moves it, in the same work.
|
||||
- **A-126** (A-82, A-101; principle 7) Who holds a seat now is read by one mesh-sdk helper beside the seat list
|
||||
(`seatlist`), through the assigner's `assignments` verb; operatorask and the view call it, and no part keeps its own
|
||||
copy. The builder's holder at a catalog revision (`versions.HolderOf`) answers another question and stays.
|
||||
- **A-127** (A-115 over A-109, A-110) `serves` is read only beside a process's `bus`: mesh-sdk's process parse, the
|
||||
grants part's process-serve composition and the validator's test follow A-115; a `serves` inside `bus` is refused.
|
||||
- **A-128** (A-116; principle 7) A `reads` in seat form `seat:<seat>.<state>` is parsed by mesh-sdk's state reader as
|
||||
its own kind, kept apart from a module's read; the validator checks the seat declares the state, and the holder is
|
||||
resolved where it is read, by A-126's helper.
|
||||
- **A-129** (§657; principle 4) The seat's `kept-points` answer gains `unrestored`, the asides whose record ended at
|
||||
`removed`, read from the engine; the tool `recovery_kept_points` shows it, and the health check reads the same.
|
||||
- **A-130** (A-124, §557; principle 7) The events `set-aside` and `aside-discarded` carry `names` as `Aside` does; the
|
||||
recovery module's two bounds are the settings `dump-wait-minutes` (default 60) and `point-keep-days`.
|
||||
- **A-131** (corrects A-119's first view; §616; principle 7) The builder's first view of the assignments is asked of
|
||||
the view's own first-view tool on its holder (`<holder>.assignments`), the holder found at runtime by A-126's helper
|
||||
and the builder among the view's callers; the seat-form `consumes` of the view covers it, and the seat verb
|
||||
`assignments` keeps its own answer.
|
||||
- **A-132** (A-123, §556; principle 1) The recovery module learns the fingerprint a node runs for a module from the
|
||||
node-engine seat's `running` event, kept last per subject and read as §556 (a `consumes` of its manifest); no call.
|
||||
- **A-133** (§657, §567, §554; principles 3 and 7) The recovery module's manifest follows the validator: (a) its health
|
||||
check is its tool beside the process check the other modules declare; (b) the `digests` state's cap is given in the
|
||||
template's own form at 4 MiB, never as a second option beside it; (c) the template states `digests` and `findings`
|
||||
are the module's own by its `requires` and are not listed again in the process's `bus`, which keeps its own account.
|
||||
- **A-134** (A-16, A-26, A-28; principles 7 and 8) The licence manager's `binding.changed` carries the consumer as its
|
||||
further part (`binding.changed.<consumer>`), the one form of G's seat events; mesh-vault and claude-code adopt it
|
||||
through their views reader, and mesh-vault's two followers share its one writer.
|
||||
- **A-135** (corrects A-133(b); ADR 0595 §3; principle 7) The state template takes no cap: `digests` declares none and
|
||||
the template's fixed cap stands; §567's 4 MiB is the size expected, not a setting.
|
||||
- **A-136** (§483, §496; principle 1) The watchman's read of the build seats' steps goes, as §483 says: it times a
|
||||
running ask from the build seat's step-progress event; its `reads seat:node-build-*.steps` and the holder lookup
|
||||
for it go with it, and no holder event is needed.
|
||||
- **A-137** (§681, A-123; principle 2) Before a keeper's snapshot is restored, the recovery module sets aside the bus
|
||||
names of the restore's module and kept data from the catalog entry of the fingerprint the node is running; for an
|
||||
unprotected snapshot, the `stream` snapshot-get answers.
|
||||
- **A-138** (§557, §551, A-77, A-130; principles 1 and 7) The wait on snapshot-restored and the expiry are bounds as
|
||||
well: settings `snapshot-restore-wait-minutes` (default 90) and `expire-every-hours` (default 24); every bound of
|
||||
the module may be a fraction, so a whole test sets seconds; the expiry runs on a bound key's expiry, not a timer.
|
||||
- **A-139** (§681; principle 7; code shared between modules is in mesh-sdk) The chunks of a seat verb's answer are
|
||||
received with mesh-sdk's own form (`buscalls`), the one the bus module sends with; where mesh-sdk has no receive,
|
||||
it is added there, never built in the recovery module.
|
||||
- **A-140** (§681; principles 3 and 7) After its cancel is asked at the bound, the recovery module waits one more bound
|
||||
of `snapshot-restore-wait-minutes` for that act's snapshot-restored; none heard is "answers neither". A keeper that
|
||||
answers `busy` ends the act failed before any aside, naming the keeper's running act.
|
||||
- **A-141** (§562; principles 1 and 7) The recovery module learns of a move from its own stop: from the stop signal
|
||||
on it answers every new call with an error naming the stop, cancels the running act so its own failure path runs,
|
||||
and ends every accepted act not ended `stopped` with its ending event; a restart is the same.
|
||||
- **A-142** (§496, §602; principles 1 and 7) A `step-ended` said while the watchman was down reaches it on its
|
||||
start through its durable consumer of EVENTS, and ends the stuck ask then; nothing re-reads the steps and no
|
||||
expiry is added.
|
||||
- **A-143** (A-77, A-138; principle 1) The recovery module's dump wait and snapshot-restored wait are bound keys in
|
||||
its state `bounds` as well, their expiry heard on its watch; no timer stays in the process.
|
||||
- **A-144** (ADR 0625 §4 over §614 and §670's placing; principle 7) A client imports only its own seat's contract,
|
||||
mesh-sdk and its domain SDK: the key codec `meshcatalogkey` moves into mesh-delivery's domain SDK, imported there
|
||||
by the builder and the catalog client, and the source-branches follower's use of the `mesh-delivery` seat's types
|
||||
moves into the domain SDK too; the catalog client keeps the mesh-catalog contract and the domain SDK only.
|
||||
- **A-145** (§621, §494, §697, ADR 0633 §6; principle 7) The second reader release carries only §621's words and what
|
||||
the trunk validator needs to judge the new indexer's manifest and the `by-caller` definitions; §529's finding-stream
|
||||
rules and A-116/A-128's seat reads are G's own validator rules, judged by the change's validator, and stay in G.
|
||||
- **A-146** (A-145, §529, A-30; principle 5) The second reader release also carries §529/A-30's exception for a
|
||||
foundation part's kept old sections, and only that, since the trunk validator must accept the new indexer manifest.
|
||||
- **A-147** (A-123, A-132, A-137; principle 3) While a module runs at two fingerprints (a move under way), a recovery
|
||||
act on its kept data is refused, naming both; the caller asks again once the move has ended.
|
||||
- **A-148** (A-145; principle 7, one release one check) A trunk fault that fails a release's own check rides that
|
||||
release as its own commit, as fix 1975's distribution test did: the second reader release carries the builder's
|
||||
by-hand answer fix (an ask is answered as put only after its put; issue 1126, ADR 0668 §1).
|
||||
- **A-149** (§634, §527, A-120; principles 2 and 7) The node-engine knows its own name at start from its applied
|
||||
store: the record of the module whose fingerprint is its own `MESH_FINGERPRINT`; the finding writer, discovery and
|
||||
the vault login use it. Only on a first start with no such record do they wait for the first composing to name it.
|
||||
- **A-150** (§563, A-107, A-115; principles 1 and 7) restic's `night` process, which dumps, gets a `bus` of its own
|
||||
(its own broker account, as the recovery module's process has) and says item-started and item-ended and reads the
|
||||
recovery module's events itself; no local request to the tools runtime, and the backup does not move into it.
|
||||
- **A-151** (§465, ADR 0614 §2; principle 7) The three build modules `require` the provision `artifact-store` to read a
|
||||
group member's build, with a `binds` file as go-package-registry's (`at`, `serves.port`), and read the blob by the
|
||||
store's own path `<at>:<port>/v2/<module>/<artifact>/blobs/<digest>`; they never write into it, so ADR 0614 §2
|
||||
holds, and their manifest tests assert the read.
|
||||
- **A-152** (§472, §492; principles 3 and 7) The toolchain's `digest` is the sha256 of the toolchain program the build
|
||||
module runs (the `go` or `node` it finds), computed at its start and again when the toolchain's version changes; no
|
||||
field is added to the provision.
|
||||
- **A-153** (§559, E §1; principles 7 and 8) The tool runner resolves a `deliver: new` subscription's key
|
||||
`<emitter>.<event>` to its subjects with mesh-sdk's `buscompose.EventSubjects`, the one rule from an event name to
|
||||
its subjects (a seat's event subject or the module's); a pattern with a wildcard is refused with `{error}`.
|
||||
- **A-154** (design 46 approval mark §4, §16, §19; principles 7 and 8) The approval mark's three findings take scope
|
||||
`machine` and the id `<node>.<module>`, `<node>.<ask id>` or `<node>.approvals`, escaped as a key's name, with the
|
||||
subject of the tool runner on that machine, as the runner's first-view findings already do.
|
||||
- **A-155** (§561, §563, §302, A-77; principles 1, 2 and 3) restic's put-off wait for a restore is a bound key in a
|
||||
state of its own on the bus, bounded by the setting `put-off-bound`: the key is the put-off record `backed-up` reads,
|
||||
its expiry raises `backup-waits` and ends the wait, so the night never holds its lock without a bound and no file
|
||||
or timer keeps it. BKP7 is not built: §555 gives `restore` its class's default with no own-item limit.
|
||||
- **A-156** (§610, A-82, design 51 *The move*; principle 7) Once distribution's `callers` list goes, the windows'
|
||||
driver takes "every build module" as the modules that claim a build seat (`node-build-*`) in the catalog half it
|
||||
follows, the one source A-82 gives; the holder's own per-verb `Allowed` checks go with the list, since §610's class
|
||||
defaults are applied where the call is read, and the refusal of an unproven caller is that reader's.
|
||||
- **A-157** (design 46 approval mark §16, §19, ADR 0668 §1, A-77; principles 1 and 3) Each try of `warranted` is a
|
||||
`DoKept`; past its bound the call is made again when the hand-act seat's holder is heard again (the seat-holders
|
||||
view the runner follows) and at each start; `call-unanswered` is raised on the expiry of a five-minute bound key per
|
||||
ask, not a timer. A failed read or write of `approvals` is made again at the next marked call and at the next
|
||||
membership change the runner hears.
|
||||
- **A-158** (design 46 approval mark §16, §677; principle 8) A marked call's answer is `{answer, approval}`: the tool's
|
||||
own answer and the line naming the approval, the form a process holder's `gated-ended` has, so a caller reads one
|
||||
form from both gates. A-157's bound key lives in the runner's state `bound` as `<node>.unanswered.<ask id>`.
|
||||
- **A-159** (§466, §472; principles 3 and 7) A build's `resolved{package, version, digest}` names what was used: its
|
||||
`version` is the commit the ask names, and its `digest` the sha256 of what was laid (the Go module cache zip, the
|
||||
npm tarball), or the member build's digest when it was laid from `builds`.
|
||||
- **A-160** (§64, §190, §321, A-85, A-93; principle 7) A run's candidate is keyed by its run id `run-<id>` from the
|
||||
runner's `run-started`, beside `try-<n>`; each member {module, commit, branch} maps to its repository by the
|
||||
registrations and to the branch's revision view as its trunk; its manifests and fingerprints come from the same
|
||||
branch take-in the indexer computes for any commit (its files read through the seat `git`), so no checker preview
|
||||
is heard.
|
||||
- **A-161** (§522, §81, §170; principle 1) An event carries what its hearers need to say it: `recovery-merged` gains
|
||||
`module` and `branch`, and a forge-made record's `record-opened` gains `repository`, `pull`, `commit` and `branch`,
|
||||
in their contracts and from their emitters; the teller makes no call per message.
|
||||
- **A-162** (amends A-100; §575, A-94, ADR 0596; principle 1) The builder's `build.ended` is said for every end of a
|
||||
build, carrying `outcome` (done or failed) and, when failed, `error`; the assigner's hold says "failed: <error>"
|
||||
from it and "is being built" while no `build.ended` of that fingerprint is heard. The assigner never reads the
|
||||
builder's `builds` bucket; its read there (put.go:85, the manifest's `reads`) goes.
|
||||
- **A-163** (§543, §544; principles 7 and 8) The asker in an ask's accept subject is the asking seat's own name
|
||||
(`mesh-pipeline-runner`, `mesh-catalog`, `mesh-wants`, `mesh-builds`, `mesh-delivery-checker`), not a nickname, so
|
||||
steprun checks it against the callers the build seat's definition lists, read from the seat list; no table of its
|
||||
own and no name map. A body's kind is the word the seat definition gives; a queue keeps its own name.
|
||||
- **A-164** (§468, §76, §495, A-115; principles 4 and 7) The runner's process writes each durable consumer's oldest
|
||||
unacknowledged age into a state of the runner's own when it changes; the bundle's `runner_health` reads that state,
|
||||
its own module's, and asks no seat verb of itself; tools stay the bundle's (A-115).
|
||||
- **A-165** (§610, A-156; principles 3 and 7) The class defaults of §610 are applied where a seat's verb is answered:
|
||||
mesh-sdk's seat server (`conn.AnswerSeat`) and the tool runner's `serveSeats` read `seatdef.EffectiveCallers` and
|
||||
refuse a caller outside them, the unproven one included, before the holder runs; a holder keeps no caller check of
|
||||
its own.
|
||||
- **A-166** (A-164, §76, §495; principles 1 and 7) The runner keeps a state `consumers` (history 1, a key per
|
||||
consumer) holding the time its oldest unacknowledged message was delivered, written when a message is delivered or
|
||||
acknowledged; `runner_health` works the age out as now minus that time when it is read, so nothing is read on a
|
||||
timer.
|
||||
- **A-167** (§354, §63, §22, §64, §357, §298; principles 1, 2, 3 and 7) For the assigner's composition step (AS5): its
|
||||
`run` builds the node-engine's composer (`mesh-compose`) at the fingerprint the node-engine runs; each machine's
|
||||
input is asked of that node's engine by `compose-candidate`; the machines are the nodes the branch's revision places
|
||||
the module on; a node is asleep when the watchman's `machine-away` is heard for it; `mesh-compose` reading
|
||||
`MESH_RUN_INPUT` and answering the run's output is the node-engine's, a row of its own. An entry held for want of a
|
||||
provider (AS14) carries a field `held` naming the candidates, never only a log line. A revert (AS17) is a want that
|
||||
goes back to an earlier fingerprint, which the assigner computes itself; a kept data's `form` is read from the
|
||||
catalog entry's kept declarations (`catalogue.KeptDeclarations`). AS14's named seat and contribution edge, and
|
||||
AS17's ask of the operator, go to a design line.
|
||||
- **A-168** (E §33, design 46 approval mark §5; principle 3) For a marked wait on a module the runner does not serve,
|
||||
the marks are the union of the newest entry of every followed branch; where the runner serves the module, its
|
||||
served entry is added to that union. A mark is never lost.
|
||||
- **A-169** (§579; principles 2 and 7) A pins state keys a revision by the revision object's own name (the assigner's
|
||||
`trunk.<n>`, the indexer's `<branch>.<revision>`), value `{records, missing?}`, and holds two more keys in the same
|
||||
form in every holder of pins: `made`, written once by create-only when the state is first used, and
|
||||
`pins-stated`, the `at` of the applied event; pins count only once a `pins-stated` later than `made` is applied.
|
||||
- **A-170** (§600, §481, §415, A-163; principles 7 and 8) Every ask to a build seat rides `accept.<queue>.<asker>`;
|
||||
its kind is in its body only, as §600 says. A build seat definition's `stream` names only the queue, and the
|
||||
generated accept subjects follow; the kind in the subject goes. §543's main subject takes `build` and `run` as
|
||||
written, the checker's asks riding it until it goes.
|
||||
- **A-171** (§579, A-169; principles 2 and 8) The `pinned` event's `catalog` entries name their branch: each is
|
||||
`{branch, revision}`, in the mesh-delivery seat's definition and from the gate that says it, so a holder keys the
|
||||
pin `<branch>.<revision>` from what the event says.
|
||||
- **A-172** (§653, §431, A-161; principles 1 and 7) With the Approve per merge gone, the teller's merge-ask path goes
|
||||
whole (its reads of the gate's `asks`, its handlers and their tests); the teller keeps `answers` and `answered` for
|
||||
the assigner's asks only. A view's answer reaches it by the view's event, as §431 says, never by reading the
|
||||
view's state. The gate fills `recovery-merged`'s `branch` from the branch it merged into and its `module` from the
|
||||
catalog half's registrations; while no path of the gate opens a forge-made record, the teller says none.
|
||||
- **A-173** (§507, §512, §532, A-103; principle 7) The view derives each watched branch's source on its own: the
|
||||
source row and `delivered` per branch, a repository's reach from the source of the first branch of its
|
||||
registration (before the first registrations, the branches `source` answers), and `view_tiers` with no revision
|
||||
answering per branch; no branch is named in its code.
|
||||
- **A-174** (§64, §190, A-160, A-93; principles 2 and 7) The indexer keeps a state `runs` (key `run-<id>`, value the
|
||||
members and the modules whose pipeline ended), written on `run-started` and on each of the run's `pipeline-ended`,
|
||||
removed with the candidate, so a restart loses nothing. A member on a branch its registration does not watch takes
|
||||
the trunk's revision view as the base of its take-in; one on a watched branch takes that branch's.
|
||||
- **A-175** (§512, §431, A-43, A-172; principles 1 and 7) The teller says its `answers` as a plain views family
|
||||
`views.answers`, and the view says its own `answers` the same way; each follows the other's by mesh-sdk's
|
||||
`ProcessReader.Follow` (first view through the owner's tool, then its views family). That is the "event" A-172 and
|
||||
§431 mean; no own follower and no module-event adopt are added.
|
||||
- **A-176** (A-166, §579, §76; principle 7; code shared between modules is in mesh-sdk) A process writes its durable
|
||||
consumers' facts (stream, name, acknowledged sequence, pending, last event applied and its `at`, and when its oldest
|
||||
unacknowledged message was delivered) into its module's own state `consumers` (history 1, a key per consumer) through
|
||||
one mesh-sdk helper, after each message it applies; the runner and the indexer use it, and their tools read that
|
||||
state.
|
||||
- **A-177** (design 46 approval mark §4; principle 7) mesh-sdk's compiled `catalogue.Verb` gains `NeedsApproval`,
|
||||
copied by `SeatsFromDefinitions` from the definition, so the validator and every reader of the seat list read a
|
||||
verb's mark from one place.
|
||||
- **A-178** (A-175, A-43; principles 7 and 8) The view says its `answers` as other owners say theirs: a read tool
|
||||
`view_read_answers` (mesh-sdk `views.ReadTool`, in the view's tools bundle, the teller among its callers) and a
|
||||
marks state `view-marks` (history 1), as the assigner and the indexer have; the teller's side is the same, with
|
||||
`teller_read_answers` and `teller-marks`.
|
||||
- **A-179** (refines A-165; §610, §336; principles 3 and 7) Where a seat's verb is answered, an unproven caller is
|
||||
refused, and so is every caller when the verb's effective callers are empty; a proven caller is taken as among
|
||||
them, since the grants part composes a verb's call subject only for the callers its effective list names, so the
|
||||
bus holds the rest out. `seatdef.DefaultCallers` follows §610 (act, warrant and accept: operator, sessions and
|
||||
invokers).
|
||||
- **A-180** (§64, §420, A-160, A-174; principles 1 and 7) The runner places a run's pipelines when it hears the
|
||||
catalog seat's `catalog-revision` for the candidate `run-<id>` (a candidate's revision, its subject naming it),
|
||||
reading the candidate's entries and fingerprints through the seat's `revision` verb with the candidate's group;
|
||||
`<id>` is a random 12-hex id the runner makes, so two runs of one commit are two pipelines.
|
||||
- **A-181** (amends A-100 and A-162; §556; principles 1 and 2) The builder's `build.ended` stream keeps the last
|
||||
message per subject (one per fingerprint), and the assigner reads it whole at its start as §556 reads a kept
|
||||
stream, then hears it; so what was built, and a failed build's error, survive its restart and it reads no builder
|
||||
bucket. `assigner_put`'s fingerprint-prefix form goes: a put names the whole fingerprint.
|
||||
@@ -0,0 +1,379 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-12
|
||||
decisions:
|
||||
- "ADR 0666"
|
||||
- "ADR 0656"
|
||||
- "ADR 0596"
|
||||
- "ADR 0567"
|
||||
- "ADR 0565"
|
||||
- "ADR 0540"
|
||||
- "ADR 0633"
|
||||
- "ADR 0579"
|
||||
- "ADR 0571"
|
||||
- "ADR 0564"
|
||||
- "ADR 0539"
|
||||
- "ADR 0330"
|
||||
- "ADR 0429"
|
||||
- "ADR 0259"
|
||||
- "ADR 0126"
|
||||
- "ADR 0112"
|
||||
- "ADR 0084"
|
||||
---
|
||||
|
||||
# 47, milestone G: the provider a requirement points to, the ask of a held revert, the edge an acting rule gives, the take-in of every watched branch, and the seats of the status view's and the assigner's views
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
*§708 to §710 answer the assigner's develop questions on build-map rows AS14 and AS17 (2026-11-12), which the lead's
|
||||
answer A-167 ([G-20](G-20-lead-answers.md)) sent to a design line: which field names the seat whose holder provides a
|
||||
requirement, which field says a requirement carries contributed values and the edge's kind word (§357), and the form
|
||||
of the assigner's ask when a revert is held (§300). §711 answers the indexer's develop question on build-map row IX8
|
||||
(2026-11-12), which the lead's answer A-212 (G-22, the lead's second answers file) sent to a design line: which acting steps
|
||||
give an edge of kind 5 (§47), between which entries, and what a catalog entry's `acts` holds. §712 to §715 answer
|
||||
the indexer's develop questions on rows IX5 to IX7 (2026-11-12), which A-213 sent to design lines: the validation's
|
||||
run before take-in, what holds a pending revision while it runs, and how a watched branch other than the trunk gets
|
||||
its merges, its pending revision and its groups. §716 answers the lead's question of 2026-11-12 (A-279) that no
|
||||
principle settles: where the gate keeps a group's members and each member's base before the take-in. §717 answers
|
||||
developer J27's question of 2026-11-12 on A-210: which seat the status view's answers and the assigner's asks are
|
||||
views of, and how a follower starts before their holder is known. Each is the
|
||||
design's choice within the record it cites, tested against [the principles](../../../00-META/principles.md).*
|
||||
|
||||
## The provider a requirement points to
|
||||
|
||||
- §708 **The named seat is the seat that delivers the requirement's provision; no catalog entry field names it.**
|
||||
(ADR 0330 §2; ADR 0126 §3; ADR 0084 §3, §4; ADR 0571 §4; design 27 *A module provider*) This completes §357's
|
||||
order for the assigner's `Compute`. A catalog entry's `requires` stay bare provision names. For each requirement
|
||||
R of a placed module on node N, in this order:
|
||||
1. **The named seat's holder.** The seat whose definition's field `delivers` (mesh-sdk `seatdef`) is R, among the
|
||||
seat definitions the entry's catalog graph revision names in its `definitions` (each read whole through the
|
||||
seat `mesh-catalog`'s verb `seat-definitions`, as every part reads the catalog half). Its holder is read from
|
||||
the same revision's placements, from their `holds`, as the assigner's state `seat-holders` keeps them: the key
|
||||
`<seat>` for a seat held once for the mesh, `<seat>.<N>` for a node seat. A seat that delivers R and that no
|
||||
placement holds holds the entry, naming the seat; no other step is tried (design 27, step 1).
|
||||
2. **A pin.** The placed module's `bindings` entry for R, else N's placement `pins` entry for R.
|
||||
3. **The provider on N.** The one module placed on N whose catalog entry's `provides` names R.
|
||||
4. **The only provider.** The one module placed on any node whose catalog entry's `provides` names R.
|
||||
|
||||
Each step that answers gives the entry the edge of kind `provisioning`, its `node`, `module` and `fp` the
|
||||
provider's and its `provision` R. Where no step answers and more than one candidate is placed, the entry carries
|
||||
`held`, a list with one item per requirement held: `requirement` R, `seat` (the unheld seat, step 1 only) and
|
||||
`candidates`, each `{node, module}`, sorted; the assigner writes no want from a held entry, so the node keeps what
|
||||
it runs, and the field is read with the entry through the seat `mesh-wants`' verbs. An optional requirement
|
||||
(ADR 0330 §3) with no candidate is left absent: no edge, no hold. A hard requirement with no candidate is held
|
||||
with `candidates` empty. Why: the seat definition already carries `delivers`, and the assigner already keeps who
|
||||
holds which seat from the placements, so nothing new is declared and no part outside the assigner's inputs is read
|
||||
(ADR 0656 §2; principles 1 and 7); a hold is said in the graph, never only in a log (principle 3; design 47 G
|
||||
A-167). How it is checked: unit (assignment graph), one row per step, the seat's holder winning over a provider
|
||||
on N, an unheld delivering seat holding the entry with `seat` named, two candidates and none on N held with both
|
||||
named, an optional requirement with none left absent.
|
||||
|
||||
- §709 **A requirement carries contributed values where the manifest's `contributes` names it; its edge is of kind
|
||||
`contribution`.** (ADR 0656 §2; §174, §175, §357, §442) This completes §357's contribution edge. The catalog entry
|
||||
gains `contributes`: the requirement names the manifest's `contributes` object has as keys, sorted, its local
|
||||
names not carried; the indexer fills it, since it alone reads a manifest (§442). `requires` keeps naming every
|
||||
requirement, those only `contributes` names included, as mesh-sdk's `catalogue.Manifest.Wants` lists them, since
|
||||
contributing to a requirement is requiring it. For each requirement in `contributes` that §708 resolves, the
|
||||
consumer's entry carries, beside its `provisioning` edge, an edge of kind `contribution` to the same provider, its
|
||||
`provision` the requirement. The kind word is `contribution`, the glossary's and the seat `mesh-wants`' `Edge`
|
||||
`kind` (provisioning, contribution or acting); the assigner's `EdgeContribution` joins `EdgeProvisioning` and
|
||||
`EdgeActing`. The `Edge` fields' descriptions in the seat `mesh-wants`' definition become: `provision`, for
|
||||
provisioning and contribution, the requirement; `seat`, for acting only, the seat whose rule gave the edge. A seat
|
||||
contribution (the manifest's `contributions`, a piece given to a seat's holder on the node) gives no edge in G.
|
||||
Why: the manifest already says which requirements carry contributed values, and the edge kind already has its word
|
||||
in the glossary and the contract (principles 7 and 8). How it is checked: unit (indexer), a manifest whose
|
||||
`contributes` names `route` gives an entry whose `contributes` and `requires` both name it; unit (assignment
|
||||
graph), that entry gets a `provisioning` and a `contribution` edge to the route's provider, and an entry with no
|
||||
`contributes` gets none of kind `contribution`.
|
||||
|
||||
## The ask of a held revert
|
||||
|
||||
- §710 **The assigner asks the operator about a held revert as it asks about a failed backup.** (ADR 0539 §16;
|
||||
ADR 0259 §3, §4; ADR 0564 §2; §297, §300, §363, §461, §541, §700, §706) This completes §300. When a node's have
|
||||
says the phase `held` for a revert, with the reason `restore point missing` (§300, a missing point or no
|
||||
`restored`) or `restore point damaged` (§567), the assigner:
|
||||
- **Asks**, through mesh-sdk's `operatorchannelclient` on the seat `operator-channel`, as its other asks are made,
|
||||
an ask of the new kind `revert`, kept in its state `asks` under `revert.<node>.<module>.<fingerprint hex>`, the
|
||||
fingerprint the reverted build's; like the backup ask it needs no proof of who answered, so the view may answer
|
||||
it. Its headline: "The revert of <module> on <node> has no restore point". Its explanation names the reverted
|
||||
fingerprint, the kept data, its form and the node's reason in its own words, and says "<module> keeps running
|
||||
its newer build until you answer. Started on the live data, the older build reads data written in the newer
|
||||
form." Two options, each of level acknowledge: `hold`, "Keep it held", does "the node keeps running the newer
|
||||
build, and the revert waits"; `live`, "Start on the live data", does "the node starts the older build on the
|
||||
live data as it is, with nothing restored". It expires after the assigner's `AskLasts`, `OnExpiry` "the module
|
||||
keeps running its newer build". The answer is read from the router's record of the ask (§700).
|
||||
- **Raises the condition**, through mesh-sdk's finding writer (§461), with the finding `revert-held` (scope
|
||||
`delivery`, id `<node>.<module>`), so the condition keeper's key is `delivery.<node>.<module>.revert-held`;
|
||||
severity `warning`; its summary "The revert of <module> on <node> is held: <reason> (<kept data>, form <form>);
|
||||
the ask about it waits for your answer"; its `needs` "Nothing for you to do.", so the operator's one message is
|
||||
the ask (§706).
|
||||
- **On the answer**: `live` writes the node's want again with the want's new field `revert` `live` and `words`
|
||||
"the operator answered \"live\" to the revert ask (asking <n>)", as the backup's answer is written; the node
|
||||
agent, reading `revert` `live` from its own want and from nothing else, starts the reverted build with no
|
||||
`restore` and no aside taken, calls no catch-up tool, and applies as any want. `hold` writes nothing: the hold
|
||||
stands. The finding is cleared once the reverted build runs, or when a newer want of that module on that node
|
||||
replaces the held one, which also drops the ask (§363). An ask that ends unanswered is raised again at once
|
||||
while the hold stands; one answered `hold` is not asked again.
|
||||
- **Its tools**: `assigner_read_asks` reads the ask and `assigner_read_findings` the finding, both existing;
|
||||
`assigner_ask_again` raises it again by hand.
|
||||
|
||||
Why: ADR 0539 §16 has the assigner ask, raise the condition and write the want again naming the answer for a hold
|
||||
before an apply, and the backup ask is that shape already built (principle 7); the finding writer is the one way
|
||||
G's parts raise a condition (§383); the ask alone reaches the operator, as O-9 had it for §706 (principle 3, the
|
||||
hold is still loud in the condition). How it is checked: whole (assigner, a fixture node agent saying `held` with
|
||||
`restore point missing`): the ask under `revert.<node>.<module>.<hex>` with both options and the finding raised;
|
||||
`hold` writes no want and the newer build keeps running; `live` writes the want with `revert` `live` and its
|
||||
words, and the finding clears once the have names the reverted build; a newer want drops the ask and clears the
|
||||
finding; an unanswered ask raised again. Unit (node-engine): a want with `revert` `live` starts the reverted build
|
||||
with no `restore` call; unit (`meshwantsclient`): a `revert` other than `live` is refused.
|
||||
|
||||
## The edge an acting rule gives
|
||||
|
||||
- §711 **An acting rule gives an edge of kind 5 from the acting part to each entry it acts on, in the steps that act on
|
||||
a delivered version.** (ADR 0656 §1; ADR 0666 §14, §19; §47, §49, §203, §265, §266, §270, §324, §374, §378, §535,
|
||||
§569, A-208) This completes §47's row 5 for the indexer, which writes a catalog entry's edges (A-208).
|
||||
- **The entry's `acts`.** The indexer fills a catalog entry's field `acts` when it makes the entry: for each seat
|
||||
its manifest's `claims` names, each acting rule that seat's definition carries, among the definitions the entry's
|
||||
catalog graph revision names in its `definitions` (read through the seat `mesh-catalog`'s verb
|
||||
`seat-definitions`, as §708), as one item `{seat, step, on, bound}`: `seat` the claimed seat, `step`, `on` and
|
||||
`bound` as the definition writes them, `bound` left absent where the rule names none (its step's default, §266,
|
||||
is the reader's). Items sorted by `seat`, then `step`; a module that claims no seat with a rule has no `acts`. It
|
||||
is what the runner (§535) and the builder (§546) read, and what the edges below are derived from; nothing else
|
||||
writes it. The contract's description of `step` gains `pipeline` (§203).
|
||||
- **The steps.** `grant`, `compose`, `place` and `check`, and from G's delivery `pipeline` in `check`'s place
|
||||
(ADR 0666 §19; §65), each give kind 5: a grant or a credential, a composition, a placement, a verdict, each made
|
||||
for the dependent's new version (ADR 0666 §14). `build` gives kind 3 (§47, §267), never kind 5. `take-in` and
|
||||
`validate` give no edge: an entry is in a revision only once the indexer has taken it in and validated it, so
|
||||
such an edge would always be met (principle 7).
|
||||
- **Between which entries.** For each entry B with such a rule, and each other entry A of the same revision that
|
||||
the rule's `on` matches (`every`; a language, A having an artifact of that language; `kind:image`, A having an
|
||||
artifact of kind image, §324): one edge `kind` 5, `from` B's delivered artifact, `to` A's delivered artifact (the
|
||||
artifact the entry's first process resource runs, else its first, as kinds 4 and 6 have it), `word` `acts`, `why`
|
||||
`<seat> <step>`, `stale` false (it only orders, §378), `declared` false. No edge goes to B itself, nor to an entry
|
||||
B's new version is built from or needs: one with a path of edges of kinds 1 to 4 to B in that revision (a
|
||||
package, a fetched artifact, a build module or base, a provider), which B's running version acts on (§270). An
|
||||
entry with no artifact has no edge. A rule of a seat no entry claims gives none; the step's own holder says that
|
||||
(§233, §535).
|
||||
- **How it is met.** The edge is met when B's new version has acted for A's build, as the holder's `wait-ended`
|
||||
says and §374 records; for `compose` per node, by the assigner (§268). It is a delivery edge, so a cycle of it
|
||||
with kinds 2, 4 and 6 only is delivered as one step, and one with kind 1 or 3 is refused naming it (§49, §569).
|
||||
|
||||
Why: the rule already says the step and the entries, and the artifact a delivery orders is the one kinds 4 and 6
|
||||
already name, so no manifest word is added and no module is named in code (principles 7 and 8); a cycle left by a
|
||||
rule is refused loudly at the check (principle 3). How it is checked: unit (indexer's graph), an entry claiming a
|
||||
seat with rules `grant` on `every` and `build` on `go` carries both in `acts` with its seat; a grants part of rule
|
||||
`grant` on `every` gives kind 5 to every other entry but none to itself nor to the build module and package it is
|
||||
built from; a rule `compose` on `kind:image` gives edges only to entries with an image artifact; a rule `build`
|
||||
gives no kind 5; an entry with no rule has no `acts`.
|
||||
|
||||
## The validation before take-in, and the take-in of every watched branch
|
||||
|
||||
- §712 **The validation is one `run` of the indexer's manifest validator over two cases, refusing only what the revision causes.**
|
||||
(ADR 0666 §5, §6; ADR 0579 §2; ADR 0578 §2; §16, §206, §338, §354, §467, §481, §493, A-213) This completes §16's ask.
|
||||
For a pending revision of watched branch B (§713), the running indexer asks the seat `node-build-go` the accepted
|
||||
kind `run`, on `mesh.seat.node-build-go.accept.run.indexer` (§481; its callers §338), its step key
|
||||
`indexer.<node>.<id>` (§467), `<id>` `validate-<s>-<n>`, `<s>` the stream sequence of the newest merge the revision
|
||||
covers and `<n>` the count of asks for it, from 1. Its job:
|
||||
- **`program`**: the indexer's own program `cmd/mesh-judge`, under the Go module the `package` of the indexer
|
||||
entry's Go artifact names, at that entry's `path`, built at the revision's head of that entry's `repository`;
|
||||
the indexer entry is the entry of B's revision being validated that claims the seat `mesh-catalog`, else, when
|
||||
B watches no indexer, the trunk's current revision's. No repository or module name is fixed in code (ADR 0666
|
||||
§5); the program's own directory is the indexer's own code.
|
||||
- **`heads`**: none, since the manifest validator reads only its request; **`args`**: none; **`reader`** "manifest validator of
|
||||
<B> at <s>".
|
||||
- **`input`**: the manifest validator's request, served by the seat `mesh-catalog`'s verb `input` and kept in `inputs` as §354
|
||||
has it, any size (ADR 0666 §16): case `change`, every module of B's current revision by its entry's manifest,
|
||||
each one the pending merges carry replaced by the merged message's manifest bytes, one added for a new
|
||||
directory; case `trunk`, every module of B's current revision as it stands (the name is the validator's, not the
|
||||
branch); `seats` the definitions the merges' trees carry (the main file, *Validated with the seat definitions
|
||||
its merges carry*); `packages` and `predefined` as the take-in's own manifest validator has them; no `machines`.
|
||||
- **Its verdict.** The revision is refused when the case `change` refuses a manifest the revision adds or changes
|
||||
(its digest differs from the `trunk` case's) with a problem the `trunk` case lacks, or names a problem `(between
|
||||
manifests)` the `trunk` case lacks (ADR 0579 §2). A problem only in unchanged manifests refuses nothing; their
|
||||
entries keep their verdicts (ADR 0579 §1). A refusal sets the pending value's `refused` to each manifest and its
|
||||
problems, said in `take-in.pending` (§493).
|
||||
|
||||
Why: the manifest validator already answers two cases for exactly this rule, as the checker ran it, and the build seat's `run`
|
||||
is the one way a program built from heads runs (principle 7; ADR 0666 §6). How it is checked: whole (indexer, a
|
||||
fixture Go build module): a revision whose changed manifest gains a problem stays pending, `refused` naming it; a
|
||||
revision whose unchanged manifest has one is taken in; the job names the indexer entry's repository, path and the
|
||||
revision's head, and no heads.
|
||||
|
||||
- §713 **A pending revision names its ask, and resumes on the ask's end; a run that could not validate is no
|
||||
refusal.** (ADR 0666 §6, §18, §24; ADR 0633 §5; §16, §18, §19, §24, §269, §483, A-213; the main file *A wait is
|
||||
bounded, said and recoverable*) This completes §16's wait, and amends the main file's *A revision that names another
|
||||
indexer moves only that indexer* in that every revision now waits there for its validation. Every merge of a watched branch is first written to
|
||||
`pending` as the main file has it (key `m.<sequence>`, or a group's key, §715), its value gaining `validation`:
|
||||
`ask` (the `run` ask's id), `asked` (when) and `input` (its digest). The indexer puts the ask, acknowledges the
|
||||
merged message, and the take-in ends there. It hears the end through a durable consumer of its own of the seat
|
||||
`node-build-go`'s event `step-ended` (§24), taking only ends whose step key starts with `indexer.`, and reads the
|
||||
answer from that step key; at its start it reads each pending value's `validation` and its step key, so an end said
|
||||
while it was down is not lost. An end whose ask is not the one the pending value names now is dropped, written in
|
||||
its journal. A later merge of B while an ask is out joins the pending revision (the main file) and is asked anew
|
||||
with the next `<n>`. On the end:
|
||||
- **accepted**: the revision is taken in as the main file has it, by the indexer it names (§16), and its keys go;
|
||||
- **refused**: it stays pending with `refused` (§712), asked again only as part of a later merge of B, as a
|
||||
revert is (§19);
|
||||
- **could not validate**: the step ended `could not run`, the program exited other than 0, or its answer holds not
|
||||
both cases: it stays pending with `validation`'s new field `failed`, the build seat's words, said in
|
||||
`take-in.pending`, never as a refusal and never as passed (ADR 0666 §18; principle 3). It is asked again by a
|
||||
later merge of B and by the indexer's new tool `indexer_validate_again` (key; class `act`, §391's default callers,
|
||||
written in the hand-act log), which also reads the ask's state.
|
||||
|
||||
An ask no build node takes waits on the work queue, said by `build-capacity-waits` after 60 minutes (§483); every
|
||||
case is timed by `take-in-waits` at 60 minutes (§19). `indexer_pending` shows `validation` whole. Why: a take-in that
|
||||
holds no call open survives a restart from its own state, and only an answer the manifest validator gave refuses (principles 1,
|
||||
3 and 4). How it is checked: whole (indexer): a restart while the ask is out takes the revision in on the end; a
|
||||
`could not run` end leaves it pending with `failed` and `indexer_validate_again` asks `<n>`+1; an end of a replaced
|
||||
ask is dropped.
|
||||
|
||||
- §714 **Every watched branch's merges come on the forge bridge's stream, each naming its branch.** (ADR 0666 §6,
|
||||
§11; §11, §13, §18, §493, A-90, A-93, A-208) This completes A-208's per-branch take-in. The forge bridge publishes
|
||||
the merged message of every landed commit of every watched branch the registrations name, heard on `branch-moved`
|
||||
and read by the seat `git`'s verb `branch` as §11 has it, the trunk's included, since from G no merge is the gate's
|
||||
(ADR 0666 §11). The merged message (`forgemsg.Forge`) gains `base`, the watched branch it landed on; the forge
|
||||
bridge's filters to the trunk alone (its merged message and its forge-made merge) go. The indexer's `pending` keeps
|
||||
its key `m.<sequence>`, the stream's sequence being one across branches, and its value names `base`; a merge waits
|
||||
only behind a pending value of its own `base` (§18), and is taken in by A-93's take-in of that branch. A value
|
||||
written before G names no `base` and is the trunk's, the only branch the forge bridge published before G. The
|
||||
gate's `source-moved` takes nothing in. Why: one take-in for every branch, from the stream every merge already
|
||||
comes on, and no second source of the same merges (principles 1 and 7). How it is checked: whole (forge bridge,
|
||||
indexer): a merge on `beta` publishes a merged message with `base` `beta`, pending behind a pending `beta` revision
|
||||
while a `main` merge is taken in.
|
||||
|
||||
- §715 **A group gathers per watched branch, and its end names that branch.** (ADR 0666 §13; ADR 0633 §5; §42 to
|
||||
§46, §293, §714, A-208) This completes §43 and §44 for a branch other than the trunk. A group's members on watched
|
||||
branch B are those whose `base` in the seat `mesh-delivery`'s verb `groups` is B (its `Member` carries `base`). The
|
||||
forge bridge sets the group on each landed member's merged message of any watched branch, and publishes
|
||||
`forge.group.merged` (`forgemsg.ForgeGroupEnd`, gaining `base`) once `groups` says every member with that `base`
|
||||
has landed, once per `base`. The indexer gathers the members in `pending` under `g.<group>.<base>`, each part
|
||||
escaped as a key name, and takes them in as one source revision of B on that end, after §712's validation; a key
|
||||
`g.<group>` written before G is the trunk's. `indexer_pending`, `indexer_take_in_pending`, `indexer_drop_pending` and
|
||||
`bridge_group_end_again` take the `base`; a group that never ends on B is said at 30 minutes (§45). Why: a group
|
||||
is delivered on the branch its members land on, read from the one verb that knows its members (principles 1
|
||||
and 7). How it is checked: whole (forge bridge, indexer): two members landing on `beta` give one `forge.group.merged`
|
||||
with `base` `beta` and one `beta` source revision; a group whose `beta` member never lands raises `take-in-waits`
|
||||
at 30 minutes.
|
||||
|
||||
- §716 **The gate keeps a group's members from their announcement, in its states `deliveries` and `groups`, heard on
|
||||
the seat `git`'s pull events.** (ADR 0666 §13; ADR 0656 §7; ADR 0633 §4, §5; §42, §43, §45, §151, §156, §613, §715,
|
||||
A-236) This completes §42 and §715: ADR 0666 §13 has the gate form a group and its group verb say every member, and
|
||||
before G's delivery the gate kept a group from the announcement in `deliveries` and `groups`, which §613 already
|
||||
moves to template states under the same names; §716 has G's gate go on writing them. The gate's manifest `consumes`
|
||||
the seat `git`'s `pull-updated` beside the `pull-merged` and `pull-closed` it consumes, through its one durable
|
||||
consumer, and `invokes` `seat:git.open-pulls` and `seat:git.pull`. Each event carries the pull request's
|
||||
repository, number, head, `base` and head branch (`branch`).
|
||||
- **What it writes, in today's forms.** On `pull-updated` the gate writes the delivery under its id
|
||||
`<repository>@<first 12 of the head>`, escaped as a key name, as today, with `repository`, `number`, `commit` (the
|
||||
head), `base`, `branch`, `group` and `state` `open`; a newer head of the same pull request takes the older's place
|
||||
in its group and the older key is deleted. Pull requests with one head branch form one group, keyed by that branch
|
||||
escaped as a key name, its `members` the delivery ids, as today (§42): a group of one with no member in a record is
|
||||
no group: its key is deleted and its member's `group` cleared. `pull-merged` sets the delivery's `state` to `merged`. A delivery copied from
|
||||
before G (§613) in another state of the old table counts `open`. Only the gate writes either state.
|
||||
- **Who deletes it.** The gate: a delivery on `pull-closed` of a pull request not merged; a delivery once the record
|
||||
that carries it opens (§151's take-in, or §156's switch), its `base` copied onto the record's member (`RecordPull`
|
||||
gains `base`) and, for a member with no delivery, the take-in's `branch`, said in its journal; a group key once no
|
||||
member remains outside a record. A deletion is made in the old section too (§613).
|
||||
- **How `groups` answers.** A group is answered from its records and its `groups` key together, each member once,
|
||||
a record's first: a record's member with its copied `base` and `state` `merged`, a kept member with its delivery's
|
||||
`base`, `head`, `state` and `open` while not merged. So before any take-in the verb names every member and its
|
||||
base, and the forge bridge reads them as §715 has it (A-236).
|
||||
- **When a member has landed.** For §715, a member has landed once `groups` says it `merged` and the bridge has
|
||||
published its merged message. The bridge asks `groups` at each such message it publishes and at each
|
||||
`pull-merged` it hears of a group it holds open (`bridge_groups`), so the end follows whichever comes last, never
|
||||
a timer.
|
||||
- **At its start**, after §613's copy, the gate reads `open-pulls` once and writes each open pull request it lacks
|
||||
or keeps at another head. For each delivery it keeps `open` that the answer does not name, it reads `pull`: merged
|
||||
sets `merged`, closed deletes it. A read that fails is said in its journal and made again at the next start. Until
|
||||
then, a group whose end never comes is said by §45's 30-minute `take-in-waits`, and the indexer's
|
||||
`indexer_take_in_pending` takes it in as gathered.
|
||||
|
||||
`merges` stays as §156 has it, read by the switch alone. Why: the gate forms the group and its verb says every
|
||||
member (ADR 0666 §13), from its own states heard on the seat `git`'s events. It keeps the two states and forms it
|
||||
already holds, adding no state and no new tool (principles 1, 4 and 7). A missed event is put right from the forge's
|
||||
own record at the start, and a group that stays open is said (principle 3). How it is checked: whole (gate), two
|
||||
pull requests on one head branch, bases `main` and `beta`. `groups` names both with their bases before any
|
||||
take-in; a newer head replaces its member; a `pull-closed` before the merge removes it. A `take-in.taken` of the
|
||||
`main` member opens a record with `base` `main`, and the `beta` member stays in `groups`. A `pull-merged` missed
|
||||
while the gate was down is set from `pull` at its start. Whole (forge bridge): the group's end is published once
|
||||
both the member's merged message and its `pull-merged` have come, in either order.
|
||||
|
||||
## The seats of the status view's and the assigner's views
|
||||
|
||||
- §717 **The status view's answers are a view of the seat `mesh-delivery-view`, the assigner's asks and wants are
|
||||
views of the seat `mesh-wants`, and a follower finds each by its seat and starts without it.** (ADR 0540 §1, §6;
|
||||
ADR 0429 §3; ADR 0567 §5, §6; ADR 0571 §6; ADR 0565; ADR 0539 §16; ADR 0596; §7, §200, §428, §532, §616, §699,
|
||||
§710, §614, A-126, A-131, A-175, A-178, A-210, A-295) This answers developer J27's question of 2026-11-12, met building A-210
|
||||
and A-296 (G-22): A-210 has a part find another module as the holder of the seat it holds, but the status view
|
||||
claims no seat while the teller follows its `answers` by its module name, and the assigner holds two seats while
|
||||
the teller follows its `asks` by its module name. It completes A-175 and A-178 for both views.
|
||||
- **The seat `mesh-delivery-view`.** ADR 0540 §1 names the seat the status view holds, `mesh-delivery-view`; G
|
||||
defines it. Its definition is in the Delivery domain's contracts module, `seats/mesh-delivery-view/` beside the
|
||||
domain's other seats, with a states fixture as theirs have: `scope` mesh, `grain` whole, `domain` Delivery,
|
||||
`decision` ADR 0540. It serves no verb and declares no seat event and no `family`: the status view's tools stay
|
||||
its own (one seat, many tools), each caller reaching them at the holder A-210 finds, and its events `delivered`,
|
||||
`moving`, `settled` and `stuck` stay the holder's own on the subjects ADR 0540 §1 names, so every `consumes` of
|
||||
them keeps its entry and a part that builds their subject in code builds it from the holder A-210 finds. It
|
||||
declares one state, `answers` (template state, history 5, `view: true` by §699), whose one key form is
|
||||
`<ask key>.view`, `<ask key>` a key of the seat `mesh-wants`' state `asks` of a kind that needs no proof of who
|
||||
answered, and whose value is the status view's answer: `v`, `asking` (the asking it answers), `option`, `words`.
|
||||
Only the holder writes it, the person's answer in the web view, and only the holder deletes a key: once the ask's
|
||||
key has left `asks`, heard on the `asks` view it follows, so no answer outlives its ask. The status view's manifest `claims` the seat with scope mesh and an empty `serves`. Its
|
||||
first-view tool of `answers` is named for the state, `answers` (mesh-sdk's `views.ReadTool`, the teller among
|
||||
its callers, A-131's form), in place of A-178's `view_read_answers`, which goes.
|
||||
- **The seat `mesh-wants` owns `asks` and `wants`.** Every kind the assigner keeps in its state `asks` (hold,
|
||||
restore, backup, record, outside, revert and a placement proposal's `assignments`) is an ask whether what a node
|
||||
runs moves, and ADR 0567 §6 names the states of `mesh-module-assigner` without `asks`, so `asks` is a state of
|
||||
`mesh-wants`: its definition gains `asks` (template state, history 5, `view: true`), its key forms the eight kinds'
|
||||
keys as the assigner's `asks` package writes them, its value the ask as the assigner composes it. Its `wants`,
|
||||
which the definition already declares, gains `view: true`. The holder's first-view tools are named for the
|
||||
states: `wants`, as it exists, and `asks`, new, in place of `assigner_read_asks`, which goes.
|
||||
- **The followers name the seat.** The teller's manifest `consumes` `seat:mesh-wants.views.asks` and
|
||||
`seat:mesh-delivery-view.views.answers` in place of `mesh-delivery-assigner.views.asks` and
|
||||
`mesh-delivery-view.views.answers`, and its `invokes` loses `mesh-delivery-assigner.assigner_read_asks` and
|
||||
`mesh-delivery-view.view_read_answers`, the seat-form `consumes` covering each first view (A-131). The status
|
||||
view's manifest `consumes` `seat:mesh-wants.views.asks`, `seat:mesh-wants.views.wants` and
|
||||
`seat:mesh-module-assigner.views.assignments` in place of the three `mesh-delivery-assigner.views` entries, and
|
||||
its `invokes` loses `assigner_read_asks`, `assigner_read_wants` and `assigner_read_assignments`. Neither part's
|
||||
code names the assigner or the status view (§7, §428).
|
||||
- **What the seat form puts on the bus.** A seat's view is said where its holder says it, on the holder's own
|
||||
subject `mesh.mod.<holder>.event.views.<state>`, and the grants part resolves a `consumes` entry
|
||||
`seat:<seat>.views.<state>` to that subject for the seat's holder on record, granting nothing while the seat has
|
||||
none (§616). The subject is module-scoped, so a follower must know the holder, and follows it as it moves.
|
||||
- **A follower finds and re-finds the holder from the seat-holders view.** mesh-sdk's `views.ProcessReader` gains
|
||||
`FollowSeat`, given a seat and a state, and the holder function A-295 gives a part, which answers from what the
|
||||
part last heard of the seat `mesh-module-assigner`'s view `seat-holders`. The part follows that view as the tool
|
||||
runner does (§616): the module that claims `mesh-module-assigner` is read from the catalog half the part follows
|
||||
(§614, the trunk's entry whose manifest `claims` the seat), so no code names the assigner's module; the view is
|
||||
named `seat:mesh-module-assigner.views.seat-holders` in the part's `consumes`, its first view asked of the
|
||||
claimant's tool `seat-holders`, and a catalog revision naming another claimant drops it and follows the new one
|
||||
(§532). `FollowSeat` returns at once a picture that reads as empty, its follower not ready, and never fails for
|
||||
want of a holder. At each change of the seat-holders view it reads the seat's holder: on a first holder it follows
|
||||
as `Follow` does after `Start` (§532), the first view asked of the holder's tool named for the state, then its
|
||||
views family; on another holder it drops the view of the old one and follows the new one, the picture handing on
|
||||
the new holder's state whole; with no holder it keeps the picture it has and follows nothing. Nothing is asked
|
||||
again on a timer. While no holder is followed the follower's status names the seat in place of its owner, waiting
|
||||
since its start or since its holder left, and past five minutes the reader writes §200's `view-waits` naming the
|
||||
seat and the state, cleared when a holder's first view arrives. The teller's two follows and the status view's
|
||||
three go through `FollowSeat`, each apart, so the teller raises the assigner's asks whether or not the status
|
||||
view's answers are read (issue 1906, as today); the teller gains the catalog half's reader of §614 for this. The
|
||||
builder's and the checker's own loop for the assignments, buildrun's `AssignerHolder`, which asks every 5 seconds,
|
||||
goes: both follow `assignments` through `FollowSeat`. `seatlist.HolderOf`'s call stays for a caller that asks
|
||||
once, as A-210 has it.
|
||||
|
||||
Why: ADR 0540 §1 already names the seat the status view holds, and §616 and §699 give a view named by its seat;
|
||||
`asks` goes where the kinds it holds belong, its proposal kind kept with them so the teller follows one view. The
|
||||
holder is heard from the seat-holders view as A-295 has it, never asked on a timer, and a moved holder is followed
|
||||
as it moves (principles 1, 3, 7 and 8). How it is checked: unit (mesh-sdk `views`), a `FollowSeat` with no holder
|
||||
returns at once and writes `view-waits` naming the seat past five minutes; a seat-holders change naming a holder
|
||||
takes its first view from `<holder>.<state>` and hears its family; a second change naming another holder drops the
|
||||
first and follows the second; no ask is made between changes. Unit (manifest validator):
|
||||
`seat:mesh-delivery-view.views.answers` and `seat:mesh-wants.views.asks` are accepted, and a seat view of a state
|
||||
without `view` is refused. Whole (teller): started before any module holds `mesh-wants`, it raises an assigner's
|
||||
ask once the holder is placed, and takes an answer the status view writes. The code review: no module name of the
|
||||
assigner or the status view in the teller's, the status view's, the builder's or the checker's code, and no
|
||||
timer that asks for a holder again.
|
||||
@@ -0,0 +1,588 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-12
|
||||
decisions:
|
||||
- "ADR 0667"
|
||||
---
|
||||
# 47, milestone G — the lead's answers to develop's questions, continued
|
||||
|
||||
The answers of [G-20](G-20-lead-answers.md) go on here, since that part reached its size bound: each one line, naming
|
||||
the principle that settles it, cited as "design 47 G A-n".
|
||||
|
||||
- **A-182** (§31, §350, §119; principles 2 and 7) A superseded step's ask is dropped where it stands: a taken step
|
||||
keeps the node `ask-taken` named, and the drop is asked of that node's holder of the build seat; an untaken ask's drop
|
||||
is asked of any holder, which records it in the seat's shared drop key, so no holder takes it. Steps on `mesh-builds`
|
||||
and `mesh-wants`, whose seats serve no `drop`, are only ended superseded.
|
||||
- **A-183** (design 46 approval mark §4, §23; principles 3 and 7) A manifest that marks a verb nothing enforces is
|
||||
refused by the validator wherever it runs, as every rule's problem is; the indexer's "set aside to wait" is only its
|
||||
reading of a field it does not yet know (the reader edge of §23), so no new waiting verdict is added.
|
||||
- **A-184** (clarifies A-170; §543; principle 3) Until main's subject goes, it also takes the checker's `check` and
|
||||
`recheck` asks, so no ask that runs today is refused by G; a queue is only `delivery`, `check` or `recheck`, and
|
||||
steprun's `AskKinds` lists no `step` queue.
|
||||
- **A-185** (A-180; principle 7) The catalog client (`meshcatalogclient`'s catalog follower), which already hears every
|
||||
`catalog-revision`, hands a candidate's revision it does not keep to a hook its caller sets; the runner sets it to
|
||||
place a run's pipelines. mesh-sdk's event hub keeps its one handler per pattern.
|
||||
- **A-186** (A-179; principles 3 and 7) `conn.AnswerSeat` takes the seat's definition from its caller, which reads it
|
||||
from the seat list it already follows; a verb the definition does not hold, or a definition not yet read, is
|
||||
refused for every caller and said so, so a holder answers only once its seat list is read.
|
||||
- **A-187** (A-176, §76; principle 3) The consumer ledger keeps a message's first delivery time: a message delivered
|
||||
again (asked again, or after a restart) keeps the time it was first delivered, so a message that never settles
|
||||
grows old and §76's check fires for it.
|
||||
- **A-188** (A-172, §170, §151; principles 3 and 7) A take-in writes its entries (each with its module) into the
|
||||
gate's record, so `recovery-merged`'s `module` is filled from the record; no forge-made open path is built, since
|
||||
G ends the mark (§151). A record of the old milestone form, made before G, says no branch.
|
||||
- **A-189** (§376, §374, A-181; principles 1, 2 and 7) At its start the assigner reads whole, as A-181 reads
|
||||
`build.ended`, the streams of its own `version-delivered` and of the catalog seat's `version-released`, so it says
|
||||
each version delivered once and knows the released ones. A dependent whose want changes fingerprint waits, by an
|
||||
edge of kind 2, 4 or 6, for its dependency's newest released version on the dependent's branch not yet said
|
||||
delivered; on a branch where no version is released there is no wait.
|
||||
- **A-190** (ADR 0668 §1, §2; principles 3 and 7) A kept call is kept only while what it serves still stands; for the
|
||||
indexer's four: `messageVia` while the commit's take-in is still pending, `stepsVia` while the candidate is held,
|
||||
`sourceVia` while the branch is watched, `versionsVia` while the module is in that branch's newest view. Once
|
||||
that ends, the call stops and is not said unanswered. `pin-missing` takes scope `module`, id
|
||||
`mesh-delivery-indexer.pin.<key>`.
|
||||
- **A-191** (§33, §540; principle 3) A `pipeline-failed` finding whose commit no pull request merged, or whose `pull`
|
||||
read fails, is written with no `owner` (so it reaches the operator at once) and says why in its explanation; a
|
||||
failed read is said and the owner is written at the next step end of that commit.
|
||||
- **A-192** (ADR 0596, ADR 0603 §8, §499, §541, §520; principles 1 and 7) The builder says `wait-held` and `wait-ended`
|
||||
for a key queued behind a package, in the one wait form with its `part`, and the watchman times it from those; its
|
||||
reads of the builder's `builds`, the node-engine's `have` and `mesh-host.health` go, replaced by the node-engine's
|
||||
`running` (with `healthy`) and its wait events.
|
||||
- **A-193** (§64, §73, §373, §420, A-180, A-185; principles 2, 3 and 7) For a run: `run` writes each member's
|
||||
pipeline at once (key `<member branch>.<id>.<module>`, running, no steps, `Run` its id), and the candidate's
|
||||
revision places its steps by compare-and-set; `pipeline-started` is said when the steps are placed, with the
|
||||
fingerprint and the candidate's revision; `run-started`'s subject names one member's module, branch and commit, or
|
||||
for several the first member's module and branch and `run-<id>`; a member naming no branch is refused. The hook is
|
||||
called once per candidate revision at the catalog follower's one subscription, whatever branches are followed, so
|
||||
a runner following none still hears it. As §373 says, a run builds each member and its dependents: the dependents'
|
||||
pipelines come from the candidate's entries whose fingerprints the members change.
|
||||
- **A-194** (principle 4; §64) The indexer's state `runs` and its run candidates get tools as every kept thing does:
|
||||
`indexer_runs` reads them and `indexer_run_drop` drops one by hand (its hand act written first); a run id is
|
||||
`run-` and the runner's random 12-hex id, which the indexer's wider pattern accepts.
|
||||
- **A-195** (§65, §266, §276, §534, §19, A-77, A-149; principles 2, 3 and 7) The runner's `pipeline` wait is a field on
|
||||
each held pipeline's record (`held`: since, and the fingerprint awaited), no new state. A take-in changes the runner
|
||||
when its own module's entry (its own name, A-149) gets a new fingerprint; every pipeline started after that take-in
|
||||
is held. The pipelines' records are on the bus, so whichever runner holds the seat when the wait ends starts them:
|
||||
the wait ends on the node-engine seat's `running` for the runner's module at the awaited fingerprint with `healthy`
|
||||
true, on any node; its 30 minutes are a bound key, whose expiry starts the held pipelines and writes a finding.
|
||||
- **A-196** (§553 with A-91; principle 7; the operator, 2026-11-09: merges need no approval) An empty status makes a
|
||||
step `optional`, as §553 says, except the approval step: a policy that names no approval status asks the operator
|
||||
nothing, as A-91 says; only a policy that names approval asks for it.
|
||||
- **A-197** (ADR 0596, A-192, A-149; principles 1, 3 and 7) What only the node-engine knows of itself it raises as its
|
||||
own findings through its finding writer: a have's failed or removed phase and its detail, its kept-data findings,
|
||||
an unreachable tunnel, and every part of its health report (machine network, rewritten network, machine units,
|
||||
relogin needed, directory used as found, node account fallback). The watchman stops reading `node-engine.have` and
|
||||
`mesh-host.health` and raising those; it keeps only what it sees across parts.
|
||||
- **A-198** (§677; principle 7; shared code is in mesh-sdk) The act's form, `ActDigest` and the ask's form move from
|
||||
`operatorchannelclient` into mesh-sdk's `go/operatorask`, which the client re-exports, so the seat server in
|
||||
`go/process` holds the gate with no import of mesh-contracts and no cycle between the two.
|
||||
- **A-199** (A-185, A-193; principle 7) mesh-sdk's `ProcessReader` gains `AdoptEvent(seat, event, hear)`, a seat event
|
||||
heard on the one subscription with no picture, made at once if no follower made it; meshcatalogclient's
|
||||
`WireCandidatesProcess` adopts `catalog-revision` through it and hands each candidate revision to the hook.
|
||||
- **A-200** (§502, A-188; principles 7 and 8) A taken-in delivery record's `milestone` is `<branch>.<source revision>`,
|
||||
so the watchman ties a `record-opened` to the `take-in.taken` of that branch and source revision by its milestone;
|
||||
`record-opened` gains nothing, and a record is covered only by what is late for its own revision's entries.
|
||||
- **A-201** (§189, §190, A-160, A-174; principle 7) The assigner keeps a candidate's assignment graph per run (key
|
||||
`run-<id>`), made when the candidate's revision is heard and dropped on that run's last `pipeline-ended`, as the
|
||||
indexer keeps the candidate; the gate's `heads-replaced` drops nothing in the assigner.
|
||||
- **A-202** (§31; principles 2 and 7) A pipeline's record and `pipeline-started` say their cause: an optional
|
||||
`published` {artifact, version} set when a package's publish started it; a newer publish of the same artifact on
|
||||
the same branch supersedes the running pipelines whose `published` names that artifact at an older version.
|
||||
- **A-203** (design 46 approval mark §6, G §195, §424; principles 1, 7 and 8) The withheld path's forms: the tool
|
||||
runner's announcement carries `fingerprint` (its own `MESH_FINGERPRINT`) beside `capabilities`; the node's record
|
||||
gains `runner-capabilities` and `runner-fingerprint`, its merge into `capabilities` staying for placement; the
|
||||
assignment half's per-node item gains `readings` and `fingerprint`, its digest over all of it, read from the node
|
||||
record the assigner holds; the grants part follows the assignment half by `mesh-wants`' `revision` and its
|
||||
`assignment-revision` event; its user list carries `withheld: [{module, subject, why}]`, and the seat `mesh-grants`'
|
||||
verb `withheld` takes `{node}` and answers `{node, modules: [{module, subjects: [{subject, why}]}]}`.
|
||||
- **A-204** (§498; principle 2) The assigner keeps which put waits it said in its own state on the bus, so a put
|
||||
that ends while the assigner is stopped gets its `wait-ended` at the next start.
|
||||
- **A-205** (§441, §195; principle 7) `grants-for` reads its candidate's catalog half through the catalog seat's
|
||||
`revision`: for a run, with `group` `run-<id>` (the candidate's newest); for a source revision, with `branch` and a
|
||||
new optional argument `source`, answering the branch's revision taken in from that source revision. It answers the
|
||||
permissions the composed memberships gain and lose with the candidate's entries in place of the branch's newest.
|
||||
- **A-206** (A-195, §65, §534, A-181; principles 3, 4 and 8) The runner's held pipelines are released by hand through
|
||||
a seat verb `release` of `mesh-pipeline-runner` (class act, its hand act written first), beside `start` and
|
||||
`ask-again`, its tool `runner_release`; the bound's expiry writes the finding `pipeline-waits` (scope `module`, id
|
||||
`<branch>.<id>.<module>`); the wait's `on` gains the value `runner version`; its `clock-from` is the `build.ended`
|
||||
of the awaited fingerprint, heard as A-181 says.
|
||||
- **A-207** (§86, §81, §415, §71, §275, §335, §441; principles 1, 2 and 7) The runner keeps a state `recoveries`
|
||||
(commit to why) written when it hears the mesh-delivery seat's `recovery-merged`, read at a pipeline's start and
|
||||
resume; a recovery heard after its pipeline started asks nothing again. `widens` is computed from two reads: the
|
||||
catalog seat's `revision` of the candidate against the branch's newest (the serving parts' entries and fingerprints,
|
||||
found by holder from the seat list; the acting rules `step`, `on` and `bound` and the credentials the entries
|
||||
declare), and the grants part's `grants-for` (the permissions); no other verb.
|
||||
- **A-208** (ADR 0656, §558, §605, §16, §293, §600, §18, §104, §44-§46, A-93, A-190; principle 7) The indexer writes a
|
||||
catalog entry's edges of kinds 1 to 6, as the catalog graph is its own; the long form of `invokes` is read by the
|
||||
second reader release's `InvokeFields`, which G merges. Every watched branch's take-in uses the trunk's machinery,
|
||||
per branch: its own pending revision (holding only that branch) and its own groups. `stepsVia` is open while the
|
||||
entry's fingerprint is still in the branch's newest revision; `messageVia` while a pending entry names the merge or
|
||||
a take-in of that commit runs.
|
||||
- **A-209** (design 46 approval mark §13, §677, §73; principles 3 and 7) A digest in a key is escaped with mesh-sdk's
|
||||
`keys.EscapeName`, the one escape form for every name in a key, in both gates: `<node>.act.<escaped digest>`. The
|
||||
kind (a test's in-memory state accepting a key the bus refuses) is fixed where it lives: mesh-sdk's in-memory state
|
||||
for tests refuses every key the bus would.
|
||||
- **A-210** (§7, §428, A-126, A-131, ADR 0596; principles 3 and 7) A module that calls another module's tool or hears
|
||||
its module events finds that module as the holder of the seat it holds, with `seatlist.HolderOf`, when it calls,
|
||||
asking again after a failure and never stopping its start for it; a read of another module's bucket goes (each by
|
||||
its own row). A tool's callers list names no module: it takes the holders of the seats `node-tool-runner` and
|
||||
`mesh-healers` from the seat list when a call is checked, beside `operator`.
|
||||
- **A-211** (A-204; principles 2 and 7) The assigner's said put waits live in a state of their own, `put-waits`
|
||||
(history 1, key `<node>.<module>`, value the wait's id and the put), so no form is shared with §706's waits and the
|
||||
record outlives a supersede.
|
||||
- **A-212** (§47, §432, A-208; principle 8) An edge of kind 2 comes from a `needs` reference to another module's
|
||||
artifact: that is what an artifact "fetches by reference at its start", so the referenced artifact is published
|
||||
before the referring one delivers. Kind 5 goes to a design line (which acting steps give it, and between what).
|
||||
- **A-213** (corrects A-86; §16, §354, §206) The indexer does ask node-build-go's `run` for its validation of a
|
||||
revision before its take-in, as §16 says; A-86 holds only for `KeepInput`, which the indexer's `input` verb calls
|
||||
for a run on request's asker. The run's input and job, what holds the pending revision meanwhile, and how a non-trunk
|
||||
branch's pending revision and groups are made go to design lines.
|
||||
- **A-214** (§366, §623; principle 7) `show` and `kept` read each store's fill through the bundle's state call
|
||||
`mesh/state.info` (§623), as the forge module's read tool does; the runner's process writes nothing for it.
|
||||
`pipeline-failed` takes scope `merge`, id the commit.
|
||||
- **A-215** (§71, §275, §335, A-207; principles 3 and 7) For `widens`, the pipeline's own revision (the candidate) is
|
||||
compared with the branch's newest revision: an acting rule widens when an entry's `Acts` gains a rule, or a rule's
|
||||
`on` now names more, or its `bound` is longer; a credential widens when an entry's manifest names an own secret or
|
||||
provision of a credential it did not name before; permissions come from `grants-for`.
|
||||
- **A-216** (amends A-181; principles 2 and 7) The builder's `build.ended` stream, kept last per subject, has no age
|
||||
bound, only a cap in bytes: one end per fingerprint is the record of what was built, so a module unchanged for any
|
||||
time is still known built after a restart.
|
||||
- **A-217** (§298, §300, A-167, A-132; principles 2 and 7) A want reverts when its fingerprint is one the module had
|
||||
in an earlier assignment graph revision than the one it now runs from, by the revisions' own numbers; the running
|
||||
manifest is the catalog entry of the fingerprint the node-engine seat's `running` names, read through the catalog
|
||||
seat's `entry`, and `FormChanges` compares it with the want's entry.
|
||||
- **A-218** (A-206, §696; principle 3) `pipeline-waits` is cleared when the node-engine seat's `running` says the
|
||||
awaited runner fingerprint healthy; A-206's `runner version` holds over §696's wording.
|
||||
- **A-219** (refines A-198; §677; principle 7, one form for each thing) Only `Act` and `ActDigest` move into mesh-sdk's
|
||||
`go/operatorask`; the ask's types stay the generated seat contract's. The gate in `go/process` takes the operator
|
||||
channel's calls (ask, read the warrant, record) as an interface its caller passes, which `operatorchannelclient`
|
||||
implements over the generated types, so no form is copied and no import cycle arises. The seat client's
|
||||
`wait: held` lives with `process.Conn`'s call, as there is no `go/seats`.
|
||||
- **A-220** (§71, §441, A-167, A-207, A-215; principle 7) The serving parts `widens` compares are the holders of the
|
||||
seats `mesh-pipeline-runner` (the runner), `mesh-catalog` (step detection), each step's own seat (the build seats
|
||||
that run lint and tests) and `node-engine` (the composer, A-167), found by `seatlist.HolderOf`.
|
||||
- **A-221** (§509, A-208; principle 8, one word per thing) On every forge message `branch` names the watched branch
|
||||
the move changed, on `merged` as on `pushed`; a merged pull request's own branch is kept as `head` (a group is
|
||||
known by its head branch); `pushed`'s head before is `before`.
|
||||
- **A-222** (A-193, A-199, §350, A-77; principles 2 and 3) An adopted event whose handler fails is asked again by
|
||||
the bus, not acknowledged; at its start the runner reads, for each run pipeline with no steps yet, its candidate's
|
||||
revision through `revision`, so a placing lost while it was down is taken up. A run's rerun asks with the
|
||||
candidate's catalog half. An unanswered drop is said `call-unanswered` on a five-minute bound key's expiry.
|
||||
- **A-223** (corrects A-221; principle 8) A merged pull request's own branch is `head-branch`; `head` stays the head
|
||||
commit, as everywhere in the forge messages.
|
||||
- **A-224** (§677, A-219; principle 7; code shared between modules is in mesh-sdk) The approval gate is one package,
|
||||
mesh-sdk `go/approval`: the tool runner's gate (`node-tools` `internal/approval`) moves there and both the tool
|
||||
runner and `go/process`'s seat server use it, each passing the operator channel as an interface (A-219); no second
|
||||
gate is written.
|
||||
- **A-225** (A-173, §532; principles 2 and 7) The view keeps each repository's reach in its own state on the bus, so a
|
||||
restart does not lose it; its history is the source revisions heard since its start, re-asked through `source` on
|
||||
each `source-moved`, laid over the reach it kept.
|
||||
- **A-226** (A-167, §354, §64, §63, A-174; principles 2 and 7) The composition step asks each node's
|
||||
`compose-candidate` with the key the step's pipeline names: a run's candidate `run-<id>`, or for a branch pipeline
|
||||
the branch's revision `<branch>.<revision>`, which the node-engine reads as it reads a candidate. It runs
|
||||
`mesh-compose` once per node-engine fingerprint among the machines (the change's build where the change alters the
|
||||
node-engine, §63), the program read from that fingerprint's catalog entry through `entry`, each run's request
|
||||
joining the inputs of the machines on that fingerprint.
|
||||
- **A-227** (withdraws A-221 and A-223; §714, §715) As design line §714 says, a merged message gains `base`, the
|
||||
watched branch it landed on; `branch` stays the pull request's branch and `head` its head commit, as today;
|
||||
`pushed` keeps `branch` for the branch pushed and gains `before`.
|
||||
- **A-228** (A-219, A-224; principle 7) Everything the gate reads beyond itself crosses into mesh-sdk `go/approval` as
|
||||
an interface in the gate's own terms, its caller translating from the generated contracts: the catalog's entries
|
||||
(module, fingerprint, serves, manifest), a finding writer (raise and clear by kind and id), the seat holders (a
|
||||
holder function), and the operator channel (ask, record, and claim with the warrant and the act). The gate's own
|
||||
kept form `Kept` moves into `go/approval`, which `operatorchannelclient` re-exports.
|
||||
- **A-229** (§708, §709; principle 7) As §709 says, a catalog entry's `requires` names every requirement as
|
||||
`catalogue.Manifest.Wants` lists them, optional ones included; a reader that means "required, not optionally" (the
|
||||
module graph's `Requirers`, the assigner's halves) tells the optional ones apart by the entry's manifest
|
||||
(`IsOptional`).
|
||||
- **A-230** (§554, §565, A-124; principle 7) A restore point's `Point` gains `names`, the bus names it took, one per
|
||||
bus object as `Aside`'s; the recovery module fills it when it takes a point, and the grants part reads
|
||||
`points[].names` and `asides[].names` from `kept-points`.
|
||||
- **A-231** (§544, §601, A-163, A-170; principles 3 and 7) An asker names the queue it asks in its manifest's `invokes`
|
||||
as `seat:<seat>.accept.<queue>`, read by one mesh-sdk `catalogue` helper that the validator, the indexer's kind-6
|
||||
edge and the grants part share; every asker of a build seat gains that entry in G, so the named workers stand from
|
||||
G's delivery and the purge finds nothing to remove.
|
||||
- **A-232** (§712 with §600, A-163, A-170; principle 7) The validation run rides `accept.check.<asker>` with its kind
|
||||
`run` in the body and the asker the indexer's seat name `mesh-catalog`, as every ask does; its id is the usual
|
||||
form (`NewID`), and the revision's sequence and try number are kept in the pending value's `validation`. §712's
|
||||
subject and id are read so.
|
||||
- **A-233** (A-181, A-189; principle 7) mesh-sdk's `HearStream` gains a form that reads an age-kept stream whole from
|
||||
its first message by an ordered consumer at every start, then follows the durable; the grants part grants that
|
||||
ordered consumer on every heard seat kept stream, as it grants `STREAM.INFO`, with no manifest word.
|
||||
- **A-234** (§711, ADR 0656 §6; principle 2) The indexer fills each catalog revision's `definitions` (the seat
|
||||
definitions it was taken in with, by digest) when it keeps the revision, and the `acts` fill reads from them; until
|
||||
then reading the trunk's from its `seat-definitions` state stands.
|
||||
- **A-235** (A-201, A-174, §190, RN9; principles 1 and 7) The runner says `group-ended` with group `run-<id>` once every
|
||||
pipeline it placed for the run has ended, as it says a group's end; the assigner drops its candidate graph on it, and
|
||||
the indexer drops the run's candidate on it in place of counting members itself. The kept object's key stays the
|
||||
seat's form `candidate.<group>.<heads>` with group `run-<id>`.
|
||||
- **A-236** (§509, §714, §715, §11, A-227; principles 2 and 7) A `pushed` message's new head is `head`; the bridge
|
||||
keeps each watched branch's last head written in its own state `heads` (key `branch.<owner>.<name>.<escaped
|
||||
branch>`), `before` read from it and its start read comparing with it; `pushed` carries the same take-in inputs as
|
||||
`merged`. For §715 the mesh-delivery seat's `groups` member gains `base` in the contract, which the gate fills as
|
||||
§716 has it (from the seat `git`'s pull events, before any take-in) and the bridge reads.
|
||||
- **A-237** (§712, §354, ADR 0666 §16, A-139; principle 7) A run's input is any size: the indexer keeps it in its state
|
||||
`inputs` with the state's `-objects` store holding a large value (mesh-sdk's `stateobjects`), and serves it through
|
||||
its `input` verb answered in chunks (`buscalls.SeatChunks`, a verb marked `chunks`); `RunInputBound` goes, and the
|
||||
holder takes the input by that verb.
|
||||
- **A-238** (§712; principle 7) A run ask may name no head: `RunJob.check` takes an empty `Heads` when the program
|
||||
is named, and the holder lays an empty `MESH_RUN_HEADS`; the validation's program is built from the indexer's
|
||||
entry alone.
|
||||
- **A-239** (design 46 approval mark §6, H-21; principles 3 and 7) The node-engine's `composition` answer gains
|
||||
`withheld` [{subject, why}] (the module's entry of the grants part's `withheld` for this node) and
|
||||
`withheld-unknown`, the words "withheld unknown: the grants part does not answer" when the verb fails; its
|
||||
manifest's `uses` gains `mesh-grants` beside the `invokes` of `seat:mesh-grants.withheld` H-21 added.
|
||||
- **A-240** (§354, ADR 0596; principle 1) The assigner waits on its composition run's answer by hearing the build
|
||||
seat's `step-ended` for that ask, never by reading the seat's step state.
|
||||
- **A-241** (§712, A-234; principle 3) The validation's `seats` are the branch's revision's seat definitions (A-234)
|
||||
with the merges' changed definitions laid over them, so a revision that touches no definition is still judged
|
||||
against the whole seat list.
|
||||
- **A-242** (§512, §186, §376, §378; principles 1, 2 and 7) The assigner's `difference` fills `Tiers` and each
|
||||
change's `after` (the kind of edge that orders it), computed in its compare; the view orders nothing itself. A
|
||||
`view_tiers` call for a source revision compares the kept assignment revision whose catalog for that branch is that
|
||||
source revision with the revision kept before it. The view keeps a state `waits` (key owner, step, node, module)
|
||||
written by its process from every part's `wait-held` and `wait-ended` it consumes, read by its own read tool.
|
||||
- **A-243** (corrects A-226's joining; A-237, A-238; principle 7) The composition step runs one request per machine,
|
||||
each carrying that machine's input; no inputs are joined. A-237 holds for every run's input (the assigner's and
|
||||
`mesh-wants`' `input` too): any size, kept with an `-objects` store, answered in chunks. The composition run names
|
||||
no head (A-238); its program is the node-engine fingerprint's entry (repository, commit, directory) with the
|
||||
composer's package.
|
||||
- **A-244** (§677, A-186, A-219; principles 7 and 8) `wait: held` travels as a bus header on the request, never as an
|
||||
argument, so no verb's input and no act's digest changes; the holder's held answer is a reply field `held`
|
||||
{asked, call} beside result, error and node. `process.Conn`'s seat call sends the header on every seat call and
|
||||
waits for `gated-ended` only when the reply says `held`. A process holder applies the seat definition's
|
||||
`needs-approval` alone (the definition `AnswerSeat` is given); it reads no catalog entry.
|
||||
- **A-245** (§298, §368; principle 7) §298's order edges are held under the assigner's rule 7, as its acting waits
|
||||
are; a change of the assigner's state forms bumps its manifest's version once, in the commit that changes them.
|
||||
- **A-246** (§376, §498, A-211; principles 2 and 4) A dependent's delivery wait is kept in a state of its own,
|
||||
`delivery-waits` (key the module, value the `wait-held` said), read by `assigner_waits` beside the others;
|
||||
`assigner_release` {module, step: delivery} writes the hand-act log first and ends the wait `released`.
|
||||
- **A-247** (§713, A-232; principles 1, 2 and 7) The pending value's `validation` gains `accepted` (the seq it was
|
||||
accepted at), so a revision held for another indexer is never asked again; `validation` sits on the newest pending
|
||||
value of the revision alone. The answer is read from the `step-ended` event's own output and output object, never
|
||||
through the build seat's `steps`; the ask is put through the delivery SDK's `buildrun.Asker`.
|
||||
- **A-248** (A-163, A-232; principle 8) `buildrun.Asker` gains the asker its subject names (the caller's seat name,
|
||||
A-163), empty meaning the builder until the asker rename at the join; the ask's own `Asker` field is left as it is.
|
||||
- **A-249** (A-237; principles 7 and 8) A bundle takes a chunked seat answer through its ordinary seat call: the tool
|
||||
runner receives the chunks with `buscalls.SeatChunks` and hands the bundle the whole answer, so no bundle receives
|
||||
chunks itself. mesh-build-go's `inputOf` calls the mesh-catalog seat's `input` that way and decodes that verb's
|
||||
own answer form.
|
||||
- **A-250** (§376; principles 1, 2 and 4) `assigner_delivered_again` writes the hand-act log, then an ask in the
|
||||
assigner's own state `delivered-again` (key the module, value {version, by, why}) and answers "asked". The
|
||||
assigner's process hears that write on its own state at once, says `version-delivered` again when every present
|
||||
placing machine runs the version healthy and deletes the ask, else keeps it with its reason, read by
|
||||
`assigner_waits`.
|
||||
- **A-251** (§17, §383, §437; principle 8) The finding `branch-unprotected` has scope `merge`, id the owner,
|
||||
repository and branch escaped as one key name (`<owner>~<repository>~<branch>`), severity warning, and that scope
|
||||
and id as its subject.
|
||||
- **A-252** (§354, A-167, A-174, A-226, A-243; principles 1, 2 and 7) The composition step's machines are the nodes
|
||||
whose placement of the module names the step's branch in the seat's assignments (no branch read as the trunk); a
|
||||
run on request on an unwatched branch takes the nodes placing the module on the trunk. Each request's cases and seats
|
||||
come from the step's catalog revision through mesh-catalog's `revision` (each entry's manifest digest and verdict,
|
||||
its `definitions`), depth compose, the one machine, one case `change`. A machine's node-engine build is what the
|
||||
assigner keeps from the node-engine seat's own events, never read from the node-engine's bucket; where the step's
|
||||
revision's node-engine entry differs from the trunk's, that build serves every machine. One run ask per machine.
|
||||
- **A-253** (A-249; principle 8) The tool runner hands a bundle a chunked answer as the verb's own answer with the
|
||||
whole file added as the member `file` (base64).
|
||||
- **A-254** (§712, §354, A-240, A-247, ADR 0596; principles 1, 2 and 7) A build seat's `step-ended` of a run carries
|
||||
the run's answer (its standard output's last line): inline as `output` up to the event bound, else as
|
||||
`output-object`, the name of an object the build seat keeps, read through the seat's verb `output` answered in
|
||||
chunks. Every build seat (`node-build-go`, `node-build-image`, `node-build-node`) says it the same way, from the
|
||||
shared step runner; no asker reads the seat's step state.
|
||||
- **A-255** (§677, A-77, A-244; principles 1, 7 and 8) The header is `x-wait: held`. A process holder's
|
||||
`gate-unanswered` is a bound key with its time to live in the `approvals` state, as the tool runner's `bound` is,
|
||||
never a clock wait. A process holder's act names no node, so two instances of the holder raise one ask.
|
||||
- **A-256** (corrects A-251's separator; principle 8) The finding's id is the owner, repository and branch joined by
|
||||
`/` and escaped as one key name (`keys.EscapeName`); a key holds no `~`.
|
||||
- **A-257** (A-249, A-253, A-254; principles 7 and 8) The tool runner sends a bundle's answer of a verb marked
|
||||
`chunks` in chunks, from the whole file the bundle answers in its member `file`, the mirror of A-249: no bundle
|
||||
sends chunks itself.
|
||||
- **A-258** (§298, A-210; principles 1 and 8) §298's "bus module" is the holder of the seat `mesh-broker`, and its
|
||||
"`node-backup` holder" the holder of the seat `node-backup`, each read from the seat list as every holder is: the
|
||||
assigner names no module.
|
||||
- **A-259** (§298, A-245, §266, §268; principle 7) §298's three order edges are acting waits of the assigner, kept
|
||||
in its state `waits` under `order.<node>.<module>.<part>` beside `acting.`, said as `wait-held` when the want is
|
||||
held and `wait-ended` (ran, released, replaced) when it moves, with the step `placing`; `assigner_waits` reads
|
||||
them and `assigner_release` releases them as it does the acting waits. No new mechanism.
|
||||
- **A-260** (§298, ADR 0633 §1, A-181; principles 1 and 7) For §298's third edge the trunk's head is the highest
|
||||
source revision (`Src`) among the trunk branch's module values the assigner holds; the wait ends when a
|
||||
`take-in.taken` of the trunk branch with `source` at or above it is heard. The assigner consumes
|
||||
`mesh-delivery-indexer.take-in.taken` (a `consumes` entry), heard as it hears `build.ended`; a wait still held
|
||||
after a restart is said again as `wait-held` and ends at the next such take-in.
|
||||
- **A-261** (A-252, A-234; principles 2 and 3) A composition request's `Seats` carry, for each (seat, digest) of the
|
||||
step's catalog revision, the definition whose digest it is: from the revision's `definitions` once A-234 fills
|
||||
them, until then from mesh-catalog's `seat-definitions` where the trunk's digest matches; a digest neither holds
|
||||
ends the step `could not run`, naming the seat and the digest.
|
||||
- **A-262** (§496; principles 1 and 7) The assigner says a running composition step's progress once a minute with a
|
||||
ticker inside the running step, stopped at its end, as mesh-delivery-sdk's `steprun` says its own beat (pull.go);
|
||||
no bound key and no new key form.
|
||||
- **A-263** (§712, §676; principles 7 and 8) §676 writes `package` (the Go module its `go.mod` names) for every Go
|
||||
artifact, a `bundle` too, since a bundle is built from that Go module; §712 reads it there.
|
||||
- **A-264** (§677, §697, A-186; principle 7) The event `gated-ended`, the verb `approvals` and the by-caller form of
|
||||
a marked verb are added in mesh-sdk's `seatdef.Resolve`, which the seat generator and every reader of a definition
|
||||
(the seat list, the grants part, the validator, `AnswerSeat`) go through: one place, one definition for all; the
|
||||
seat files stay as written.
|
||||
- **A-265** (A-264, design 46 approval mark §20; principles 4 and 7) A-264 holds for every marked definition. Where a
|
||||
tool bundle holds the seat (the login shell's `execute`), the tool runner, which already holds that bundle's gate,
|
||||
answers the marked verb by caller and serves `approvals` for the seats its bundles hold, as `AnswerSeat` does for a
|
||||
process; no holder changes and no mark moves.
|
||||
- **A-266** (A-264, A-265, ADR 0132; principle 7) `approvals` is the gate's verb, served wherever a marked verb is:
|
||||
`CanHold` and the validator's same rule ask no holder to name it in its claim's `serves` or its tools.
|
||||
- **A-267** (§496, §481, §22, A-170; principle 7) The composition step's `step-progress` carries `asker` `runner`, the
|
||||
one asker that sends the accepted kind `step` on `mesh-wants`; the asker rename after join 11 (A-163, A-170) reads
|
||||
it from the accept subject's asker token instead.
|
||||
- **A-268** (corrects A-263; §712, §676, §491; principle 7) §676's `package` stays for package artifacts only, since a
|
||||
bundle's Go module named as a package collides under §491's package names when two modules share one `go.mod`.
|
||||
§712's job names the indexer entry's repository, `path` and the revision's head, and the program's directory;
|
||||
`mesh-build-go` reads the Go module from the `go.mod` it builds there, as it does for every build.
|
||||
- **A-269** (§509, §714; principles 7 and 8) A `pushed` message's `head` stands where a `merged` message's `merge`
|
||||
stands: pending refs, the newest load and the revision's head key on it alike.
|
||||
- **A-270** (A-255, §677; principles 3 and 7) A holder's approvals state is declared with `key-ttl` beside §677's
|
||||
48 hours and 64 MiB, since its bound keys `unanswered.<call>` need it; the validator refuses a holder of a marked
|
||||
verb whose approvals state lacks it.
|
||||
- **A-271** (A-265, §677; principle 7) The tool runner's `approvals` answer has the form of `AnswerSeat`'s, read from
|
||||
the runner's own record of the calls its gate holds; it keeps no `call.` keys of its own for it.
|
||||
- **A-272** (§677, ADR 0596; principle 1) The `GateChannel` over `process.Conn` asks and reads an ask only through
|
||||
mesh-sdk's `operatorchannelclient` on the seat `operator-channel`, never through the router's bucket.
|
||||
- **A-273** (corrects A-272; §700, A-74, A-89; principles 1 and 7) The `GateChannel` over `process.Conn` is §700's:
|
||||
`operatorchannelclient.Client` given, as its `Record`, a get of the key under `<module>.` in the seat
|
||||
`operator-channel`'s state `asks` (the bucket formed from the seat's holder on the seat list and `StateAsks`, A-74)
|
||||
on the process's own connection; the verb `asks` stays as it answers. The ask is published by a new `go/process`
|
||||
method that publishes on a seat's accept subject (`mesh.seat.<seat>.accept.<queue>.<caller>`), on the a3g line,
|
||||
as `Emit` publishes an event.
|
||||
- **A-274** (§529, A-13, A-30, §584, §627; principles 1 and 7) The held finding writer takes its subjects from the
|
||||
part's own binding: when the process was handed a binding of the local name `finding-events` (an `events`
|
||||
template its manifest requires), it publishes under that binding's subjects
|
||||
(`mesh.bus.<module>.finding-events.<event>.<node>`); with no such binding it publishes on the old stream
|
||||
(`mesh.stream.<module>.event.<event>.<node>`). The module passes no subjects (A-13 holds) and the SDK keeps no
|
||||
foundation list.
|
||||
- **A-275** (§558, A-102; principles 3 and 7) The node-engine's self-composition reads a manifest's `invokes` with
|
||||
mesh-sdk's `catalogue.readInvokes`, which reads the short and the long form alike; no part keeps its own reader of a
|
||||
manifest field, and a form it cannot read is refused, never taken as no seats.
|
||||
- **A-276** (A-218, A-206; principles 3 and 7) A `pipeline-waits` finding names the awaited runner fingerprint in
|
||||
one evidence entry `awaited <fp>`, written when the finding is raised; the runner clears exactly the findings whose
|
||||
entry names the fingerprint its node-engine says `running` healthy. Its summary is never parsed.
|
||||
- **A-277** (§677, A-270, ADR 0595; principles 3 and 7) A state's `max-mib` is an option of the template `state`
|
||||
(the second reader release reads it from 1 to 8192, and since mesh-delivery #206 the grants part carries it onto the
|
||||
bucket). The manifest validator refuses an `approvals` state that lacks `key-ttl`, whose `ttl-seconds` is not 172800, or whose
|
||||
`max-mib` is above 64; with none declared the bucket takes the default 64 MiB.
|
||||
- **A-278** (A-179, A-186; principle 7, and code shared between modules lives in mesh-sdk) The seat-definition
|
||||
function `AnswerSeat` takes is written once, in mesh-sdk's `seatlist` (the definition of a seat from the list a part
|
||||
follows; none until the list is read, none for a seat it does not hold), and every holder passes it. A part that
|
||||
serves a seat verb follows the seat list with its grant, as the pipeline runner and mesh-recovery now do.
|
||||
- **A-279** (A-236, §715, §716; principles 1, 3 and 7; corrected four times, this form stands) A group member's `base`
|
||||
is its pull request's base branch as the seat `git` says it in `pull-updated`, kept by the gate on the member's
|
||||
delivery in its state `deliveries` from the announcement, and copied onto the record's member when the take-in
|
||||
opens the record. Its earlier forms (the base on the `merges` entry the Approve makes) are dropped: from G no
|
||||
Approve makes an entry, and the gate writes `merges` no more (§156). Design line §716 says it whole: the event, the
|
||||
states and their keys, who deletes them, and how `groups` answers.
|
||||
- **A-280** (A-274, §529; principles 1 and 7) A tool bundle's finding writer (`views.NewStdioHeldWriter`) changes
|
||||
nothing: the tool runner already publishes an emitted event on the subject of the first stream of the module's
|
||||
membership that carries it (`node-tools` `bus.eventSubject`). The grants part lists a module's required
|
||||
`finding-events` stream in its membership with that subject and the finding events, so a bundle's findings land
|
||||
under its binding as a process's do; with no such stream they stay on EVENTS.
|
||||
- **A-281** (§156, §716; principles 2 and 7) The switch runs once, as §156 says: after it has carried today's state in,
|
||||
the gate writes the key `switch.done` (the gate's fingerprint and the time) in the state that holds its records, and a
|
||||
later start that finds it runs no switch, so the deliveries §716 writes never become records at a start. A revert's
|
||||
restore brings back the state from before G without the key, so delivering G again switches again, still safe to run
|
||||
twice by the record's key.
|
||||
- **A-282** (A-115, §544, issue 1930; principles 3 and 7; corrected once) An ask's step bound key is never deleted
|
||||
to be made again. `stepBounds.Arm` creates it if absent; when the step moves, the waiter writes it again by an update
|
||||
against the revision it read, so the bound still counts from the step's last movement (issue 1930); a waiter whose
|
||||
update loses to another's reads the key again and takes the newer bound as armed.
|
||||
- **A-283** (A-277, ADR 0595; principle 7) The holder of the seat `mesh-broker` makes a template `state` bucket with
|
||||
the `max-mib` its requirement declares, the default cap where none is declared, as the grants part does for a
|
||||
state section; one cap rule for every state bucket.
|
||||
- **A-284** (ADR 0573 §2, ADR 0500 §5; principles 5 and 7) In a group's check, a TypeScript member is built
|
||||
against the group's own heads, as a Go member is: the check packs every `@novox/mesh-*` package a member's
|
||||
merge changes (the SDK's `lib` artifact given its path, and a member's new packages such as another member's
|
||||
contracts) by the form ADR 0500 §5 gives the SDK, and serves them to every member that imports them; the
|
||||
latest published version is used only for a package the group does not change.
|
||||
- **A-285** (A-284; principles 7 and 8) The fetch of a build step carries the group's packages as one list,
|
||||
`group-packages` (beside `packages`, which stays the Go mesh packages' versions), each a package of a member at
|
||||
its commit with `path`, its directory in that member (empty for the member's root); the single `package` field
|
||||
and its tagged form go. The build module packs each and places it by
|
||||
its `package.json` name in every member that names it, in place of the registry's. The SDK's `lib` artifact names
|
||||
its paths (`package.json`, `package-lock.json`, `tsconfig.json`, `src`) in mesh-sdk's manifest, so the indexer
|
||||
holds them and a change to them is a changed package.
|
||||
- **A-286** (issue 1921; principle 3) The checker previews and judges every member whose fetch merged, whatever its
|
||||
later steps ended with; only a member whose head does not merge, or whose fetch did not answer, stays
|
||||
unpreviewed, and the check says which of the two. A check step that errors after its fetch merged (a level that
|
||||
does not compile or generate) ends failed with its answer all the same: its fetch and the levels so far, beside
|
||||
its error, so the checker previews from it. A failed test or a failed level never hides the manifest validator or
|
||||
the composition.
|
||||
- **A-287** (principle 4) A module's kept announcement names every state its manifest declares, so each can be
|
||||
read by its tools; gitea's `KEPT` gains `failed-steps`, `idle` and `transfers`.
|
||||
- **A-288** (principle 3) A refusal's test holds what the refusal must say, not its earlier wording: the one tool
|
||||
that may do the refused act and that it runs on the operator's Approve of that call.
|
||||
- **A-289** (§371; principles 3 and 8) A package left unpublished by a build that reserved no version (§371's other
|
||||
watched branch, §505's manifest without MAJOR.MINOR, a candidate) counts as done, and the builder's unpublished
|
||||
count holds no package at all: a reserving build's package is published or its build fails, and a done key is never
|
||||
built again under a reservation, so no seat republishes a package (principle 7). The count holds images only.
|
||||
- **A-290** (A-254, §467; principles 3 and 7) A step's key in its build module's steps state keeps the step's answer
|
||||
only within the state's bound per value; an answer over it, a check's or a run's alike, is kept as the object
|
||||
`output.<ask>` in the module's outputs store and the key names it, and every reader (the checker, the asker) reads
|
||||
the answer from the key or the object it names. No step ends passed in one place and failed in another.
|
||||
- **A-291** (A-254, A-290; principle 7, and code shared between modules lives in the SDK) A process receives a seat
|
||||
verb's chunked answer through one helper of mesh-sdk's process package, which subscribes to the call's chunks
|
||||
before it asks and answers the whole body; the checker, the indexer, the assigner and the recovery keeper (whose
|
||||
own receive moves into it) read a step's answer kept as an object through it.
|
||||
- **A-292** (§590, issue 1975; principles 1 and 3) The node-engine's first statement keeps the bootstrapped seats (no
|
||||
'not yet'); every later change is said as `manifest-reads-changed` at once. A say the bus does not take is said
|
||||
again within the same link through the SDK's bus retry until the bus takes it, and each failed attempt is written
|
||||
to the engine's journal with the event's name; the held-for-the-next-link form stays only for a link that is lost.
|
||||
- **A-293** (§421, §628, ADR 0593 §6, issue 1966 U1/U8; principles 1 and 3) In by-entries mode the node-engine still
|
||||
says a placed module's newest build that ended failed as a refused key with the build's words: it hears the
|
||||
builder's build-ended event for that fingerprint (an event, not a read of the builder's builds), so no reader waits
|
||||
on a build that will never be done; a queued or running build stays unpublished. A placed module's newest build is
|
||||
the last trunk build-ended event (one naming no branch) the engine heard for that module, from the builder's
|
||||
build-ended events read whole at start and then followed, as the assigner hears them (A-181, §556): a failed end
|
||||
writes the refused key `<node>.<module>.<fp>` with the event's words, a later done end of the module (at any
|
||||
fingerprint) deletes the refused key such an end wrote, so the state never says refused for a done build, and an
|
||||
end naming a branch moves nothing.
|
||||
- **A-294** (corrects A-267; §600, A-170; principle 7, the seat definition is the one source) The seats mesh-wants and
|
||||
mesh-builds are not build seats: their accepted kind `step` stays without a token (not `by-caller`), and the
|
||||
composition step's step-progress names its asker as the one caller its seat's definition lists, read from the seat
|
||||
list; only the three build seats carry the asking seat's name as a token.
|
||||
- **A-295** (A-210; principles 1 and 7, one shared mechanism in the SDK) A tool's callers list names a seat as the entry
|
||||
`seat:<seat>`, resolved in mesh-sdk's verb callers by a holder function the part is given; that function follows
|
||||
the assigner's seat-holders view (as the watcher does) and answers from what it last heard, never one call per
|
||||
checked call; each part's list becomes the operator, `seat:node-tool-runner` and `seat:mesh-healers`.
|
||||
- **A-296** (A-210, A-131; principles 1 and 2) Where A-210 makes a part find another by the seat it holds, the part's
|
||||
manifest takes the seat forms in the same change (`seat:<seat>.views.<state>` for a consume, the seat's verb for an
|
||||
invoke), and a part names its own buckets from its credential's module, never from a constant. Which seat a module
|
||||
that holds none (the delivery view) or several (the assigner) is found by is a design line (§717).
|
||||
- **A-297** (A-232, A-111; principle 7, one form per fact) The pipeline runner's step ask id takes the usual ask id
|
||||
form: base 36 of the pipeline's start time in milliseconds, then six hex digits of the sha256 of the pipeline, the
|
||||
artifact and the step, so the same placement makes the same id at every start (and the bus dedupes on it); a rerun
|
||||
uses its own start time.
|
||||
- **A-298** (§717, §614, §616, A-295; principle 7, one shared mechanism) The claimant of a seat is read from the
|
||||
catalog half in one place, the catalog seat's client (`Branches.Claimant(seat)`), and one follower there follows
|
||||
the claimant's seat-holders view, drops and follows again on each catalog change, and answers the SDK's holder
|
||||
function; every part calls it once and FollowSeat builds on it. The assigner answers its own holder function from
|
||||
its own seat-holders picture. The seat-holders first-view tool names its callers in A-131's module form for now
|
||||
(the teller holds no seat to be named by); naming them by seat waits for every follower to hold one.
|
||||
- **A-299** (§717, corrects its count; principles 3 and 7) The seat mesh-delivery-view's state `answers` takes one key
|
||||
form per kind of mesh-wants' `asks` forms with `.view` after it (`hold.<module>.<fingerprint>.view`,
|
||||
`restore.<node>.<module>.<fingerprint>.view`, and so for each), so its generated reader reads every real key; the
|
||||
kinds are the seven the asks package writes (hold, restore, backup, record, outside, revert, assignments), not eight.
|
||||
- **A-300** (§717, A-131, A-210; principles 4 and 7) The status view's process follows the wants by its seat-form
|
||||
first view; its tools program keeps the invoke `assigner_read_wants`, because the view's tools read a want by its
|
||||
key and its history (`view_tiers`' since of a want, `assigner_want`), which a first-view tool (pages after a
|
||||
position) does not answer; `assigner_read_asks` and `assigner_read_assignments` go as §717 says.
|
||||
- **A-301** (§717, A-131; principle 7, one form per seat view) The status view's first-view tool `answers` is mesh-sdk's
|
||||
first-view tool (the one with a callers list, as the assigner's `asks` and `wants` are), its callers the teller in
|
||||
A-131's form; §717's "views.ReadTool" reads as this.
|
||||
- **A-302** (§717, A-298; principles 1 and 7) The indexer follows the assignments through FollowSeat as well, the
|
||||
claimant of mesh-module-assigner read from its own catalog (it holds the catalog seat), so buildrun's 5-second
|
||||
holder loop goes for every part.
|
||||
- **A-303** (§717, A-302; principles 2 and 7) The status view names its followed pictures in the seat form
|
||||
(`<seat>_<state>`), the reach's watches (`reachBuckets`, its history watch included) too, since they match the names
|
||||
the followed pictures are told; where a name must be the holder's real bucket (only the watchdogs' stuck key, which
|
||||
matches what the watchman writes), the view takes it at run time from the holder FollowSeat follows, through one
|
||||
accessor of the SDK's process reader; no code names the assigner's module. *Corrected 2026-11-12: it first named the
|
||||
reach history's bucket names as real buckets too, for kept entries' sake; the view's kept reach holds no bucket name.*
|
||||
- **A-304** (ADR 0525 §4, A-284, A-285; principles 7 and 1; the operator's yes, 2026-11-12) The checker holds no
|
||||
step: within each of its two phases (every module that is not TypeScript, then the TypeScript modules after
|
||||
`groupPackages`) it puts every ask of the phase on the check stream at once and then waits for all of them, so the
|
||||
build nodes pull them at their own capacity; a declined ask is put again as today, and the phase's verdict is judged
|
||||
in the same order as before, whatever order the asks end in.
|
||||
- **A-305** (ADR 0227 rule 8, ADR 0240 rule 2; principle 7) The pipeline runner's process declares the health `unit`,
|
||||
as every other process of mesh-delivery does; `runner_health` stays a tool and judges nothing for the node-engine.
|
||||
- **A-306** (§626, §419; principle 7) A Go module that G creates has no count at a commit of main, so §626 does not
|
||||
hold it; the test budget (at most 50 integration and 500 unit tests) does. A test added to a module that exists on
|
||||
main counts under §626, a contract's generated `TestFixtures` included.
|
||||
- **A-307** (corrects A-239; §635; principle 7) The node-engine's manifest gains no `uses`: its `invokes` of
|
||||
`seat:mesh-grants.withheld` gives the served-verb edge, as §635 says; A-239's last clause (`uses` gains
|
||||
`mesh-grants`) is withdrawn, and the rest of A-239 holds.
|
||||
- **A-308** (§628, §421, ADR 0596 §2; principle 1) §628's list of the indexer's states the node-engine still reads is
|
||||
whole: `candidates`, `manifests` and `verdicts`, for compose-candidate alone. Its watch of the indexer's `modules`
|
||||
(selfcompose shadow.go) goes; what it took from a module's record it reads from the catalog half's entry the want
|
||||
names (§421).
|
||||
- **A-309** (A-290, A-254, research 071; principles 7 and 2) A build module's `outputs` objects live as long as the
|
||||
step key that names them: the store's age is the steps state's time to live (two days), its cap 256 MiB, and the
|
||||
bus's own age removes an object; nothing else deletes one. The milestone's lifetime list gains this line.
|
||||
- **A-310** (§615, §506; principles 7 and 3) A compose request's manifest named by digest also names the fingerprint
|
||||
whose catalog graph entry holds it: `composer.Manifest` gains `fingerprint` beside `digest`, and the checker, the one
|
||||
part that sends digests, fills it (a pull's `Fps`, else the trunk revision's entry of that module). The Go build module
|
||||
reads the entry by `seat:mesh-catalog.entry` with that fingerprint, takes its manifest whole, and refuses the request,
|
||||
saying which module, when the entry's `manifest-digest` differs or a digest comes without a fingerprint. A manifest
|
||||
sent whole (`raw`, the assigner and the indexer) is read as today.
|
||||
- **A-311** (design 34 every-event §19; principle 7) The tool runner's mesh_wait keeps its floor of one second; its test runs the
|
||||
independent one-second rows as parallel subtests, and no clock hook is added to `bus.Conn` or the wait.
|
||||
- **A-312** (§421, §628, ADR 0596 §2; principles 1 and 3) For a module an assignment candidate adds to a node that has
|
||||
no want for it there, the node-engine's shadow asks `seat:mesh-catalog.module` for the trunk's fingerprint when it
|
||||
composes that candidate, then takes that fingerprint's entry by `seat:mesh-catalog.entry`; a call, no watch, no
|
||||
picture of the catalog. Its manifest invokes `seat:mesh-catalog.module`; a module the answer gives no fingerprint is
|
||||
held, the verb's reason named.
|
||||
- **A-310's shape:** `composer.Manifest` is `{Raw, Digest, Fingerprint}` (wire `fingerprint`), and `composer.Fill`'s
|
||||
getter takes the `Manifest`.
|
||||
- **A-313** (corrects A-309 and A-290's "outputs store"; §58, §547, ADR 0666 §16; principle 7) A step answer over the
|
||||
steps state's bound is kept in the steps state's own objects store, `steps-objects`, which the build module's
|
||||
`requires` sizes with the state option `objects-max-mib: 256`; it lives as every `<state>-objects` does (removed on
|
||||
replace, an object whose key is gone kept for the drop tool), so no `outputs` require and no age option for the
|
||||
objects template are added. A-309's lifetime line is withdrawn.
|
||||
- **A-314** (A-313, §58, §547; principle 7) The three build modules' `steps` moves from the old `state` section into
|
||||
`requires` as a state with `ttl-seconds: 172800` and `objects-max-mib: 256`; its bucket stays `<module>_steps`, so
|
||||
the seat's `steps` state is served from it as before. The step runner names the store through
|
||||
`catalogue.StateObjectsName("steps")`, and the manifest validator accepts `objects-max-mib` on the state template.
|
||||
- **A-315** (test 36, §637, §555, §405; principles 2, 3, 5 and 7) Test 36 takes the fingerprints of before G from the
|
||||
mesh, not from a file: during G's check the trunk is before G, so it asks `seat:mesh-catalog.module` for the
|
||||
indexer, the assigner, the gate, the backup holder and the bus module, and fetches each build by its fingerprint
|
||||
through `seat:oci-artifact-registry` under the checker's account, as §637 says; the address of both is the bus the
|
||||
check gives the test. Each fetched build is started the way every module is, by the node-engine's own apply of a
|
||||
want on the test's bus, under the module's own account; the test starts no bare process. Where the seat does not
|
||||
answer, the test fails, saying which build it could not fetch and why; it never skips.
|
||||
- **A-316** (§379, §421, §628, A-312; principles 2 and 7) A node-engine composes a candidate only from what its asker
|
||||
gives it, as it composes a want. The assigner's candidate `<proposal>.<node>` gains `added`: by module, for each
|
||||
module the candidate adds to that node without a want there, the `version` and `parts` the want it would write
|
||||
carries, in the same wire forms (the seat format reuses nothing across repositories, so the assigner's seat
|
||||
defines the two properties itself and a fixture holds them to the want's), made by the same code that writes wants. The node-engine's shadow builds such a module from that
|
||||
entry and its `added` parts; a module with artifacts the candidate gives no parts is held, saying so. Nothing reads
|
||||
the builder's states or asks the registry for it.
|
||||
- **A-317** (A-316; principles 1 and 7) A proposal's candidates have one writer, the assigner's main process (the
|
||||
passer), which holds the heard builds and released versions: on hearing a proposal it writes each node's candidate
|
||||
with `added` filled, as `layAgain` does. The verbs' tool process writes the proposal alone and no candidate, so no
|
||||
node-engine sees a candidate before its parts. If a verb's answer needs the candidates, it waits on the passer's write
|
||||
(an event or the state's watch), never a timer.
|
||||
- **A-318** (A-210, A-296, §428, §717; principle 7, and a milestone's scope is fixed at its start) A part reaches
|
||||
another module's state or verb in the seat form wherever that module's seat declares it (A-210). Where no seat
|
||||
declares it, or where a process has no seat-form follower for a raw state (the SDK's FollowSeat follows views
|
||||
only), the read keeps its module form in G; giving those seats the states and verbs, and processes a seat-form state
|
||||
follower, is the work after G this list names. The provisioner's `provisions` is declared by its seat, so it is read
|
||||
in the seat form (an earlier draft of this answer listed it wrongly). In mesh-delivery at 81cb2d7a:
|
||||
- the builder (no seat state or verb): builds/builds.go:50 `BuilderModule`, builds/log.go:21 `BuildLogRead`
|
||||
(`build-log`), builds/versions.go:14 `BuilderVersionsAddress` (`builder_versions`); view-tools main.go:477, :486;
|
||||
view main.go:180 and view.go:692, checker main.go:279, grants buscompose/state.go:361 (`BuildsBucket(BuilderModule)`);
|
||||
indexer main.go:515; the view manifest's `mesh-delivery-builder.builds` and `.build-log` reads;
|
||||
- the grants part's composition inputs: buscompose/names.go:12-16 and their buckets, inputs.go:271 (`nats`),
|
||||
buildbacklog.go:13;
|
||||
- the view process's raw states (no seat-form follower): `node-engine_have`, `mesh-delivery-watchman_presence` and
|
||||
`_stuck` (main.go:180, :285-287, internal/view/names.go) and view-tools main.go:477, :482 that share those reads;
|
||||
the indexer's `candidates` and `taken` (`mesh-delivery-indexer.views.*`; `taken` undeclared, no state of
|
||||
mesh-catalog is a view); the teller's `answers` (the teller holds no seat); the gate's and the watchman's states;
|
||||
- mesh-enrolment main.go:148, :149, :178 (the assigner's `assignments` view and `assigner_read_assignments`; a stdio
|
||||
module's views reader has no FollowSeat);
|
||||
- the indexer's tools `indexer_read_*` and `indexer_pending` (the seat serves no such verb): the manifests' invokes
|
||||
name the module, the code takes the module from the seat's holder.
|
||||
- **A-319** (§698, A-312; principles 1 and 7) The node-engine's `entry-again` asks again every entry cache the
|
||||
node-engine keeps, its own composer's and its shadow's, so an entry once answered not-found in the shadow is taken in
|
||||
on the same act; the shadow re-answers its candidates on that answer. No cache is polled.
|
||||
- **A-320** (the operator, 2026-11-12: "an integration test is not an e2e test, it should still mock all external
|
||||
dependencies, but test the internal wiring"; withdraws §637 and A-315) The check runs each module's unit and
|
||||
integration tests and nothing more. Test 36 is proven in two parts: each module's integration test proves its own
|
||||
wiring of the undo with every other module a stand-in (the node-engine's existing test at
|
||||
`cmd/mesh-host/recovery_integration_test.go` is that form), and the whole undo end to end belongs to the test lab (the
|
||||
operator, 2026-11-12: paused until its performance is fixed), not to a check and not to a live node. No check
|
||||
fetches or starts real builds.
|
||||
- **A-321** (A-210, A-303; principles 3 and 7) The watchman awaits a followed state by its seat and state
|
||||
(`mesh-wants`, `wants`) and resolves the bucket name at each use through the process reader's holder accessor; a start
|
||||
never waits for a holder. A seeded pulse, stuck key or finding of a bucket not yet resolved is kept, and closed only
|
||||
after the late read of the resolved bucket.
|
||||
- **A-322** (A-298, A-321; principle 7) A message on a process's event hub has one taker. The watchman replaces its
|
||||
catch-all of seat events by one handler per seat it consumes; for the seat `mesh-catalog` it takes each event it
|
||||
consumes except `registration-changed`, which the branches follower takes, and whatever the watchman needs of that event
|
||||
it takes from the follower. The SDK's hub keeps one taker per message.
|
||||
@@ -0,0 +1,374 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
design: 47
|
||||
milestone: G
|
||||
updated: 2026-11-11
|
||||
decisions:
|
||||
- "ADR 0666"
|
||||
- "ADR 0656"
|
||||
- "ADR 0648"
|
||||
- "ADR 0645"
|
||||
- "ADR 0644"
|
||||
- "ADR 0641"
|
||||
- "ADR 0639"
|
||||
- "ADR 0636"
|
||||
- "ADR 0633"
|
||||
- "ADR 0621"
|
||||
- "ADR 0619"
|
||||
- "ADR 0618"
|
||||
- "ADR 0615"
|
||||
- "ADR 0614"
|
||||
- "ADR 0603"
|
||||
- "ADR 0597"
|
||||
- "ADR 0596"
|
||||
- "ADR 0595"
|
||||
- "ADR 0571"
|
||||
- "ADR 0567"
|
||||
- "ADR 0564"
|
||||
- "ADR 0548"
|
||||
- "ADR 0493"
|
||||
- "ADR 0441"
|
||||
- "ADR 0437"
|
||||
- "ADR 0421"
|
||||
- "ADR 0363"
|
||||
---
|
||||
|
||||
# 47, milestone G: the delivery record, what the gate owes the forge, the state table, the two graphs and a change's graph difference
|
||||
|
||||
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
||||
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
||||
|
||||
## The delivery record, and what the gate owes the forge
|
||||
|
||||
### The delivery record
|
||||
|
||||
*ADR 0656; ADR 0595, ADR 0596, ADR 0597, ADR 0603 §7, ADR 0493 §3, §11, ADR 0633 §4, §5, ADR 0441 §1, ADR 0548 §8,
|
||||
ADR 0564 §2, ADR 0363 §2.*
|
||||
|
||||
§141 From research 085's milestone the gate, holder of the seat `mesh-delivery`, keeps one **delivery record** per
|
||||
milestone's delivery, a ledger instance (ADR 0595's template `ledger`, ADR 0597) it alone writes (ADR 0656). It
|
||||
holds:
|
||||
|
||||
- §142 **the group's state**: the heads, the verdicts, the checks and the asks, which `merges` holds today; from milestone
|
||||
G's delivery the pipelines' `step-started`, `step-ended` and `pipeline-ended` in place of the verdicts, the checks and
|
||||
the asks, kept as long as the record (ADR 0666 §9);
|
||||
- §143 **the trace**: the milestone, its issues, its pull requests, their commits, and the graph revisions the delivery
|
||||
moved between;
|
||||
- §144 **what happened to each entry on each node**: for every entry of the graph difference, taken in, built, checked,
|
||||
composed, wanted, running healthy, held (with the hold's reason, so a reader of the record sees why an entry stands
|
||||
still without asking its holder), failed (with what failed), or away (ADR 0656).
|
||||
|
||||
§145 It holds no tiers, and only the gate orders work by it, so it is no second copy of the keys, the objection research
|
||||
063 had to a stored plan (research 085, Findings 10). Its fields, retention and tools are this design's choices under
|
||||
ADR 0656.
|
||||
|
||||
- §146 **The outcomes arrive as events, through a durable consumer of the gate's own.** The gate's manifest `consumes`
|
||||
each step event of *What each step says* (below), and `wait-held` and `wait-ended` of each holder. The consumer
|
||||
resumes from the last event applied; an event applied twice changes nothing; an event is applied only when it comes
|
||||
from its owner's account; and running healthy is taken from the node-engine seat's `running` event, never from a
|
||||
have read across the seam (ADR 0596).
|
||||
- §147 **It ends when every present node runs its entries**, and the gate says `record-ended` on the seat `mesh-delivery`
|
||||
with the record's milestone and its nodes away. It names the nodes away, each with since when, and ends without
|
||||
waiting for them, as a node away holds nothing (ADR 0603 §7).
|
||||
- §148 **A record open too long is a finding.** (§502) The watchman writes the finding `record-open` for a record still open 24
|
||||
hours after its merge (a delivery of the whole mesh takes under an hour, so a day means something stands still) and
|
||||
clears it on `record-ended`; the condition keeper raises it (ADR 0564 §2). The finding names the entries still held
|
||||
or failed, read from the record's own events. It is written only while no other condition covers the record: while
|
||||
an entry of it is held by a wait that raised `acting-part-late`, or a store that holds it raised `full`, that
|
||||
condition already tells the operator why the record is open, and a second one for the same cause would only be noise.
|
||||
- §149 **A node away that wakes adds its outcomes.** When it wakes and catches up by its wants (*A change's graph
|
||||
difference is its delivery*, below), its outcomes reach the record through the same consumer as any other, and the
|
||||
record shows the node no longer away.
|
||||
- §150 **An Approve given holds at its heads, until G's delivery.** An Approve the operator gave for a group's heads holds while those heads
|
||||
stand: a check of the same heads asked again, after a main move or for any other reason, records its verdict in
|
||||
the record and asks the operator nothing again (issue 1972). The Approve is withdrawn only as ADR 0441 §1 has it,
|
||||
when a member stops being mergeable, and a carried verdict is read as current (ADR 0548 §8). From G's delivery no
|
||||
Approve is asked per merge; an `approval` step's answer is the runner's, kept in the record as its step's end
|
||||
(ADR 0666 §10).
|
||||
- §151 **From G's delivery, a record for every landed merge.** A session merges through the seat `git` and the gate merges
|
||||
nothing, so every merge is one the gate did not make: the gate opens a record for each source revision of a watched
|
||||
branch the indexer takes in, a group's members in one, and `forge-made` no longer marks a record apart. A merge its
|
||||
author names a recovery is marked `recovery` (*A recovery fix is an ordinary merge*).
|
||||
- §152 **A small fix has a record of its own.** A small fix is a milestone of one issue (ADR 0493 §3), so it has a
|
||||
delivery record like any milestone; nothing is merged outside a released milestone (ADR 0493 §11).
|
||||
- §153 **It pins the graph revisions it points at.** The gate says, as the seat `mesh-delivery`'s events `pinned` and
|
||||
`freed`, which catalog and assignment graph revisions a record starts and stops pointing at, and as
|
||||
`heads-replaced` that a candidate's heads were replaced or its pull request closed. Only the gate's account may
|
||||
publish them (ADR 0363 §2). The indexer and the assigner hear them through a durable consumer each, declared in
|
||||
their manifests' `consumes`, which resumes from the last event applied after a restart, so a `freed` said while one
|
||||
of them was down is still applied; neither reads the gate's state (ADR 0596; *Two graphs, one source of truth*,
|
||||
below).
|
||||
- §154 **A record is kept for 90 days by default**, the gate's setting `record-retention`. Ninety days covers every trace
|
||||
the operator has asked of a past delivery so far, which went back weeks, never months; the setting can keep records
|
||||
longer. When the retention removes a record, the gate says `freed` for each revision it pinned.
|
||||
- §155 **Its size, its cap and its growth.** A record's state of 30 modules on four nodes is about 30 KiB, against the
|
||||
bus's 256 KiB value limit (research 085, Findings 8). The ledger keeps every operation beside that state: 30 modules
|
||||
on four nodes with seven outcomes each is about 840 outcome operations a record, at about 0.2 KiB each about 170 KiB,
|
||||
so a record costs about 200 KiB with its snapshot. At an estimated 20 deliveries a day (a guess from the days of
|
||||
research 085, not a measurement) the ledger grows about 4 MiB a day, about 360 MiB at the default retention; its cap
|
||||
is 768 MiB, twice that, so a busier month than estimated still fits. Removing a record at its retention removes its
|
||||
operations and its snapshot with it, so the ledger's size follows the retention, not the mesh's age.
|
||||
- §156 **The switch carries today's state in, once.** At the new gate's start, `merges` and every group in flight become
|
||||
records, and from then on the gate writes `merges` no more; `turns`, which nothing writes while the check gates no
|
||||
merge, is dropped with the hold (*Nothing merges into a repository under check*). The switch is safe to run
|
||||
twice: a group in flight becomes a record keyed by its milestone and heads, and a switch that finds that record
|
||||
already there leaves it as it is, so delivering milestone G again after a revert makes no second record and asks
|
||||
nothing again. A revert of the gate is the delivery system's restore of its kept data (*A module's kept data on the
|
||||
bus, backed up and restored with its delivery*).
|
||||
- §157 **The forge bridge asks the seat for a group.** It reads a group's members from the seat `mesh-delivery`'s verb
|
||||
`groups`, never the gate's `merges` state, both to set the group on each merged message and to publish a group's
|
||||
end again (ADR 0656, which changes ADR 0633 §4 and §5; ADR 0596). Its account holds no grant to read `merges`.
|
||||
- §158 **What the gate owes the forge is said, not watched, and a merge made at the forge has a record too** (*What the
|
||||
gate owes the forge*, below).
|
||||
- §159 **Its tools**, against the four lines a module's tools answer:
|
||||
|
||||
| Line | The gate |
|
||||
|---|---|
|
||||
| read everything it holds | `gate_record` (one record whole: the group's state, the trace, each entry's outcome per node, the nodes away), `gate_records` (every record with its fill against the value limit, and the ledger's fill against its cap), `gate_consumer` (its durable consumer: the last event applied per family, the events waiting, the age of the oldest) |
|
||||
| do by hand what it does itself | `gate_record_repair` (for one record, asks each owner for the outcome of each entry missing one through that owner's read verb: the indexer's `take-in` read, the builder's build read, the checker seat's check read (from G's delivery the runner's `show`), the seat `mesh-wants`'s `entry`, and the node-engine seat's verb `composition` of that node; applies the answers as the consumer would) |
|
||||
| recover without help | `gate_record_end` (end a record left open, a node gone for good or an entry that never runs, with the reason, recorded in the hand-act log of the seat `mesh-hand-acts`) |
|
||||
| one seat, many tools | the seat `mesh-delivery`'s verbs, with `groups` answering a group's members and the acts said for it |
|
||||
|
||||
- §160 **How it is checked.** In the gate's whole-module test: the gate stopped and restarted in the middle of a delivery
|
||||
leaves the record complete with no outcome counted twice; the events `pinned`, `freed`, `heads-replaced` and
|
||||
`record-ended` are said; a `freed` from an account other than the gate's is refused by the bus; a group approved
|
||||
before the switch is not asked again after it, nor after milestone G is delivered again following a revert; a record ends once every present node runs its
|
||||
entries, naming the nodes away; a held and a failed entry are kept with their reason; a record open past 24 hours
|
||||
raises `record-open`, cleared by `record-ended`; a node away that wakes adds its outcomes and is no longer shown
|
||||
away; a group's heads approved and checked again ask the operator nothing again; a small fix's merge gets a record
|
||||
of its own; a record past its retention is removed and frees its revisions; the tools read a record, list the
|
||||
records with their fill, and a repair completes a record missing an outcome through the owners' read verbs. In the
|
||||
forge bridge's whole-module test, a group's members are read from `groups` and its end published again from it, and
|
||||
a read of `merges` by its account is refused. Live: one call of `gate_record` traces an issue of the registry
|
||||
milestone to each node's running build, also after six later merges; a node away is named with since when and its
|
||||
outcome taken on waking; a record of 30 modules on four nodes stays under 256 KiB; a group checked again at old
|
||||
heads after a main move is not asked of the operator again (issue 1972).
|
||||
|
||||
### What the gate owes the forge
|
||||
|
||||
*ADR 0596 §1, §2, ADR 0656 §7, ADR 0437 §8, ADR 0441 §5, ADR 0633 §1, §4, ADR 0363 §2, ADR 0564 §2; from research 085's
|
||||
milestone, this design's choices under ADR 0596 and ADR 0656 §7.*
|
||||
|
||||
§161 The forge bridge today performs what the gate's states `merges` and `owed` ask (a merge, a status, the delivery's
|
||||
view, a comment, a line on the commit's note) by watching those keys (ADR 0437 §8), and the forge module writes the
|
||||
checker's `mesh/check` from a watch of the checker's state `owed`: reads of another module's bus state that ADR 0596
|
||||
§2 ends when their owner moves. From research 085's milestone:
|
||||
|
||||
- §162 **Every act is an event, under one id.** The gate says each act it owes as the event `owed` of the seat
|
||||
`mesh-delivery`, on `mesh.seat.mesh-delivery.event.owed.<repository>.<number>`, carrying the act, the pull request,
|
||||
the head and an id; the checker says its status the same way, as the event `owed` of the seat
|
||||
`mesh-delivery-checker` on `mesh.seat.mesh-delivery-checker.event.owed.<repository>.<number>`. Only the owner's
|
||||
account publishes each (ADR 0363 §2). The subject per repository and pull request lets the bridge, a person and a
|
||||
test read one pull request's acts alone. The seat events are kept on each owner's stream `owed`, 7 days and at most
|
||||
64 MiB (an estimated 20 deliveries a day, with about 15 acts of about 1 KiB each, is about 2 MiB over 7 days).
|
||||
- §163 **Every act is safe to repeat at the forge.** A status and the delivery's view replace what stands; a merge of a
|
||||
pull request already merged at that head is answered performed; a comment and a line of the note carry the act's
|
||||
id, and the seat `git`'s verb skips an id the pull request or note already holds. This is chosen over recording the
|
||||
id before the act: an id recorded and then not performed (the bridge stopped between the two) would be lost, while
|
||||
an act repeated changes nothing. The bridge's own state `performed`, a key-value state in the new form (ADR 0595),
|
||||
form 1, one key per id with the outcome and when, kept 7 days as the stream is, its size the one its template fixes
|
||||
(ADR 0595 §3), is therefore a
|
||||
saving and a read, never what keeps an act from happening twice.
|
||||
- §164 **Every outcome is said, and the gate records it.** The bridge says each act's outcome on its own stream `forge` as
|
||||
the event `owed.ended`, with the id and one of `performed`, `refused` (the forge's own words), `could not be
|
||||
performed` (the forge did not answer within the call's bound, with the error) or `undecodable` (an act it cannot
|
||||
read, with why): an act it cannot decode is said, never only logged. The gate hears the stream `forge` through its
|
||||
durable consumer, writes the outcome into the delivery record, and says an act's id is answered only once an
|
||||
outcome is recorded. The checker does the same for its own acts: it hears `owed.ended` for its own ids, records each
|
||||
outcome in its own state (it watches only its own state), says again at its start, under the same id, every act of
|
||||
its own still unanswered, and lists one unanswered past 15 minutes in its own stalled read.
|
||||
- §165 **What is unanswered is said again, and stalls by a bound.** At its start the gate says again, under the same id,
|
||||
every act its records hold unanswered. An act unanswered 15 minutes after it was said is listed by `stalled`, which
|
||||
the condition keeper raises as the stalled line (ADR 0421 §7, ADR 0564 §2): fifteen minutes is many times a
|
||||
forge call's time, and short beside the check's hour.
|
||||
- §166 **A group's merges follow one another, until G's delivery.** The gate says a group's next `merge-pull` only once the previous member's
|
||||
merged message is heard (ADR 0441 §5), so a member the forge refuses holds the rest, and none is merged ahead.
|
||||
- §167 **From G's delivery the gate owes no merge, and the checker nothing.** (§510) A session calls `merge-pull` itself (ADR 0666
|
||||
§11), and a group's members land one by one, the group ending when every member has (*Groups*); the checker's `owed`
|
||||
goes with the checker, and the runner owes the forge nothing, a pipeline's result living on the bus. The gate still
|
||||
owes the delivery's view, a comment and the note's line, performed by the forge bridge as above.
|
||||
- §168 **The bridge catches up from the seat.** At its start the bridge resumes its durable consumer and also reads
|
||||
`groups`, which answers, per group, the acts the gate said with their ids and their recorded outcomes; an id said
|
||||
and not answered is performed. Its tools: `bridge_owed` (the ids it performed with their outcomes, and its
|
||||
consumer's backlog: the last event applied, the events waiting, the age of the oldest), `bridge_owed_again`
|
||||
(perform one id again now) and `bridge_owed_drop` (drop one id with its reason, written in the hand-act log of the
|
||||
seat `mesh-hand-acts`, and said as `owed.ended` with the outcome `dropped`).
|
||||
- §169 **The switch carries what is owed.** (§542, §592) At the new gate's start (*The switch carries today's state in, once*) every key
|
||||
of `owed` the old bridge has not performed becomes an act of its record, with an id made of the key and
|
||||
its revision; the new bridge counts as performed every id whose key and revision the old bridge's own bookkeeping
|
||||
names, so nothing is done twice and nothing dropped. A key of `merges` becomes no act: §542 says it as
|
||||
`approval-carried`, and §592 has the whole merge path from G's delivery.
|
||||
- §170 **A merge made at the forge has a record too, until G's delivery** (from it, §151 and §592). A merge the forge reports for which no record holds a `merge-pull`
|
||||
of that pull request at that head (a person merged it in the forge's own page, as on 2026-11-07 and 11-09) is taken
|
||||
in like any other merge (*Taken in by the indexer it names*), and the gate opens a delivery record for it, marked
|
||||
`forge-made`, with no Approve, so its trace and its outcomes on the nodes are kept like every delivery's and no
|
||||
later merge waits behind a merge the gate never heard. The gate says it as the delivery's `transition` with the why
|
||||
`forge-made`, which the teller passes on to the operator (the operator, 1921's note of 2026-11-07: "a merge made in
|
||||
the forge is taken in like any other").
|
||||
- §171 **How it is checked.** In the forge bridge's whole-module test: an `owed` said while the bridge is stopped is
|
||||
performed once after its restart; the bridge stopped between an act and its `owed.ended` performs it again on its
|
||||
restart and the forge holds it once (the comment and the note by their id); an act said twice is performed once; an
|
||||
undecodable act is said `undecodable`; `bridge_owed`, `bridge_owed_again` and `bridge_owed_drop` answer, the drop
|
||||
writing the hand-act log; its account cannot read the gate's or the checker's state. In the gate's: an act left
|
||||
unanswered is said again at the gate's start under its id and listed by `stalled` after 15 minutes; the merge path
|
||||
of G's gate is checked as §592 says. Acceptance tests 53 and 51.
|
||||
|
||||
## The state table, from milestone G's delivery
|
||||
|
||||
§172 **From milestone G's delivery the gate keeps no merge states.** With no check before a merge and no Approve per merge
|
||||
(ADR 0666 §10, §17), nothing is `proposed`, `ready`, `rejected` or `held` before a merge: a commit's states are its
|
||||
pipeline's steps, kept by the runner (*Pipeline steps and the policy*). The table, the gate's `table`, `recheck` and
|
||||
`close` and the one key per pull request go with the check; `stalled` lists the acts owed to the forge past their 15
|
||||
minutes and the records `record-open` names.
|
||||
|
||||
## Two graphs, one source of truth
|
||||
|
||||
*ADR 0656, which supersedes ADR 0645 to ADR 0648; ADR 0595,
|
||||
ADR 0596, ADR 0603 §2, §6, §7, ADR 0615 §1 to §6, ADR 0621 §1, §2, ADR 0614 §3, ADR 0567 §5, ADR 0571 §8, ADR 0614 §4, ADR 0618 §2, ADR 0619 §1 to §3, ADR 0636 §1,
|
||||
§3, §4, §7, ADR 0639 §7, §8, ADR 0564 §2, ADR 0363 §2.*
|
||||
|
||||
§173 From research 085's milestone. One graph answered two questions, which modules exist and which version runs where,
|
||||
and one module running as several placed versions would have given one entry two shapes and one key two writers; two
|
||||
graphs answer them, the second computed from the first, so there is one source of truth (ADR 0656; research 085,
|
||||
Findings 11). How each is kept, read and retained is this design's choice under ADR 0656.
|
||||
|
||||
```
|
||||
manifests ──► indexer (mesh-catalog) ──► catalog graph revision ──┐
|
||||
├──► assigner (mesh-wants) ──► assignment graph revision
|
||||
placements (mesh-module-assigner) ───────────────────────────────┤ │ │
|
||||
node capabilities (node-engine seat) ────────────────────────────┘ │ └─► wants, from a trunk revision only
|
||||
▼
|
||||
a change's assignment graph − the current one = its graph difference = its delivery
|
||||
```
|
||||
|
||||
- §174 **The catalog graph is the indexer's.** The indexer, holder of the seat `mesh-catalog`, keeps the modules as their
|
||||
manifests declare them; it is today's module graph, and *module graph* stays its other name (ADR 0656). At each
|
||||
revision it holds one entry per module: its bases, its packages, the toolchain version and recipe version of the
|
||||
build module that builds it as ADR 0603 §2 has them, its provides and requires, its invokes and serves, and its
|
||||
fingerprint, with the rules the seats declare (*An acting part's new version acts first*, below).
|
||||
- §175 **The assignment graph is the assigner's, computed and never written by hand.** The assigner, holder of the seats
|
||||
`mesh-module-assigner` and `mesh-wants`, computes the module versions placed on nodes, with the edges between
|
||||
versions, from the catalog graph, the placements and the nodes' capabilities, and nothing else writes it
|
||||
(ADR 0656). Each entry is one module version on a node; its edges are provisioning, contribution, and the acting
|
||||
parts' versions that are to act on it (ADR 0656), whose rules and versions come in with the catalog graph. It is
|
||||
kept, but it is no stored plan: it has one writer, no part orders work by a stored plan, and any revision is computed
|
||||
again from the inputs it names and compared with the one kept, by a tool (below), so a kept revision is a checked
|
||||
derivation, never a second source of the kind research 063 removed (research 085, Findings 5).
|
||||
- §176 **Each graph is kept per revision, and each revision names its inputs** (ADR 0656). The indexer keeps a catalog
|
||||
graph revision for each source revision it takes in; the assigner keeps an assignment graph revision for each change
|
||||
of its inputs, naming the catalog graph revision, the placements and the capability readings it was computed from.
|
||||
Both parts are the foundation (ADR 0619 §1), so both keep their revisions as objects declared in an `objects`
|
||||
section in the old form, each with a cap it declares there, never in the new form (ADR 0619 §2), and neither waits
|
||||
on the bus module to start; the manifest validator refuses either manifest that declares them in the new form
|
||||
(ADR 0619 §3). An assignment graph revision is announced by an event of the seat `mesh-wants` and served by its
|
||||
verbs. This replaces ADR 0603 §2's one key per module with five past values as the graph's form (ADR 0656, which
|
||||
changes ADR 0603 §2); the keys are written no more, and a revert of milestone G restores them from their backup
|
||||
(*Reverted*, below).
|
||||
- §177 **A revision is made by a merge, and also with no merge.** The indexer makes a catalog graph revision when it
|
||||
takes a source revision in. The assigner makes an assignment graph revision on each new catalog graph revision, on
|
||||
the seat `mesh-module-assigner`'s event `assignments-changed` (ADR 0567 §5, ADR 0641), and on the node-engine seat's event
|
||||
`manifest-reads-changed`, which a node-engine says when the capabilities it states change and when its node returns
|
||||
(ADR 0614 §4, ADR 0618 §3, §4; §590), so the assigner follows an event and never reads a node record across the seam (ADR 0596). A
|
||||
placement change is traced like a merge. At its first start with no revision kept, the indexer takes in the head the
|
||||
gate's source names for every repository by its catch-up path and keeps that as revision 1, and its verb `revision`
|
||||
answers that it waits until then (lead-47's ruling in G's prepare: a first start has no merge to make a revision).
|
||||
- §178 **A revision is kept while a record pins it or it is current.** A revision of either graph, and the input revisions
|
||||
it names, is removed when no delivery record pins it and it is not current: neither trunk's newest revision nor the
|
||||
newest of an open candidate. Pins are counted per revision, so a revision two records point at stays until both
|
||||
have said `freed`. The indexer and the assigner remove one only on applying `freed` or on a revision ceasing to be
|
||||
current, never on a timer and never by reading the gate's state (ADR 0596; *The delivery record*, above). A trace
|
||||
must read every revision it moved between, and a revision neither pinned nor current is read by nobody.
|
||||
- §179 **An unchanged entry is stored once.** A revision is kept as its list of entries' digests, and each entry as an
|
||||
object of its own named by its digest, so an entry that two revisions share is stored once; an entry object is
|
||||
removed when no kept revision lists it and no want or running build names its fingerprint (§443). Consecutive revisions differ by the few entries a delivery moves, so this
|
||||
keeps the growth to what changes. It is chosen over a larger cap because whole revisions would grow with every
|
||||
revision whatever changed, and a cap would only delay the same fill.
|
||||
- §180 **The caps and the growth** (estimates, to be read from the fill tools once the milestone runs). A whole assignment
|
||||
graph revision of about 120 modules on four nodes is under the bus's 256 KiB value limit (its unit test), and a
|
||||
catalog graph revision's list of about 120 entries about 4 KiB, its entries, each carrying its manifest whole
|
||||
(§443), about 15 KiB each. A record pins the revisions before and after its
|
||||
delivery, and consecutive records share one, so at an estimated 20 deliveries a day about 20 revisions of each graph
|
||||
a day stay pinned. A delivery moving 5 modules on four nodes adds about 20 assignment entries of about 1 KiB and a
|
||||
list of about 15 KiB, and 5 catalog entries of about 15 KiB and a list of about 4 KiB: about 0.7 MiB a day of assignment graph and
|
||||
1.6 MiB of catalog graph, about 63 MiB and 144 MiB at the records' default retention of 90 days. The caps are 512 MiB
|
||||
for each, many times the steady state. Placement-only and capability-only revisions are pinned by no record and go
|
||||
when they stop being current.
|
||||
- §181 **A store filling is said before it is full.** The indexer, the assigner and the gate each say their fill against
|
||||
its cap in their read tools, and the event `filling` of their seat when a store passes 80 % of its cap; the watchman
|
||||
writes the finding `store-filling` naming the part and the store, the condition keeper raises it (ADR 0564 §2), and
|
||||
it clears when the store falls below 70 %. A full cap refuses loudly, naming the part and the store: the indexer
|
||||
keeps no new revision and says the merge pending with the reason `objects full`, the assigner keeps its last
|
||||
revision and writes no new want, and the gate opens no new record and holds the merge; each says it as the event
|
||||
`full` of its seat, which the condition keeper raises as urgent. When a removal brings the store back below its cap,
|
||||
the part says `room` of its seat, takes the refused work up again (the pending merge, the next revision, the held
|
||||
record) and the condition clears; a condition that never clears would hide the next fill. Nothing is dropped to make
|
||||
room: a removal is only the rule above, and the setting `record-retention` or a larger cap is the operator's.
|
||||
- §182 **What happened is not in the graph.** An entry says what is to run and what it waits on; whether it was built,
|
||||
wanted or runs healthy is said by the parts as their events and kept in the delivery record (ADR 0656).
|
||||
- §183 **The want stays the one record a node reads.** The assigner writes `want.<node>.<module>` from a trunk
|
||||
revision's assignment graph only; a node-engine reads its want, never the assignment graph, and the have stays what
|
||||
it runs (ADR 0656, which changes ADR 0571 §8).
|
||||
- §184 **From G's delivery, per watched branch and between artifacts.** The catalog graph is kept once per watched branch,
|
||||
each revision naming its branch, and its edges run between artifact references, of the six kinds of *Dependencies
|
||||
between artifacts* (ADR 0666 §6, §14). The assigner computes a node's entries from the catalog graph of the branch its
|
||||
placement names, and writes a want from a revision of that branch whose `required` steps passed, in place of a trunk
|
||||
revision's alone (ADR 0666 §9, which changes ADR 0656 §2).
|
||||
- §185 **Reverted.** Only the new form is written: the indexer, the assigner and the gate keep nothing of ADR 0603 §2's
|
||||
keys or of `merges` and `turns` beside the new forms, since nothing reads them after milestone G. A revert of
|
||||
milestone G is a change like any other, checked and delivered, and the delivery system restores what it must
|
||||
(*A module's kept data on the bus, backed up and restored with its delivery*, under *A backup before the apply*).
|
||||
Milestone G changes the form of these kept data, and raises their `form` (a kept data declaring none reads as form
|
||||
0, so G's own move takes their restore points):
|
||||
|
||||
| Part | Kept data before G (form 0) | After G | Restored at a revert |
|
||||
|---|---|---|---|
|
||||
| the indexer | the state `modules` (ADR 0603 §2's one key per module) and its durable consumer's position on the forge bridge's stream `forge`; the states `holds`, `engine-reads` and `hold-bounds` of its mesh-wide hold | catalog graph revisions as objects, form 1; `modules` no longer declared, nor its tool `indexer_read_modules` or its view; the mesh-wide hold, its three states and its tools `indexer_holds` and `indexer_engine_reads` gone, the wait per node being the assigner's (*Composing waits per node, by the assigner*); every reader of it reading the catalog half (*Every other reader of which modules exist*, under *The revision view*, below) | yes: `modules` and the hold's three states as they stood at G's delivery, and the consumer's position |
|
||||
| the gate | the states `merges` and `turns` | the delivery record's ledger, form 1; `merges` and `turns` no longer declared | yes: `merges` and `turns` as they stood at G's delivery |
|
||||
| the assigner | the wants, written from the indexer's `modules` | the same wants, form 0, written from the assignment graph; the assignment graph revisions as objects, form 1 | no |
|
||||
|
||||
The wants keep their form: their keys and values are unchanged, only what the assigner computes them from changes,
|
||||
so G raises no `form` on them and a revert restores none. Restoring the wants would send nodes back to older
|
||||
fingerprints until the reverted assigner computed them again; leaving them, the nodes keep what runs and the reverted
|
||||
assigner writes them again from the caught-up indexer. The assigner's placement and every other kept data of the
|
||||
three keep form 0 and are never restored, so nothing decided since (a placement, a verdict, the source revision) is
|
||||
set back. The restored `modules` is as it stood at G's delivery, so a module added since is missing from it until
|
||||
the reverted indexer takes in its next source revision, and a reverted reader that learns which modules exist from
|
||||
`modules` does not name it until then. What a reader granted since stays: the forge module's write grants on a
|
||||
repository it learnt of from the catalog half are kept, since it grants writes and takes none away, so a revert
|
||||
costs no repository its account's writes.
|
||||
|
||||
### A change's graph difference is its delivery
|
||||
|
||||
- §186 **The difference names everything a plan named.** (§512) For a change (a pull request's heads, a delivery group's
|
||||
heads, or a merge), the indexer gives its catalog graph and the assigner its assignment graph. Compared with the
|
||||
current revision, the difference names the new versions, the moved edges, the touched nodes and the order, read off
|
||||
the edges, and no plan is stored (ADR 0656). The assigner serves it by the seat `mesh-wants`'s verb `difference`,
|
||||
for two revisions of the assignment graph or for a candidate against trunk. ADR 0603 §6's tiers, as ADR 0633 §5
|
||||
changes it, are a view worked out from that difference (ADR 0656, which changes ADR 0603 §6), read through
|
||||
`difference`, with the waits and holds read through their owners' tools and the pending revisions through
|
||||
`indexer_pending`.
|
||||
- §187 **The preview is a candidate's difference.** Issue 1874's plan preview of a group before its merge is its
|
||||
candidate's difference, read by one call of `difference`, and needs no code of its own: a candidate's graphs are
|
||||
computed for its check anyway, so the preview costs nothing more.
|
||||
- §188 **A changed fingerprint with no difference is refused by name.** When a module's fingerprint in the change
|
||||
differs from the running one while the difference is empty, the assigner refuses it, naming the module, and the
|
||||
check fails naming it.
|
||||
- §189 **A candidate's graphs are kept apart, and dropped with its heads.** A candidate's two graphs are computed per
|
||||
check, kept apart from trunk's, and dropped on the gate's event `heads-replaced`, said when the candidate's heads
|
||||
were replaced or its pull request closed. A candidate costs a computed graph per check.
|
||||
- §190 **Only a trunk revision writes wants**, whether a merge taken in or a placement or capability change made it; a
|
||||
candidate's graph writes none (ADR 0656). From G's delivery a want comes from a revision of the watched branch a
|
||||
placement chose whose `required` steps passed, and a candidate is a run on request's: the indexer builds it on the
|
||||
runner's `run-started` and drops it on that run's `pipeline-ended`, the gate's `heads-replaced` no longer making
|
||||
candidates (ADR 0666 §9, §18).
|
||||
- §191 **Each part acts on its own entries.** The builder, the build modules and the grants part each act when their own
|
||||
entries in the new revision differ from what they run (ADR 0656), reading them through the seat `mesh-wants`'s verbs
|
||||
and events, never its bus state (ADR 0596), and naming no other module (ADR 0656). The node-engine acts on its want
|
||||
alone, as it does today: the want is the one record a node reads, and handing every node the assignment half would
|
||||
have each node read every other node's placements.
|
||||
- §192 **A node asleep catches up when it wakes.** A node away holds nothing and catches up by its wants (ADR 0603 §7);
|
||||
a want's new form reaches only a node-engine that says it reads it (ADR 0614 §4, ADR 0618 §2).
|
||||
- §193 **No delivery goes ahead of a change.** A change is delivered as its own difference, its acting parts' new versions
|
||||
first in their own steps (ADR 0656); the one exception is the delivery before research 085's milestone (*The
|
||||
bootstrap delivery, and the one milestone after it*, under *Milestones*; ADR 0644).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user