elanous 0.2.34 → 0.2.35

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 (611) 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/{L04y73dlwKeTW-GetaTF3 → T1mBQqmkwKDLGtyisDCOd}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{5014f970-22bef0b5210f7d60.js → 0368f25d-ad84af92fefee68f.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1165.bad13fc9151a7288.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/13.02a07436dec15f89.js +32 -0
  7. package/apps/pwa/out/_next/static/chunks/{2124.ed5bf480cea7f85b.js → 1530.0e612369b6fd7ea0.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1540.af38731fa7c4a3e0.js +41 -0
  9. package/apps/pwa/out/_next/static/chunks/1674.2a65fd5668e1cc5f.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1850.f2b470ba5a471da0.js +24 -0
  11. package/apps/pwa/out/_next/static/chunks/193.62da94cd0b88323b.js +74 -0
  12. package/apps/pwa/out/_next/static/chunks/1975.260f6d249fac5f7c.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/1982-67cd8dfe8682325f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/1993-c0007b7ad0c0ef41.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1999-bb415351c8a2846c.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/1c735de4.f993a30bbd3c57c4.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/1dc5a567.9b092bfae3d7099b.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/{8368.014caa8de2f5af34.js → 2018.a741297bce759490.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/2085.8f950b4bda1cbbc7.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/2159-6179a961376956b9.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/216.b5c2a421aa1d28a5.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/2251.49fbe369f74c372c.js +148 -0
  23. package/apps/pwa/out/_next/static/chunks/2286-2939577360d3dcc0.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2625.5d870aa999f33db1.js +34 -0
  25. package/apps/pwa/out/_next/static/chunks/2668.34d21539e62bf111.js +77 -0
  26. package/apps/pwa/out/_next/static/chunks/2833-0fe47af99ed13c29.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/2930.d0edb4f108787ef7.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2973.77318e7d85f59477.js +62 -0
  29. package/apps/pwa/out/_next/static/chunks/3103.0df8da390948bb84.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/3123.2bee9ba15d320bd2.js +90 -0
  31. package/apps/pwa/out/_next/static/chunks/3162-6cf652bf7124d8cf.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{1644.35dd25240ae24e4b.js → 3408.21d1570602f4b6c0.js} +2 -2
  33. package/apps/pwa/out/_next/static/chunks/3577-7340009f7007a795.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{3121.779fafe3e44612c5.js → 3589.5444a06bcf6e775c.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/359-2250789a2d3499e7.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/3618.f514792d6c59d7fd.js +36 -0
  37. package/apps/pwa/out/_next/static/chunks/3669.343f9119aa276001.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{9131-fce7256c4180c4a5.js → 3677-4215f826f6aa68e5.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/3736.c2efad489604cc3b.js +79 -0
  40. package/apps/pwa/out/_next/static/chunks/3817.f9aeab2bb6675d73.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/3f7c4ee5.3dde54f01a816613.js +122 -0
  42. package/apps/pwa/out/_next/static/chunks/4182-0b9b1251c29cc0cf.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4346.8601952e73bf5803.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/{6053-d893974de92e6198.js → 4441-875b97f5821ed58a.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/4582.bd68683c076649e3.js +286 -0
  46. package/apps/pwa/out/_next/static/chunks/4605-6e623f55aec77422.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4607.5cc27468628eb4bf.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/4627.f07508b3936fd6c0.js +24 -0
  49. package/apps/pwa/out/_next/static/chunks/{1895-60ed0e341c658bd4.js → 4705-194e64e47ca0fbdb.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/{5576-82d3703636d5c4b9.js → 4718-17d2af636796a330.js} +3 -3
  51. package/apps/pwa/out/_next/static/chunks/4747-c22262c093e46807.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/4775-178215aed0daba61.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/4815-6ffb8c3c991cc45d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4834-beca8b47ad29a898.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5023-fb4802b6d2492b02.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/{7431.3051e9b8a4372d2d.js → 5101.e32282718f99ed6f.js} +2 -2
  57. package/apps/pwa/out/_next/static/chunks/5154.a118f3957ed14e36.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/5210-a2e0015e3d4d5d9c.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/5356.84ac712cc15928bf.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/5401.22352141b6fd336e.js +23 -0
  61. package/apps/pwa/out/_next/static/chunks/{2987-30cf9c2eb7b8bdd5.js → 5483-dd5d7697f6bdb106.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/5637.281e5dec4ea6ecf1.js +94 -0
  63. package/apps/pwa/out/_next/static/chunks/5662.02eaa029af05ad46.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/56d372f2.681ab4ec4c998fae.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/5760.22456f433c6f42c0.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/{7386.5d8e7e42e818dfb6.js → 5852.db1f7449f4f74612.js} +2 -2
  67. package/apps/pwa/out/_next/static/chunks/5891-0f11a7d7362a9072.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5943.264d14a44ca32513.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/604-1b3c39df083c02aa.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/6121-5f97072330738caf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/6193.07d8ca5af4b09086.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6203-b3fea3a60d69ff14.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/{3110.0a7065f25c841818.js → 6236.e18cced2d5ba6afb.js} +3 -3
  74. package/apps/pwa/out/_next/static/chunks/6286.d638fb5953e2e757.js +8 -0
  75. package/apps/pwa/out/_next/static/chunks/630.fe5a46428d63b013.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6301.ae0245f507e4395e.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/6673.fbbe5699f6ea5991.js +27 -0
  78. package/apps/pwa/out/_next/static/chunks/6777-f6d301efb5de828d.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6786.423f24c88de034c7.js +93 -0
  80. package/apps/pwa/out/_next/static/chunks/6854-cef64a948c5e122d.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/6866-79aa5b4e47cac6aa.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/6b89c8bd.24df687a820500b3.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/7037-fe27f001db30d323.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/7080.2d826860534fd88e.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7118.8731be28c3ebb14b.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7196.44241bc1df3e26fe.js +149 -0
  87. package/apps/pwa/out/_next/static/chunks/7235.c7c4e6cb5c693c2d.js +104 -0
  88. package/apps/pwa/out/_next/static/chunks/{7005.1623d2aea78eb85f.js → 7385.50a308a477bdfc21.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/{6086.913a69e6fa3e4a8b.js → 7644.17e37ba6f1d4226e.js} +4 -4
  90. package/apps/pwa/out/_next/static/chunks/7706-58bfb1546637f217.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7839.5b91455338c9fd3f.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7857-31f13ee96f4011aa.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/81.f74cb60312fe4001.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/8219.baa715d1deee80e2.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/8256.e3ac8421a71f82f4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/8310-444a6a895c402f12.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/8423.f7dda8aca4fcaae5.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{7193-cf355cca5ba6c5d5.js → 8437-8d5492094b166cfa.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{2554.3b5e200f5f5cfcd6.js → 8444.4c26b379ca2b9596.js} +4 -4
  100. package/apps/pwa/out/_next/static/chunks/8496.75649af8652c74f4.js +226 -0
  101. package/apps/pwa/out/_next/static/chunks/8557-e7898769499cd56f.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8576.65bb654b524f3614.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8609.558ce2f8f9aea2be.js +64 -0
  104. package/apps/pwa/out/_next/static/chunks/8614.caec96cfb91f4ae7.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/{2547.dd27303624cb4da8.js → 8637.47f5b35afcae5e6b.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/8714.ff0b7ce3ec3e3ae2.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8732.367f44a916202353.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/9141-7fd1f73a5400bdc2.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/921.c4267d0d69a65404.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/{9550.d5f76828f972058c.js → 9214.63a510d39178e997.js} +2 -2
  111. package/apps/pwa/out/_next/static/chunks/9261.3962c3321b047691.js +115 -0
  112. package/apps/pwa/out/_next/static/chunks/936-26efd1a0efdd44d3.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/{8548.036c344973968847.js → 938.66ad1ef0e5abae1c.js} +1 -1
  114. package/apps/pwa/out/_next/static/chunks/{3414.172ad4ca2d9b08e8.js → 9512.5a76207a730e0491.js} +2 -2
  115. package/apps/pwa/out/_next/static/chunks/9627-4b9efe616b27e2e2.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/967-2aa39b2d04b4c690.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9734.40eb1e558c29ccea.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9908.3aa54f7a586a73dc.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/{6740.43de73fc427c9fe2.js → 9952.5daf5f94063a2b3e.js} +1 -1
  120. package/apps/pwa/out/_next/static/chunks/9954-ca8d29752bdbb45a.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/9974-507fa67cb94066dd.js +1 -0
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-ce9e00f7fb45506a.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-fa59d6d17daa3389.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-53f68bbe7bd4b774.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-1c500fbb9f4a7a04.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-99d72673aa5c307c.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-d2d5adc1e9484c88.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c607c5aae637f1cc.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-fa8c23596d555c0f.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-bdab8c5e855c8416.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-406b47745b53f269.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-a4276386b7051f50.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-8a26bab9ad375f25.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-775f48aa1dc7a2d5.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-1c4dd5f959d0c558.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-c48938a8377c9f70.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-ce9e00f7fb45506a.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-c6e7905859425aa9.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-c280f4252452e830.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-cf8c57498f20970a.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-ca3a17caf409fc83.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-d7bff0f0905a662c.js → page-26e6c18a1a750cf6.js} +1 -1
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-3abc872cc6aeee39.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-f7481b7b2174fe3d.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1ae7ef8077680337.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-47c5d4db587266bd.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-415f1e243c7ccb13.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-73f380f17b76f95c.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-2991073ad66ee2d9.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-1f5a60f70cb4dd0c.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fbad60d1e358fb2c.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-bac7dee94e5eae4a.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-398047d6949e8ca5.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-2d33a2231540ca89.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-5bf1e6bf70bcb96c.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-ba59c19376a0917d.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-111f187139add30f.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-acaae197d5be5779.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0df20b0f546a0d7d.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-ce9e00f7fb45506a.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-27e1202d24b8fe35.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-bda78c50a78363e5.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-da592691293d9c9f.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f832994317a2e9f0.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/{page-df3445b207ceceab.js → page-dc0a532f5b9196ec.js} +1 -1
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-cfccbbeae0473404.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-527e2073af84c81b.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-bac86e9112671357.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6d59dff410e9d76a.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-53a61f8be09f02d2.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-db8f559e763c17f9.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c02aa4cc66455093.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{9a8cb466.cc9fc9e6e46038aa.js → b2f6ba4d.fb56fdbbf798df33.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/b6705a4f-b9a9047de9eecfd0.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/{4fa33ebd.21c1bd5b294496e6.js → cbe38a15.5a032598e39970c6.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/{abb4b98b.163e0f56e84ae35d.js → d06f9160.a3d9062f95de8ad7.js} +1 -1
  177. package/apps/pwa/out/_next/static/chunks/{9b48c871.204d5d13e7809bd0.js → da5176c6.6773b6c5994e1d99.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/framework-50e6acebc630849b.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-7a1ae342d5ed75e6.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-app-7ad7d505ba1f92b4.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-02edba75232cb235.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-97d6ebae3e8eee4a.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-e986adaa429163e8.js +1 -0
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +22 -22
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +22 -22
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +22 -22
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +21 -21
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +21 -21
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +22 -22
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +21 -21
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +22 -22
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +22 -22
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +21 -21
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +22 -22
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +22 -22
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +22 -22
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +22 -22
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +21 -21
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +21 -21
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +22 -22
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +22 -22
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +22 -22
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +21 -21
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +21 -21
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +22 -22
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +22 -22
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +21 -21
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +21 -21
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +21 -21
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +22 -22
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +22 -22
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +21 -21
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +22 -22
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +22 -22
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +22 -22
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +22 -22
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +22 -22
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +21 -21
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +22 -22
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +22 -22
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +22 -22
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +22 -22
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +22 -22
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +22 -22
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +22 -22
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +22 -22
  277. package/bin/elanous.mjs +3 -2
  278. package/catalog/providers/opengateway.yaml +25 -0
  279. package/docs/goal-context/01-goal-authoring-checks.md +8 -0
  280. package/docs/goal-context/03-verification-menu.md +24 -0
  281. package/docs/goal-context/04-mock-scenarios.md +100 -0
  282. package/docs/goal-context/README.md +4 -0
  283. package/graphs/catalog/catalog.yaml +1 -1
  284. package/graphs/default-loop.yaml +57 -3
  285. package/graphs/document-loop.yaml +51 -3
  286. package/graphs/implement-loop.yaml +53 -4
  287. package/graphs/journey/ta-journey.yaml +11 -1
  288. package/graphs/journey/units/author-dialogue/author-dialogue.yaml +42 -0
  289. package/graphs/journey/units/author-dialogue/observe.ts +96 -0
  290. package/graphs/journey/units/author-dialogue/recipes.yaml +10 -0
  291. package/graphs/operate-loop.yaml +51 -3
  292. package/graphs/research-loop.yaml +137 -3
  293. package/graphs/test-diet/test-diet.yaml +3 -0
  294. package/package.json +1 -1
  295. package/scripts/coord-role-pack.ts +19 -6
  296. package/scripts/hq/standby-snapshot.ts +17 -12
  297. package/scripts/release-loop/docs-land-node.ts +21 -5
  298. package/scripts/release-loop/gate-baseline-receipt.ts +155 -0
  299. package/scripts/release-loop/gate-node.ts +181 -22
  300. package/scripts/release-loop/gate-partial.ts +65 -11
  301. package/scripts/release-loop/gate-radius-live.ts +177 -0
  302. package/scripts/release-loop/gate-radius-shadow.ts +1 -1
  303. package/scripts/release-loop/node-verdict.ts +12 -1
  304. package/scripts/release-loop/npm-publish-node.ts +66 -12
  305. package/scripts/release-loop/probe-pod-host-dirs-live.ts +5 -0
  306. package/scripts/release-loop/pwa-remote.ts +166 -0
  307. package/scripts/release-loop/pwa-scenarios-node.ts +19 -3
  308. package/scripts/release-loop/resume-release.ts +41 -4
  309. package/scripts/release-loop/unattended-release.ts +2 -0
  310. package/scripts/test-diet/lib.ts +6 -1
  311. package/scripts/test-diet/node.ts +194 -13
  312. package/scripts/ux-sim.ts +39 -0
  313. package/src/agent/self-ambient-cache.ts +8 -0
  314. package/src/agent-substrate/acp-reviewer.ts +39 -4
  315. package/src/agent-substrate/pr-reviewer.ts +17 -0
  316. package/src/agent-substrate/self-review-cli.ts +62 -13
  317. package/src/autopilot/pr-manager.ts +215 -36
  318. package/src/budget/run-usage-rollup.ts +1 -0
  319. package/src/chat/index.ts +0 -2
  320. package/src/chat/pickers/modal-runtime.ts +10 -27
  321. package/src/cli/agent-cli.ts +13 -2
  322. package/src/cli/doctor-cli.ts +4 -1
  323. package/src/cli/light-entry-route.ts +46 -6
  324. package/src/cli/pod-cli.ts +77 -3
  325. package/src/cli/pr-cli.ts +5 -2
  326. package/src/cli/pr-land-pod-shadow-job.ts +3 -0
  327. package/src/cli/release-checklist-read-cli.ts +79 -0
  328. package/src/cli/release-cli.ts +72 -102
  329. package/src/cli/remote-resolve.ts +1 -1
  330. package/src/cli/root-tui-entry.ts +48 -0
  331. package/src/cli/sim-cli.ts +22 -0
  332. package/src/cli/tasks-cli.ts +540 -31
  333. package/src/cli/usage-cli.ts +13 -11
  334. package/src/clipboard/history.ts +179 -13
  335. package/src/config.ts +8 -1
  336. package/src/dashboard/chat/acp-chat.ts +8 -0
  337. package/src/dashboard/index.ts +78 -10
  338. package/src/dashboard/render/theme-resolver.ts +33 -4
  339. package/src/domains/schedule-registry.ts +16 -2
  340. package/src/git-fs/idle-refresh.ts +76 -0
  341. package/src/git-fs/index.ts +18 -7
  342. package/src/git-fs/worktree.ts +42 -4
  343. package/src/graph-kinds/registry.ts +2 -2
  344. package/src/graph-runner/runner.ts +153 -10
  345. package/src/grok/credential.ts +10 -3
  346. package/src/harness/harness-cli-command.ts +112 -28
  347. package/src/harness/harness-incidents.ts +33 -0
  348. package/src/harness/launch-utterance-ledger.ts +208 -0
  349. package/src/impl-discipline/detector.ts +16 -4
  350. package/src/impl-discipline/system-prompt.ts +11 -0
  351. package/src/impl-discipline/types.ts +2 -2
  352. package/src/index.ts +496 -143
  353. package/src/intake-plane/already-built-hint.ts +4 -2
  354. package/src/intake-plane/requirement-funnel.ts +29 -1
  355. package/src/light-cli.ts +27 -11
  356. package/src/llm/opengateway-models.ts +11 -0
  357. package/src/llm/provider-credentials.ts +14 -0
  358. package/src/llm/provider-detect.ts +8 -1
  359. package/src/llm/runtime-model-family.ts +2 -1
  360. package/src/llm.ts +213 -56
  361. package/src/model-tier/llm-tier-map.ts +15 -1
  362. package/src/native-tool-catalog.ts +15 -1
  363. package/src/nexus/api/loop-resources.ts +13 -1
  364. package/src/oauth/codex.ts +7 -0
  365. package/src/ops/overview-collect.ts +18 -3
  366. package/src/ops/overview.ts +20 -7
  367. package/src/pr-tool/census.ts +261 -27
  368. package/src/pty-shell/pty-manifest.ts +7 -0
  369. package/src/release-loop/checklist.ts +24 -0
  370. package/src/release-loop/landed-but-yellow.ts +129 -5
  371. package/src/release-loop/light-rc.ts +1 -0
  372. package/src/release-loop/rehearsal.ts +2 -2
  373. package/src/repl/index.ts +174 -17
  374. package/src/repl/ops-msg-bridge.ts +32 -0
  375. package/src/self-dev/ask-launch-flow.ts +44 -5
  376. package/src/self-dev/cli-help-text.ts +17 -0
  377. package/src/self-dev/dev-cli.ts +11 -9
  378. package/src/self-dev/draft-residue.ts +37 -1
  379. package/src/self-dev/draft-sweep.ts +137 -11
  380. package/src/self-dev/draft-triage-rules.ts +107 -0
  381. package/src/self-dev/fleet-reduce.ts +3 -2
  382. package/src/self-dev/graph-front-nodes.ts +112 -0
  383. package/src/self-dev/graph-journey-nodes.ts +1 -0
  384. package/src/self-dev/harness-run-cli.ts +3 -15
  385. package/src/self-dev/orchestrate-cli-text.ts +10 -0
  386. package/src/self-dev/review-provider-fallback.ts +35 -1
  387. package/src/self-dev/self-orchestrate-runtime.ts +1 -11
  388. package/src/self-dev/shard-aggregate.ts +42 -5
  389. package/src/self-dev/shard-run.ts +8 -1
  390. package/src/self-implement/blocked-draft-policy.ts +20 -0
  391. package/src/self-implement/classify-shadow.ts +3 -7
  392. package/src/self-implement/doctor-visit.ts +18 -1
  393. package/src/self-implement/gate-baseline.ts +56 -17
  394. package/src/self-implement/gate-cli.ts +1 -0
  395. package/src/self-implement/gate-remote.ts +31 -20
  396. package/src/self-implement/goal-author-decompose.ts +275 -47
  397. package/src/self-implement/goal-author.ts +250 -0
  398. package/src/self-implement/goal-elaborate.ts +295 -0
  399. package/src/self-implement/goal-refine.ts +270 -0
  400. package/src/self-implement/graph-overlay-yaml.ts +14 -3
  401. package/src/self-implement/orchestrator.ts +152 -45
  402. package/src/self-implement/rework-budget-judgment.ts +5 -8
  403. package/src/self-implement/run-stop.ts +10 -1
  404. package/src/self-implement/running-runs.ts +29 -1
  405. package/src/self-implement/scope-adjacency.ts +113 -0
  406. package/src/self-implement/seams.ts +127 -24
  407. package/src/self-implement/stop-autoheal.ts +87 -1
  408. package/src/session-runtime/index.ts +1 -1
  409. package/src/setup/llm-env-detect.ts +5 -0
  410. package/src/skills/tools/agent.ts +140 -14
  411. package/src/skills/tools/msg-ask.ts +100 -0
  412. package/src/skills/tools/ops-ledger.ts +2 -1
  413. package/src/steward/hold-hygiene.ts +24 -6
  414. package/src/steward/triage-budget.ts +194 -0
  415. package/src/steward/triage.ts +20 -3
  416. package/src/task-agent/actions.ts +69 -1
  417. package/src/task-agent/green-sweep.ts +4 -0
  418. package/src/task-agent/live-moves.ts +160 -4
  419. package/src/task-agent/parent-host.ts +123 -3
  420. package/src/task-agent/shadow.ts +7 -1
  421. package/src/task-agent/task-hand.ts +54 -41
  422. package/src/task-agent/tier-split.ts +127 -0
  423. package/src/task-agent/work-order-build.ts +2 -0
  424. package/src/task-agent/work-order.ts +3 -2
  425. package/src/task-orchestrator/generator-prompt.ts +60 -11
  426. package/src/task-orchestrator/surfaces/pod-account-broker.ts +33 -0
  427. package/src/task-orchestrator/surfaces/pod-child-providers.ts +1 -1
  428. package/src/task-orchestrator/surfaces/pod-command-job.ts +45 -7
  429. package/src/task-orchestrator/surfaces/pod-image-source.ts +89 -1
  430. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -8
  431. package/src/task-orchestrator/surfaces/pod-manifest-test-normalize.ts +8 -2
  432. package/src/task-orchestrator/surfaces/pod-provenance.ts +76 -0
  433. package/src/task-orchestrator/surfaces/pod-source-receive.ts +18 -0
  434. package/src/task-orchestrator/surfaces/pod-warm-provenance.ts +81 -0
  435. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +6 -2
  436. package/src/task-orchestrator/surfaces/pod-warm-worker-protocol.ts +3 -0
  437. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +37 -5
  438. package/src/task-orchestrator/surfaces/self-implement-pod.ts +368 -36
  439. package/src/task-orchestrator/surfaces/self-implement.ts +23 -0
  440. package/src/tool-runtime/ops-ledger-runtimes.ts +10 -2
  441. package/src/tui/README.md +2 -0
  442. package/src/tui/chat/background-job-status.ts +54 -0
  443. package/src/tui/chat/log-view.ts +80 -0
  444. package/src/tui/entry.ts +406 -0
  445. package/src/tui/input/composer.ts +144 -0
  446. package/src/tui/seat-mail-boot.ts +59 -0
  447. package/src/tui/slash/compact.ts +100 -0
  448. package/src/tui/slash/new-session.ts +35 -0
  449. package/src/tui-client/local-chat-turn.ts +239 -0
  450. package/src/tui-client/local-harness-command.ts +63 -0
  451. package/src/tui-client/local-provider-command.ts +184 -0
  452. package/src/user-config.ts +4 -2
  453. package/src/ux-sim/event-monkey.ts +378 -0
  454. package/src/ux-sim/fake-bin.ts +42 -7
  455. package/src/ux-sim/fault-nextmove-dispatch.ts +266 -0
  456. package/src/ux-sim/fault-pod-codex-503.ts +211 -0
  457. package/src/ux-sim/fault-scope-escape-lift.ts +189 -0
  458. package/src/version/packed-revision.json +1 -1
  459. package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +0 -79
  460. package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +0 -94
  463. package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +0 -23
  465. package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +0 -115
  467. package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +0 -8
  468. package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/3001-5a52788c813b6113.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/3103.84470c8445962f04.js +0 -34
  479. package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/3411-f87314c6c66ebc73.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/3498.cf2a15a8a9e5b926.js +0 -226
  485. package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/3819.163d4777f0a597f6.js +0 -32
  487. package/apps/pwa/out/_next/static/chunks/3837.722e053fb9f321ad.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +0 -104
  490. package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +0 -286
  491. package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +0 -149
  492. package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +0 -148
  493. package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +0 -64
  494. package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +0 -74
  497. package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/5028-f0722e6114c313e7.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/5249.96a848656b582a93.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +0 -62
  504. package/apps/pwa/out/_next/static/chunks/5446.edf141cff9ef647d.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/5489.ec2dd1b5272a19dc.js +0 -24
  506. package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +0 -77
  517. package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +0 -90
  520. package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +0 -1
  524. package/apps/pwa/out/_next/static/chunks/7448.478958ffbbba0ac0.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/7562.948255cbc3e79d33.js +0 -41
  527. package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +0 -1
  529. package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +0 -27
  530. package/apps/pwa/out/_next/static/chunks/7952.b192c31b6a0e00cc.js +0 -1
  531. package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +0 -36
  533. package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +0 -1
  538. package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +0 -1
  539. package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +0 -1
  541. package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/8817.61bb7ffbba502ecb.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +0 -93
  547. package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +0 -24
  549. package/apps/pwa/out/_next/static/chunks/9739.8d5f36ac67a3b236.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/app/field/page-4fb31eb476dcbad3.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-398c0faa173203f7.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +0 -122
  603. package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/ffdc3a04.b9c23c1b28dd4a96.js +0 -1
  605. package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +0 -1
  611. /package/apps/pwa/out/_next/static/{L04y73dlwKeTW-GetaTF3 → T1mBQqmkwKDLGtyisDCOd}/_ssgManifest.js +0 -0
@@ -0,0 +1,177 @@
1
+ /** A hard rehearsal's immutable gate evidence may select the next release's cut tests, but never authorizes a carry. */
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { changedFilesBetween, planPartialRegate, testFileOfId, type PartialPlan } from './gate-partial.js';
6
+ import { CODE_FILE, gitClosureTree, testsReachingChanged } from './gate-closure.js';
7
+ import { versionOnlyBumps } from './gate-radius-shadow.js';
8
+ import { gateAssignableTestsAtCommit, gateBaselineReceipt, type GateBaselineReceipt } from './gate-baseline-receipt.js';
9
+ import { parsePodPool, remoteImageId } from '../../src/task-orchestrator/surfaces/pod-pool.js';
10
+ import { podCommandImageFor } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
11
+ import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
12
+
13
+ const SHA = /^[0-9a-f]{40}$/i;
14
+ const DIGEST = /^sha256:[0-9a-f]{64}$/i;
15
+ const TEST = /\.test\.(?:tsx?|jsx?|mts|cts)$/;
16
+
17
+ export function hardRehearsalGateVersion(version: string, runId: string): string {
18
+ const id = /^hard-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(runId)?.[1];
19
+ if (!/^\d+\.\d+\.\d+$/.test(version) || !id) throw new Error('invalid hard rehearsal gate identity');
20
+ return `${version}-rc.${BigInt(`0x${id.replaceAll('-', '')}`).toString(10)}`;
21
+ }
22
+
23
+ export function hardRehearsalGateArgs(version: string, runId: string, commit: string, previous: string, pool: string): string[] {
24
+ return ['scripts/release-loop/gate-node.ts', '--commit', commit, '--version', hardRehearsalGateVersion(version, runId),
25
+ '--baseline-version', previous, '--executor', 'pod', '--pod-pool', pool, '--pod-shards', '12', '--rehearsal-baseline', '--json'];
26
+ }
27
+
28
+ export interface LiveRadiusDecision { plan: PartialPlan | null; reason: string; rehearsalRunId?: string }
29
+
30
+ /** Read the image ID on every candidate host before deciding. A later Job must still prove its actual image. */
31
+ export function preflightGateImageDigest(pool: string): string | null {
32
+ try {
33
+ const image = podCommandImageFor([], true);
34
+ const members = parsePodPool(pool);
35
+ if (!members.length) return null;
36
+ const ids = members.map((member) => {
37
+ if (member.sshHost) return remoteImageId(member, image);
38
+ const result = spawnSync('docker', ['image', 'inspect', image, '--format', '{{.Id}}'], { encoding: 'utf8', timeout: 30_000, maxBuffer: SPAWN_SYNC_MAX_BUFFER });
39
+ return result.status === 0 ? result.stdout.trim() : null;
40
+ });
41
+ return ids.every((id) => id && DIGEST.test(id) && id === ids[0]) ? ids[0]! : null;
42
+ } catch { return null; }
43
+ }
44
+
45
+ /** The current sweep ID and exact root inventory prevent stale Pod logs from proving a new partial run's image. */
46
+ export function partialGateImagesMatch(root: string, version: string, digest: string, commit: string,
47
+ sweep: { id: string; shards: number; files: readonly string[] } | undefined): boolean {
48
+ if (!SHA.test(commit) || !sweep || !/^[0-9a-f-]{36}$/i.test(sweep.id)
49
+ || !Number.isSafeInteger(sweep.shards) || sweep.shards < 1) return false;
50
+ try {
51
+ const dir = join(root, 'release', version, 'gate-logs', 'cut-partial');
52
+ const names = readdirSync(dir).filter((name) => /^pod-\d+(?:-[\w-]+)?\.json$/.test(name));
53
+ const allRows = names.map((name) => ({ name, ...JSON.parse(readFileSync(join(dir, name), 'utf8')) as {
54
+ commit?: string; sweepId?: string; shardCount?: number; files?: unknown; imageDigest?: string;
55
+ attemptId?: string; parentAttempt?: string } }));
56
+ const rows = allRows.filter((row) => row.sweepId === sweep.id);
57
+ const roots = rows.filter((row) => /^pod-\d+\.json$/.test(row.name));
58
+ if (roots.length !== sweep.shards || roots.some((row) => row.shardCount !== sweep.shards
59
+ || row.commit !== commit || !Array.isArray(row.files) || !row.attemptId || row.imageDigest !== digest)) return false;
60
+ if (allRows.some((row) => /^pod-\d+\.json$/.test(row.name)
61
+ && row.commit === commit && row.sweepId !== sweep.id)) return false;
62
+ if (allRows.some((row) => row.commit === commit && !row.sweepId)) return false;
63
+ const indices = roots.map((row) => Number(/^pod-(\d+)\.json$/.exec(row.name)![1]));
64
+ if (new Set(indices).size !== sweep.shards || indices.some((index) => index < 0 || index >= sweep.shards)) return false;
65
+ const files = roots.flatMap((row) => row.files as string[]);
66
+ if (files.some((file) => typeof file !== 'string')
67
+ || JSON.stringify(files.sort()) !== JSON.stringify([...sweep.files].sort())) return false;
68
+ const attempts = new Set(roots.map((row) => row.attemptId));
69
+ let pending = allRows.filter((row) => !/^pod-\d+\.json$/.test(row.name));
70
+ for (let count = 0; pending.length && count < allRows.length; count++) {
71
+ const connected = pending.filter((row) => row.parentAttempt && attempts.has(row.parentAttempt));
72
+ if (!connected.length) break;
73
+ if (connected.some((row) => row.sweepId !== sweep.id || row.commit !== commit
74
+ || !row.attemptId || row.imageDigest !== digest)) return false;
75
+ for (const row of connected) attempts.add(row.attemptId);
76
+ pending = pending.filter((row) => !connected.includes(row));
77
+ }
78
+ return !pending.some((row) => row.sweepId === sweep.id || (row.commit === commit && !row.sweepId));
79
+ } catch { return false; }
80
+ }
81
+
82
+ /** Every missing or mismatched proof returns a full gate. The caller uses this only when no explicit resume plan exists. */
83
+ export function planHardRehearsalRadius(input: { root: string; repo: string; version: string; forCommit: string;
84
+ graphPins: { graphSha: string; recipeSha: string } | null; envFingerprint: string;
85
+ imageDigest: string | null }): LiveRadiusDecision {
86
+ const full = (reason: string): LiveRadiusDecision => ({ plan: null, reason });
87
+ if (!/^\d+\.\d+\.\d+$/.test(input.version) || !SHA.test(input.forCommit)) return full('invalid release identity');
88
+ if (!input.graphPins || !/^[0-9a-f]{64}$/i.test(input.envFingerprint) || !input.imageDigest || !DIGEST.test(input.imageDigest))
89
+ return full('current graph, environment or Pod image identity unmeasured');
90
+ try {
91
+ const stamps = ['hard-5h', 'hard-mid'].map((step) => {
92
+ const path = join(input.root, 'release', input.version, 'rehearsal', `${step}.json`);
93
+ if (!existsSync(path)) return null;
94
+ const stamp = JSON.parse(readFileSync(path, 'utf8')) as {
95
+ version?: string; step?: string; outcome?: string; sha?: string; endedAt?: string; hardGateRunId?: string;
96
+ hardGate?: { commit?: string; executor?: string; outcome?: string; introduced?: unknown[] };
97
+ };
98
+ return { step, stamp, ended: Date.parse(stamp.endedAt ?? '') };
99
+ }).filter((row): row is NonNullable<typeof row> => row !== null);
100
+ if (!stamps.length) return full('no clean hard rehearsal receipt');
101
+ if (stamps.some((row) => !Number.isFinite(row.ended))) return full('hard rehearsal completion time unmeasured');
102
+ stamps.sort((a, b) => b.ended - a.ended);
103
+ if (stamps.length > 1 && stamps[0]!.ended === stamps[1]!.ended) return full('hard rehearsal order ambiguous');
104
+ for (const { step, stamp } of stamps.slice(0, 1)) {
105
+ if (stamp.version !== input.version || stamp.step !== step || stamp.outcome !== 'ok' || !stamp.sha || !SHA.test(stamp.sha)
106
+ || stamp.hardGate?.outcome !== 'ok' || stamp.hardGate.commit !== stamp.sha || stamp.hardGate.executor !== 'pod'
107
+ || !Array.isArray(stamp.hardGate.introduced) || stamp.hardGate.introduced.length)
108
+ return full('latest hard rehearsal is not clean');
109
+ let gateVersion: string;
110
+ try { gateVersion = hardRehearsalGateVersion(input.version, stamp.hardGateRunId ?? ''); }
111
+ catch { return full('hard rehearsal run identity invalid'); }
112
+ const dir = join(input.root, 'release', gateVersion);
113
+ const verdict = JSON.parse(readFileSync(join(dir, 'gate-verdict.json'), 'utf8')) as {
114
+ commit?: string; outcome?: string; baselineReceipt?: GateBaselineReceipt };
115
+ const failures = JSON.parse(readFileSync(join(dir, 'gate-failures.json'), 'utf8')) as {
116
+ commit?: string; failures?: string[]; errors?: string[] };
117
+ const receipt = verdict.baselineReceipt;
118
+ if (verdict.commit !== stamp.sha || verdict.outcome !== 'ok' || failures.commit !== stamp.sha
119
+ || !Array.isArray(failures.failures) || failures.failures.length !== 0
120
+ || !Array.isArray(failures.errors) || failures.errors.length !== 0
121
+ || !receipt || receipt.schema !== 1 || !receipt.valid || receipt.version !== gateVersion || receipt.commit !== stamp.sha
122
+ || receipt.graphSha !== input.graphPins.graphSha || receipt.recipeSha !== input.graphPins.recipeSha
123
+ || receipt.envFingerprint !== input.envFingerprint || receipt.imageDigest !== input.imageDigest.toLowerCase()
124
+ || !Number.isSafeInteger(receipt.files) || receipt.files < 1 || !Number.isSafeInteger(receipt.shards) || receipt.shards < 1)
125
+ return full('hard rehearsal receipt or current execution identity differs');
126
+ const recomputed = gateBaselineReceipt({ root: input.root, version: gateVersion, commit: stamp.sha,
127
+ fullSweep: true, graphPins: input.graphPins, envFingerprint: input.envFingerprint });
128
+ if (!recomputed.valid || recomputed.imageDigest !== receipt.imageDigest || recomputed.files !== receipt.files
129
+ || recomputed.shards !== receipt.shards) return full(`hard rehearsal Pod proof differs: ${recomputed.reason}`);
130
+ const ancestor = spawnSync('git', ['merge-base', '--is-ancestor', stamp.sha, input.forCommit], { cwd: input.repo, maxBuffer: SPAWN_SYNC_MAX_BUFFER });
131
+ if (ancestor.status !== 0 || ancestor.error) return full('hard rehearsal commit is not an ancestor');
132
+ const expectedFiles = gateAssignableTestsAtCommit(input.repo, stamp.sha);
133
+ const measuredFiles = readdirSync(join(dir, 'gate-logs', 'cut')).filter((name) => /^pod-\d+\.json$/.test(name))
134
+ .flatMap((name) => {
135
+ const row = JSON.parse(readFileSync(join(dir, 'gate-logs', 'cut', name), 'utf8')) as { files?: unknown };
136
+ return Array.isArray(row.files) ? row.files : [];
137
+ });
138
+ if (!expectedFiles || JSON.stringify(measuredFiles.sort()) !== JSON.stringify(expectedFiles)
139
+ || measuredFiles.length !== receipt.files)
140
+ return full('hard rehearsal file inventory differs from receipt');
141
+ const tree = gitClosureTree(input.repo, input.forCommit);
142
+ const tests = tree.files.filter((file) => TEST.test(file));
143
+ const changed = changedFilesBetween(input.repo, stamp.sha, input.forCommit);
144
+ const bumps = versionOnlyBumps(input.repo, stamp.sha, input.forCommit, input.version, changed);
145
+ const effective = changed.filter((file) => !bumps.includes(file));
146
+ // A version-only JSON/lock bump is still observable to tests that read it. Follow those literal readers and
147
+ // opaque tests before removing the bump from the ordinary code-only partial planner.
148
+ const versionEnvReaders = bumps.includes('package.json')
149
+ ? tree.files.filter((file) => CODE_FILE.test(file) && tree.read(file)?.includes('npm_package_version')) : [];
150
+ const bumpRadius = bumps.length ? testsReachingChanged(tree, [...bumps, ...versionEnvReaders], tests) : null;
151
+ if (bumpRadius && !bumpRadius.ok) return full(bumpRadius.reason);
152
+ const planned = planPartialRegate({ version: input.version, priorCommit: stamp.sha, forCommit: input.forCommit,
153
+ changedFiles: effective, priorFailures: failures.failures, priorErrors: failures.errors ?? [],
154
+ existsAtTip: (file) => tree.files.includes(file),
155
+ closure: (code) => testsReachingChanged(tree, code, tests) });
156
+ if (!planned.ok && effective.length) return full(planned.reason);
157
+ let fileSet: string[];
158
+ let base: PartialPlan;
159
+ if (planned.ok) {
160
+ if (planned.plan.kind === 'carry' && !bumpRadius?.ok) return full('docs-only carry requires full gate across releases');
161
+ base = planned.plan.kind === 'carry' ? { ...planned.plan, kind: undefined } : planned.plan;
162
+ fileSet = planned.plan.files;
163
+ } else {
164
+ let priorFiles: string[];
165
+ try { priorFiles = [...failures.failures, ...(failures.errors ?? [])].map(testFileOfId); }
166
+ catch { return full('hard rehearsal failure ids unreadable'); }
167
+ base = { version: input.version, priorCommit: stamp.sha, forCommit: input.forCommit,
168
+ files: [], priorFailures: failures.failures, priorErrors: failures.errors ?? [] };
169
+ fileSet = priorFiles;
170
+ }
171
+ const files = [...new Set([...fileSet, ...(bumpRadius?.ok ? bumpRadius.tests : [])])].sort();
172
+ if (!files.length || files.some((file) => !tests.includes(file))) return full('empty or missing rehearsal radius requires full gate');
173
+ return { plan: { ...base, files }, reason: 'hard rehearsal receipt and import radius verified', rehearsalRunId: stamp.hardGateRunId };
174
+ }
175
+ return full('no clean hard rehearsal receipt');
176
+ } catch (error) { return full(`hard rehearsal evidence unreadable: ${String(error)}`); }
177
+ }
@@ -38,7 +38,7 @@ function blob(repo: string, commit: string, file: string): unknown {
38
38
  }
39
39
 
40
40
  /** Remove only the fields version-node itself changes; any other JSON difference leaves a full shadow plan. */
41
- function versionOnlyBumps(repo: string, from: string, to: string, version: string, changed: readonly string[]): string[] {
41
+ export function versionOnlyBumps(repo: string, from: string, to: string, version: string, changed: readonly string[]): string[] {
42
42
  if (!changed.includes('package.json')) return [];
43
43
  const prior = blob(repo, from, 'package.json') as Record<string, unknown>;
44
44
  const next = blob(repo, to, 'package.json') as Record<string, unknown>;
@@ -72,6 +72,17 @@ export function cutoffNotesEmpty(data: Record<string, unknown> | undefined): boo
72
72
  return Array.isArray(data?.escalate) && (data.escalate as Array<{ kind?: string }>).some((item) => item?.kind === 'notes-empty');
73
73
  }
74
74
 
75
+ /**
76
+ * RELEASE-PWA-NODE-RESILIENCE ① — the pwa node's argv. `release.loop.pwaRemote` (copied into the run input) picks
77
+ * the SSH host the suite runs on; absent = this machine (today's argv, byte-identical). One setting, like `gateRemote`.
78
+ */
79
+ export function pwaNodeArgs(commit: string, input: Record<string, unknown>): string[] {
80
+ const remote = input.pwaRemote;
81
+ if (remote !== undefined && (typeof remote !== 'string' || !remote.trim())) throw new Error(`release.loop.pwaRemote must be an ssh host: ${String(remote)}`);
82
+ return ['scripts/release-loop/pwa-scenarios-node.ts', '--commit', commit, '--with-costly', '--json',
83
+ ...(typeof remote === 'string' ? ['--remote', remote.trim()] : [])];
84
+ }
85
+
75
86
  // Existing cutoff/PWA/TUI programs have different output conventions; normalize them at the graph boundary.
76
87
  if (import.meta.main) {
77
88
  const node = process.argv[2];
@@ -84,7 +95,7 @@ if (import.meta.main) {
84
95
  const predecessor = node === 'pwa' ? 'gate' : node === 'tui' ? 'upgrade' : 'version-release';
85
96
  if (context.outputs[predecessor]?.outcome !== 'ok') throw new Error(`${predecessor} did not pass`);
86
97
  const args = node === 'cutoff' ? ['scripts/release-loop/cutoff.ts', '--version', version, '--cutoff', commit, '--baseline', `v${context.input.previousVersion}`, '--json']
87
- : node === 'pwa' ? ['scripts/release-loop/pwa-scenarios-node.ts', '--commit', commit, '--with-costly', '--json']
98
+ : node === 'pwa' ? pwaNodeArgs(commit, context.input)
88
99
  : node === 'tui' ? ['scripts/release-loop/tui-sim-node.ts', '--commit', commit, '--json'] : (() => { throw new Error(`unknown node: ${node}`); })();
89
100
  const run = runCommand('bun', args);
90
101
  if (run.stderr) process.stderr.write(run.stderr);
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
  import { spawnSync } from 'node:child_process';
3
- import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
5
+ import { basename, dirname, join, resolve } from 'node:path';
6
6
  import { debug } from '../../src/debug/log.js';
7
7
  import { sendOutbound } from '../../src/domains/outbound-alert.js';
8
8
  import { effectiveInstanceRoot, prodInstanceRoot } from '../../src/instance/resolve.js';
@@ -10,8 +10,11 @@ import { emitNodeResult, readGraphContext, type GraphContext } from './node-verd
10
10
  import { prereleaseKind } from './release-version.js';
11
11
 
12
12
  type Result = { outcome: 'ok' | 'fail'; verdict: 'pass' | 'fail'; summary: string; npm?: 'published' | 'already-published' | 'staged-not-visible'; humanAction?: 'npm-support' };
13
- type Exec = (command: string, args: string[], env?: NodeJS.ProcessEnv) => { status: number | null; stdout: string; stderr: string };
13
+ type ExecResult = { status: number | null; stdout: string; stderr: string; durationMs?: number; signal?: NodeJS.Signals | null };
14
+ type Exec = (command: string, args: string[], env?: NodeJS.ProcessEnv) => ExecResult;
14
15
  type Registry = (url: string) => Promise<{ status: number; body: unknown }>;
16
+ const REGISTRY_FETCH_TIMEOUT_MS = 15_000;
17
+ type PublishSpawn = { version: string; durationMs: number; status: number | null; signal: NodeJS.Signals | null };
15
18
  export interface NpmPublishDeps {
16
19
  exec?: Exec;
17
20
  registry?: Registry;
@@ -21,16 +24,27 @@ export interface NpmPublishDeps {
21
24
  productionRoot?: string;
22
25
  alert?: (text: string, kind: string) => boolean;
23
26
  log?: (version: string, waitedMinutes: number) => void;
27
+ publishSpawn?: (event: PublishSpawn) => void;
24
28
  }
25
29
 
26
30
  const exec: Exec = (command, args, env) => {
31
+ const startedAt = performance.now();
27
32
  const result = spawnSync(command, args, { encoding: 'utf8', env: env ?? process.env, maxBuffer: 1024 * 1024, timeout: 300_000 });
28
- return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? String(result.error ?? '') };
29
- };
30
- const registry: Registry = async (url) => {
31
- const response = await fetch(url, { headers: { accept: 'application/json' }, signal: AbortSignal.timeout(15_000) });
32
- return { status: response.status, body: response.ok ? await response.json() : null };
33
+ return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? String(result.error ?? ''), durationMs: Math.ceil(performance.now() - startedAt), signal: result.signal };
33
34
  };
35
+ export async function fetchNpmRegistry(url: string, timeoutMs = REGISTRY_FETCH_TIMEOUT_MS): Promise<{ status: number; body: unknown }> {
36
+ const timeout = AbortSignal.timeout(timeoutMs);
37
+ let response: Response;
38
+ try {
39
+ response = await fetch(url, { headers: { accept: 'application/json' }, signal: timeout });
40
+ return { status: response.status, body: response.ok ? await response.json() : null };
41
+ }
42
+ catch (error) {
43
+ if (timeout.aborted) throw Object.assign(new Error(`registry request exceeded ${timeoutMs}ms`), { name: 'NpmRegistryFetchTimeoutError', timeoutMs });
44
+ throw error;
45
+ }
46
+ }
47
+ const registry: Registry = fetchNpmRegistry;
34
48
  const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
35
49
  const PACKAGE_URL = 'https://registry.npmjs.org/elanous';
36
50
 
@@ -44,6 +58,33 @@ function metadata(value: unknown): { name?: string; version?: string } {
44
58
  if (!value || typeof value !== 'object') return {};
45
59
  return value as { name?: string; version?: string };
46
60
  }
61
+ function registryFailure(error: unknown, url: string): Error {
62
+ const name = error instanceof Error ? error.name : '';
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ const endpoint = (() => { try { return new URL(url).pathname; } catch { return url; } })();
65
+ if (name === 'NpmRegistryFetchTimeoutError') {
66
+ const timeoutMs = error && typeof error === 'object' && 'timeoutMs' in error && typeof error.timeoutMs === 'number' ? error.timeoutMs : REGISTRY_FETCH_TIMEOUT_MS;
67
+ return new Error(`npm registry fetch timeout after ${timeoutMs}ms (${endpoint || '/'})`);
68
+ }
69
+ return new Error(`npm registry fetch failed (${endpoint || '/'}): ${message}`);
70
+ }
71
+ function publishLogDirectory(context: GraphContext, stateDir: string): string {
72
+ const contextLocation = process.env.ELANOUS_GRAPH_CONTEXT;
73
+ if (contextLocation && !contextLocation.trimStart().startsWith('{')) {
74
+ const contextPath = resolve(contextLocation);
75
+ const runDirectory = dirname(contextPath);
76
+ const runDirectoryName = basename(runDirectory);
77
+ if (basename(contextPath).endsWith('.json') && runDirectoryName.endsWith('.json.contexts')) {
78
+ const pathRunId = runDirectoryName.slice(0, -'.json.contexts'.length);
79
+ if (context.runId && pathRunId !== context.runId) throw new Error('npm publish graph context runId/path mismatch');
80
+ return join(runDirectory, 'npm-publish');
81
+ }
82
+ }
83
+ if (context.runId && /^[a-zA-Z0-9._-]+$/.test(context.runId))
84
+ return join(stateDir, 'graph-runs', 'release-loop', `${context.runId}.json.contexts`, 'npm-publish');
85
+ // Direct non-graph calls have no run folder; retain compatibility in their version output directory.
86
+ return join(stateDir, 'release', context.input.version, 'npm-publish');
87
+ }
47
88
 
48
89
  export async function runNpmPublish(context: GraphContext = readGraphContext(), deps: NpmPublishDeps = {}): Promise<Result> {
49
90
  const version = context.input.version;
@@ -68,9 +109,16 @@ export async function runNpmPublish(context: GraphContext = readGraphContext(),
68
109
  const pkg = metadata(JSON.parse(archive.stdout));
69
110
  if (pkg.name !== 'elanous' || pkg.version !== version) throw new Error('npm archive package name/version mismatch');
70
111
  const inspect = async (): Promise<{ visible: boolean; tagged: boolean }> => {
71
- const [manifest, tags] = await Promise.all([readRegistry(`${PACKAGE_URL}/${version}`), readRegistry(PACKAGE_URL)]);
72
- if (manifest.status !== 200 && manifest.status !== 404) throw new Error(`npm registry version lookup failed (${manifest.status})`);
73
- if (tags.status !== 200) throw new Error(`npm registry tags lookup failed (${tags.status})`);
112
+ const versionUrl = `${PACKAGE_URL}/${version}`;
113
+ const tagsUrl = PACKAGE_URL;
114
+ let manifest: { status: number; body: unknown };
115
+ let tags: { status: number; body: unknown };
116
+ const read = (url: string) => readRegistry(url).catch((error: unknown) => { throw registryFailure(error, url); });
117
+ [manifest, tags] = await Promise.all([read(versionUrl), read(tagsUrl)]);
118
+ if (manifest.status !== 200 && manifest.status !== 404)
119
+ throw new Error(`npm registry version lookup failed (${manifest.status}; ${new URL(versionUrl).pathname})`);
120
+ if (tags.status !== 200)
121
+ throw new Error(`npm registry tags lookup failed (${tags.status}; ${new URL(tagsUrl).pathname})`);
74
122
  const tagged = tags.body && typeof tags.body === 'object' && 'dist-tags' in tags.body
75
123
  ? (tags.body as { 'dist-tags'?: Record<string, unknown> })['dist-tags']?.[distTag] : undefined;
76
124
  if (manifest.status === 200 && (metadata(manifest.body).name !== 'elanous' || metadata(manifest.body).version !== version))
@@ -90,7 +138,13 @@ export async function runNpmPublish(context: GraphContext = readGraphContext(),
90
138
  try {
91
139
  const npmrc = join(dir, 'npmrc');
92
140
  writeFileSync(npmrc, `//registry.npmjs.org/:_authToken=${token}\n`, { mode: 0o600, flag: 'wx' });
93
- published = run('npm', ['publish', candidate, '--ignore-scripts', '--tag', distTag, '--access', 'public'], { ...process.env, NPM_CONFIG_USERCONFIG: npmrc });
141
+ const logDir = publishLogDirectory(context, stateDir);
142
+ mkdirSync(logDir, { recursive: true, mode: 0o700 });
143
+ chmodSync(logDir, 0o700);
144
+ const publishStartedAt = performance.now();
145
+ published = run('npm', ['publish', candidate, '--ignore-scripts', '--tag', distTag, '--access', 'public', '--logs-dir', logDir], { ...process.env, NPM_CONFIG_USERCONFIG: npmrc });
146
+ const durationMs = published.durationMs ?? Math.ceil(performance.now() - publishStartedAt);
147
+ try { (deps.publishSpawn ?? ((event) => debug.log('release-loop.npm', 'publish-spawn', event)))({ version, durationMs, status: published.status, signal: published.signal ?? null }); } catch { /* telemetry must not mask publish result */ }
94
148
  } finally { rmSync(dir, { recursive: true, force: true }); }
95
149
  const output = `${published.stdout}\n${published.stderr}`;
96
150
  const staged = published.status !== 0 && /\bE409\b/i.test(output) && /previously staged/i.test(output);
@@ -20,6 +20,8 @@ function option(name: string, fallback?: string): string {
20
20
  }
21
21
 
22
22
  async function main(): Promise<void> {
23
+ const actor = process.env.ELANOUS_SEAT?.trim() || process.env.USER?.trim() || process.env.LOGNAME?.trim();
24
+ if (!actor) throw new Error('pod provenance actor missing: ELANOUS_SEAT or USER/LOGNAME required');
23
25
  const context = option('context');
24
26
  const host = option('host');
25
27
  const cluster = option('cluster', 'elanous-pool');
@@ -51,6 +53,9 @@ async function main(): Promise<void> {
51
53
  let completed = false;
52
54
  try {
53
55
  const result = await runPodCommand({
56
+ provenance: { actorKind: process.env.ELANOUS_SEAT?.trim() ? 'seat' : 'human', actor, character: 'host-dirs-probe',
57
+ entrance: 'probe', kind: 'probe', run: name, phase: 'exec', requestedAt: new Date().toISOString(),
58
+ reason: `host directory probe ${arm}` },
54
59
  command: ['sh', '-c', 'printf cache-ok > /bun-cache/.host-dir-probe && printf slots-ok > /gate-install-slots/.host-dir-probe && test "$(cat /bun-cache/.host-dir-probe)" = cache-ok && test "$(cat /gate-install-slots/.host-dir-probe)" = slots-ok && rm /bun-cache/.host-dir-probe /gate-install-slots/.host-dir-probe && echo HOST_DIRS_WRITTEN'],
55
60
  name,
56
61
  namespace, image, imageCommit: null, deadlineSeconds: 120, pollMs: 1_000, statusUnreadableMs: 30_000,
@@ -0,0 +1,166 @@
1
+ // RELEASE-PWA-NODE-RESILIENCE ① (0.2.36 · 대표 10-20 «PWA 단계도 원격에서»): the release `pwa` node can run its
2
+ // scenario suite on an SSH host (`release.loop.pwaRemote`, e.g. node-b) instead of the busy release machine.
3
+ // 0.2.34 (10-20 18:50~19:13): at 1-min load 100+ on mbp the CDP attach went past 5s and T1b failed twice on reruns,
4
+ // while the same commit alone passed — the release machine's load, not the PWA, decided the verdict.
5
+ //
6
+ // Same shape as the gate's host executor (`--gate-executor host` · `gateRemote` · `gateRemoteMirror`):
7
+ // ① ensure the bare mirror on the host ② push the commit to `refs/elanous/pwa/<sha>`
8
+ // ③ one ssh call: clone the mirror at <sha> into a temp dir, install, run THAT commit's
9
+ // `pwa-scenarios-node.ts` locally there, print its JSON verdict, remove the temp dir.
10
+ // ⛔ No silent fallback: when any step fails the node reports `error` with the stage and the host's own words
11
+ // and exits 2. It never reruns the suite on the release machine.
12
+ import { spawnSync } from 'node:child_process';
13
+ import { debug } from '../../src/debug/log.js';
14
+
15
+ /** `[user@]host`. A leading `-` is refused separately (ssh/git would read it as an option — `-oProxyCommand=…`). */
16
+ export const PWA_REMOTE_HOST_PATTERN = /^(?:[\w.-]+@)?[\w.-]+$/;
17
+ export const DEFAULT_PWA_REMOTE_MIRROR = '~/mirror/elanous-agent.git';
18
+ /** Build (≤15m) + boot + warm-up + suite + reruns on the host; the local ssh deadline is only a backstop. */
19
+ export const PWA_REMOTE_TIMEOUT_MS = 75 * 60_000;
20
+ /** The host kills its own work first (shorter than the local backstop) so the temp clone and the ref are cleaned there. */
21
+ export const PWA_REMOTE_LIMIT_SECONDS = 70 * 60;
22
+ /** rc of a run the Perl supervisor stopped after its deadline. */
23
+ const LIMIT_RCS = new Set([124]);
24
+
25
+ export type PwaRemoteStage = 'input' | 'mirror' | 'push' | 'run' | 'timeout' | 'parse';
26
+
27
+ export interface PwaRemoteOptions {
28
+ host: string;
29
+ commit: string;
30
+ withCostly: boolean;
31
+ reruns: number;
32
+ /** Bare mirror on the host (absolute or `~/…`). Default = the gate's mirror. */
33
+ mirror?: string;
34
+ /** Local repository that holds `commit` (pushed from here). */
35
+ repo: string;
36
+ timeoutMs?: number;
37
+ }
38
+
39
+ export interface PwaRemoteResult {
40
+ /** Exit code for the graph node: 0 pass/flaky · 1 fail · 2 could not run. */
41
+ exitCode: 0 | 1 | 2;
42
+ output: Record<string, unknown>;
43
+ }
44
+
45
+ interface Spawned { rc: number; stdout: string; stderr: string; error?: string }
46
+ export type PwaRemoteSpawn = (cmd: string, args: string[], opts: { cwd: string; timeoutMs: number }) => Spawned;
47
+
48
+ const defaultSpawn: PwaRemoteSpawn = (cmd, args, opts) => {
49
+ const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !/^(https?|all)_proxy$/i.test(k)));
50
+ const r = spawnSync(cmd, args, { cwd: opts.cwd, env, encoding: 'utf8', timeout: opts.timeoutMs, killSignal: 'SIGKILL', maxBuffer: 64 * 1024 * 1024 });
51
+ return { rc: r.status ?? 255, stdout: r.stdout ?? '', stderr: r.stderr ?? '', ...(r.error ? { error: String(r.error) } : {}) };
52
+ };
53
+
54
+ export const shQuote = (value: string): string => `'${value.replace(/'/g, `'\\''`)}'`;
55
+
56
+ /** Validate the host and mirror before anything is spawned. */
57
+ export function checkPwaRemote(host: unknown, mirror: unknown): { host: string; mirror: string } {
58
+ if (typeof host === 'string' && host.trim().startsWith('-')) throw new Error(`invalid pwa remote host: ${host.trim()} — a leading «-» would be read as an ssh/git option`);
59
+ if (typeof host !== 'string' || !PWA_REMOTE_HOST_PATTERN.test(host.trim())) throw new Error(`invalid pwa remote host: ${String(host)}`);
60
+ const m = mirror === undefined ? DEFAULT_PWA_REMOTE_MIRROR : mirror;
61
+ if (typeof m !== 'string' || !/^(?:\/|~\/)[\w./-]+$/.test(m) || m.split('/').includes('..')) throw new Error(`invalid pwa remote mirror path: ${String(m)}`);
62
+ return { host: host.trim(), mirror: m };
63
+ }
64
+
65
+ /** `~/x` stays unquoted at the front so the remote shell expands it; the rest is quoted. */
66
+ function remotePath(path: string): string {
67
+ return path.startsWith('~/') ? `"$HOME"/${shQuote(path.slice(2))}` : shQuote(path);
68
+ }
69
+
70
+ /**
71
+ * The single remote script for step ③ — `sh -c` so the remote login shell (zsh on node-b) does not change its meaning.
72
+ * - Limit: Perl supervises a separate process group at PWA_REMOTE_LIMIT_SECONDS.
73
+ * On timeout it kills the group, including the shell's children, then exits 124.
74
+ * - Cleanup: traps on EXIT · HUP · INT · TERM remove the temp clone and this run's mirror ref `refs/elanous/pwa/<sha>`
75
+ * on success and failure alike — the ref only had to live until the clone fetched it, and deleting «our own» ref
76
+ * (not a sweep of `refs/elanous/pwa/*`) cannot race another run on a different commit.
77
+ */
78
+ export function remoteRunScript(opts: { commit: string; mirror: string; withCostly: boolean; reruns: number; limitSeconds?: number }): string {
79
+ const sha = shQuote(opts.commit);
80
+ const ref = shQuote(`refs/elanous/pwa/${opts.commit}`);
81
+ const limit = String(opts.limitSeconds ?? PWA_REMOTE_LIMIT_SECONDS);
82
+ const nodeArgs = ['--commit', opts.commit, '--json', '--reruns', String(opts.reruns), ...(opts.withCostly ? ['--with-costly'] : [])].map(shQuote).join(' ');
83
+ const body = [
84
+ `git clone --quiet --shared --no-checkout ${remotePath(opts.mirror)} "$1/repo" >&2 || exit 2`,
85
+ `cd "$1/repo" && git fetch --quiet origin ${ref} >&2 && git checkout --quiet --detach ${sha} >&2 || exit 2`,
86
+ 'bun install --frozen-lockfile >&2 || exit 2',
87
+ '(cd apps/pwa && bun install --frozen-lockfile) >&2 || exit 2',
88
+ `exec bun scripts/release-loop/pwa-scenarios-node.ts ${nodeArgs}`,
89
+ ].join('\n');
90
+ // Arm the deadline only after the child confirms its new group exists. Otherwise a busy host
91
+ // can deliver SIGALRM between fork and setpgrp, making kill(-pid) miss every descendant.
92
+ const supervisor = [
93
+ 'my $seconds = shift;',
94
+ 'pipe(my $ready_r, my $ready_w) or exit 127;',
95
+ 'my $pid = fork(); defined($pid) or exit 127;',
96
+ 'if ($pid == 0) { close $ready_r; defined(setpgrp(0, 0)) or exit 127; syswrite($ready_w, "1") == 1 or exit 127; close $ready_w; exec @ARGV or exit 127; }',
97
+ 'close $ready_w; my $ready = ""; unless (read($ready_r, $ready, 1) == 1) { kill 9, -$pid; kill 9, $pid; waitpid($pid, 0); exit 127; } close $ready_r;',
98
+ 'local $SIG{ALRM} = sub { kill 9, -$pid; waitpid($pid, 0); exit 124; };',
99
+ 'alarm $seconds; waitpid($pid, 0); alarm 0;',
100
+ 'exit(($? & 127) ? 128 + ($? & 127) : ($? >> 8));',
101
+ ].join(' ');
102
+ return [
103
+ 'PATH=$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:$PATH; export PATH',
104
+ 'command -v perl >/dev/null 2>&1 || { echo "pwa remote requires perl" >&2; exit 127; }',
105
+ 'D=$(mktemp -d /tmp/pwa-node-remote-XXXXXX) || exit 2',
106
+ `cleanup() { cd /tmp; rm -rf "$D"; git --git-dir=${remotePath(opts.mirror)} update-ref -d ${ref} 2>/dev/null; }`,
107
+ `trap 'cleanup' EXIT`,
108
+ `trap 'cleanup; trap - EXIT; exit 129' HUP`,
109
+ `trap 'cleanup; trap - EXIT; exit 130' INT`,
110
+ `trap 'cleanup; trap - EXIT; exit 143' TERM`,
111
+ `lim() { perl -e ${shQuote(supervisor)} ${limit} "$@"; }`,
112
+ `lim sh -c ${shQuote(body)} pwa-node "$D"`,
113
+ ].join('\n');
114
+ }
115
+
116
+ const COMMIT_40 = /^[0-9a-f]{40}$/;
117
+
118
+ const tail = (text: string, n = 600): string => text.trim().slice(-n);
119
+
120
+ function lastJson(stdout: string): Record<string, unknown> | undefined {
121
+ const line = stdout.split('\n').reverse().find((l) => l.trim().startsWith('{'));
122
+ if (!line) return undefined;
123
+ try {
124
+ const value: unknown = JSON.parse(line);
125
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
126
+ } catch { return undefined; }
127
+ }
128
+
129
+ /** Run the PWA scenario node on `host`. Never falls back to the local machine. */
130
+ export function runPwaRemote(opts: PwaRemoteOptions, spawn: PwaRemoteSpawn = defaultSpawn): PwaRemoteResult {
131
+ const { host, mirror } = checkPwaRemote(opts.host, opts.mirror);
132
+ if (!COMMIT_40.test(opts.commit)) {
133
+ const error = `remote ${host} input refused: commit must be a full 40-hex sha (got ${JSON.stringify(opts.commit)}) — a short sha can name a different commit on the host mirror`;
134
+ debug.log('release-loop.pwa', 'remote-failed', { host, stage: 'input', rc: null, detail: error }, { level: 'warn' });
135
+ return { exitCode: 2, output: { verdict: 'error', commit: opts.commit, withCostly: opts.withCostly, executor: 'host', host, cells: [], error, remote: { stage: 'input', rc: null } } };
136
+ }
137
+ const timeoutMs = opts.timeoutMs ?? PWA_REMOTE_TIMEOUT_MS;
138
+ const base = { commit: opts.commit, withCostly: opts.withCostly, executor: 'host', host };
139
+ const started = Date.now();
140
+ debug.log('release-loop.pwa', 'remote-start', { host, mirror, commit: opts.commit, withCostly: opts.withCostly, reruns: opts.reruns });
141
+ const fail = (stage: PwaRemoteStage, run: Spawned | undefined, detail: string): PwaRemoteResult => {
142
+ const error = `remote ${host} ${stage} failed${run ? ` (rc=${run.rc})` : ''}: ${detail || 'no output'} — 원격 실패는 mbp 로 폴백하지 않는다`;
143
+ debug.log('release-loop.pwa', 'remote-failed', { host, stage, rc: run?.rc ?? null, detail: detail.slice(0, 600), elapsedMs: Date.now() - started }, { level: 'warn' });
144
+ return { exitCode: 2, output: { verdict: 'error', ...base, cells: [], error, remote: { stage, rc: run?.rc ?? null } } };
145
+ };
146
+
147
+ // ① mirror — a bare repo the commit can be pushed to (the gate uses the same one).
148
+ const ensure = `test -d ${remotePath(mirror)} || { mkdir -p "$(dirname ${remotePath(mirror)})" && git init --quiet --bare ${remotePath(mirror)}; }`;
149
+ const m = spawn('ssh', ['--', host, `sh -c ${shQuote(ensure)}`], { cwd: '/tmp', timeoutMs: 120_000 });
150
+ if (m.rc !== 0) return fail('mirror', m, tail(`${m.stderr}\n${m.error ?? ''}`));
151
+
152
+ // ② push the commit from the release machine.
153
+ const pushTarget = mirror.startsWith('~/') ? mirror.slice(2) : mirror;
154
+ const p = spawn('git', ['push', '--quiet', '--force', '--', `${host}:${pushTarget}`, `${opts.commit}:refs/elanous/pwa/${opts.commit}`], { cwd: opts.repo, timeoutMs: 600_000 });
155
+ if (p.rc !== 0) return fail('push', p, tail(`${p.stderr}\n${p.error ?? ''}`));
156
+
157
+ // ③ one call: clone · install · run the commit's own node there.
158
+ const script = remoteRunScript({ commit: opts.commit, mirror, withCostly: opts.withCostly, reruns: opts.reruns });
159
+ const r = spawn('ssh', ['--', host, `sh -c ${shQuote(script)}`], { cwd: '/tmp', timeoutMs });
160
+ const data = lastJson(r.stdout);
161
+ if (!data) return fail(r.rc === 0 ? 'parse' : LIMIT_RCS.has(r.rc) ? 'timeout' : 'run', r, tail(`${r.stderr}\n${r.error ?? ''}`));
162
+ const verdict = data.verdict;
163
+ const exitCode: 0 | 1 | 2 = verdict === 'pass' || verdict === 'flaky' ? (r.rc === 0 ? 0 : 2) : verdict === 'fail' ? 1 : 2;
164
+ debug.log('release-loop.pwa', 'remote-result', { host, verdict: typeof verdict === 'string' ? verdict : null, rc: r.rc, exitCode, elapsedMs: Date.now() - started });
165
+ return { exitCode, output: { ...data, ...base, ...(exitCode === 2 && typeof data.error !== 'string' ? { error: `remote ${host} run rc=${r.rc} verdict=${String(verdict)}: ${tail(r.stderr, 300)}` } : {}) } };
166
+ }
@@ -3,6 +3,10 @@
3
3
  * Release-loop ④ — run the PWA scenario suite on one commit, in isolation, and print one verdict.
4
4
  *
5
5
  * bun scripts/release-loop/pwa-scenarios-node.ts --commit <sha> [--with-costly] [--reruns 2] [--json]
6
+ * [--remote <ssh-host> [--remote-mirror <path>]]
7
+ *
8
+ * `--remote` (release.loop.pwaRemote) runs the same steps on that host — see pwa-remote.ts.
9
+ * A remote failure is reported as `error` (exit 2); it never falls back to this machine.
6
10
  *
7
11
  * Steps: temp worktree at <sha> → `nexus build` → isolated nexus (`--test`, leased test port)
8
12
  * → warm-up pass (discarded — the first page load after boot is slow and made T4a/N5a fail on the
@@ -18,14 +22,17 @@ import { tmpdir } from 'node:os';
18
22
  import { join, resolve } from 'node:path';
19
23
  import { decideVerdict, exitCodeFor, idsToRerun, type CellRun, type NodeVerdict } from './pwa-node-verdict';
20
24
  import { envLiteral } from '../../src/platform/env-literal.js';
25
+ import { checkPwaRemote, runPwaRemote } from './pwa-remote';
21
26
 
22
- interface Options { commit: string; withCostly: boolean; reruns: number; json: boolean }
27
+ interface Options { commit: string; withCostly: boolean; reruns: number; json: boolean; remote?: string; remoteMirror?: string }
23
28
 
24
- function parseArgs(argv: readonly string[]): Options {
29
+ export function parseArgs(argv: readonly string[]): Options {
25
30
  const opts: Options = { commit: '', withCostly: false, reruns: 2, json: false };
26
31
  for (let i = 0; i < argv.length; i++) {
27
32
  const arg = argv[i];
28
33
  if (arg === '--commit') opts.commit = argv[++i] ?? '';
34
+ else if (arg === '--remote') opts.remote = argv[++i] ?? '';
35
+ else if (arg === '--remote-mirror') opts.remoteMirror = argv[++i] ?? '';
29
36
  else if (arg === '--with-costly') opts.withCostly = true;
30
37
  else if (arg === '--json') opts.json = true;
31
38
  else if (arg === '--reruns') opts.reruns = Number(argv[++i] ?? '2');
@@ -33,6 +40,8 @@ function parseArgs(argv: readonly string[]): Options {
33
40
  }
34
41
  if (!/^[0-9a-f]{7,40}$/i.test(opts.commit)) throw new Error('--commit <sha> is required');
35
42
  if (!Number.isInteger(opts.reruns) || opts.reruns < 0 || opts.reruns > 5) throw new Error('--reruns must be 0..5');
43
+ if (opts.remoteMirror !== undefined && opts.remote === undefined) throw new Error('--remote-mirror requires --remote');
44
+ if (opts.remote !== undefined) checkPwaRemote(opts.remote, opts.remoteMirror);
36
45
  return opts;
37
46
  }
38
47
 
@@ -65,6 +74,13 @@ async function main(): Promise<number> {
65
74
  let opts: Options;
66
75
  try { opts = parseArgs(process.argv.slice(2)); } catch (e) { log(String(e)); return 2; }
67
76
  const repo = resolve('.');
77
+ if (opts.remote !== undefined) {
78
+ log(`remote ${opts.remote} · ${opts.commit.slice(0, 10)}`);
79
+ const remote = runPwaRemote({ host: opts.remote, ...(opts.remoteMirror ? { mirror: opts.remoteMirror } : {}), commit: opts.commit, withCostly: opts.withCostly, reruns: opts.reruns, repo });
80
+ if (opts.json) console.log(JSON.stringify(remote.output));
81
+ else console.log(`pwa-node: ${String(remote.output.verdict)} · ${opts.commit.slice(0, 10)} · host ${opts.remote}${typeof remote.output.error === 'string' ? `\n error: ${remote.output.error}` : ''}`);
82
+ return remote.exitCode;
83
+ }
68
84
  const work = mkdtempSync(join(tmpdir(), 'pwa-node-'));
69
85
  const worktree = join(work, 'tree');
70
86
  let nexusPid: number | undefined;
@@ -137,4 +153,4 @@ async function main(): Promise<number> {
137
153
  return verdict ? exitCodeFor(verdict.verdict) : 2;
138
154
  }
139
155
 
140
- process.exit(await main());
156
+ if (import.meta.main) process.exit(await main());