elanous 0.2.20 → 0.2.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/aBi6EyMtLsrl7J8eH4kF7/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/088d8e8a.8069543d082bec12.js +160 -0
  5. package/apps/pwa/out/_next/static/chunks/1109.78a7a3288c0259d9.js +148 -0
  6. package/apps/pwa/out/_next/static/chunks/115.1c6690b8604f81c4.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/119fc678.6f591cdb84ca5791.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1262.76a3f08e67c5f816.js +309 -0
  9. package/apps/pwa/out/_next/static/chunks/1307-08b716476f1d50af.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1358d292.5c87f12cad6eaa91.js +122 -0
  11. package/apps/pwa/out/_next/static/chunks/1438.27a1a7b8f2308004.js +149 -0
  12. package/apps/pwa/out/_next/static/chunks/1442.02d85cd88f4c1eac.js +79 -0
  13. package/apps/pwa/out/_next/static/chunks/1501-1aad662d82ba43f7.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1584.9e2dd53da6e6d4b1.js +24 -0
  15. package/apps/pwa/out/_next/static/chunks/1586.b989b0614afb77e6.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1609.2ad355062870151f.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1612-f6df0fc0eed7157b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1923.37f1d5fe7ebe3860.js +27 -0
  19. package/apps/pwa/out/_next/static/chunks/1948.5f35b39eef222a94.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/1993.3fd074a2a246234e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2140.d2daf9ee96e295d1.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2212.0359851457473efc.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/224.583e562365a52f74.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2328.0f37465646bafa7d.js +36 -0
  25. package/apps/pwa/out/_next/static/chunks/2372.bf71372184991937.js +286 -0
  26. package/apps/pwa/out/_next/static/chunks/{2489-ff55a35b202c0f94.js → 2489-d238d3e2eb5b2bf5.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/2549-a8515c86f9e8cb96.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2655.72cc6a97276c5acc.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2743-ef6e8c3ced74cce5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/2840-5458ad50c49cac6a.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2856.f75d5baef7f49b74.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3344.0de4b0ec5a454b7a.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/335.a13f339e97893437.js +94 -0
  34. package/apps/pwa/out/_next/static/chunks/3394.646348f34b2500be.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3443.6276909b53d72d53.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3495.abd4254d808bf2dc.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/3522-1de1319d028ac89c.js +25 -0
  38. package/apps/pwa/out/_next/static/chunks/3721.ba5b82db0d9a5572.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3857-5985236d9307f676.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3919.9ff7dc25afefee33.js +74 -0
  41. package/apps/pwa/out/_next/static/chunks/3998.bd9a69d9b50e9bd2.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/4003.ab6db1dc266576c9.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4216.e8ac94b9fe6b96ab.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4327.c44dc2b55cb43ee5.js +34 -0
  45. package/apps/pwa/out/_next/static/chunks/4401.94257d497b6d26bc.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/4559.17ca2cdbaa8d414f.js +115 -0
  47. package/apps/pwa/out/_next/static/chunks/4588.ad3364cb7fb97ada.js +86 -0
  48. package/apps/pwa/out/_next/static/chunks/4706.97e734bd4c9816ca.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4982.6e44352f751b9a1b.js +77 -0
  50. package/apps/pwa/out/_next/static/chunks/5342.49c5c5a530e62b50.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5360-08a80abdf3a4da99.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/5431.66c58ab1327ee0b7.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5633-d6852385000738ef.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/5717.bcfcb96e701b82e0.js +90 -0
  55. package/apps/pwa/out/_next/static/chunks/5755-30d102586789bf7f.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/5957.3f1a4012388dd962.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5977-5b9e8a45a5b0c823.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/602.9bf02ac8e9502598.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/6020.3a1b347e2d2dfacc.js +8 -0
  60. package/apps/pwa/out/_next/static/chunks/{6158.af2e93b7adff709b.js → 6158.566c890adc06a459.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/6375.fce1d174ba6d0324.js +64 -0
  62. package/apps/pwa/out/_next/static/chunks/6546.92a828aef441271f.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/658.957fdb0a8c6c6c8e.js +287 -0
  64. package/apps/pwa/out/_next/static/chunks/6706.323590aabb8b1293.js +226 -0
  65. package/apps/pwa/out/_next/static/chunks/6747.b67947f293a4c02d.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/6767.8e6c5f6e744fcd9f.js +38 -0
  67. package/apps/pwa/out/_next/static/chunks/6868.000b24a3045c5574.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6946.bba74e9c96050f30.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/7033.a558f1b2f6912a53.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/7065.46029c26c53eac50.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/7087.6fae577d09249bc1.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/7175-350f4ce2873bc038.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/722.de8d9801e0f6d414.js +41 -0
  74. package/apps/pwa/out/_next/static/chunks/7497.57981e9d7a75a52a.js +24 -0
  75. package/apps/pwa/out/_next/static/chunks/7630.2b9a68659053ff4e.js +193 -0
  76. package/apps/pwa/out/_next/static/chunks/7672.4af122ad892117c7.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/771.6ab889a233e7dae7.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/7899.597af4a9996c907f.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/79b78388.663188133449d707.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/8096.f7da0776cfde6300.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/8194.484f51e273d9abd2.js +217 -0
  82. package/apps/pwa/out/_next/static/chunks/8769.815ca69879f608b4.js +104 -0
  83. package/apps/pwa/out/_next/static/chunks/8857-aa1cc4ac90af4d40.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/88729267.9c6c000157c83f2f.js +62 -0
  85. package/apps/pwa/out/_next/static/chunks/8f835b4f.048eeb02299e3e12.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/9068.5f2764fccf86c1b2.js +93 -0
  87. package/apps/pwa/out/_next/static/chunks/9247-55bcfbb0e8057f59.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/9451.ebfb0dcc9c02250f.js +23 -0
  89. package/apps/pwa/out/_next/static/chunks/9559.e648369b1d57cf9a.js +62 -0
  90. package/apps/pwa/out/_next/static/chunks/9832.2ab2e818013574c7.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/a9f2b54d-40c16bd0efdb04b8.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-c855c24de7db74fe.js → page-88459e0903da2803.js} +1 -1
  93. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-85b316bb4235980b.js → page-d85c43f46f58716b.js} +1 -1
  94. package/apps/pwa/out/_next/static/chunks/app/board/{page-9b2b26a33cb2cd42.js → page-4e645600579ae952.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c9955fde67b1bcb5.js → page-87edbfd19117823b.js} +1 -1
  96. package/apps/pwa/out/_next/static/chunks/app/bots/page-7f0c2b7801ac6a54.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/ceo/page-bf1f030a9d1adffd.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/app/chat/{page-4ba4abe096b6c2a8.js → page-cbaf66b1e1804adf.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/app/consult/{page-575d4a8b4f7becb2.js → page-80ed511e02c81412.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/app/control/{page-f5d56ff5f45ad164.js → page-9f5610caaa06178c.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/app/decisions/{page-2260e9eef0357421.js → page-ec8f3575e339de74.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/app/design-check/page-86d753e460f1a989.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/app/editor/page-4166a7d51cf0a57d.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/app/exec/{page-466bf0ee16ad1432.js → page-2a19c5cd3b562580.js} +1 -1
  105. package/apps/pwa/out/_next/static/chunks/app/field/{page-a23bc71713bcc08f.js → page-b6a09cde2e07521a.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/app/inside/page-f7eae1518ca0c353.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/app/intake/page-456a8573ad811381.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/app/layout-8462d6a543cbb2d9.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/app/live/page-2974cdfb87c0f9c9.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/app/live-run/page-f927ffa25f3c86d1.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/app/loops/{page-35bfec2e631d3eae.js → page-abd86cd832aaa011.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/app/market/page-79daf68f6256adaa.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-f9ccb1b1adbea14d.js → page-8c3f168fc3f91939.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/app/morning/{page-ec102c9b8b691e37.js → page-786fb4504d0f2fa9.js} +1 -1
  115. package/apps/pwa/out/_next/static/chunks/app/not-found-75209f14597dcb8f.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/app/observatory/page-bf30affe3a85af7e.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1187a44b7515434b.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-3d0910185a66ed54.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f401ee1dd5a26e25.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/app/outputs/page-5dfe7604fd842784.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/app/page-76cf838050a8d681.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-db03ae3589a81c9e.js → page-7ec8e289b4c14df6.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-4a6463b97f0ad94f.js → page-8c8ac17d52be2a79.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-75bffecc9aa1004e.js → page-d359ab048d63a9d9.js} +1 -1
  125. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-b8b345c880bf78d1.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/settings/{page-31d5b875760bb62d.js → page-f151cc10ad026940.js} +1 -1
  127. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-9875f103057da433.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/setup/page-9789328af9a9aa62.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/share/{page-46d5201d2207a511.js → page-bf495a1e284f4e00.js} +1 -1
  130. package/apps/pwa/out/_next/static/chunks/app/showroom/page-a263d94bca4be22a.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/tasks/page-3962eab8ec58d16e.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/term/{page-cf0a4b4abead71ea.js → page-9abcfac07e02a542.js} +1 -1
  133. package/apps/pwa/out/_next/static/chunks/app/today/page-bee49f927c333abb.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/trace/page-bac2d56dcb02d955.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/vault/page-937cbcc13176295c.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d09495594f7f480d.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0acc8e198f7b0dfb.js → page-69cb93d1a2e57e0e.js} +1 -1
  138. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-34882c84f84021c5.js → page-72ec543928d67c3e.js} +1 -1
  139. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-82906eeda0186a7b.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/d70879db.c66c56a95a91efc6.js +66 -0
  141. package/apps/pwa/out/_next/static/chunks/f8626309.a4e5d362b1d55e59.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/webpack-f2b4016b9b981bc8.js +1 -0
  143. package/apps/pwa/out/_next/static/css/{d3427996da11e480.css → b9d8151e48998417.css} +1 -1
  144. package/apps/pwa/out/_next/static/css/f2bd5257e6af88d8.css +1 -0
  145. package/apps/pwa/out/approvals/index.html +1 -1
  146. package/apps/pwa/out/approvals/index.txt +14 -14
  147. package/apps/pwa/out/autopilot/index.html +1 -1
  148. package/apps/pwa/out/autopilot/index.txt +14 -14
  149. package/apps/pwa/out/board/index.html +1 -1
  150. package/apps/pwa/out/board/index.txt +14 -14
  151. package/apps/pwa/out/botlab/index.html +1 -1
  152. package/apps/pwa/out/botlab/index.txt +14 -14
  153. package/apps/pwa/out/bots/index.html +1 -1
  154. package/apps/pwa/out/bots/index.txt +14 -14
  155. package/apps/pwa/out/ceo/index.html +1 -1
  156. package/apps/pwa/out/ceo/index.txt +14 -14
  157. package/apps/pwa/out/chat/index.html +1 -1
  158. package/apps/pwa/out/chat/index.txt +14 -14
  159. package/apps/pwa/out/consult/index.html +1 -1
  160. package/apps/pwa/out/consult/index.txt +14 -14
  161. package/apps/pwa/out/control/index.html +1 -1
  162. package/apps/pwa/out/control/index.txt +14 -14
  163. package/apps/pwa/out/decisions/index.html +1 -1
  164. package/apps/pwa/out/decisions/index.txt +14 -14
  165. package/apps/pwa/out/design-check/index.html +1 -1
  166. package/apps/pwa/out/design-check/index.txt +14 -14
  167. package/apps/pwa/out/editor/index.html +1 -1
  168. package/apps/pwa/out/editor/index.txt +15 -15
  169. package/apps/pwa/out/exec/index.html +1 -1
  170. package/apps/pwa/out/exec/index.txt +14 -14
  171. package/apps/pwa/out/field/index.html +1 -1
  172. package/apps/pwa/out/field/index.txt +14 -14
  173. package/apps/pwa/out/healthz +1 -1
  174. package/apps/pwa/out/index.html +1 -1
  175. package/apps/pwa/out/index.txt +14 -14
  176. package/apps/pwa/out/inside/index.html +1 -1
  177. package/apps/pwa/out/inside/index.txt +15 -15
  178. package/apps/pwa/out/intake/index.html +1 -1
  179. package/apps/pwa/out/intake/index.txt +14 -14
  180. package/apps/pwa/out/live/index.html +1 -1
  181. package/apps/pwa/out/live/index.txt +14 -14
  182. package/apps/pwa/out/live-run/index.html +1 -0
  183. package/apps/pwa/out/live-run/index.txt +35 -0
  184. package/apps/pwa/out/loops/index.html +1 -1
  185. package/apps/pwa/out/loops/index.txt +14 -14
  186. package/apps/pwa/out/market/index.html +1 -1
  187. package/apps/pwa/out/market/index.txt +14 -14
  188. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  189. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  190. package/apps/pwa/out/morning/index.html +1 -1
  191. package/apps/pwa/out/morning/index.txt +14 -14
  192. package/apps/pwa/out/observatory/index.html +1 -1
  193. package/apps/pwa/out/observatory/index.txt +14 -14
  194. package/apps/pwa/out/ops/checklist/index.html +1 -1
  195. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  196. package/apps/pwa/out/ops/release/index.html +1 -1
  197. package/apps/pwa/out/ops/release/index.txt +14 -14
  198. package/apps/pwa/out/ops/seats/index.html +1 -1
  199. package/apps/pwa/out/ops/seats/index.txt +14 -14
  200. package/apps/pwa/out/outputs/index.html +1 -1
  201. package/apps/pwa/out/outputs/index.txt +14 -14
  202. package/apps/pwa/out/reflection/index.html +1 -1
  203. package/apps/pwa/out/reflection/index.txt +15 -15
  204. package/apps/pwa/out/scheduler/index.html +1 -1
  205. package/apps/pwa/out/scheduler/index.txt +14 -14
  206. package/apps/pwa/out/sessions/index.html +1 -1
  207. package/apps/pwa/out/sessions/index.txt +14 -14
  208. package/apps/pwa/out/settings/devices/index.html +1 -1
  209. package/apps/pwa/out/settings/devices/index.txt +14 -14
  210. package/apps/pwa/out/settings/index.html +1 -1
  211. package/apps/pwa/out/settings/index.txt +14 -14
  212. package/apps/pwa/out/setup/done/index.html +1 -1
  213. package/apps/pwa/out/setup/done/index.txt +14 -14
  214. package/apps/pwa/out/setup/index.html +1 -1
  215. package/apps/pwa/out/setup/index.txt +14 -14
  216. package/apps/pwa/out/share/index.html +1 -1
  217. package/apps/pwa/out/share/index.txt +14 -14
  218. package/apps/pwa/out/showroom/index.html +1 -1
  219. package/apps/pwa/out/showroom/index.txt +14 -14
  220. package/apps/pwa/out/tasks/index.html +1 -1
  221. package/apps/pwa/out/tasks/index.txt +14 -14
  222. package/apps/pwa/out/term/index.html +1 -1
  223. package/apps/pwa/out/term/index.txt +14 -14
  224. package/apps/pwa/out/today/index.html +1 -1
  225. package/apps/pwa/out/today/index.txt +14 -14
  226. package/apps/pwa/out/trace/index.html +1 -1
  227. package/apps/pwa/out/trace/index.txt +14 -14
  228. package/apps/pwa/out/vault/index.html +1 -1
  229. package/apps/pwa/out/vault/index.txt +14 -14
  230. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  231. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  232. package/apps/pwa/out/workflows/index.html +1 -1
  233. package/apps/pwa/out/workflows/index.txt +14 -14
  234. package/apps/pwa/out/workspace/index.html +1 -1
  235. package/apps/pwa/out/workspace/index.txt +14 -14
  236. package/apps/pwa/out/worktrees/index.html +1 -1
  237. package/apps/pwa/out/worktrees/index.txt +14 -14
  238. package/graphs/catalog/catalog.yaml +1 -1
  239. package/graphs/drafts/recipes.yaml +6 -6
  240. package/graphs/implement-loop.yaml +277 -5
  241. package/graphs/test-diet/recipes.yaml +3 -0
  242. package/graphs/test-diet/test-diet.yaml +3 -1
  243. package/package.json +1 -1
  244. package/scripts/coord-channel-watch.sh +31 -4
  245. package/scripts/docs-lint.ts +4 -1
  246. package/scripts/f-session-monitor.ts +69 -0
  247. package/scripts/hq/standby-snapshot.ts +7 -3
  248. package/scripts/knowledge-compress.ts +11 -0
  249. package/scripts/marketing/generate-ax-brief.ts +18 -1
  250. package/scripts/measure-nl-routing-corpus.ts +11 -3
  251. package/scripts/nightly-docops.ts +90 -3
  252. package/scripts/release-loop/gate-node.ts +5 -5
  253. package/scripts/release-loop/shard-plan.ts +50 -5
  254. package/scripts/research-coo-admin-dates.ts +39 -0
  255. package/scripts/rhythm/daily.ts +2 -1
  256. package/scripts/skills/public-check.ts +7 -2
  257. package/scripts/smart-merge/resync-shadow.ts +5 -3
  258. package/scripts/test-diet/lib.ts +81 -1
  259. package/scripts/test-diet/node.ts +48 -2
  260. package/scripts/verify-coo-admin-evidence.ts +83 -0
  261. package/src/acp/client.ts +4 -2
  262. package/src/agent/run-context.ts +4 -0
  263. package/src/agent-env/profile.ts +25 -5
  264. package/src/agent-mission/driver.ts +26 -12
  265. package/src/agent-mission/supervisor-metrics.ts +17 -2
  266. package/src/agent-substrate/acp-reviewer.ts +12 -5
  267. package/src/agent-substrate/pr-reviewer.ts +11 -4
  268. package/src/agent-substrate/review-intent.ts +102 -3
  269. package/src/autopilot/build/llm-conflict-merge.ts +22 -8
  270. package/src/autopilot/pr-manager.ts +18 -8
  271. package/src/away/release-watch.ts +47 -8
  272. package/src/briefing/brief-items.ts +104 -11
  273. package/src/browser-cdp/client.ts +10 -5
  274. package/src/chat/index.ts +1 -0
  275. package/src/cli/brief-cli.ts +20 -2
  276. package/src/cli/config-classify.ts +83 -0
  277. package/src/cli/control-cli.ts +14 -0
  278. package/src/cli/decisions-cli.ts +26 -16
  279. package/src/cli/logs-model-input.ts +1 -1
  280. package/src/cli/ops-cli.ts +11 -0
  281. package/src/cli/ops-overview-cli.ts +50 -0
  282. package/src/cli/pod-cli.ts +1 -0
  283. package/src/cli/pr-cli.ts +65 -8
  284. package/src/cli/release-cli.ts +21 -4
  285. package/src/cli/tasks-cli.ts +214 -1
  286. package/src/connectors/types.ts +6 -6
  287. package/src/context-bus/context-now-surfaces.ts +18 -0
  288. package/src/context-bus/coord-events.ts +32 -4
  289. package/src/context-bus/long-term-memory.ts +23 -6
  290. package/src/context-bus/nightly-condense.ts +169 -21
  291. package/src/control/control-status.ts +87 -0
  292. package/src/control-plane/light-member.ts +5 -3
  293. package/src/control-plane/member.ts +172 -7
  294. package/src/core-turn/run-goal-loop.ts +22 -1
  295. package/src/dashboard/index.ts +40 -4
  296. package/src/dashboard/slash-runtime/dashboard-handlers.ts +9 -0
  297. package/src/decisions/decision-cards.ts +20 -1
  298. package/src/decisions/decision-ledger.ts +11 -8
  299. package/src/decisions/web-push-decision-cards.ts +19 -3
  300. package/src/discord-self-message.ts +2 -2
  301. package/src/domains/coo-admin-dates.evidence.json +876 -0
  302. package/src/domains/coo-admin-tool.ts +48 -14
  303. package/src/domains/ops-status.ts +53 -0
  304. package/src/domains/outbound-alert.ts +24 -1
  305. package/src/esc-abort-gate.ts +3 -1
  306. package/src/exec-requests/planner.ts +6 -0
  307. package/src/graph-kinds/registry.ts +6 -6
  308. package/src/graph-runner/graph-cli.ts +91 -2
  309. package/src/graph-runner/graph-run-api.ts +20 -0
  310. package/src/graph-runner/graph-tick.ts +73 -12
  311. package/src/graph-runner/runner.ts +43 -11
  312. package/src/graph-unify/wf-to-graph.ts +79 -0
  313. package/src/graph-wizard/archetypes.ts +88 -0
  314. package/src/graph-wizard/generate.ts +577 -0
  315. package/src/graph-wizard/saved-steps.ts +120 -0
  316. package/src/graph-wizard/steps.ts +206 -0
  317. package/src/harness/harness-cli-command.ts +186 -32
  318. package/src/harness/harness-queue.ts +58 -14
  319. package/src/harness/launch-advice.ts +23 -0
  320. package/src/index.ts +43 -11
  321. package/src/intake-plane/digest.ts +18 -11
  322. package/src/knowledge/conclusion-compress.ts +93 -0
  323. package/src/knowledge/know-find.ts +9 -2
  324. package/src/knowledge/query.ts +20 -0
  325. package/src/llm.ts +26 -16
  326. package/src/loops/neighbor-runtime.ts +88 -0
  327. package/src/loops/orchestrator/author-depth.ts +29 -11
  328. package/src/loops/orchestrator/overlap.ts +7 -1
  329. package/src/loops/orchestrator/resync-shadow.ts +1 -1
  330. package/src/loops/orchestrator/tick.ts +35 -2
  331. package/src/loops/orchestrator/traffic.ts +2 -0
  332. package/src/loops/registry.ts +5 -0
  333. package/src/market/publish.ts +17 -1
  334. package/src/maturity/feature-maturity.ts +4 -2
  335. package/src/mss/logging/redaction.ts +17 -0
  336. package/src/nexus/api/draft-metrics.ts +128 -0
  337. package/src/nexus/api/graph-peer-edit.ts +77 -0
  338. package/src/nexus/api/graph-wizard-api.ts +61 -0
  339. package/src/nexus/api/graphs-api.ts +193 -17
  340. package/src/nexus/api/harness-traversal.ts +205 -0
  341. package/src/nexus/api/http-server.ts +122 -6
  342. package/src/nexus/api/plugins-market.ts +24 -0
  343. package/src/nexus/index.ts +3 -2
  344. package/src/nexus/outbound/router.ts +28 -8
  345. package/src/oauth/claude-pty-maturity.ts +24 -0
  346. package/src/oauth/fallback-chain.ts +27 -14
  347. package/src/onboarding/first-run.ts +3 -0
  348. package/src/ops/overview-collect.ts +345 -0
  349. package/src/ops/overview-seat-era.ts +90 -0
  350. package/src/ops/overview.ts +223 -0
  351. package/src/plugins/maker/market-bundle.ts +9 -0
  352. package/src/plugins/maker/plugin-maker.ts +9 -3
  353. package/src/plugins/maker/wizard-generate.ts +73 -9
  354. package/src/policy/llm-policy.ts +11 -6
  355. package/src/release-loop/checklist-board.ts +27 -0
  356. package/src/release-loop/checklist.ts +38 -0
  357. package/src/release-loop/gate-shards.ts +7 -2
  358. package/src/seat-address/seat-address.ts +1 -0
  359. package/src/seat-loop/seat-loop.ts +174 -20
  360. package/src/seat-loop/seat-report.ts +29 -1
  361. package/src/self-dev/ask-launch-flow.ts +4 -2
  362. package/src/self-dev/ask-launch-io.ts +3 -1
  363. package/src/self-dev/dev-cli.ts +90 -8
  364. package/src/self-dev/dev-pipeline.ts +20 -1
  365. package/src/self-dev/draft-sweep.ts +45 -8
  366. package/src/self-dev/graph-journey-nodes.ts +157 -0
  367. package/src/self-dev/graph-journey-route.ts +105 -0
  368. package/src/self-dev/launch-preflight.ts +43 -3
  369. package/src/self-dev/orchestrate.ts +3 -0
  370. package/src/self-dev/release-path-guard.ts +7 -4
  371. package/src/self-dev/review-provider-fallback.ts +83 -0
  372. package/src/self-dev/run-supervisor.ts +5 -1
  373. package/src/self-implement/frozen-merges.ts +23 -0
  374. package/src/self-implement/goal-author.ts +14 -0
  375. package/src/self-implement/harness-policy-gates.ts +7 -5
  376. package/src/self-implement/heal-intake.ts +3 -0
  377. package/src/self-implement/host-regate.ts +28 -14
  378. package/src/self-implement/l8-merge-queue.ts +80 -8
  379. package/src/self-implement/orchestrator.ts +84 -28
  380. package/src/self-implement/seams.ts +8 -3
  381. package/src/self-implement/self-implement-cli.ts +4 -0
  382. package/src/self-implement/self-implement-runtime.ts +12 -0
  383. package/src/session-runtime/index.ts +5 -3
  384. package/src/shell-env-bootstrap.ts +35 -6
  385. package/src/steward/triage.ts +25 -8
  386. package/src/storage/file-lock.ts +33 -4
  387. package/src/task-agent/card-evidence.ts +6 -0
  388. package/src/task-agent/completion-evidence.ts +31 -3
  389. package/src/task-agent/cover.ts +61 -0
  390. package/src/task-agent/live-moves.ts +126 -3
  391. package/src/task-agent/parent-host.ts +315 -0
  392. package/src/task-agent/shadow.ts +19 -4
  393. package/src/task-agent/task-hand.ts +11 -2
  394. package/src/task-orchestrator/surfaces/pod-command-job.ts +41 -6
  395. package/src/task-orchestrator/surfaces/pod-gate-deps.ts +123 -0
  396. package/src/task-orchestrator/surfaces/pod-pool.ts +138 -15
  397. package/src/task-orchestrator/surfaces/self-implement-pod.ts +39 -6
  398. package/src/task-orchestrator/surfaces/self-implement.ts +7 -0
  399. package/src/telegram-commands.ts +8 -0
  400. package/src/telegram-project-command.ts +6 -0
  401. package/src/telegram.ts +2 -2
  402. package/src/tool-runtime/browser-runtime.ts +87 -3
  403. package/src/tool-runtime/index.ts +1 -0
  404. package/src/typecheck-ratchet.ts +2 -1
  405. package/src/user-config.ts +55 -8
  406. package/src/version/packed-revision.json +1 -1
  407. package/src/workflow-runtime/executor.ts +24 -2
  408. package/src/workflow-runtime/triggers/registry.ts +27 -0
  409. package/src/workflow-runtime/triggers/schedule-source.ts +30 -3
  410. package/apps/pwa/out/_next/static/Bam1YziK2iH9bN4K0utGk/_buildManifest.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/1307-77fad80cf54477c6.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
  416. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +0 -25
  425. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/bots/page-7a852956c74015a3.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/design-check/page-7c058069c5e7fa63.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/intake/page-9cd565005b93aca6.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/live/page-474cb5888ea0e8f3.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/market/page-9ca21f9ad3208a61.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/not-found-a9c2cfa8dda922a5.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +0 -1
  445. package/apps/pwa/out/_next/static/chunks/app/setup/page-68793c6e10331d66.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/app/showroom/page-d81c07a7b4f618ae.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/app/tasks/page-be1c4e8697f22360.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +0 -1
  449. package/apps/pwa/out/_next/static/chunks/app/trace/page-0fd895fab9b62c1b.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/webpack-c260571513bf43c2.js +0 -1
  454. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +0 -1
  455. /package/apps/pwa/out/_next/static/{Bam1YziK2iH9bN4K0utGk → aBi6EyMtLsrl7J8eH4kF7}/_ssgManifest.js +0 -0
  456. /package/skills/project-onboarding/{skill.md → SKILL.md} +0 -0
@@ -1,14 +1,58 @@
1
- import { readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync } from 'node:fs';
1
+ import { readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync, unlinkSync } from 'node:fs';
2
+ import { gzipSync, gunzipSync } from 'node:zlib';
2
3
  import { dirname, join, resolve } from 'node:path';
3
4
  import { streamLLM } from '../llm.js';
4
- import { condenseContextDay, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
5
- import { listCoordEvents, type CoordEvent } from './coord-events.js';
5
+ import { condenseContextDay, guardianRetireAged, readCondenseEvents, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
6
+ import type { CoordEvent } from './coord-events.js';
6
7
  import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
7
8
  import { acquireLockAsync } from '../storage/file-lock.js';
8
9
 
9
10
  type Stage = 'collect' | 'condense' | 'record' | 'conflict-candidates';
10
11
  type Input = { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; summaries?: Record<string, MemorySummary> };
11
- type Context = { graphId?: string; nodeId?: string; input: Input | null; outputs: Record<string, { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; items?: MemoryItem[] } | null> };
12
+ type Context = { graphId?: string; nodeId?: string; input: Input | null; outputs: Record<string, { day?: string; events?: CoordEvent[]; decisions?: DecisionEntry[]; items?: MemoryItem[]; memoryItems?: MemoryItem[] } | null> };
13
+ type NightlyTask = (source: MemorySource, assignee: 'nightly-task-agent', recipe: 'sleep-review') => Promise<MemorySummary>;
14
+ type GuardianTask = (items: readonly MemoryItem[], assignee: 'guardian', now: Date) => MemoryItem[] | Promise<MemoryItem[]>;
15
+ type NightlyAssignment = { owner: 'coordinator'; assignee: 'nightly-task-agent'; contract: 'librarian'; recipe: 'sleep-review'; source: MemorySource; summary?: MemorySummary };
16
+ type GuardianAssignment = { owner: 'guardian'; assignee: 'guardian'; recipe: 'retire-aged'; items: readonly MemoryItem[]; now: string }
17
+ | { owner: 'guardian'; assignee: 'guardian'; recipe: 'store-snapshot'; root: string; project: string; seat: string; value: MemoryItem[] }
18
+ | { owner: 'guardian'; assignee: 'guardian'; recipe: 'store-candidates'; root: string; value: unknown[] };
19
+ type Assignment = NightlyAssignment | GuardianAssignment;
20
+
21
+ /** Execute an assigned recipe in a separate, short-lived task worker, never in the coordinator's stage process. */
22
+ async function dispatchAssignment<T>(assignment: Assignment): Promise<{ result: T; workerPid: number }> {
23
+ const worker = Bun.spawn([process.execPath, import.meta.path, '--assigned-task'], {
24
+ stdin: 'pipe', stdout: 'pipe', stderr: 'pipe',
25
+ });
26
+ worker.stdin.write(JSON.stringify(assignment));
27
+ worker.stdin.end();
28
+ const [stdout, stderr, exit] = await Promise.all([
29
+ new Response(worker.stdout).text(), new Response(worker.stderr).text(), worker.exited,
30
+ ]);
31
+ if (exit !== 0) throw new Error(`nightly ${assignment.recipe} task failed: ${stderr.trim()}`);
32
+ return { result: JSON.parse(stdout) as T, workerPid: worker.pid };
33
+ }
34
+
35
+ async function executeAssignment(assignment: Assignment): Promise<MemorySummary | MemoryItem[] | { stored: true }> {
36
+ if (assignment.owner === 'coordinator' && assignment.assignee === 'nightly-task-agent'
37
+ && assignment.contract === 'librarian' && assignment.recipe === 'sleep-review') {
38
+ return summarize(assignment.source, assignment.summary ? { [assignment.source.source]: assignment.summary } : undefined, assignment.recipe);
39
+ }
40
+ if (assignment.owner === 'guardian' && assignment.assignee === 'guardian') {
41
+ if (assignment.recipe === 'retire-aged') return guardianRetireAged(assignment.items, new Date(assignment.now));
42
+ if (assignment.recipe === 'store-snapshot') {
43
+ if (!assignment.value.every(item => item.project === assignment.project && item.seat === assignment.seat)) {
44
+ throw new Error('guardian memory group mismatch');
45
+ }
46
+ guardianSave(snapshotPath(assignment.root, assignment.project, assignment.seat), assignment.value);
47
+ return { stored: true };
48
+ }
49
+ if (assignment.recipe === 'store-candidates') {
50
+ guardianSave(join(assignment.root, 'context-memory', 'conflict-candidates.json'), assignment.value);
51
+ return { stored: true };
52
+ }
53
+ }
54
+ throw new Error('invalid nightly assignment');
55
+ }
12
56
 
13
57
  const dayBefore = (date: Date) => new Date(date.getTime() - 86_400_000).toISOString().slice(0, 10);
14
58
  const fileName = (value: string) => encodeURIComponent(value).replace(/\./g, '%2E');
@@ -25,6 +69,39 @@ function save(file: string, value: unknown): void {
25
69
  renameSync(temp, file);
26
70
  }
27
71
 
72
+ const GUARDIAN_SIZE_BUDGET_BYTES = 16 * 1024 * 1024;
73
+
74
+ /** Guardian-owned snapshot hygiene. The JSON ledger stays byte-for-byte in its existing format. */
75
+ function guardianSave(file: string, value: unknown): void {
76
+ const text = JSON.stringify(value, null, 2) + '\n';
77
+ const previous = existsSync(file) ? readFileSync(file) : undefined;
78
+ const backup = `${file}.gz`;
79
+ if (previous) {
80
+ // Write the new backup beside the old one and verify it on disk before it replaces the old backup,
81
+ // so a failed or corrupt backup write never destroys the previous backup.
82
+ const backupTemp = `${backup}.${process.pid}.tmp`;
83
+ try {
84
+ writeFileSync(backupTemp, gzipSync(previous));
85
+ if (!gunzipSync(readFileSync(backupTemp)).equals(previous)) throw new Error('guardian backup integrity check failed');
86
+ renameSync(backupTemp, backup);
87
+ } catch (error) {
88
+ if (existsSync(backupTemp)) unlinkSync(backupTemp);
89
+ throw error;
90
+ }
91
+ }
92
+ try {
93
+ if (Buffer.byteLength(text) > GUARDIAN_SIZE_BUDGET_BYTES) throw new Error('guardian memory size budget exceeded');
94
+ save(file, value);
95
+ if (readFileSync(file, 'utf8') !== text) throw new Error('guardian memory integrity check failed');
96
+ } catch (error) {
97
+ if (previous) writeFileSync(file, gunzipSync(readFileSync(backup)));
98
+ else if (existsSync(file)) unlinkSync(file);
99
+ const temp = `${file}.${process.pid}.tmp`;
100
+ if (existsSync(temp)) unlinkSync(temp);
101
+ throw error;
102
+ }
103
+ }
104
+
28
105
  async function withCurrentDay<T>(root: string, day: string, write: () => T): Promise<T> {
29
106
  const dir = join(root, 'context-memory');
30
107
  mkdirSync(dir, { recursive: true });
@@ -39,7 +116,7 @@ async function withCurrentDay<T>(root: string, day: string, write: () => T): Pro
39
116
  if (!lock.stillHeld()) throw new Error('nightly memory write lock lost');
40
117
  // Reserve the day before touching either output, so a failed writer cannot let an older run overwrite it.
41
118
  if (!latest || latest < day) save(marker, day);
42
- return write();
119
+ return await write();
43
120
  } finally { lock.release(); }
44
121
  }
45
122
 
@@ -64,20 +141,38 @@ function storedItems(root: string): MemoryItem[] {
64
141
  return items;
65
142
  }
66
143
 
67
- export async function summarize(source: MemorySource, injected?: Record<string, MemorySummary>): Promise<MemorySummary> {
144
+ /** A sleep-review rejection: counted as `not-promoted` by the condenser, never as a malformed summary. */
145
+ function notPromoted(value: Record<string, unknown>): MemorySummary {
146
+ const text = (key: string) => typeof value[key] === 'string' ? value[key] as string : '';
147
+ return { promote: false, project: text('project'), topic: text('topic'), summary: text('summary') };
148
+ }
149
+
150
+ export function summaryInstruction(recipe: 'standard' | 'sleep-review' = 'standard'): string {
151
+ const system = 'Summarize this one-line context event as a compact project/seat memory item. Return ONLY JSON: {"project":"...","topic":"...","summary":"...","claim":{"key":"...","value":"..."}}. Omit claim when the event makes no comparable factual assertion. Never invent a project, topic or claim.';
152
+ if (recipe === 'standard') return system;
153
+ // SLEEP1 builds on this librarian recipe; the guardian owns storage hygiene, not promotion judgments.
154
+ return `${system.replace('"project":"..."', '"promote":false,"project":"..."')} You are the nightly task agent (librarian). Sleep review (librarian contract): apply the coordinator promotion policy. Return a boolean "promote" for every source. Set promote=true only for sourced, durable project/seat facts or decisions; set promote=false for chatter or speculative claims. A correction with a later timestamp wins; return a comparable claim only for an explicit factual assertion so opposing claims can become OP conflict candidates. Do not perform guardian hygiene (retention, compression, size, backup, integrity or rollback).`;
155
+ }
156
+
157
+ export async function summarize(source: MemorySource, injected?: Record<string, MemorySummary>, recipe: 'standard' | 'sleep-review' = 'standard'): Promise<MemorySummary> {
68
158
  if (injected) {
69
159
  const result = injected[source.source];
70
160
  if (!result) throw new Error(`missing test summary: ${source.source}`);
161
+ if (recipe === 'sleep-review' && typeof result.promote !== 'boolean') throw new Error('invalid memory summary');
162
+ if (recipe === 'sleep-review' && result.promote === false) return notPromoted(result as unknown as Record<string, unknown>);
71
163
  return result;
72
164
  }
73
165
  const response = await streamLLM([
74
- { role: 'system', content: 'Summarize this one-line context event as a compact project/seat memory item. Return ONLY JSON: {"project":"...","topic":"...","summary":"...","claim":{"key":"...","value":"..."}}. Omit claim when the event makes no comparable factual assertion. Never invent a project, topic or claim.' },
166
+ { role: 'system', content: summaryInstruction(recipe) },
75
167
  { role: 'user', content: JSON.stringify(source) },
76
168
  ], () => {}, { maxTokens: 350, usageRole: 'research' });
77
169
  const parsed: unknown = JSON.parse(response.replace(/^```(?:json)?\s*|\s*```$/g, '').trim());
78
170
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid memory summary');
79
171
  const value = parsed as Record<string, unknown>;
172
+ // A librarian rejection needs no project/topic/summary — chatter often has none to give.
173
+ if (recipe === 'sleep-review' && value.promote === false) return notPromoted(value);
80
174
  if (typeof value.project !== 'string' || typeof value.topic !== 'string' || typeof value.summary !== 'string' ||
175
+ (recipe === 'sleep-review' && typeof value.promote !== 'boolean') ||
81
176
  (value.claim !== undefined && (!value.claim || typeof value.claim !== 'object' || Array.isArray(value.claim) ||
82
177
  typeof (value.claim as Record<string, unknown>).key !== 'string' || typeof (value.claim as Record<string, unknown>).value !== 'string'))) {
83
178
  throw new Error('invalid memory summary');
@@ -85,8 +180,14 @@ export async function summarize(source: MemorySource, injected?: Record<string,
85
180
  return value as unknown as MemorySummary;
86
181
  }
87
182
 
183
+ const defaultGuardianTask: GuardianTask = (items, assignee, now) => {
184
+ if (assignee !== 'guardian') throw new Error('invalid guardian assignee');
185
+ return guardianRetireAged(items, now);
186
+ };
187
+
88
188
  /** Stage outputs travel only through the graph runner's per-run contexts. No decision ledger write occurs here. */
89
- export async function runNightlyStage(stage: Stage, context: Context, root: string, dryRun: boolean, now = new Date()): Promise<Record<string, unknown>> {
189
+ export async function runNightlyStage(stage: Stage, context: Context, root: string, dryRun: boolean, now = new Date(),
190
+ nightlyTask?: NightlyTask, guardianTask: GuardianTask = defaultGuardianTask): Promise<Record<string, unknown>> {
90
191
  const input = context.input ?? {};
91
192
  if (dryRun && stage !== 'collect' && (!input.events || !input.decisions || !input.summaries)) {
92
193
  throw new Error('dry-run requires fake events, decisions and summaries');
@@ -103,51 +204,98 @@ export async function runNightlyStage(stage: Stage, context: Context, root: stri
103
204
  if (dryRun && (!input.events || !input.decisions || !input.summaries)) {
104
205
  throw new Error('dry-run requires fake events, decisions and summaries');
105
206
  }
106
- const events = input.events ?? listCoordEvents({ since: new Date(Date.parse(`${day}T00:00:00Z`) - 86_400_000).toISOString() });
107
- const decisions = input.decisions ?? (dryRun ? [] : new DecisionLedger().list({ status: 'all' }));
207
+ const events = input.events ?? readCondenseEvents(new Date(Date.parse(`${day}T00:00:00Z`) - 86_400_000).toISOString());
208
+ const decisions = input.decisions ?? (dryRun ? [] : new DecisionLedger({ stateDir: root }).list({ status: 'all' })
209
+ .filter(decision => decision.raisedBy.agent !== 'context-condense'));
108
210
  return { outcome: 'ok', day, events, decisions };
109
211
  }
110
212
  const collected = context.outputs.collect;
111
213
  if (!collected?.day || !collected.events || !collected.decisions) throw new Error('collect output missing');
112
214
  if (stage === 'condense') {
215
+ const assignments: Array<{ owner: 'coordinator'; assignee: 'nightly-task-agent'; contract: 'librarian'; recipe: 'sleep-review'; workerPid: number }> = [];
113
216
  const items = await condenseContextDay(collected.day, dryRun ? [] : storedItems(root).filter(item => Date.parse(item.updatedAt) < Date.parse(`${collected.day}T00:00:00Z`)), {
114
217
  events: () => collected.events!, decisions: () => collected.decisions!,
115
- summarize: source => summarize(source, input.summaries),
116
- }, now);
117
- return { outcome: 'ok', items };
218
+ summarize: async source => {
219
+ if (dryRun) return summarize(source, input.summaries, 'sleep-review');
220
+ if (nightlyTask) return nightlyTask(source, 'nightly-task-agent', 'sleep-review');
221
+ const { result, workerPid } = await dispatchAssignment<MemorySummary>({ owner: 'coordinator', assignee: 'nightly-task-agent',
222
+ contract: 'librarian', recipe: 'sleep-review', source, ...(input.summaries ? { summary: input.summaries[source.source] } : {}) });
223
+ assignments.push({ owner: 'coordinator', assignee: 'nightly-task-agent', contract: 'librarian', recipe: 'sleep-review', workerPid });
224
+ return result;
225
+ },
226
+ }, now, { promotion: 'sleep-review', retention: 'guardian' });
227
+ return { outcome: 'ok', items, assignments };
118
228
  }
119
229
  const items = context.outputs.condense?.items;
120
230
  if (!items) throw new Error('condense output missing');
121
231
  if (stage === 'record') {
122
232
  const grouped = new Map<string, MemoryItem[]>();
123
- for (const item of items) {
233
+ // The guardian task owns retirement; the librarian never mutates the stored status.
234
+ const guardianRun = guardianTask === defaultGuardianTask && !dryRun
235
+ ? await dispatchAssignment<MemoryItem[]>({ owner: 'guardian', assignee: 'guardian', recipe: 'retire-aged', items, now: now.toISOString() })
236
+ : { result: await guardianTask(items, 'guardian', now), workerPid: undefined };
237
+ const memoryItems = guardianRun.result;
238
+ for (const item of memoryItems) {
124
239
  const file = snapshotPath(root, item.project, item.seat);
125
240
  grouped.set(file, [...grouped.get(file) ?? [], item]);
126
241
  }
127
- if (!dryRun) await withCurrentDay(root, day, () => {
128
- for (const [file, group] of grouped) save(file, group);
242
+ if (!dryRun) await withCurrentDay(root, day, async () => {
243
+ // The record is all-or-nothing: each guardian write rolls back its own file, and if a later group
244
+ // fails (size budget, integrity, worker error) the files this record already wrote are restored too.
245
+ const written: Array<{ file: string; previous: Buffer | undefined; previousBackup: Buffer | undefined }> = [];
246
+ const snapshot = (path: string) => existsSync(path) ? readFileSync(path) : undefined;
247
+ try {
248
+ for (const [file, group] of grouped) {
249
+ const first = group[0]!;
250
+ written.push({ file, previous: snapshot(file), previousBackup: snapshot(`${file}.gz`) });
251
+ const stored = await dispatchAssignment<{ stored: true }>({ owner: 'guardian', assignee: 'guardian', recipe: 'store-snapshot',
252
+ root, project: first.project, seat: first.seat, value: group });
253
+ if (!stored.result.stored) throw new Error('guardian snapshot not stored');
254
+ }
255
+ } catch (error) {
256
+ // Restore both the snapshot and its backup to what they were before this record started.
257
+ for (const { file, previous, previousBackup } of written.reverse()) {
258
+ for (const [path, before] of [[file, previous], [`${file}.gz`, previousBackup]] as const) {
259
+ if (before) writeFileSync(path, before);
260
+ else if (existsSync(path)) unlinkSync(path);
261
+ }
262
+ }
263
+ throw error;
264
+ }
129
265
  });
130
- return { outcome: 'ok', files: [...grouped.keys()], items: items.length, memoryItems: dryRun ? items : undefined, dryRun };
266
+ return { outcome: 'ok', files: [...grouped.keys()], items: items.length, memoryItems, dryRun,
267
+ guardian: { assignee: 'guardian', recipe: 'retire-aged', retired: memoryItems.filter(item => item.status === 'retired').length,
268
+ workerPid: guardianRun.workerPid } };
131
269
  }
132
270
  if (stage === 'conflict-candidates') {
133
271
  if (!context.outputs.record) throw new Error('record output missing');
134
- const candidates = items.filter(item => item.conflict && item.status === 'active').map(item => ({
272
+ const recorded = context.outputs.record.memoryItems;
273
+ if (!recorded) throw new Error('guardian record output missing');
274
+ const candidates = recorded.filter(item => item.conflict && item.status === 'active').map(item => ({
135
275
  project: item.project, topic: item.topic, owner: item.conflict!.owner,
136
276
  sources: item.conflict!.sources, status: 'candidate' as const,
137
277
  }));
138
- if (!dryRun) await withCurrentDay(root, day, () => save(join(root, 'context-memory', 'conflict-candidates.json'), candidates));
278
+ if (!dryRun) await withCurrentDay(root, day, async () => {
279
+ const stored = await dispatchAssignment<{ stored: true }>({ owner: 'guardian', assignee: 'guardian', recipe: 'store-candidates',
280
+ root, value: candidates });
281
+ if (!stored.result.stored) throw new Error('guardian candidates not stored');
282
+ });
139
283
  return { outcome: 'ok', candidates, published: 0 };
140
284
  }
141
285
  throw new Error(`unknown stage: ${stage}`);
142
286
  }
143
287
 
144
- if (import.meta.main) {
288
+ if (import.meta.main && process.argv[2] === '--assigned-task') {
289
+ const assignment = await new Response(Bun.stdin.stream()).json() as Assignment;
290
+ console.log(JSON.stringify(await executeAssignment(assignment)));
291
+ } else if (import.meta.main) {
145
292
  const stage = process.argv[2] as Stage;
146
293
  const path = process.env.ELANOUS_GRAPH_CONTEXT;
147
294
  if (!path || !['collect', 'condense', 'record', 'conflict-candidates'].includes(stage)) throw new Error('invalid nightly graph stage');
148
295
  const context = JSON.parse(readFileSync(path, 'utf8')) as Context;
149
296
  if (context.graphId !== 'nightly-condense' || context.nodeId !== stage) throw new Error('nightly graph context mismatch');
150
297
  const statePath = resolve(path, '..', '..', '..', '..');
151
- const result = await runNightlyStage(stage, context, statePath, process.env.ELANOUS_GRAPH_DRY_RUN === '1');
298
+ const result = await runNightlyStage(stage, context, statePath, process.env.ELANOUS_GRAPH_DRY_RUN === '1',
299
+ new Date());
152
300
  console.log(JSON.stringify(result));
153
301
  }
@@ -0,0 +1,87 @@
1
+ import { readHqLease } from '../hq/hq.js';
2
+ import { fileLeaseStore } from '../hq/lease.js';
3
+ import { join } from 'node:path';
4
+ import { effectiveInstanceRoot } from '../instance/resolve.js';
5
+ import { getElanousConfigDirOverride } from '../elanous-config-dir.js';
6
+ import { readLandingFreeze } from '../release-loop/landing-freeze.js';
7
+ import { entriesFromRegistry, checkLoops, countLoopStates } from '../loops/checker.js';
8
+ import { computeDrift, discoverUniverses } from '../cli/config-drift.js';
9
+ import { debug } from '../debug/log.js';
10
+
11
+ export type ControlStatusState = 'ok' | 'warn' | 'unmeasured';
12
+ export type ControlStatusRowName = '집' | '판' | '자원' | '자격' | '동결·몫' | '루프' | 'config';
13
+ export interface ControlStatusRow {
14
+ row: ControlStatusRowName;
15
+ state: ControlStatusState;
16
+ text: string;
17
+ reason?: string;
18
+ }
19
+
20
+ export interface ControlStatusDeps {
21
+ readLease?: () => ReturnType<typeof readHqLease> | Promise<ReturnType<typeof readHqLease>>;
22
+ readVersion?: () => ControlStatusRow | Promise<ControlStatusRow>;
23
+ readResources?: () => ControlStatusRow | Promise<ControlStatusRow>;
24
+ readCredentials?: () => ControlStatusRow | Promise<ControlStatusRow>;
25
+ readFreeze?: () => ReturnType<typeof readLandingFreeze> | Promise<ReturnType<typeof readLandingFreeze>>;
26
+ readLoops?: () => ReturnType<typeof entriesFromRegistry> | Promise<ReturnType<typeof entriesFromRegistry>>;
27
+ readDrift?: () => ReturnType<typeof computeDrift> | Promise<ReturnType<typeof computeDrift>>;
28
+ now?: () => Date;
29
+ log?: typeof debug.log;
30
+ }
31
+
32
+ const missing = (row: ControlStatusRowName, reason: string): ControlStatusRow => ({ row, state: 'unmeasured', text: '못 쟀다', reason });
33
+
34
+ /** Read-only snapshot: each source fails independently; no lease renewal, freeze mutation or config sync. */
35
+ export async function collectControlStatus(deps: ControlStatusDeps = {}): Promise<ControlStatusRow[]> {
36
+ const rows: ControlStatusRow[] = [];
37
+ const add = async (row: ControlStatusRowName, read: () => ControlStatusRow | Promise<ControlStatusRow>) => {
38
+ let result: ControlStatusRow;
39
+ try { result = await read(); }
40
+ catch (error) {
41
+ // Source errors may contain file contents or credentials; report the failure class, not the raw message.
42
+ result = missing(row, error instanceof Error ? `읽기 실패(${error.name})` : '읽기 실패');
43
+ }
44
+ rows.push(result);
45
+ try { (deps.log ?? debug.log.bind(debug))('control.status', 'row', { row, state: result.state }); }
46
+ catch { /* logging must not mask another row */ }
47
+ };
48
+
49
+ await add('집', async () => {
50
+ const readLease = deps.readLease ?? (() => readHqLease(getElanousConfigDirOverride() !== undefined
51
+ ? { store: fileLeaseStore(join(effectiveInstanceRoot(), 'hq', 'lease.json')), hostPath: join(effectiveInstanceRoot(), 'hq', 'host') }
52
+ : {}));
53
+ const { record, ageSeconds, expired } = await readLease();
54
+ if (!record) return missing('집', '본부 임대 기록 없음');
55
+ return { row: '집', state: expired ? 'warn' : 'ok',
56
+ text: `본부 ${record.holder} · 세대 ${record.generation} · ${ageSeconds}초 전 · ${expired ? '만료' : '유효'}` };
57
+ });
58
+ await add('판', deps.readVersion ?? (() => missing('판', '기계별 install.json·데몬 판·공개 판 원천 미연결')));
59
+ await add('자원', deps.readResources ?? (() => missing('자원', '기계별 Pod 실행/대기/실패·풀 상한·호스트 부하 원천 미연결')));
60
+ await add('자격', deps.readCredentials ?? (() => missing('자격', '계정별 만료·쿼터·갱신자 원천 미연결')));
61
+ await add('동결·몫', async () => {
62
+ const freeze = await (deps.readFreeze ?? readLandingFreeze)();
63
+ return { row: '동결·몫', state: freeze ? 'warn' : 'unmeasured',
64
+ text: freeze ? `동결 켬 · ${freeze.reason} · 끝 ${freeze.until ?? '미지정'} · 자리별 몫 못 쟀다` : '동결 끔 · 자리별 몫 못 쟀다',
65
+ reason: '자리별 몫/도는 수 원천 미연결' };
66
+ });
67
+ await add('루프', async () => {
68
+ const { entries, scope } = await (deps.readLoops ?? entriesFromRegistry)();
69
+ if (entries.length === 0) return missing('루프', `${scope} 루프 목록에 측정 대상 없음`);
70
+ const results = checkLoops(entries, (deps.now ?? (() => new Date()))());
71
+ const counts = countLoopStates(results);
72
+ if (counts.unknown + counts.unregistered + counts.off === results.length) return missing('루프', '활성 등록 루프의 마지막 실행/예정 시각을 잴 수 없음');
73
+ const failing = results.filter(result => result.state === 'failing').map(result => result.id);
74
+ const late = results.filter(result => result.state === 'late').map(result => result.id);
75
+ return { row: '루프', state: failing.length || late.length || counts.unknown || counts.unregistered || counts.off ? 'warn' : 'ok',
76
+ text: `failing ${counts.failing}${failing.length ? ` (${failing.join(', ')})` : ''} · late ${counts.late}${late.length ? ` (${late.join(', ')})` : ''} · unknown ${counts.unknown} · unregistered ${counts.unregistered} · off ${counts.off}` };
77
+ });
78
+ await add('config', async () => {
79
+ const report = await (deps.readDrift ?? (() => computeDrift(discoverUniverses(getElanousConfigDirOverride() !== undefined
80
+ ? { prodRoot: effectiveInstanceRoot(), registryPath: null, treeScanRoots: [] } : {}))))();
81
+ if (report.prodStatus !== 'ok' || !report.registry.ok || report.universes.unreadable > 0 || report.universes.compared === 0) {
82
+ return missing('config', `드리프트 비교 불가(집 ${report.prodStatus} · 레지스트리 ${report.registry.ok ? '읽음' : '못 읽음'} · 비교 ${report.universes.compared} · 못 읽은 우주 ${report.universes.unreadable})`);
83
+ }
84
+ return { row: 'config', state: report.rows.length ? 'warn' : 'ok', text: `드리프트 키 ${report.rows.length}` };
85
+ });
86
+ return rows;
87
+ }
@@ -67,12 +67,14 @@ export async function runLightMember(opts: LightMemberOptions): Promise<LightMem
67
67
  const root = opts.root ?? effectiveInstanceRoot();
68
68
  const configured = readMachineProfile(root);
69
69
  if (configured) assertJoinedMachineId(configured.id, root);
70
- const machineAttrs = () => {
70
+ // measureLoad is async (harness parents · disk I/O); a pending Promise would serialize as `{}` and blank `load`.
71
+ const pageoutBaseline: { value: number } = { value: Number.NaN };
72
+ const machineAttrs = async () => {
71
73
  const profile = readMachineProfile(root);
72
74
  if (profile && profile.id !== machine) {
73
75
  throw new Error(`machine id ${profile.id} differs from joined machine ${machine}`);
74
76
  }
75
- return { load: measureLoad(now), ...(profile ? { duties: profile.duties, seats: profile.seats } : {}) };
77
+ return { load: await measureLoad(now, pageoutBaseline), ...(profile ? { duties: profile.duties, seats: profile.seats } : {}) };
76
78
  };
77
79
  const interval = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
78
80
  if (!Number.isFinite(interval) || interval <= 0 || interval > MAX_RETRY_MS) throw new Error('invalid member heartbeat interval');
@@ -119,7 +121,7 @@ export async function runLightMember(opts: LightMemberOptions): Promise<LightMem
119
121
  while (!signal.aborted) {
120
122
  const resources: LightMemberResult['resources'] = [];
121
123
  try {
122
- const attrs = machineAttrs();
124
+ const attrs = await machineAttrs();
123
125
  await post(machineId, registered.has(machineId) ? { attrs } : {
124
126
  id: machineId, kind: 'machine', machine, name: machine, owner: '',
125
127
  attrs, observedAt: now(), ttlMs: TTL_MS,
@@ -1,5 +1,7 @@
1
- import { readFileSync } from 'node:fs';
2
- import { cpus, freemem, loadavg, totalmem } from 'node:os';
1
+ import { execFile } from 'node:child_process';
2
+ import { readFileSync, promises as fs } from 'node:fs';
3
+ import { promisify } from 'node:util';
4
+ import { cpus, freemem, loadavg, platform, totalmem } from 'node:os';
3
5
  import { join } from 'node:path';
4
6
  import { debug } from '../debug/log.js';
5
7
  import { effectiveInstanceRoot } from '../instance/resolve.js';
@@ -12,13 +14,173 @@ const MAX_RETRY_MS = 300_000;
12
14
  // Ordinary ticks start within the 10-minute TTL; first failure retries within 30 seconds.
13
15
  const MAX_INTERVAL_MS = MAX_RETRY_MS;
14
16
  const REQUEST_TIMEOUT_MS = 5_000;
17
+ const PROCESS_PROBE_TIMEOUT_MS = 2_000;
18
+ const execFileAsync = promisify(execFile);
15
19
 
16
- export function measureLoad(now: () => number = Date.now): {
20
+ type Measurement<T> = { value: T; source: string; observedAt: number; status: 'ok' }
21
+ | { value: null; source: string; observedAt: number; status: 'unmeasured'; reason: string };
22
+
23
+ function unmeasured<T>(source: string, observedAt: number, reason: string): Measurement<T> {
24
+ return { value: null, source, observedAt, status: 'unmeasured', reason };
25
+ }
26
+
27
+ export interface HarnessParentSummary {
28
+ count: Measurement<number>;
29
+ oldestAgeSeconds: Measurement<number>;
30
+ versionMismatchCount: Measurement<number>;
31
+ }
32
+
33
+ // Pod image commit is fixed at process startup; file paths and mutable package versions are not process identities.
34
+ const STARTUP_IMAGE_COMMIT = process.env.ELANOUS_IMAGE_COMMIT?.trim();
35
+ const HARNESS_PARENT_COMMAND = /^(?:(?:\S*\/)?(?:bun|node)\s+)?(?:\S*\/)?elanous\.mjs\s+harness\s+(?:say|ask)(?:\s|$)/;
36
+
37
+ export function parseHarnessParentPs(text: string): HarnessProcessList {
38
+ if (!text.trim()) return { status: 'incomplete', stage: 'ps-parse' };
39
+ const records: Array<{ pid: number; command: string; elapsedSeconds: number }> = [];
40
+ let incomplete = false;
41
+ for (const line of text.split('\n')) {
42
+ const candidate = line.trim().match(/^\d+\s+\S+\s+(.+)$/)?.[1];
43
+ if (!candidate || !HARNESS_PARENT_COMMAND.test(candidate)) continue;
44
+ const match = line.trim().match(/^(\d+)\s+(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)\s+(.+)$/);
45
+ if (!match) { incomplete = true; continue; }
46
+ const pid = Number(match[1]);
47
+ const days = Number(match[2] ?? 0);
48
+ const hours = Number(match[3] ?? 0);
49
+ const minutes = Number(match[4]);
50
+ const seconds = Number(match[5]);
51
+ if (!Number.isSafeInteger(pid) || pid <= 0 || minutes >= 60 || seconds >= 60 || (match[3] !== undefined && hours >= 24)) { incomplete = true; continue; }
52
+ records.push({ pid, elapsedSeconds: days * 86_400 + hours * 3_600 + minutes * 60 + seconds, command: match[6]! });
53
+ }
54
+ return incomplete ? { status: 'incomplete', stage: 'ps-parse' } : { status: 'ok', records };
55
+ }
56
+
57
+ interface HarnessProcessList {
58
+ status: 'ok' | 'incomplete' | 'failed';
59
+ stage?: string;
60
+ records?: readonly { pid: number; command: string; elapsedSeconds: number; version?: string }[];
61
+ }
62
+
63
+ async function readHarnessProcessVersion(pid: number): Promise<string | undefined> {
64
+ if (platform() !== 'linux') return undefined;
65
+ try {
66
+ const env = await fs.readFile(`/proc/${pid}/environ`, { encoding: 'utf8', signal: AbortSignal.timeout(PROCESS_PROBE_TIMEOUT_MS) });
67
+ const versions = env.split('\0').filter(entry => entry.startsWith('ELANOUS_IMAGE_COMMIT='))
68
+ .map(entry => entry.slice('ELANOUS_IMAGE_COMMIT='.length).trim());
69
+ return versions.length === 1 && versions[0] ? versions[0] : undefined;
70
+ } catch { return undefined; }
71
+ }
72
+
73
+ export async function listHarnessParents(
74
+ run: typeof execFileAsync = execFileAsync,
75
+ readVersion: (pid: number) => Promise<string | undefined> = readHarnessProcessVersion,
76
+ ): Promise<HarnessProcessList> {
77
+ try {
78
+ const { stdout } = await run('ps', ['-axo', 'pid=,etime=,command='], {
79
+ encoding: 'utf8', timeout: PROCESS_PROBE_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024,
80
+ });
81
+ const parsed = parseHarnessParentPs(stdout);
82
+ if (parsed.status !== 'ok' || !parsed.records) return parsed;
83
+ const records = await Promise.all(parsed.records.map(async record => {
84
+ const version = await readVersion(record.pid).catch(() => undefined);
85
+ return { ...record, ...(version ? { version } : {}) };
86
+ }));
87
+ return { status: 'ok', records };
88
+ } catch { return { status: 'failed', stage: 'ps-exec' }; }
89
+ }
90
+
91
+ /** A process command/path is not the version loaded into a running process. Only startup-stamped identities count. */
92
+ export function measureHarnessParents(
93
+ observedAt = Date.now(),
94
+ observation: HarnessProcessList = { status: 'failed', stage: 'ps-exec' },
95
+ currentVersion?: string,
96
+ ): HarnessParentSummary {
97
+ const source = 'ps:harness-processes';
98
+ if (observation.status !== 'ok' || !observation.records) {
99
+ const reason = observation.status === 'incomplete' ? 'ps-parse incomplete' : (observation.stage ?? 'ps-exec');
100
+ return { count: unmeasured(source, observedAt, reason), oldestAgeSeconds: unmeasured(source, observedAt, reason),
101
+ versionMismatchCount: unmeasured(source, observedAt, reason) };
102
+ }
103
+ const ok = (value: number): Measurement<number> => ({ value, source, observedAt, status: 'ok' });
104
+ const records = observation.records.filter(record => HARNESS_PARENT_COMMAND.test(record.command));
105
+ const count = ok(records.length);
106
+ const oldestAgeSeconds = records.length === 0
107
+ ? unmeasured<number>(source, observedAt, 'no-parents')
108
+ : records.every(record => Number.isFinite(record.elapsedSeconds) && record.elapsedSeconds >= 0)
109
+ ? ok(Math.max(...records.map(record => record.elapsedSeconds)))
110
+ : unmeasured<number>(source, observedAt, 'elapsed-unavailable');
111
+ const versionMismatchCount = records.length === 0 ? ok(0)
112
+ : currentVersion && records.every(record => typeof record.version === 'string' && record.version.length > 0)
113
+ ? ok(records.filter(record => record.version !== currentVersion).length)
114
+ : unmeasured<number>(source, observedAt, 'version-unavailable');
115
+ return { count, oldestAgeSeconds, versionMismatchCount };
116
+ }
117
+
118
+ export interface DiskIoPressure {
119
+ pageouts: Measurement<number>;
120
+ io: Measurement<{ someAvg10: number; fullAvg10: number }>;
121
+ }
122
+
123
+ export async function measureDiskIoPressure(
124
+ observedAt = Date.now(),
125
+ deps: { platform?: NodeJS.Platform; vmStat?: () => Promise<string> | string; readPressure?: () => Promise<string> | string } = {},
126
+ pageoutBaseline?: { value: number },
127
+ ): Promise<DiskIoPressure> {
128
+ const os = deps.platform ?? platform();
129
+ let pageouts: Measurement<number> = unmeasured('vm_stat:pageouts', observedAt, 'platform');
130
+ if (os === 'darwin') {
131
+ try {
132
+ const text = await (deps.vmStat ?? (async () => (await execFileAsync('vm_stat', [], {
133
+ encoding: 'utf8', timeout: PROCESS_PROBE_TIMEOUT_MS, maxBuffer: 1024 * 1024,
134
+ })).stdout))();
135
+ // Real macOS vm_stat prints `Pageouts:`; older/other formats print `Pages paged out:`.
136
+ const value = text.match(/^(?:Pageouts|Pages paged out):\s*([\d,]+)\s*\.?\s*$/m)?.[1];
137
+ if (value && Number.isSafeInteger(Number(value.replaceAll(',', '')))) {
138
+ const total = Number(value.replaceAll(',', ''));
139
+ pageouts = pageoutBaseline
140
+ ? Number.isFinite(pageoutBaseline.value) && total >= pageoutBaseline.value
141
+ ? { value: total - pageoutBaseline.value, source: 'vm_stat:pageouts', observedAt, status: 'ok' }
142
+ : unmeasured('vm_stat:pageouts', observedAt, 'baseline-unavailable')
143
+ : { value: total, source: 'vm_stat:pageouts', observedAt, status: 'ok' };
144
+ if (pageoutBaseline) pageoutBaseline.value = total;
145
+ }
146
+ else pageouts = unmeasured('vm_stat:pageouts', observedAt, 'invalid-output');
147
+ } catch { pageouts = unmeasured('vm_stat:pageouts', observedAt, 'vm_stat-failed'); }
148
+ }
149
+ let io: DiskIoPressure['io'] = unmeasured('/proc/pressure/io', observedAt, 'platform');
150
+ if (os === 'linux') {
151
+ try {
152
+ const text = await (deps.readPressure ?? (() => fs.readFile('/proc/pressure/io', {
153
+ encoding: 'utf8', signal: AbortSignal.timeout(PROCESS_PROBE_TIMEOUT_MS),
154
+ })))();
155
+ const some = text.match(/^some\s+avg10=(\d+(?:\.\d+)?)(?:\s|$)/m)?.[1];
156
+ const full = text.match(/^full\s+avg10=(\d+(?:\.\d+)?)(?:\s|$)/m)?.[1];
157
+ if (some !== undefined && full !== undefined && Number.isFinite(Number(some)) && Number.isFinite(Number(full)))
158
+ io = { value: { someAvg10: Number(some), fullAvg10: Number(full) }, source: '/proc/pressure/io', observedAt, status: 'ok' };
159
+ else io = unmeasured('/proc/pressure/io', observedAt, 'invalid-output');
160
+ } catch { io = unmeasured('/proc/pressure/io', observedAt, 'pressure-read-failed'); }
161
+ }
162
+ return { pageouts, io };
163
+ }
164
+
165
+ export async function measureLoad(
166
+ now: () => number = Date.now,
167
+ pageoutBaseline?: { value: number },
168
+ deps: { listParents?: () => Promise<HarnessProcessList>; diskIo?: (at: number, baseline?: { value: number }) => Promise<DiskIoPressure>; currentVersion?: string } = {},
169
+ ): Promise<{
17
170
  loadAvg: number[]; cpuCount: number; freeMem: number; totalMem: number; observedAt: number;
18
- } {
171
+ harnessParents: HarnessParentSummary; diskIo: DiskIoPressure;
172
+ }> {
173
+ const observedAt = now();
174
+ const [parents, diskIo] = await Promise.all([
175
+ (deps.listParents ?? listHarnessParents)().catch((): HarnessProcessList => ({ status: 'failed', stage: 'ps-exec' })),
176
+ (deps.diskIo ?? ((at, baseline) => measureDiskIoPressure(at, {}, baseline)))(observedAt, pageoutBaseline)
177
+ .catch((): DiskIoPressure => ({ pageouts: unmeasured<number>('vm_stat:pageouts', observedAt, 'probe-failed'),
178
+ io: unmeasured<{ someAvg10: number; fullAvg10: number }>('/proc/pressure/io', observedAt, 'probe-failed') })),
179
+ ]);
19
180
  return {
20
181
  loadAvg: loadavg(), cpuCount: cpus().length,
21
- freeMem: freemem(), totalMem: totalmem(), observedAt: now(),
182
+ freeMem: freemem(), totalMem: totalmem(), observedAt,
183
+ harnessParents: measureHarnessParents(observedAt, parents, deps.currentVersion ?? STARTUP_IMAGE_COMMIT), diskIo,
22
184
  };
23
185
  }
24
186
 
@@ -50,6 +212,7 @@ export interface MemberHeartbeatOptions {
50
212
  intervalMs?: number;
51
213
  fetch?: (input: URL | RequestInfo, init?: RequestInit) => Promise<Response>;
52
214
  now?: () => number;
215
+ measure?: (now: () => number, pageoutBaseline: { value: number }) => Promise<Awaited<ReturnType<typeof measureLoad>>>;
53
216
  }
54
217
 
55
218
  /** 넥서스 멤버의 기계 신원 — OS hostname 이 아니라 임대·관제가 쓰는 기계 식별자(`resolveMachineName`)를 쓴다.
@@ -81,6 +244,8 @@ export function startMemberHeartbeat(opts: MemberHeartbeatOptions): () => void {
81
244
  let failures = 0;
82
245
  let lastFailure: string | undefined;
83
246
  const machineId = opts.machine.id;
247
+ const pageoutBaseline: { value: number } = { value: Number.NaN };
248
+ const machineLoad = () => (opts.measure ?? measureLoad)(now, pageoutBaseline);
84
249
  // 자원의 `machine` 칸 = 기계 «이름»(가벼운 멤버·임대와 같은 값). 기계 범위 토큰은 이 칸을 토큰의 기계와 대조한다(server.ts requireMachine).
85
250
  const machineName = opts.machine.name;
86
251
 
@@ -106,7 +271,7 @@ export function startMemberHeartbeat(opts: MemberHeartbeatOptions): () => void {
106
271
  const register = async (): Promise<void> => {
107
272
  await post('/v1/resources/register', {
108
273
  ...opts.machine, kind: 'machine', machine: machineName, owner: '',
109
- attrs: { ...opts.machine.attrs, load: measureLoad(now) }, observedAt: now(), ttlMs: MAX_RETRY_MS * 2,
274
+ attrs: { ...opts.machine.attrs, load: await machineLoad() }, observedAt: now(), ttlMs: MAX_RETRY_MS * 2,
110
275
  });
111
276
  await post('/v1/resources/register', {
112
277
  ...opts.instance, kind: 'instance', machine: machineName, owner: '',
@@ -125,7 +290,7 @@ export function startMemberHeartbeat(opts: MemberHeartbeatOptions): () => void {
125
290
  try {
126
291
  if (!registered) await register();
127
292
  else {
128
- await post(`/v1/resources/${encodeURIComponent(machineId)}/heartbeat`, { attrs: { load: measureLoad(now) } });
293
+ await post(`/v1/resources/${encodeURIComponent(machineId)}/heartbeat`, { attrs: { load: await machineLoad() } });
129
294
  heartbeatsSinceInstanceRefresh++;
130
295
  if (heartbeatsSinceInstanceRefresh * interval >= MAX_RETRY_MS) {
131
296
  await post(`/v1/resources/${encodeURIComponent(opts.instance.id)}/heartbeat`, {});