elanous 0.2.13 → 0.2.15

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 (524) 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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -0,0 +1,90 @@
1
+ // Read-only HQ fence inventory: use the installed crontab, never install a proposed replacement.
2
+ import { debug } from '../debug/log.js';
3
+ import { FENCE_ROLES } from './hq.js';
4
+ import { inventoryCrontab, listSchedules, openSchedulesDb, parseCronLine, readCrontab } from '../domains/schedule-registry.js';
5
+ import { cronLaunchedActionsWithFence, listAllLoops, shellSegments, type LoopRegistryOptions } from '../loops/registry.js';
6
+
7
+ type SuggestedRole = 'cron' | 'seat-loop' | 'release-run' | 'conatus' | 'git-push' | 'ledger-cli' | 'unknown';
8
+ export interface UnfencedCron {
9
+ raw: string;
10
+ cron: string;
11
+ command: string;
12
+ recommendedRole: SuggestedRole;
13
+ suggestedLine: string;
14
+ /** Unknown roles are not executable fence arguments; the operator must choose a role. */
15
+ manualReview?: true;
16
+ }
17
+
18
+ /** Only infer a specific role where the launched command names that activity. */
19
+ function roleFor(command: string): SuggestedRole {
20
+ const launched = shellSegments(command).map(segment => segment.replace(/^cd\s+\S+\s*&&\s*/, '').replace(/(?:^|\s)(?:--message|--label|--note)\s+(?:'[^']*'|"[^"]*")/g, '')).join(' ');
21
+ if (/(?:^|[\s;&|])(?:\S*\/)?git\s+push\b|(?:^|[\s/])git-push\b|tree-sync\.sh\b/i.test(launched)) return 'git-push';
22
+ if (/(?:^|[\s/])(?:seat-loop|(?:op|tc|mk|ux)-seat|coord-bridge)(?:[.\s/-]|$)/i.test(launched)) return 'seat-loop';
23
+ if (/(?:^|[\s/])(?:release-run|landing-heal)(?:[.\s/-]|$)|\brelease\s+(?:run|publish|place|rebalance)\b/i.test(launched)) return 'release-run';
24
+ if (/(?:^|[\s/])conatus(?:[.\s/-]|$)/i.test(launched)) return 'conatus';
25
+ if (/\b(?:ledger|checklist)\s+(?:set|write|update|add|create|delete)\b|(?:^|[\s/])ledger-cli(?:[.\s/-]|$)/i.test(launched)) return 'ledger-cli';
26
+ if (/(?:^|[\s/])(?:steward|draft-cleanup|test-diet|contract-coordinator|market-posture|mission-request-judge)(?:[.\s/-]|$)/i.test(launched)) return 'cron';
27
+ if (/\b(?:graph\s+run|harness\s+queue\s+tick|card\s+intake-scan|flow\s+tick)\b/i.test(launched)) return 'cron';
28
+ return 'unknown';
29
+ }
30
+
31
+ const READ_ONLY = /^(?:hq (?:heartbeat|arbiter-check|seen|lease status|fence-audit)|(?:logs|config) (?:query|get|list)|loop (?:status|list)|card list|harness queue list)(?: |$)/i;
32
+ /** `hq fence --role <x>` with a role the CLI rejects never runs the payload fenced. */
33
+ function isFenceRole(role: string | undefined): boolean {
34
+ return !!role && (FENCE_ROLES as readonly string[]).includes(role);
35
+ }
36
+ function isReadOnly(script: string, args: string[]): boolean {
37
+ if (/^(?:cd|echo|printf|date|true)$/.test(script.split('/').pop() ?? '')) return true;
38
+ if (!/^(?:elanous|elanous\.mjs|eln)$/.test(script.split('/').pop() ?? '')) return false;
39
+ return READ_ONLY.test(args.slice(0, 3).join(' '));
40
+ }
41
+ const quote = (command: string) => `'${command.replaceAll("'", "'\"'\"'")}'`;
42
+
43
+ /** Reuse the installed loop inventory without touching the production schedule DB. A registry fenceRole
44
+ * may describe only one job or segment, so each launched action is checked independently below. */
45
+ export function hqFenceAudit(deps: {
46
+ read?: () => string;
47
+ loops?: typeof listAllLoops;
48
+ loopOptions?: LoopRegistryOptions;
49
+ log?: typeof debug.log;
50
+ } = {}): UnfencedCron[] {
51
+ const text = (deps.read ?? readCrontab)();
52
+ const parsed = text.split('\n').map(raw => ({ raw, job: parseCronLine(raw) })).filter(
53
+ (entry): entry is { raw: string; job: { cron: string; command: string } } => entry.job !== null,
54
+ );
55
+ // Inventory into an in-memory registry so the production schedule DB is never written by an audit.
56
+ let schedules: ReturnType<typeof listSchedules> = [];
57
+ try {
58
+ const db = openSchedulesDb(':memory:');
59
+ try {
60
+ inventoryCrontab(db, { crontab: text });
61
+ schedules = listSchedules(db);
62
+ } finally { db.close(); }
63
+ } catch { /* schedule metadata is advisory; the installed crontab is the source */ }
64
+ // Registry rows are hints only: a fenceRole on one segment must not hide an unfenced sibling.
65
+ // A broken graph or schedule inventory must not prevent auditing the installed crontab.
66
+ let loops: ReturnType<typeof listAllLoops> = [];
67
+ try { loops = (deps.loops ?? listAllLoops)({ ...deps.loopOptions, schedules }); } catch { /* cron inspection remains available */ }
68
+ const results: UnfencedCron[] = [];
69
+ for (const { raw, job: { cron, command } } of parsed) {
70
+ const launches = cronLaunchedActionsWithFence(command);
71
+ const unfenced = launches.filter(({ script, args, fenceRole }) => script && !isFenceRole(fenceRole) && !isReadOnly(script, args));
72
+ if (!unfenced.length) continue;
73
+ const unfencedCommand = unfenced.map(({ script, args }) => [script, ...args].join(' ')).join(' ; ');
74
+ const registered = loops.find(loop => loop.cron === cron && loop.command === command);
75
+ // An unfenced registered orchestrator can supply a role for an opaque launch.
76
+ // If the aggregate inventory carries a fenceRole from another segment, do not
77
+ // borrow that segment's classification for the unfenced sibling.
78
+ const inferred = roleFor(unfencedCommand);
79
+ const recommendedRole = inferred === 'unknown' && registered?.kind === 'orchestrator' && !registered.fenceRole ? 'cron' : inferred;
80
+ results.push({ raw, cron, command, recommendedRole,
81
+ ...(recommendedRole === 'unknown' ? { manualReview: true as const } : {}),
82
+ suggestedLine: recommendedRole === 'unknown'
83
+ ? `# manual review (unknown role): ${cron} ${command}`
84
+ : `${cron} hq-fence ${recommendedRole} ${quote(command)}` });
85
+ }
86
+ try { (deps.log ?? debug.log.bind(debug))('hq.fence-audit', 'listed', {
87
+ unfenced: results.length, unknownRole: results.filter(row => row.recommendedRole === 'unknown').length,
88
+ }); } catch { /* observation is fail-soft */ }
89
+ return results;
90
+ }
package/src/hq/hq.ts CHANGED
@@ -4,10 +4,14 @@ import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync,
4
4
  import { homedir, hostname } from 'node:os';
5
5
  import { dirname, join } from 'node:path';
6
6
  import { spawnSync } from 'node:child_process';
7
+ import type { Command } from 'commander';
8
+ import { writeStdoutJson } from '../cli/stdout-json.js';
7
9
  import { debug } from '../debug/log.js';
8
10
  import { DEFAULT_NEXUS_HTTP_PORT } from '../nexus/default-port.js';
9
11
  import { getElanousConfigDir } from '../elanous-config-dir.js';
10
12
  import { getUserConfig, type HqConfig, type HqFenceRole } from '../user-config.js';
13
+ import { promoteHq, proposeHqPromotion, writeOpSeatRequest } from './promote.js';
14
+ import type { HqOpRequestWriter, PromoteResult } from './promote.js';
11
15
  import {
12
16
  DEFAULT_TTL_SECONDS, decideAcquire, decideArbiterCheck, decideFence, decideRelease, decideRenew, defaultSshRunner,
13
17
  fileLeaseStore, leaseExpired, parseLease, probeReachable, recordView, serializeLease, sshLeaseStore, sshReachable, localShellRunner,
@@ -16,6 +20,21 @@ import {
16
20
 
17
21
  export const FENCE_ROLES: readonly HqFenceRole[] = ['telegram-poller', 'cron', 'seat-loop', 'release-run', 'conatus', 'git-push', 'ledger-cli'];
18
22
 
23
+ /** Register only the read-only move inventory; keep it outside lease/fence mutation flows. */
24
+ export function registerHqMovePlanCommand(hqCmd: Command): void {
25
+ hqCmd.command('move-plan').description('현재 호스트의 작업·launchd 및 대상 호스트 파일을 조회(읽기 전용)')
26
+ .requiredOption('--to <host>', '이동 대상 호스트')
27
+ .option('--json', 'JSON 출력')
28
+ .action(async (opts: { to: string; json?: boolean }) => {
29
+ try {
30
+ const { hqMovePlan, formatMovePlan } = await import('./move-plan.js');
31
+ const result = hqMovePlan(opts.to);
32
+ if (opts.json) await writeStdoutJson(`${JSON.stringify(result)}\n`);
33
+ else console.log(formatMovePlan(result));
34
+ } catch (error) { console.error(`hq move-plan: ${error instanceof Error ? error.message : String(error)}`); process.exitCode = 1; }
35
+ });
36
+ }
37
+
19
38
  export interface HqDeps {
20
39
  config?: HqConfig;
21
40
  store?: LeaseStore;
@@ -29,6 +48,9 @@ export interface HqDeps {
29
48
  seenPath?: string;
30
49
  now?: () => number;
31
50
  log?: typeof debug.log;
51
+ /** Injection points for the OP seat request and the promotion runbook; production uses durable defaults. */
52
+ opRequest?: HqOpRequestWriter;
53
+ promote?: (host: string, apply: boolean) => PromoteResult;
32
54
  }
33
55
 
34
56
  interface Resolved { me: string; arbiter: string; standby: string; ttl: number; failOpen: Set<HqFenceRole>; store: LeaseStore; ssh: SshRunner; probe: HostProbe; localPath: string; seenPath: string; now: () => number; log: typeof debug.log }
@@ -247,19 +269,55 @@ export function hqHeartbeat(deps: HqDeps = {}) {
247
269
 
248
270
  /**
249
271
  * Arbiter check (cron on cloud-vm every 10 min, next to the file): probe the holder over the tailnet
250
- * (`tailscale ping` ⊕ nexus health · no ssh into home machines) and promote per rule ②.
272
+ * (`tailscale ping` ⊕ nexus health · no ssh into home machines); propose to OP by default.
251
273
  */
252
274
  export function hqArbiterCheck(deps: HqDeps & { leasePath?: string } = {}) {
253
275
  const r0 = resolve(deps);
254
276
  const r = deps.store ? r0 : { ...r0, store: fileLeaseStore(deps.leasePath ?? join(process.env.HOME ?? '', '.elanous-hq', 'lease.json')) };
255
- let outcome: { promoted: boolean; streak: number; holder?: string; generation?: number; ping?: boolean; health?: boolean } = { promoted: false, streak: 0 };
277
+ let outcome: { promoted: boolean; streak: number; holder?: string; generation?: number; ping?: boolean; health?: boolean; runbookFailed?: string } = { promoted: false, streak: 0 };
278
+ let proposal: { holder: string; generation: number; streak: number } | undefined;
279
+ const config = deps.config ?? getUserConfig().hq ?? {};
280
+ const threshold = config.autoPromote?.streak ?? 3;
281
+ const autoPromote = config.autoPromote?.enabled === true;
282
+ let leaseMoved: { from: string; to: string; generation: number } | undefined;
256
283
  const result = mutate(r, (record, now) => {
257
284
  if (!record) return { ok: false, reason: 'no lease' };
258
285
  const seen = probeReachable(record.holder, r.probe);
259
- const checked = decideArbiterCheck(record, { arbiter: r.me, standby: r.standby, arbiterReachesHolder: seen.reachable, now });
260
- outcome = { promoted: checked.promoted, streak: checked.streak, holder: checked.next.holder, generation: checked.next.generation, ping: seen.ping, health: seen.health };
286
+ const checked = decideArbiterCheck(record, { arbiter: r.me, standby: r.standby, arbiterReachesHolder: seen.reachable, now,
287
+ promote: autoPromote, promoteAfterChecks: threshold });
288
+ proposal = !autoPromote && !seen.reachable && record.holder !== r.standby && checked.streak >= threshold
289
+ ? { holder: record.holder, generation: record.generation, streak: checked.streak } : undefined;
290
+ // promoteHq's own precondition is «lease holder = target», so the lease moves first and the runbook follows.
291
+ if (checked.promoted) leaseMoved = { from: record.holder, to: checked.next.holder, generation: checked.next.generation };
292
+ outcome = { promoted: false, streak: checked.streak, holder: checked.next.holder, generation: checked.next.generation, ping: seen.ping, health: seen.health };
261
293
  return { ok: true, next: checked.next };
262
294
  });
295
+ const moved = leaseMoved as { from: string; to: string; generation: number } | undefined; // assigned inside the mutate callback
296
+ if (result.ok && moved) {
297
+ let promotion: PromoteResult | undefined;
298
+ let failure: string | undefined;
299
+ try {
300
+ promotion = (deps.promote ?? ((host: string, apply: boolean) => promoteHq(host, apply)))(moved.to, true);
301
+ if (!promotion.ok) failure = promotion.lines.filter((line) => line.status === 'failed').map((line) => `${line.step}: ${line.measurement}`).join(' · ') || 'runbook returned ok=false';
302
+ } catch (error) { failure = String(error); }
303
+ if (failure === undefined) outcome = { ...outcome, promoted: true };
304
+ else {
305
+ // The lease already names the standby but the services did not move — never report this as a promotion.
306
+ outcome = { ...outcome, promoted: false, runbookFailed: failure };
307
+ try { r.log('hq.arbiter', 'promote-runbook-failed', { ...moved, error: failure }); } catch { /* observation is fail-soft */ }
308
+ try {
309
+ (deps.opRequest ?? ((request) => writeOpSeatRequest(request)))({
310
+ key: `hq:promote-failed:${moved.to}:${moved.generation}`,
311
+ text: `자동 승격 런북 실패 — 임대는 ${moved.to}(세대 ${moved.generation})로 옮겨졌지만 서비스 이전이 끝나지 않았다: ${failure}. OP 가 \`eln hq promote --to ${moved.to}\` 로 확인한다.`,
312
+ });
313
+ } catch (error) { try { r.log('hq.arbiter', 'promote-request-failed', { ...moved, error: String(error) }); } catch { /* fail-soft */ } }
314
+ }
315
+ }
316
+ const proposed = proposal as { holder: string; generation: number; streak: number } | undefined;
317
+ if (result.ok && proposed) {
318
+ try { proposeHqPromotion({ ...proposed, standby: r.standby }, { request: deps.opRequest, log: r.log }); }
319
+ catch (error) { try { r.log('hq.arbiter', 'promote-proposal-failed', { ...proposed, error: String(error) }); } catch { /* observation is fail-soft */ } }
320
+ }
263
321
  observe(r, 'hq.lease', outcome.promoted ? 'promoted' : 'arbiter-check', { ...outcome, ok: result.ok });
264
322
  return { ok: result.ok, ...outcome };
265
323
  }
@@ -302,7 +360,7 @@ export function hqCliWriteAllowed(command: string, override = false, deps: HqDep
302
360
  }
303
361
  if (allowed) return true;
304
362
  observe(r, 'hq.fence', 'cli-refused', { command, reason: decision.reason, holder: decision.holder, generation: decision.generation });
305
- console.error(`본부는 ${decision.holder ?? 'unknown'} gen ${decision.generation ?? 'unknown'} — 거기서 쓰거나 --hq-override(관측)`);
363
+ console.error(`본부는 ${decision.holder ?? 'unknown'} gen ${decision.generation ?? 'unknown'} — 원장 쓰기는 지금 본부로 보내라 (수동 우회: --hq-override, 관측)`);
306
364
  process.exitCode = 4;
307
365
  return false;
308
366
  }
package/src/hq/lease.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // HQ lease (본부 임대) — single writer across mbp · node-b · cloud-vm with a 2-of-3 quorum (OP 10-04 08:33 · 08:40).
2
- // The arbiter (cloud-vm) keeps one record; only the arbiter promotes the standby, and only after two consecutive
2
+ // The arbiter (cloud-vm) keeps one record; promotion requires an explicit opt-in and consecutive
3
3
  // checks in which neither the arbiter nor the standby can reach the holder. Fenced work carries the generation.
4
4
  import { createHash } from 'node:crypto';
5
5
  import { existsSync, mkdirSync, readFileSync, renameSync, rmdirSync, statSync, writeFileSync } from 'node:fs';
@@ -64,9 +64,9 @@ export function recordView(record: LeaseRecord, observer: string, reachable: boo
64
64
 
65
65
  /**
66
66
  * Arbiter check (rule ②). Records the arbiter's own view; counts a dual-unreachable check only when the standby's
67
- * fresh view of the same holder also says unreachable; promotes the standby after PROMOTE_AFTER_CHECKS in a row.
67
+ * fresh view of the same holder also says unreachable; promotion is optional and its threshold is configurable.
68
68
  */
69
- export function decideArbiterCheck(record: LeaseRecord, input: { arbiter: string; standby: string; arbiterReachesHolder: boolean; now: number }):
69
+ export function decideArbiterCheck(record: LeaseRecord, input: { arbiter: string; standby: string; arbiterReachesHolder: boolean; now: number; promote?: boolean; promoteAfterChecks?: number }):
70
70
  { next: LeaseRecord; promoted: boolean; streak: number } {
71
71
  let next = recordView(record, input.arbiter, input.arbiterReachesHolder, input.now);
72
72
  const view = next.views?.[input.standby];
@@ -74,7 +74,7 @@ export function decideArbiterCheck(record: LeaseRecord, input: { arbiter: string
74
74
  const dual = !input.arbiterReachesHolder && standbyDown && record.holder !== input.standby;
75
75
  const streak = dual ? (record.dualUnreachableStreak ?? 0) + 1 : 0;
76
76
  next = { ...next, dualUnreachableStreak: streak, lastCheckAt: input.now };
77
- if (streak < PROMOTE_AFTER_CHECKS) return { next, promoted: false, streak };
77
+ if (input.promote === false || streak < (input.promoteAfterChecks ?? PROMOTE_AFTER_CHECKS)) return { next, promoted: false, streak };
78
78
  return {
79
79
  next: { holder: input.standby, generation: record.generation + 1, acquiredAt: input.now, renewedAt: input.now, ttlSeconds: record.ttlSeconds, views: {}, dualUnreachableStreak: 0, lastCheckAt: input.now, promotedFrom: record.holder },
80
80
  promoted: true, streak,
@@ -0,0 +1,126 @@
1
+ // Read-only inventory of work tied to the current HQ host before moving it.
2
+ import { spawnSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import { homedir, hostname } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { inventoryCrontab, listSchedules, openSchedulesDb, parseCronLine } from '../domains/schedule-registry.js';
7
+ import { cronLaunchedActionsWithFence, listAllLoops, type AllLoopEntry, type LoopRegistryOptions } from '../loops/registry.js';
8
+ import { defaultSshRunner, type SshRunner } from './lease.js';
9
+ import { FENCE_ROLES } from './hq.js';
10
+
11
+ type Measurement<T> = { status: 'measured'; value: T } | { status: '못 잼'; reason: string };
12
+ interface MovePlan {
13
+ to: string;
14
+ from: string | null;
15
+ unfencedCron: Measurement<Array<{ raw: string; cron: string; command: string }>>;
16
+ hostLoops: Measurement<AllLoopEntry[]>;
17
+ targetHost: Measurement<{ exists: boolean; name: string | null }>;
18
+ nexusLaunchd: Measurement<{ present: boolean; measuredOn: 'local' }>;
19
+ }
20
+
21
+ type CommandResult = { status: number | null; stdout: string; stderr: string };
22
+ interface MovePlanDeps {
23
+ crontab?: () => CommandResult;
24
+ loops?: typeof listAllLoops;
25
+ loopOptions?: LoopRegistryOptions;
26
+ ssh?: SshRunner;
27
+ launchctl?: () => CommandResult;
28
+ localHost?: () => string;
29
+ hostFile?: string;
30
+ }
31
+
32
+ const measured = <T>(value: T): Measurement<T> => ({ status: 'measured', value });
33
+ const unknown = <T>(reason: string): Measurement<T> => ({ status: '못 잼', reason });
34
+ const run = (command: string, args: string[]): CommandResult => {
35
+ const r = spawnSync(command, args, { encoding: 'utf8', timeout: 15_000, env: process.env });
36
+ return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? String(r.error ?? '') };
37
+ };
38
+ const message = (error: unknown) => error instanceof Error ? error.message : String(error);
39
+
40
+ // Only known HQ jobs qualify by script name; a random scripts/*.ts is not HQ work.
41
+ const HQ_SCRIPTS = new Set([
42
+ 'steward.ts', 'draft-cleanup.ts', 'test-diet.ts', 'contract-coordinator.ts',
43
+ 'market-posture.ts', 'mission-request-judge.ts',
44
+ ]);
45
+ function isHqAction(script: string): boolean {
46
+ if (/(?:^|\/)(?:eln|elanous|elanous\.mjs)$/i.test(script)) return true;
47
+ if (/(?:^|\/)\.elanous(?:\/|$)/i.test(script)) return true;
48
+ const match = script.match(/(?:^|\/)scripts\/([^/\s]+\.ts)$/);
49
+ return !!match && HQ_SCRIPTS.has(match[1]!);
50
+ }
51
+
52
+ export function hqMovePlan(to: string, deps: MovePlanDeps = {}): MovePlan {
53
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/.test(to) || to.includes('..')) throw new Error('hq move-plan: invalid host');
54
+ let unfencedCron: MovePlan['unfencedCron'];
55
+ let hostLoops: MovePlan['hostLoops'];
56
+ let localName: string | undefined;
57
+ let localHostError: string | undefined;
58
+ try {
59
+ const value = readFileSync(deps.hostFile ?? join(homedir(), '.elanous-hq', 'host'), 'utf8');
60
+ const name = value.endsWith('\n') ? value.slice(0, -1) : value;
61
+ if (!name || name !== name.trim() || /[\r\n]/.test(name)) throw new Error('invalid local HQ host file');
62
+ localName = name;
63
+ } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') localHostError = message(error); }
64
+ const from = deps.localHost?.() ?? localName ?? null;
65
+ let text = '';
66
+ try {
67
+ const r = (deps.crontab ?? (() => run('crontab', ['-l'])))();
68
+ if (r.status !== 0 && !(r.status === 1 && /no crontab for /i.test(r.stderr))) {
69
+ throw new Error(r.stderr.trim() || `crontab -l exit=${r.status}`);
70
+ }
71
+ text = r.status === 0 ? r.stdout : '';
72
+ const rows = text.split('\n').map(raw => ({ raw, parsed: parseCronLine(raw) }))
73
+ .filter((row): row is { raw: string; parsed: { cron: string; command: string } } => row.parsed !== null);
74
+ unfencedCron = measured(rows.filter(({ parsed }) =>
75
+ cronLaunchedActionsWithFence(parsed.command).some(action =>
76
+ !!action.script && !(FENCE_ROLES as readonly string[]).includes(action.fenceRole ?? '')
77
+ && isHqAction(action.script)))
78
+ .map(({ raw, parsed }) => ({ raw, cron: parsed.cron, command: parsed.command })));
79
+ } catch (error) { unfencedCron = unknown(message(error)); }
80
+ try {
81
+ if (unfencedCron.status !== 'measured') throw new Error(unfencedCron.reason);
82
+ // The registry accepts an already-inventoried schedule set. Never open the production DB.
83
+ const db = openSchedulesDb(':memory:');
84
+ let schedules: ReturnType<typeof listSchedules>;
85
+ try { inventoryCrontab(db, { crontab: text }); schedules = listSchedules(db); }
86
+ finally { db.close(); }
87
+ if (localHostError) throw new Error(localHostError);
88
+ if (!from) throw new Error('local HQ host identity not confirmed (~/.elanous-hq/host missing)');
89
+ // Cron rows use os.hostname(); match that label only after confirming this machine's HQ identity.
90
+ const names = new Set([from, hostname()]);
91
+ hostLoops = measured((deps.loops ?? listAllLoops)({ ...deps.loopOptions, schedules })
92
+ .filter(loop => !!loop.host && names.has(loop.host)));
93
+ } catch (error) { hostLoops = unknown(message(error)); }
94
+ let targetHost: MovePlan['targetHost'];
95
+ try {
96
+ const r = (deps.ssh ?? defaultSshRunner)(to,
97
+ 'if [ -f "$HOME/.elanous-hq/host" ]; then printf "present\\n"; cat "$HOME/.elanous-hq/host"; elif [ ! -e "$HOME/.elanous-hq/host" ]; then printf "absent\\n"; else exit 2; fi');
98
+ if (r.status !== 0) throw new Error(r.stderr.trim() || `ssh exit=${r.status}`);
99
+ const [presence, ...lines] = r.stdout.split('\n');
100
+ if (presence === 'absent') targetHost = measured({ exists: false, name: null });
101
+ else if (presence === 'present' && lines.join('\n').trim() && !/[\r\n]/.test(lines.join('\n').trim()))
102
+ targetHost = measured({ exists: true, name: lines.join('\n').trim() });
103
+ else throw new Error('target host file unreadable or empty');
104
+ } catch (error) { targetHost = unknown(message(error)); }
105
+ let nexusLaunchd: MovePlan['nexusLaunchd'];
106
+ try {
107
+ const r = (deps.launchctl ?? (() => run('launchctl', ['list', 'com.elanous.nexus'])))();
108
+ if (r.status === 0) nexusLaunchd = measured({ present: true, measuredOn: 'local' });
109
+ else if (r.status !== null && /Could not find service|service not found|not found/i.test(r.stderr)) nexusLaunchd = measured({ present: false, measuredOn: 'local' });
110
+ else throw new Error(r.stderr.trim() || `launchctl exit=${r.status}`);
111
+ } catch (error) { nexusLaunchd = unknown(message(error)); }
112
+ return { to, from, unfencedCron, hostLoops, targetHost, nexusLaunchd };
113
+ }
114
+
115
+ export function formatMovePlan(plan: MovePlan): string {
116
+ const lines = [`hq move-plan: ${plan.from ?? '못 잼'} → ${plan.to} (read-only)`];
117
+ const section = <T>(name: string, result: Measurement<T>, render: (value: T) => string[]) => {
118
+ lines.push(`${name}: ${result.status === 'measured' ? '' : `못 잼 (${result.reason})`}`.trimEnd());
119
+ if (result.status === 'measured') lines.push(...render(result.value));
120
+ };
121
+ section('① unfenced elanous crontab', plan.unfencedCron, rows => rows.length ? rows.map(row => ` ${row.raw}`) : [' none']);
122
+ section('② local host loops (loop status --all)', plan.hostLoops, rows => rows.length ? rows.map(row => ` ${row.id} · ${row.host} · ${row.title}`) : [' none']);
123
+ section('③ target ~/.elanous-hq/host', plan.targetHost, host => [` ${host.exists ? host.name : 'absent'}`]);
124
+ section('④ local launchd com.elanous.nexus', plan.nexusLaunchd, service => [` ${service.present ? 'present' : 'absent'} (measured on local machine)`]);
125
+ return lines.join('\n');
126
+ }