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,13 +1,17 @@
1
1
  import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
+ import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
4
5
  import { parse as parseYaml } from 'yaml';
5
6
  import { elanousStateRoot } from '../../autopilot/state-paths.js';
6
7
  import { debug } from '../../debug/log.js';
7
8
  import { defaultGraphsDir, loadGraphTemplatesFrom } from '../../self-implement/graph-templates.js';
8
9
  import { loadGraphTemplates } from '../../self-implement/graph-yaml.js';
9
10
  import { validateGraphYaml } from './graph-kinds.js';
11
+ import { parseWizardSteps, readWizardSteps, withWizardSteps } from '../../graph-wizard/saved-steps.js';
12
+ import type { WizardNodeStep } from '../../graph-wizard/steps.js';
10
13
  import { jsonResponse } from './json-response.js';
14
+ import { clearPeerEdit, peerEditRefusalBody, peerEditRunGate, readPeerEdit, writePeerEdit } from './graph-peer-edit.js';
11
15
 
12
16
  /** Declared graph ids only. A user string is never joined onto a filesystem path. */
13
17
  const GRAPH_ID = /^[a-z0-9-]+$/;
@@ -42,6 +46,93 @@ function mineGraphsDir(): string {
42
46
  return join(elanousStateRoot(), 'graphs');
43
47
  }
44
48
 
49
+ export type GraphPermission = 'view' | 'edit';
50
+ type GraphGrant = { recipient: string; permission: GraphPermission };
51
+ const TOKEN_PATTERN = /^eg_[A-Za-z0-9_-]{32,}$/;
52
+
53
+ function accessFile(dir: string, id: string): string {
54
+ return join(dir, '.access', `${id}.json`);
55
+ }
56
+ function tokenHash(token: string): string {
57
+ return createHash('sha256').update(token).digest('hex');
58
+ }
59
+ function accessGrants(dir: string, id: string): GraphGrant[] {
60
+ let text: string;
61
+ try { text = readFileSync(accessFile(dir, id), 'utf8'); }
62
+ catch (error) {
63
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [];
64
+ throw error;
65
+ }
66
+ const parsed: unknown = JSON.parse(text);
67
+ if (!Array.isArray(parsed) || !parsed.every((entry): entry is GraphGrant => entry !== null && typeof entry === 'object'
68
+ && typeof entry.recipient === 'string' && /^[a-f0-9]{64}$/.test(entry.recipient)
69
+ && (entry.permission === 'view' || entry.permission === 'edit'))) throw new Error('invalid graph access store');
70
+ return parsed;
71
+ }
72
+
73
+ /** Only a hash is persisted; no owner bearer or raw recipient credential goes into a graph/YAML. */
74
+ export function handleGraphAccess(pathname: string, req: Request, deps: GraphsApiDeps = {}): Response {
75
+ const match = /^\/v1\/graphs\/([^/]+)\/access$/.exec(pathname);
76
+ const id = match ? resolveId(match[1]!) : null;
77
+ if (!id) return jsonResponse({ error: 'bad_request' }, 400);
78
+ const owned = mineOwnership(id, deps);
79
+ if (owned instanceof Response) return owned;
80
+ if (req.method !== 'GET') return jsonResponse({ error: 'method-not-allowed' }, 405);
81
+ try { return jsonResponse({ grants: accessGrants(owned.mineDir, id) }); }
82
+ catch (error) { return saveFailed(id, error); }
83
+ }
84
+
85
+ export async function handleGraphAccessPut(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
86
+ const match = /^\/v1\/graphs\/([^/]+)\/access$/.exec(pathname);
87
+ const id = match ? resolveId(match[1]!) : null;
88
+ if (!id) return jsonResponse({ error: 'bad_request' }, 400);
89
+ const owned = mineOwnership(id, deps);
90
+ if (owned instanceof Response) return owned;
91
+ // Detail GET resolves a core id first; do not grant a token for a shadowed mine id.
92
+ if (indexByDeclaredId(deps.coreDir ?? defaultGraphsDir()).has(id)) return jsonResponse({ error: 'core-id-conflict', id }, 409);
93
+ let body: { recipient?: unknown; permission?: unknown };
94
+ try { body = await req.json() as typeof body; }
95
+ catch { return jsonResponse({ error: 'bad_request' }, 400); }
96
+ if (!body || typeof body.recipient !== 'string' || !TOKEN_PATTERN.test(body.recipient)
97
+ || (body.permission !== 'view' && body.permission !== 'edit')) {
98
+ return jsonResponse({ error: 'bad_request', reason: 'recipient access token and view/edit permission required' }, 400);
99
+ }
100
+ const recipient = tokenHash(body.recipient);
101
+ const dir = join(owned.mineDir, '.access');
102
+ try {
103
+ const grants = accessGrants(owned.mineDir, id).filter((grant) => grant.recipient !== recipient);
104
+ grants.push({ recipient, permission: body.permission });
105
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
106
+ const target = accessFile(owned.mineDir, id);
107
+ const temp = `${target}.${process.pid}.${randomUUID()}.tmp`;
108
+ try {
109
+ writeFileSync(temp, JSON.stringify(grants), { mode: 0o600 });
110
+ renameSync(temp, target);
111
+ } finally { rmSync(temp, { force: true }); }
112
+ } catch (error) { return saveFailed(id, error); }
113
+ return jsonResponse({ recipient, permission: body.permission });
114
+ }
115
+
116
+ /** The peer token works only on this graph and the requested read/edit operation. */
117
+ export function graphAccessForRequest(pathname: string, method: string, token: string | null, deps: GraphsApiDeps = {}): boolean {
118
+ const match = /^\/v1\/graphs\/([^/]+)(?:\/(yaml))?$/.exec(pathname);
119
+ const id = match ? resolveId(match[1]!) : null;
120
+ if (!id || !token || !TOKEN_PATTERN.test(token)) return false;
121
+ if (method !== 'GET' && !(method === 'PUT' && match?.[2] === 'yaml')) return false;
122
+ try {
123
+ if (!indexByDeclaredId(deps.mineDir ?? mineGraphsDir()).has(id)) return false;
124
+ if (indexByDeclaredId(deps.coreDir ?? defaultGraphsDir()).has(id)) return false;
125
+ const wanted = tokenHash(token);
126
+ return accessGrants(deps.mineDir ?? mineGraphsDir(), id).some((grant) => {
127
+ const stored = Buffer.from(grant.recipient, 'hex');
128
+ return timingSafeEqual(stored, Buffer.from(wanted, 'hex')) && (method === 'GET' || grant.permission === 'edit');
129
+ });
130
+ } catch (error) {
131
+ debug.log('nexus.graphs', 'access-read-failed', { id, reason: String(error) }, { level: 'warn' });
132
+ return false;
133
+ }
134
+ }
135
+
45
136
  function resolveId(raw: string): string | null {
46
137
  let id: string;
47
138
  try { id = decodeURIComponent(raw); }
@@ -109,7 +200,12 @@ function detailOf(id: string, source: 'core' | 'mine', editable: boolean, dir: s
109
200
  editable,
110
201
  entry_node: template.entryNode,
111
202
  terminal_nodes: template.terminalNodes,
112
- nodes: template.nodes.map(({ nodeId, kind, recipe, maxVisits }) => ({ node_id: nodeId, kind, recipe, max_visits: maxVisits })),
203
+ // GRAPH-GROUPS — a node's `group:` tag (when the loader carries it) rides along so the PWA can box/fold units.
204
+ nodes: template.nodes.map((node) => {
205
+ const { nodeId, kind, recipe, maxVisits } = node;
206
+ const group = (node as { group?: unknown }).group;
207
+ return { node_id: nodeId, kind, recipe, max_visits: maxVisits, ...(typeof group === 'string' && group.trim() ? { group: group.trim() } : {}) };
208
+ }),
113
209
  edges: spec.edges.map(({ from, to, on, map }) => ({
114
210
  from,
115
211
  ...(to === undefined ? {} : { to }),
@@ -178,7 +274,8 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
178
274
  const coreHit = core.ok ? indexByDeclaredId(coreDir).get(id) : undefined;
179
275
  const hit = mineHit ?? coreHit;
180
276
  if (!hit) return jsonResponse({ error: 'not-found', id }, 404);
181
- return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text });
277
+ const steps = mineHit ? readWizardSteps(mineDir, id, hit.text) : null;
278
+ return jsonResponse({ id, source: mineHit ? 'mine' : 'core', editable: Boolean(mineHit), yaml: hit.text, ...(steps ? { steps } : {}) });
182
279
  }
183
280
 
184
281
  const match = /^\/v1\/graphs\/([^/]+)$/.exec(pathname);
@@ -195,7 +292,10 @@ export function handleGraphsGet(pathname: string, dir = defaultGraphsDir(), deps
195
292
  }
196
293
 
197
294
  /** PUT writes only under the state-root graphs directory. Core YAML is never opened for write. */
198
- export async function handleGraphsPut(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
295
+ /** Who authorized a save: the owner, or a peer token holding an «edit» grant (raw token; only a hash prefix is kept). */
296
+ export type GraphSaveActor = { peerToken: string } | null;
297
+
298
+ export async function handleGraphsPut(pathname: string, req: Request, deps: GraphsApiDeps = {}, actor: GraphSaveActor = null): Promise<Response> {
199
299
  const match = /^\/v1\/graphs\/([^/]+)\/yaml$/.exec(pathname);
200
300
  if (!match) return jsonResponse({ error: 'not-found' }, 404);
201
301
  const id = resolveId(match[1]!);
@@ -209,18 +309,33 @@ export async function handleGraphsPut(pathname: string, req: Request, deps: Grap
209
309
  const mineOwns = indexByDeclaredId(mineDir).has(id);
210
310
  if (coreOwns && !mineOwns) return jsonResponse({ error: 'core-read-only', id }, 403);
211
311
  if (!mineOwns) return jsonResponse({ error: 'not-found', id }, 404);
212
- let body: { yaml?: unknown };
213
- try { body = await req.json() as { yaml?: unknown }; }
312
+ let body: { yaml?: unknown; steps?: unknown };
313
+ try { body = await req.json() as { yaml?: unknown; steps?: unknown }; }
214
314
  catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
215
315
  if (typeof body?.yaml !== 'string') return jsonResponse({ error: 'bad_request', reason: 'yaml string required' }, 400);
216
316
  const rejected = rejectGraphYaml(body.yaml, id);
217
317
  if (rejected) return rejected;
318
+ const steps = stepsOf(body.steps, body.yaml);
319
+ if (steps instanceof Response) return steps;
218
320
  const existing = indexByDeclaredId(mineDir).get(id)!;
219
321
  // Writes go only to <mine>/<id>.yaml; a hand-placed file under another name is not edited over HTTP.
220
322
  if (existing.file !== `${id}.yaml`) return jsonResponse({ error: 'file-name-mismatch', id, file: existing.file }, 409);
221
- const { version, previous } = writeWithVersion(mineDir, id, existing, body.yaml);
222
- debug.log('nexus.graphs', 'saved', { id, version, previous });
223
- return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous });
323
+ let written: { version: string; previous: string };
324
+ const editedBy = actor ? `peer:${tokenHash(actor.peerToken).slice(0, 8)}` : null;
325
+ try {
326
+ written = withWizardSteps(mineDir, id, steps, () => {
327
+ // Peer save: the marker goes down «before» the bytes, so a crash in between still refuses the run.
328
+ if (editedBy) writePeerEdit(mineDir, id, { editedBy, version: 'pending', at: new Date().toISOString() });
329
+ const result = writeWithVersion(mineDir, id, existing, body.yaml as string);
330
+ if (editedBy) writePeerEdit(mineDir, id, { editedBy, version: result.version, at: new Date().toISOString() });
331
+ else clearPeerEdit(mineDir, id);
332
+ return result;
333
+ });
334
+ }
335
+ catch (error) { return saveFailed(id, error); }
336
+ const { version, previous } = written;
337
+ debug.log('nexus.graphs', 'saved', { id, version, previous, steps: steps ? Object.keys(steps).length : 0, ...(editedBy ? { editedBy } : {}) });
338
+ return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous, ...(editedBy ? { editedBy } : {}) });
224
339
  }
225
340
 
226
341
  const VERSION_ID = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z(?:-\d{4})?$/;
@@ -331,10 +446,27 @@ export async function handleGraphsRevert(pathname: string, req: Request, deps: G
331
446
  return jsonResponse({ id, source: 'mine', editable: true, reverted: true, version, previous, restoredFrom: target });
332
447
  }
333
448
 
449
+ /** GRAPH-WIZARD-SAVE-RECIPES — optional `steps` on save (the wizard's node → library step map). Absent = keep
450
+ * whatever sidecar the graph has; present = validated against the step library and stored as a sidecar. The
451
+ * run builds commands from it server-side (src/graph-wizard/saved-steps.ts) — no client command text is kept. */
452
+ function stepsOf(raw: unknown, yaml: string): Record<string, WizardNodeStep> | null | Response {
453
+ // Only an absent field means «keep what is there»; an explicit null or any other non-object is a bad request.
454
+ if (raw === undefined) return null;
455
+ const parsed = parseWizardSteps(raw, yaml);
456
+ if (!parsed.ok) return jsonResponse({ error: 'bad_request', reason: parsed.reason }, 400);
457
+ return parsed.steps;
458
+ }
459
+
460
+ function saveFailed(id: string, error: unknown): Response {
461
+ const reason = error instanceof Error ? error.message.slice(0, 200) : String(error);
462
+ debug.log('nexus.graphs', 'save-failed', { id, reason }, { level: 'warn' });
463
+ return jsonResponse({ error: 'save-failed', id, reason }, 500);
464
+ }
465
+
334
466
  /** POST /v1/graphs {id, yaml} — a brand-new «mine» graph (blank canvas → save). Records version v1. */
335
467
  export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
336
- let body: { id?: unknown; yaml?: unknown };
337
- try { body = await req.json() as { id?: unknown; yaml?: unknown }; }
468
+ let body: { id?: unknown; yaml?: unknown; steps?: unknown };
469
+ try { body = await req.json() as { id?: unknown; yaml?: unknown; steps?: unknown }; }
338
470
  catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
339
471
  const id = typeof body?.id === 'string' ? resolveId(body.id) : null;
340
472
  if (!id) return jsonResponse({ error: 'bad_request', reason: 'id must match [a-z0-9-]' }, 400);
@@ -347,10 +479,18 @@ export async function handleGraphsCreate(req: Request, deps: GraphsApiDeps = {})
347
479
  if (indexByDeclaredId(mineDir).has(id) || existsSync(join(mineDir, `${id}.yaml`))) return jsonResponse({ error: 'exists', id }, 409);
348
480
  const rejected = rejectGraphYaml(body.yaml, id);
349
481
  if (rejected) return rejected;
350
- mkdirSync(mineDir, { recursive: true });
351
- writeFileSync(join(mineDir, `${id}.yaml`), body.yaml, { flag: 'wx' });
352
- const version = recordVersion(mineDir, id, body.yaml);
353
- debug.log('nexus.graphs', 'saved', { id, version, created: true });
482
+ const steps = stepsOf(body.steps, body.yaml);
483
+ if (steps instanceof Response) return steps;
484
+ let version: string;
485
+ try {
486
+ version = withWizardSteps(mineDir, id, steps, () => {
487
+ mkdirSync(mineDir, { recursive: true });
488
+ writeFileSync(join(mineDir, `${id}.yaml`), body.yaml as string, { flag: 'wx' });
489
+ try { return recordVersion(mineDir, id, body.yaml as string); }
490
+ catch (error) { rmSync(join(mineDir, `${id}.yaml`), { force: true }); throw error; }
491
+ });
492
+ } catch (error) { return saveFailed(id, error); }
493
+ debug.log('nexus.graphs', 'saved', { id, version, created: true, steps: steps ? Object.keys(steps).length : 0 });
354
494
  return jsonResponse({ id, source: 'mine', editable: true, saved: true, version, previous: null }, 201);
355
495
  }
356
496
 
@@ -370,8 +510,14 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
370
510
  if (reservedId) return reservedId;
371
511
  const coreDir = deps.coreDir ?? defaultGraphsDir();
372
512
  const mineDir = deps.mineDir ?? mineGraphsDir();
373
- const source = indexByDeclaredId(coreDir).get(id) ?? indexByDeclaredId(mineDir).get(id);
513
+ const fromCore = indexByDeclaredId(coreDir).get(id);
514
+ const source = fromCore ?? indexByDeclaredId(mineDir).get(id);
374
515
  if (!source) return jsonResponse({ error: 'not-found', id }, 404);
516
+ // A clone would carry a peer's unapproved change into a graph with no marker — approve first.
517
+ if (!fromCore) {
518
+ const gate = peerEditRunGate(mineDir, id);
519
+ if (!gate.ok) return jsonResponse(peerEditRefusalBody(id, gate), 409);
520
+ }
375
521
  if (indexByDeclaredId(mineDir).has(body.newId) || indexByDeclaredId(coreDir).has(body.newId)) {
376
522
  return jsonResponse({ error: 'conflict', id: body.newId }, 409);
377
523
  }
@@ -384,9 +530,39 @@ export async function handleGraphsClone(pathname: string, req: Request, deps: Gr
384
530
  return jsonResponse({ id: body.newId, source: 'mine', editable: true, clonedFrom: id }, 201);
385
531
  }
386
532
 
387
- export async function handleGraphsMutation(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response | null> {
533
+ /** POST /v1/graphs/<id>/approve {version} — owner only (the route never accepts a peer token). Approves the
534
+ * exact peer version the run refusal named; a newer peer save is a new version and needs a new approval. */
535
+ export async function handleGraphsApprove(pathname: string, req: Request, deps: GraphsApiDeps = {}): Promise<Response> {
536
+ const match = /^\/v1\/graphs\/([^/]+)\/approve$/.exec(pathname);
537
+ if (!match) return jsonResponse({ error: 'not-found' }, 404);
538
+ const id = resolveId(match[1]!);
539
+ if (!id) return jsonResponse({ error: 'bad_request', reason: 'invalid graph id' }, 400);
540
+ const owned = mineOwnership(id, deps);
541
+ if (owned instanceof Response) return owned;
542
+ let body: { version?: unknown };
543
+ try { body = await req.json() as { version?: unknown }; }
544
+ catch { return jsonResponse({ error: 'bad_request', reason: 'json body required' }, 400); }
545
+ if (typeof body?.version !== 'string' || !body.version) return jsonResponse({ error: 'bad_request', reason: 'version string required' }, 400);
546
+ let record;
547
+ try { record = readPeerEdit(owned.mineDir, id); }
548
+ catch (error) { return saveFailed(id, error); }
549
+ if (!record) return jsonResponse({ id, approved: true, version: body.version, pending: false });
550
+ if (record.version !== body.version) {
551
+ return jsonResponse({ error: 'version-mismatch', id, version: record.version, reason: '승인하려는 판이 최신 상대 변경과 다르다 — 다시 확인하라' }, 409);
552
+ }
553
+ const approved = { version: record.version, at: new Date().toISOString() };
554
+ try { writePeerEdit(owned.mineDir, id, { ...record, approved }); }
555
+ catch (error) { return saveFailed(id, error); }
556
+ debug.log('nexus.graphs', 'peer-edit-approved', { id, version: record.version, editedBy: record.editedBy });
557
+ return jsonResponse({ id, approved: true, version: record.version, editedBy: record.editedBy });
558
+ }
559
+
560
+ export async function handleGraphsMutation(pathname: string, req: Request, deps: GraphsApiDeps = {}, actor: GraphSaveActor = null): Promise<Response | null> {
561
+ // Only PUT …/yaml is open to a peer token; every other mutation is the owner's alone.
562
+ if (actor && !(req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname))) return jsonResponse({ error: 'unauthorized' }, 401);
388
563
  if (req.method === 'POST' && pathname === '/v1/graphs') return handleGraphsCreate(req, deps);
389
- if (req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname)) return handleGraphsPut(pathname, req, deps);
564
+ if (req.method === 'PUT' && /^\/v1\/graphs\/[^/]+\/yaml$/.test(pathname)) return handleGraphsPut(pathname, req, deps, actor);
565
+ if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/approve$/.test(pathname)) return handleGraphsApprove(pathname, req, deps);
390
566
  if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/clone$/.test(pathname)) return handleGraphsClone(pathname, req, deps);
391
567
  if (req.method === 'POST' && /^\/v1\/graphs\/[^/]+\/revert$/.test(pathname)) return handleGraphsRevert(pathname, req, deps);
392
568
  return null;
@@ -0,0 +1,205 @@
1
+ // HARNESS-RUN-LIVE-GRAPH (0.2.21) — one harness run's «which node, which outcome, when» for the PWA
2
+ // live/replay graph page (`/live-run/?run=<runId>`).
3
+ //
4
+ // Canonical source = the run's own ledger `~/.elanous/run-ledger/<runId>.jsonl` (federated: pod ledgers synced
5
+ // home are found through the same directories as the running-runs query):
6
+ // · `pipeline-node-entry` — one per node visit (node · round · graphId)
7
+ // · `graph-visit-budget` — the visit count the runtime itself counted
8
+ // · per-node outcome events — implemented · gated · reviewed(+review-reflect) · rework-budget · pre-pr-sync ·
9
+ // gate.postsync · pr-opened · merge-decision
10
+ // · `run-status` — terminal status of the whole run
11
+ // Durations are derived (gap to the next node entry, or to the terminal status); nothing writes a node «end».
12
+ //
13
+ // Node ids are never hardcoded for routing: any node the graph declares and the runtime enters (including the journey
14
+ // nodes HARNESS-FULL-GRAPH adds — entrance, queue, author, dispatch, landing, failure side) shows up as a step. A
15
+ // generic `pipeline-node-exit {node, outcome, detail?}` wins over the per-node heuristics below when present.
16
+ //
17
+ // ⛔ No goal body: the title is the first line of `start.feature`, cut to 80 chars. No artifact paths, no tokens.
18
+
19
+ import { loadFederatedRunLedger, loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
20
+ import { redactSecretText } from '../../debug/log.js';
21
+
22
+ export interface TraversalStepWire {
23
+ node: string;
24
+ round: number | null;
25
+ /** `pass` · `fail` · null (still running). */
26
+ outcome: string | null;
27
+ at: string;
28
+ durationMs: number | null;
29
+ visit: number;
30
+ maxVisits: number | null;
31
+ detail: string | null;
32
+ }
33
+
34
+ export interface RunTraversalWire {
35
+ runId: string;
36
+ graphId: string | null;
37
+ title: string | null;
38
+ /** `running` until the ledger carries a terminal `run-status`. */
39
+ status: string;
40
+ stage: string | null;
41
+ startedAt: string | null;
42
+ endedAt: string | null;
43
+ prNumber: number | null;
44
+ substrate: string | null;
45
+ steps: TraversalStepWire[];
46
+ ledgerEvents: number;
47
+ }
48
+
49
+ const str = (value: unknown): string | null => typeof value === 'string' && value.trim() ? value : null;
50
+ const num = (value: unknown): number | null => typeof value === 'number' && Number.isFinite(value) ? value : null;
51
+ const ms = (iso: string | undefined | null): number => iso ? Date.parse(iso) : Number.NaN;
52
+
53
+ const TERMINAL_FAIL = new Set(['failed', 'abandoned', 'cancelled', 'error', 'aborted', 'stopped']);
54
+
55
+ /** A display title from a goal's first line: path-like tokens shrink to their basename (`a/b/c.tsx` → `c.tsx`),
56
+ * then the line is redacted and cut to 80 chars. Shared with the PWA picker (`apps/pwa/src/lib/live-run-view.ts`). */
57
+ export function displayTitle(text: string | null | undefined, max = 80): string | null {
58
+ const line = text?.split('\n').map((part) => part.trim()).find(Boolean) ?? '';
59
+ const shrunk = line.replace(/\s*\[truncated[^\]]*\]\s*$/, '').replace(/(?:~|\.{0,2})?\/?(?:[\w.@-]+\/)+([\w.@-]+)/g, '$1');
60
+ const cut = shrunk.length > max ? `${shrunk.slice(0, max - 1)}…` : shrunk;
61
+ return cut ? redactSecretText(cut) : null;
62
+ }
63
+
64
+ function titleOf(entries: readonly RunLedgerEntry[]): string | null {
65
+ return displayTitle(str(entries.find((entry) => entry.event === 'start')?.data.feature));
66
+ }
67
+
68
+ /** Outcome + one-line detail of a node visit, read from the events between its entry and the next entry. */
69
+ function judgeVisit(node: string, window: readonly RunLedgerEntry[], lookback: readonly RunLedgerEntry[] = []): { outcome: 'pass' | 'fail' | null; detail: string | null } {
70
+ const last = (event: string) => [...window].reverse().find((entry) => entry.event === event)?.data;
71
+ // Some decisions are written just before the node they belong to is entered (merge-decision lands in the regate
72
+ // window, right before open-pr) — look back to the previous node entry for those.
73
+ const near = (event: string) => last(event) ?? [...lookback].reverse().find((entry) => entry.event === event)?.data;
74
+ const exit = [...window].reverse().find((entry) => entry.event === 'pipeline-node-exit' && entry.data.node === node)?.data;
75
+ const exitOutcome = str(exit?.outcome);
76
+ if (exitOutcome) {
77
+ const failed = TERMINAL_FAIL.has(exitOutcome) || exitOutcome === 'fail';
78
+ const exitDetail = str(exit?.detail)?.replace(/\s+/g, ' ').trim();
79
+ return { outcome: failed ? 'fail' : 'pass', detail: exitDetail ? redactSecretText(exitDetail.slice(0, 120)) : exitOutcome };
80
+ }
81
+ switch (node) {
82
+ case 'implement': {
83
+ const done = last('implemented');
84
+ if (!done) return { outcome: null, detail: null };
85
+ const evidence = last('off-diff-evidence');
86
+ const covered = num(evidence?.coveredEvidence); const required = num(evidence?.requiredEvidence);
87
+ const proof = covered !== null && required !== null ? ` · 증거 ${covered}/${required}` : '';
88
+ return done.ok === false ? { outcome: 'fail', detail: '구현 실패' } : { outcome: 'pass', detail: `구현 완료${proof}` };
89
+ }
90
+ case 'gate': {
91
+ const gated = last('gated');
92
+ if (!gated) return { outcome: null, detail: null };
93
+ return gated.passed === true ? { outcome: 'pass', detail: '게이트 통과' } : { outcome: 'fail', detail: '게이트 실패' };
94
+ }
95
+ case 'regate': {
96
+ const gated = last('gate.postsync');
97
+ if (!gated) return { outcome: null, detail: null };
98
+ return gated.passed === true ? { outcome: 'pass', detail: 'main 동기화 뒤 재게이트 통과' } : { outcome: 'fail', detail: '재게이트 실패' };
99
+ }
100
+ case 'review': {
101
+ const review = last('reviewed');
102
+ if (!review) return { outcome: null, detail: null };
103
+ const verdict = str(review.verdict) ?? '?';
104
+ const mustFix = num(review.mustFix);
105
+ const converged = window.some((entry) => entry.event === 'review-reflect' && entry.data.converged === true);
106
+ const base = `리뷰 ${verdict}${mustFix !== null ? ` · 꼭 고칠 것 ${mustFix}` : ''}`;
107
+ if (converged) return { outcome: 'pass', detail: `${base} · 반박 수렴` };
108
+ return { outcome: verdict === 'fail' ? 'fail' : 'pass', detail: base };
109
+ }
110
+ case 'rework': {
111
+ const budget = last('rework-budget');
112
+ const verdict = str(budget?.verdict);
113
+ return { outcome: 'pass', detail: verdict ? `재작업 예산 ${verdict}` : '재작업' };
114
+ }
115
+ case 'main-sync': {
116
+ const sync = last('pre-pr-sync');
117
+ if (!sync) return { outcome: null, detail: null };
118
+ const status = str(sync.status) ?? '?';
119
+ return { outcome: status === 'merged' || status === 'up-to-date' ? 'pass' : 'fail', detail: `main 동기화 ${status}` };
120
+ }
121
+ case 'open-pr': {
122
+ const opened = last('pr-opened');
123
+ const decision = str(near('merge-decision')?.decision);
124
+ if (!opened) return { outcome: null, detail: decision ? `병합 결정 ${decision}` : null };
125
+ const number = num(opened.number);
126
+ return { outcome: 'pass', detail: `PR${number !== null ? ` #${number}` : ''} 열림${decision ? ` · 병합 결정 ${decision}` : ''}` };
127
+ }
128
+ default:
129
+ return { outcome: null, detail: null };
130
+ }
131
+ }
132
+
133
+ export function buildRunTraversal(runId: string, entries: readonly RunLedgerEntry[]): RunTraversalWire {
134
+ const sorted = [...entries].map((entry, index) => ({ entry, index }))
135
+ .sort((a, b) => (ms(a.entry.timestamp) - ms(b.entry.timestamp)) || a.index - b.index).map(({ entry }) => entry);
136
+ const terminal = [...sorted].reverse().find((entry) => entry.event === 'run-status' && str(entry.data.runStatus));
137
+ const status = terminal ? str(terminal.data.runStatus)! : 'running';
138
+ const entryIndexes = sorted.map((entry, index) => entry.event === 'pipeline-node-entry' && str(entry.data.node) ? index : -1).filter((index) => index >= 0);
139
+ const visits = new Map<string, number>();
140
+ const steps: TraversalStepWire[] = entryIndexes.map((at, position) => {
141
+ const entry = sorted[at]!;
142
+ const node = str(entry.data.node)!;
143
+ const nextAt = entryIndexes[position + 1];
144
+ const window = sorted.slice(at + 1, nextAt ?? sorted.length);
145
+ const budget = [...sorted.slice(Math.max(0, at - 3), at)].reverse()
146
+ .find((candidate) => candidate.event === 'graph-visit-budget' && candidate.data.node === node);
147
+ const visit = num(budget?.data.visits) ?? (visits.get(node) ?? 0) + 1;
148
+ visits.set(node, visit);
149
+ const prevAt = position > 0 ? entryIndexes[position - 1]! : -1;
150
+ let { outcome, detail } = judgeVisit(node, window, sorted.slice(prevAt + 1, at));
151
+ const isLast = nextAt === undefined;
152
+ if (!isLast && outcome === null) outcome = 'pass';
153
+ if (isLast && terminal) outcome = TERMINAL_FAIL.has(status) ? 'fail' : (outcome ?? 'pass');
154
+ const endIso = nextAt !== undefined ? sorted[nextAt]!.timestamp : terminal?.timestamp;
155
+ const duration = ms(endIso) - ms(entry.timestamp);
156
+ return {
157
+ node,
158
+ round: num(entry.data.round),
159
+ outcome,
160
+ at: entry.timestamp ?? '',
161
+ durationMs: Number.isFinite(duration) && duration >= 0 ? duration : null,
162
+ visit,
163
+ maxVisits: num(budget?.data.maxVisits),
164
+ detail,
165
+ };
166
+ });
167
+
168
+ const contract = sorted.find((entry) => entry.event === 'graph-run-contract-resolved')?.data;
169
+ const pr = [...sorted].reverse().find((entry) => entry.event === 'pr-opened')?.data;
170
+ return {
171
+ runId,
172
+ graphId: str(contract?.graphId) ?? str(sorted.find((entry) => entry.event === 'pipeline-node-entry')?.data.graphId),
173
+ title: titleOf(sorted),
174
+ status,
175
+ stage: terminal ? str(terminal.data.stage) : null,
176
+ startedAt: sorted[0]?.timestamp ?? null,
177
+ endedAt: terminal?.timestamp ?? null,
178
+ prNumber: num(pr?.number),
179
+ substrate: str(contract?.actualSubstrate),
180
+ steps,
181
+ ledgerEvents: sorted.length,
182
+ };
183
+ }
184
+
185
+ export interface TraversalDeps {
186
+ loadLedger?: (runId: string) => RunLedgerEntry[] | null;
187
+ }
188
+
189
+ const USAGE = 'usage: GET /v1/harness/run-traversal?runId=<runId>';
190
+
191
+ /** GET /v1/harness/run-traversal?runId= — ordered node steps of one harness run. */
192
+ export function handleHarnessRunTraversalGet(req: Request, deps: TraversalDeps = {}): Response {
193
+ const runId = new URL(req.url).searchParams.get('runId')?.trim() ?? '';
194
+ if (!runId || /[\\/]|\.\./.test(runId)) return Response.json({ error: USAGE }, { status: 400 });
195
+ let entries: RunLedgerEntry[] | null;
196
+ try {
197
+ entries = deps.loadLedger
198
+ ? deps.loadLedger(runId)
199
+ : loadRunLedger(runId, runLedgerDir()) ?? loadFederatedRunLedger(runId);
200
+ } catch (error) {
201
+ return Response.json({ error: 'ledger-unreadable', reason: error instanceof Error ? error.message.slice(0, 200) : 'unknown' }, { status: 422 });
202
+ }
203
+ if (!entries) return Response.json({ error: 'run-not-found', runId }, { status: 404 });
204
+ return Response.json(buildRunTraversal(runId, entries));
205
+ }