toolroll 0.9.93 → 0.9.95

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 (436) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/activity-line.d.ts +7 -4
  3. package/dist/activity-line.js +9 -7
  4. package/dist/agent-fence.d.ts +10 -0
  5. package/dist/agent-fence.js +6 -3
  6. package/dist/approval-authority.d.ts +7 -1
  7. package/dist/approval-authority.js +11 -2
  8. package/dist/approval-password.d.ts +31 -0
  9. package/dist/approval-password.js +26 -0
  10. package/dist/assignment-status.d.ts +1 -1
  11. package/dist/assignment-ui.js +3 -1
  12. package/dist/assignment.js +5 -0
  13. package/dist/attest.d.ts +3 -0
  14. package/dist/attest.js +10 -0
  15. package/dist/boot-identity.d.ts +2 -1
  16. package/dist/boot-identity.js +6 -4
  17. package/dist/bounded-spawn.d.ts +57 -0
  18. package/dist/bounded-spawn.js +220 -0
  19. package/dist/browser/terminal.js +42 -42
  20. package/dist/browser/workspace.css +1 -1
  21. package/dist/browser/workspace.js +32 -32
  22. package/dist/browser-crew.js +2 -0
  23. package/dist/browser-shell.d.ts +1 -1
  24. package/dist/browser-shell.js +2 -2
  25. package/dist/browser-workspace.d.ts +50 -2
  26. package/dist/browser-workspace.js +3 -3
  27. package/dist/build-review.js +2 -0
  28. package/dist/builder-restart.d.ts +21 -0
  29. package/dist/builder-restart.js +50 -0
  30. package/dist/builder.d.ts +11 -2
  31. package/dist/builder.js +234 -101
  32. package/dist/card-catalog.d.ts +209 -0
  33. package/dist/card-catalog.js +133 -0
  34. package/dist/card-cli.d.ts +43 -0
  35. package/dist/card-cli.js +44 -0
  36. package/dist/card-render.d.ts +26 -0
  37. package/dist/card-render.js +223 -0
  38. package/dist/card-sources.d.ts +68 -0
  39. package/dist/card-sources.js +234 -0
  40. package/dist/chat-acceptance.js +1 -1
  41. package/dist/chat-actions.d.ts +106 -9
  42. package/dist/chat-actions.js +473 -46
  43. package/dist/chat-approval.d.ts +13 -0
  44. package/dist/chat-approval.js +39 -9
  45. package/dist/chat-card-part.d.ts +11 -0
  46. package/dist/chat-card-part.js +18 -0
  47. package/dist/chat-card.d.ts +46 -0
  48. package/dist/chat-card.js +99 -0
  49. package/dist/chat-channel.d.ts +2 -0
  50. package/dist/chat-channel.js +37 -14
  51. package/dist/chat-controls.d.ts +4 -0
  52. package/dist/chat-controls.js +4 -0
  53. package/dist/chat-decide.d.ts +10 -16
  54. package/dist/chat-decide.js +79 -62
  55. package/dist/chat-delivery.js +13 -4
  56. package/dist/chat-flow.d.ts +0 -27
  57. package/dist/chat-flow.js +31 -2
  58. package/dist/chat-quiet.js +4 -4
  59. package/dist/chat-task-actions.d.ts +3 -2
  60. package/dist/chat-task-actions.js +19 -5
  61. package/dist/chat-terms.d.ts +14 -2
  62. package/dist/chat-terms.js +35 -0
  63. package/dist/chat-voice.d.ts +1 -1
  64. package/dist/chat-voice.js +2 -2
  65. package/dist/check-facts.d.ts +3 -1
  66. package/dist/check-facts.js +57 -11
  67. package/dist/check-failure.d.ts +24 -3
  68. package/dist/check-failure.js +89 -13
  69. package/dist/check-levels-ui.d.ts +4 -0
  70. package/dist/check-levels-ui.js +10 -1
  71. package/dist/check-load.d.ts +85 -0
  72. package/dist/check-load.js +147 -0
  73. package/dist/check-only-standing.d.ts +36 -0
  74. package/dist/check-only-standing.js +39 -0
  75. package/dist/check-only.d.ts +15 -0
  76. package/dist/check-only.js +99 -0
  77. package/dist/check-repair.d.ts +47 -0
  78. package/dist/check-repair.js +240 -0
  79. package/dist/check-report.d.ts +2 -0
  80. package/dist/check-report.js +1 -1
  81. package/dist/check-stamp.d.ts +46 -0
  82. package/dist/check-stamp.js +93 -0
  83. package/dist/checkout-cleanup.d.ts +36 -5
  84. package/dist/checkout-cleanup.js +166 -14
  85. package/dist/claim.d.ts +6 -0
  86. package/dist/claim.js +13 -2
  87. package/dist/cli-help.d.ts +4 -1
  88. package/dist/cli-help.js +49 -30
  89. package/dist/cli.js +15 -1
  90. package/dist/code-extract.d.ts +44 -0
  91. package/dist/code-extract.js +88 -0
  92. package/dist/code-map-acts.d.ts +24 -0
  93. package/dist/code-map-acts.js +53 -0
  94. package/dist/code-map-admission.d.ts +52 -0
  95. package/dist/code-map-admission.js +295 -0
  96. package/dist/code-map-build.d.ts +63 -0
  97. package/dist/code-map-build.js +415 -0
  98. package/dist/code-map-cli.d.ts +153 -0
  99. package/dist/code-map-cli.js +103 -0
  100. package/dist/code-map-main.d.ts +1 -0
  101. package/dist/code-map-main.js +48 -0
  102. package/dist/code-map-read.d.ts +106 -0
  103. package/dist/code-map-read.js +171 -0
  104. package/dist/code-map-report.d.ts +37 -0
  105. package/dist/code-map-report.js +95 -0
  106. package/dist/code-map-shape.d.ts +172 -0
  107. package/dist/code-map-shape.js +327 -0
  108. package/dist/code-map-store.d.ts +136 -0
  109. package/dist/code-map-store.js +233 -0
  110. package/dist/coding-provider.d.ts +3 -1
  111. package/dist/coding-provider.js +12 -7
  112. package/dist/coding-update.js +3 -2
  113. package/dist/coding-workspace.d.ts +1 -1
  114. package/dist/coding-workspace.js +8 -8
  115. package/dist/console-chrome.js +2 -2
  116. package/dist/containment.d.ts +5 -0
  117. package/dist/containment.js +14 -1
  118. package/dist/contracts/chat-actions.d.ts +92 -0
  119. package/dist/contracts/chat-actions.js +20 -0
  120. package/dist/contracts/chat-card.d.ts +91 -0
  121. package/dist/contracts/chat-card.js +54 -0
  122. package/dist/contracts/chat-content.d.ts +191 -0
  123. package/dist/contracts/chat-content.js +4 -0
  124. package/dist/contracts/cli.d.ts +52 -0
  125. package/dist/contracts/cli.js +4 -0
  126. package/dist/contracts/console-api.d.ts +12 -3
  127. package/dist/contracts/console-api.js +12 -3
  128. package/dist/contracts/flow-propose.d.ts +8 -1
  129. package/dist/contracts/flow-propose.js +3 -1
  130. package/dist/contracts/flow-send.d.ts +126 -0
  131. package/dist/contracts/flow-send.js +3 -0
  132. package/dist/contracts/gateway-tools.d.ts +17 -0
  133. package/dist/contracts/gateway-tools.js +3 -1
  134. package/dist/contracts/lead-tools.d.ts +234 -0
  135. package/dist/contracts/lead-tools.js +25 -1
  136. package/dist/contracts/project-knowledge.d.ts +38 -5
  137. package/dist/contracts/project-knowledge.js +11 -5
  138. package/dist/contracts/project-memory.d.ts +3 -1
  139. package/dist/contracts/project-memory.js +3 -2
  140. package/dist/contracts/storage.d.ts +14 -0
  141. package/dist/contracts/storage.js +3 -1
  142. package/dist/contracts/store-json.d.ts +18 -0
  143. package/dist/contracts/youiyoux-request.d.ts +1 -1
  144. package/dist/contracts/youiyoux-request.js +1 -1
  145. package/dist/desktop-access.js +6 -2
  146. package/dist/discord-chat.js +14 -0
  147. package/dist/dispatch.d.ts +1 -1
  148. package/dist/dispatch.js +25 -16
  149. package/dist/dispose.d.ts +19 -0
  150. package/dist/dispose.js +55 -9
  151. package/dist/easy-start.js +2 -3
  152. package/dist/eval-oracle.js +4 -1
  153. package/dist/evals.js +2 -1
  154. package/dist/evidence-pair.d.ts +53 -0
  155. package/dist/evidence-pair.js +128 -0
  156. package/dist/exhaustion.d.ts +17 -4
  157. package/dist/exhaustion.js +44 -4
  158. package/dist/export.js +1 -1
  159. package/dist/failing-first.d.ts +31 -0
  160. package/dist/failing-first.js +44 -0
  161. package/dist/fallback-schema.d.ts +8 -0
  162. package/dist/fallback-schema.js +40 -0
  163. package/dist/fallback.d.ts +138 -0
  164. package/dist/fallback.js +605 -0
  165. package/dist/fast-start.d.ts +136 -0
  166. package/dist/fast-start.js +431 -0
  167. package/dist/fast-terms.d.ts +9 -0
  168. package/dist/fast-terms.js +23 -0
  169. package/dist/fenced-check-coverage.d.ts +62 -0
  170. package/dist/fenced-check-coverage.js +54 -0
  171. package/dist/findings.d.ts +224 -0
  172. package/dist/findings.js +449 -0
  173. package/dist/first-run.js +1 -1
  174. package/dist/flaky-ci.d.ts +31 -0
  175. package/dist/flaky-ci.js +149 -0
  176. package/dist/flaky-cli.d.ts +60 -0
  177. package/dist/flaky-cli.js +60 -0
  178. package/dist/flaky.d.ts +75 -9
  179. package/dist/flaky.js +255 -0
  180. package/dist/fleet-schema.d.ts +17 -0
  181. package/dist/fleet-schema.js +135 -0
  182. package/dist/flow-browser.js +2 -2
  183. package/dist/flow-card-task.js +3 -0
  184. package/dist/flow-code.d.ts +2 -2
  185. package/dist/flow-code.js +9 -5
  186. package/dist/flow-result-card.d.ts +22 -0
  187. package/dist/flow-result-card.js +55 -0
  188. package/dist/flow-send.js +12 -9
  189. package/dist/flow-steps.js +1 -1
  190. package/dist/flow-triggers.d.ts +1 -1
  191. package/dist/flow-triggers.js +2 -2
  192. package/dist/flow-unattended.d.ts +26 -0
  193. package/dist/flow-unattended.js +12 -3
  194. package/dist/flows-ui.js +10 -2
  195. package/dist/follow-through-brief.d.ts +12 -0
  196. package/dist/follow-through-brief.js +31 -0
  197. package/dist/follow-through-cli.d.ts +23 -0
  198. package/dist/follow-through-cli.js +153 -0
  199. package/dist/git-incident.d.ts +16 -0
  200. package/dist/git-incident.js +69 -6
  201. package/dist/github-import.d.ts +62 -0
  202. package/dist/github-import.js +146 -0
  203. package/dist/guides.js +15 -10
  204. package/dist/handover-note.d.ts +33 -0
  205. package/dist/handover-note.js +106 -0
  206. package/dist/invoke.d.ts +25 -2
  207. package/dist/invoke.js +157 -58
  208. package/dist/job-object-helper.ps1 +10 -3
  209. package/dist/keymap.d.ts +41 -0
  210. package/dist/keymap.js +51 -0
  211. package/dist/knowledge-cli.js +1 -1
  212. package/dist/knowledge-ui.d.ts +21 -4
  213. package/dist/knowledge-ui.js +75 -7
  214. package/dist/laptop-background.d.ts +16 -0
  215. package/dist/laptop-background.js +108 -2
  216. package/dist/lead-cli.js +26 -24
  217. package/dist/lead-context.js +1 -1
  218. package/dist/lead-contract.d.ts +1 -1
  219. package/dist/lead-contract.js +3 -2
  220. package/dist/lead-doors.d.ts +4 -0
  221. package/dist/lead-doors.js +2 -2
  222. package/dist/lead-progress.js +5 -0
  223. package/dist/lead-status.d.ts +2 -0
  224. package/dist/lead-status.js +6 -2
  225. package/dist/lead-tools.js +162 -34
  226. package/dist/lead.js +2 -2
  227. package/dist/lesson-notes.d.ts +81 -0
  228. package/dist/lesson-notes.js +336 -0
  229. package/dist/limits-ui.js +1 -1
  230. package/dist/local-owner.d.ts +1 -1
  231. package/dist/local-owner.js +16 -0
  232. package/dist/machine-check.d.ts +16 -0
  233. package/dist/machine-check.js +26 -0
  234. package/dist/machine-identity.d.ts +75 -0
  235. package/dist/machine-identity.js +170 -0
  236. package/dist/mcp-core.js +11 -1
  237. package/dist/memory-cli.d.ts +300 -1
  238. package/dist/memory-cli.js +62 -9
  239. package/dist/memory-pass.d.ts +16 -0
  240. package/dist/memory-pass.js +67 -3
  241. package/dist/memory-tidy.d.ts +181 -0
  242. package/dist/memory-tidy.js +523 -0
  243. package/dist/memory-use.d.ts +35 -0
  244. package/dist/memory-use.js +109 -0
  245. package/dist/merge-checklist.d.ts +55 -0
  246. package/dist/merge-checklist.js +182 -0
  247. package/dist/modes.d.ts +6 -6
  248. package/dist/modes.js +3 -1
  249. package/dist/needs-you.d.ts +31 -9
  250. package/dist/needs-you.js +110 -41
  251. package/dist/nightly-github.d.ts +2 -0
  252. package/dist/nightly-github.js +4 -4
  253. package/dist/operate-remote-arguments.js +7 -1
  254. package/dist/operate-remote.js +3 -1
  255. package/dist/operate.d.ts +6 -1
  256. package/dist/operate.js +565 -64
  257. package/dist/phase-routing.js +2 -2
  258. package/dist/plain-schema.d.ts +23 -0
  259. package/dist/plain-schema.js +94 -0
  260. package/dist/plain-words.d.ts +20 -0
  261. package/dist/plain-words.js +66 -0
  262. package/dist/plan-login.d.ts +14 -0
  263. package/dist/plan-login.js +58 -0
  264. package/dist/planner.d.ts +1 -1
  265. package/dist/planner.js +4 -2
  266. package/dist/process-liveness.d.ts +2 -0
  267. package/dist/process-liveness.js +61 -17
  268. package/dist/project-delete-ui.d.ts +19 -2
  269. package/dist/project-delete-ui.js +29 -1
  270. package/dist/project-delete.js +14 -1
  271. package/dist/project-knowledge.d.ts +18 -11
  272. package/dist/project-knowledge.js +39 -9
  273. package/dist/project-learning.d.ts +2 -1
  274. package/dist/project-learning.js +15 -45
  275. package/dist/project-memory.d.ts +5 -0
  276. package/dist/project-memory.js +22 -2
  277. package/dist/project-tools.d.ts +4 -4
  278. package/dist/project-tools.js +23 -7
  279. package/dist/proof.js +30 -5
  280. package/dist/protected-storage-look.d.ts +1 -0
  281. package/dist/protected-storage-look.js +13 -0
  282. package/dist/protected-storage.d.ts +70 -39
  283. package/dist/protected-storage.js +196 -147
  284. package/dist/provider-auth.js +1 -1
  285. package/dist/provider-capture.d.ts +34 -0
  286. package/dist/provider-capture.js +53 -0
  287. package/dist/publish.js +9 -1
  288. package/dist/pull-request-flow.js +5 -0
  289. package/dist/push.d.ts +18 -2
  290. package/dist/push.js +40 -16
  291. package/dist/release-adapters.d.ts +9 -0
  292. package/dist/release-adapters.js +76 -0
  293. package/dist/release-auto.js +13 -13
  294. package/dist/release-card.d.ts +1 -2
  295. package/dist/release-card.js +11 -0
  296. package/dist/release-carry.d.ts +7 -2
  297. package/dist/release-carry.js +49 -10
  298. package/dist/release-cli.d.ts +4 -0
  299. package/dist/release-cli.js +29 -22
  300. package/dist/release-coverage.js +1 -1
  301. package/dist/release-next.d.ts +48 -0
  302. package/dist/release-next.js +161 -0
  303. package/dist/release.d.ts +20 -1
  304. package/dist/release.js +85 -9
  305. package/dist/remote-destination.d.ts +36 -0
  306. package/dist/remote-destination.js +217 -0
  307. package/dist/remote-main.d.ts +48 -0
  308. package/dist/remote-main.js +84 -5
  309. package/dist/repository-context.d.ts +3 -2
  310. package/dist/repository-context.js +112 -70
  311. package/dist/restart-certification.d.ts +1 -0
  312. package/dist/restart-certification.js +8 -6
  313. package/dist/result-acts.d.ts +27 -19
  314. package/dist/result-acts.js +36 -25
  315. package/dist/result-follow-ups.d.ts +2 -2
  316. package/dist/result-follow-ups.js +18 -4
  317. package/dist/result-review.d.ts +1 -1
  318. package/dist/route-change-check.d.ts +4 -0
  319. package/dist/route-change-check.js +13 -0
  320. package/dist/route-change.d.ts +22 -0
  321. package/dist/route-change.js +89 -0
  322. package/dist/route-hooks.js +3 -2
  323. package/dist/route-results.d.ts +6 -1
  324. package/dist/route-results.js +14 -5
  325. package/dist/run-closeout.d.ts +1 -1
  326. package/dist/run-closeout.js +7 -5
  327. package/dist/runner-process.js +3 -2
  328. package/dist/runner.js +2 -1
  329. package/dist/scope.d.ts +28 -4
  330. package/dist/scope.js +92 -28
  331. package/dist/scout.d.ts +1 -1
  332. package/dist/scout.js +2 -1
  333. package/dist/serve.d.ts +1 -1
  334. package/dist/serve.js +26 -18
  335. package/dist/server/chat.js +16 -14
  336. package/dist/server/chrome.d.ts +1 -1
  337. package/dist/server/chrome.js +26 -35
  338. package/dist/server/console.css +73 -0
  339. package/dist/server/guards.js +3 -0
  340. package/dist/server/home.d.ts +6 -0
  341. package/dist/server/home.js +21 -13
  342. package/dist/server/http.js +1 -1
  343. package/dist/server/kit.js +1 -1
  344. package/dist/server/merge-ready-view.d.ts +10 -0
  345. package/dist/server/merge-ready-view.js +30 -0
  346. package/dist/server/pages-assets.d.ts +1 -0
  347. package/dist/server/pages-assets.js +8 -5
  348. package/dist/server/pages.js +103 -82
  349. package/dist/server/render-chat.js +16 -3
  350. package/dist/server/render-code-map.d.ts +30 -0
  351. package/dist/server/render-code-map.js +137 -0
  352. package/dist/server/render-flaky.d.ts +20 -0
  353. package/dist/server/render-flaky.js +59 -0
  354. package/dist/server/render-follow-through.d.ts +11 -0
  355. package/dist/server/render-follow-through.js +68 -0
  356. package/dist/server/render-history.js +1 -1
  357. package/dist/server/render-new-task.d.ts +26 -1
  358. package/dist/server/render-new-task.js +30 -5
  359. package/dist/server/render-pages.d.ts +1 -175
  360. package/dist/server/render-pages.js +38 -313
  361. package/dist/server/render-projects.d.ts +2 -1
  362. package/dist/server/render-projects.js +9 -7
  363. package/dist/server/render-release.d.ts +2 -0
  364. package/dist/server/render-release.js +3 -3
  365. package/dist/server/render-results.d.ts +2 -0
  366. package/dist/server/render-results.js +68 -68
  367. package/dist/server/render-tasks.d.ts +16 -2
  368. package/dist/server/render-tasks.js +177 -141
  369. package/dist/server/retired-routes.d.ts +17 -0
  370. package/dist/server/retired-routes.js +53 -0
  371. package/dist/server/route-table.d.ts +3 -0
  372. package/dist/server/route-table.js +48 -12
  373. package/dist/server/settings.js +166 -34
  374. package/dist/server/task-views.js +66 -26
  375. package/dist/server/tasks.d.ts +2 -6
  376. package/dist/server/tasks.js +250 -321
  377. package/dist/server/welcome.js +1 -1
  378. package/dist/settings-groups-ui.d.ts +1 -0
  379. package/dist/settings-groups-ui.js +4 -2
  380. package/dist/settings-kit.js +3 -2
  381. package/dist/settings-proofs.js +3 -1
  382. package/dist/slack-chat.js +14 -1
  383. package/dist/steer-mailbox.d.ts +8 -1
  384. package/dist/steer-mailbox.js +164 -6
  385. package/dist/stop-proof.js +6 -4
  386. package/dist/storage-sweep-process.js +1 -1
  387. package/dist/storage-sweep.d.ts +5 -3
  388. package/dist/storage-sweep.js +30 -9
  389. package/dist/storage-ui.js +28 -17
  390. package/dist/storage.d.ts +27 -2
  391. package/dist/storage.js +39 -2
  392. package/dist/store.d.ts +89 -7
  393. package/dist/store.js +386 -114
  394. package/dist/surface.js +67 -6
  395. package/dist/sweep-quarantine.d.ts +1 -0
  396. package/dist/sweep-quarantine.js +6 -4
  397. package/dist/tamper-scan.d.ts +4 -0
  398. package/dist/tamper-scan.js +90 -0
  399. package/dist/task-aliases.d.ts +5 -0
  400. package/dist/task-aliases.js +9 -0
  401. package/dist/task-control.d.ts +2 -1
  402. package/dist/task-control.js +4 -1
  403. package/dist/task-outcome-cli.d.ts +2 -0
  404. package/dist/task-outcome-cli.js +17 -17
  405. package/dist/task-sizing.d.ts +5 -0
  406. package/dist/task-sizing.js +2 -0
  407. package/dist/task-source.d.ts +7 -0
  408. package/dist/task-source.js +14 -0
  409. package/dist/task-status.d.ts +9 -0
  410. package/dist/task-status.js +45 -49
  411. package/dist/teardown-git.d.ts +18 -0
  412. package/dist/teardown-git.js +105 -21
  413. package/dist/telegram-lead.js +23 -6
  414. package/dist/telegram-progress.js +5 -3
  415. package/dist/telegram-status.js +6 -4
  416. package/dist/telegram.js +12 -1
  417. package/dist/test-infra.d.ts +8 -0
  418. package/dist/test-infra.js +18 -0
  419. package/dist/tidy.js +1 -1
  420. package/dist/toolroll-update.js +19 -5
  421. package/dist/tui-contract.d.ts +24 -0
  422. package/dist/tui-source.js +67 -8
  423. package/dist/tui.js +51 -51
  424. package/dist/vanished-retry.d.ts +30 -0
  425. package/dist/vanished-retry.js +174 -0
  426. package/dist/work-index.d.ts +2 -0
  427. package/dist/work-index.js +95 -66
  428. package/dist/work-summary.d.ts +1 -1
  429. package/dist/work-summary.js +7 -2
  430. package/dist/workspace-ui.d.ts +7 -3
  431. package/dist/workspace-ui.js +38 -23
  432. package/dist/worktree.d.ts +12 -3
  433. package/dist/worktree.js +91 -14
  434. package/package.json +2 -1
  435. package/dist/server/render-next.d.ts +0 -63
  436. package/dist/server/render-next.js +0 -109
@@ -48,7 +48,7 @@ export function inSentence(summary) {
48
48
  return /^\p{Lu}\p{Ll}/u.test(summary) ? summary[0].toLowerCase() + summary.slice(1) : summary;
49
49
  }
50
50
  /** The two buttons under a result ready for a person: the real next step, then a look first. */
51
- export const READY_ACTIONS = { merge: "Merge", complete: "Accept and finish", look: "Look first" };
51
+ export const READY_ACTIONS = { merge: "Merge", complete: "Accept", look: "Look first" };
52
52
  /** Whether this line asks for the result's next step, so it carries [Merge] or [Accept and finish] and [Look first]. */
53
53
  export function asksToFinish(fact) {
54
54
  return fact.update == null && fact.headline === "Ready for review" && !fact.report;
@@ -67,7 +67,7 @@ export function finishedLine(fact) {
67
67
  if (fact.report)
68
68
  return fact.lead === true ? `I wrote up ${lower}. The report is ready to read.` : `${name}: the report is ready to read.`;
69
69
  if (fact.checks === "passed")
70
- return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "Accept and finish it?"}`;
70
+ return `${ready}. Your tests passed. ${fact.pullRequest === true ? "Merge it?" : "Accept it?"}`;
71
71
  if (fact.checks === "off")
72
72
  return `${ready}. Its checks run at release, so look it over first.`;
73
73
  return `${ready}, but no tests ran. Look it over first.`;
@@ -87,6 +87,8 @@ export type CheckFactsInput = {
87
87
  * nothing reads these for a verdict, a rerun or an approval.
88
88
  */
89
89
  export declare function recordCheckFacts(store: Store, root: string, input: CheckFactsInput): void;
90
+ /** The storage sweep's own step: results older than the retention window go; their fact rows stay. */
91
+ export declare function pruneCheckResults(store: Store, now: Date): number;
90
92
  /** A check's recorded facts, or null. */
91
93
  export type CheckFact = {
92
94
  run: number;
@@ -117,7 +119,7 @@ export declare function checkResultsOf(store: Store, runId: number): {
117
119
  * accepted note's until it ends, an observation's for 90 days — exact cases only, the most recently tracked first, at
118
120
  * most 200. Those left out are counted (`leftOut`): a coverage gap, said, never hidden.
119
121
  */
120
- export declare function learnRequestFor(store: Store, repo: string, now: Date): {
122
+ export declare function learnRequestFor(store: Store, repo: string, now: Date, runId?: number): {
121
123
  learn: string[];
122
124
  leftOut: number;
123
125
  };
@@ -25,6 +25,8 @@ import { createHash } from "node:crypto";
25
25
  import { failingTestsOf } from "./gate-failure.js";
26
26
  import { checkContextOf, projectGitDir, treeFactsOf } from "./check-report.js";
27
27
  import { readVerifiedArtifact } from "./evidence.js";
28
+ import { recordObservations } from "./flaky.js";
29
+ import { considerLessonNote, markTalliesPartial, revisedFailures, tallyCheck } from "./lesson-notes.js";
28
30
  import { CHECK_REPORT_PREFIX, isExactCase, learnLineSchema, learnRequest, LEARN_MAX_BYTES, LEARN_PREFIX } from "./contracts/release-check.js";
29
31
  import { nightlyTaskRef, releaseTaskRef } from "./store.js";
30
32
  /** The sources a result can come from, and the states it can have. */
@@ -36,6 +38,8 @@ export const UNEXPLAINED = "unexplained";
36
38
  export const MAX_RESULTS_PER_CHECK = 20_000;
37
39
  /** How old a tracked observation may be and still be asked about (docs/plans/learning-loops.md, 90-day window). */
38
40
  export const OBSERVATION_WATCH_MS = 90 * 24 * 60 * 60 * 1000;
41
+ /** How long per-identity results are kept (docs/plans/learning-loops.md, "Per table"). */
42
+ const RESULT_RETAIN_MS = 180 * 24 * 60 * 60 * 1000;
39
43
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
40
44
  /**
41
45
  * The conditions a check's command starts under: its commit's verified tree facts, the approved command's digest, its
@@ -225,16 +229,47 @@ export function recordCheckFacts(store, root, input) {
225
229
  kept.set(key, row);
226
230
  }
227
231
  const at = input.now.toISOString();
228
- store.transact(() => {
229
- const inserted = store.handle.prepare(`INSERT INTO check_fact (run, repo, task_ref, head, tree, command_digest, access, harness_digest, lockfile, conditions_digest,
232
+ let recorded = false;
233
+ try {
234
+ store.transact(() => {
235
+ const inserted = store.handle.prepare(`INSERT INTO check_fact (run, repo, task_ref, head, tree, command_digest, access, harness_digest, lockfile, conditions_digest,
230
236
  conditions_class, purpose, learn_state, learn_why, recorded_at) SELECT id, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM run WHERE id = ?
231
237
  ON CONFLICT (run) DO NOTHING`).run(input.repo, input.taskRef, conditions?.head ?? null, comparable ? conditions.tree : null, conditions?.commandDigest ?? null, conditions?.access ?? null, comparable ? conditions.harnessDigest : null, comparable ? conditions.lockfile : null, comparable ? conditions.conditionsDigest : null, comparable ? conditions.conditionsClass : null, checkPurposeOf(store, input.taskRef), learnState, learnState === "not-recorded" ? learnWhy ?? "not recorded" : null, at, input.runId);
232
- if (Number(inserted.changes) !== 1)
233
- return;
234
- const insert = store.handle.prepare("INSERT INTO check_result (run, identity, state, source, recorded_at) VALUES (?, ?, ?, ?, ?)");
235
- for (const row of [...kept.values()].slice(0, MAX_RESULTS_PER_CHECK))
236
- insert.run(input.runId, row.identity, row.state, row.source, at);
237
- });
238
+ if (Number(inserted.changes) !== 1)
239
+ return;
240
+ const insert = store.handle.prepare("INSERT INTO check_result (run, identity, state, source, recorded_at) VALUES (?, ?, ?, ?, ?)");
241
+ for (const row of [...kept.values()].slice(0, MAX_RESULTS_PER_CHECK))
242
+ insert.run(input.runId, row.identity, row.state, row.source, at);
243
+ // In the same transaction: what this check adds to the flaky-test list and to accepted notes' tallies, advisory like
244
+ // everything here.
245
+ const observations = recordObservations(store, input.runId, input.now);
246
+ if (input.repo !== null)
247
+ tallyCheck(store, input.runId, input.repo, observations, input.now);
248
+ recorded = true;
249
+ });
250
+ }
251
+ catch (error) {
252
+ // Not every check was counted this month: its tallies say so.
253
+ if (input.repo !== null) {
254
+ try {
255
+ markTalliesPartial(store, input.repo, input.now);
256
+ }
257
+ catch { /* the note reads as it was */ }
258
+ }
259
+ throw error;
260
+ }
261
+ // A person's lesson note, when this check is what passed a revision. Advisory: nothing here reaches the check.
262
+ if (recorded) {
263
+ try {
264
+ considerLessonNote(store, root, input.runId, input.now);
265
+ }
266
+ catch { /* no note */ }
267
+ }
268
+ }
269
+ /** The storage sweep's own step: results older than the retention window go; their fact rows stay. */
270
+ export function pruneCheckResults(store, now) {
271
+ const cut = new Date(now.getTime() - RESULT_RETAIN_MS).toISOString();
272
+ return Number(store.handle.prepare("DELETE FROM check_result WHERE recorded_at < ?").run(cut).changes);
238
273
  }
239
274
  export function checkFactOf(store, runId) {
240
275
  const row = store.handle.prepare("SELECT * FROM check_fact WHERE run = ?").get(runId);
@@ -255,7 +290,7 @@ export function checkResultsOf(store, runId) {
255
290
  * accepted note's until it ends, an observation's for 90 days — exact cases only, the most recently tracked first, at
256
291
  * most 200. Those left out are counted (`leftOut`): a coverage gap, said, never hidden.
257
292
  */
258
- export function learnRequestFor(store, repo, now) {
293
+ export function learnRequestFor(store, repo, now, runId) {
259
294
  const since = new Date(now.getTime() - OBSERVATION_WATCH_MS).toISOString();
260
295
  let tracked;
261
296
  // Advisory: a read that fails asks about nothing, and the check runs as it would have.
@@ -267,6 +302,17 @@ export function learnRequestFor(store, repo, now) {
267
302
  catch {
268
303
  return { learn: [], leftOut: 0 };
269
304
  }
270
- const learn = learnRequest(tracked);
271
- return { learn, leftOut: tracked.length - learn.length };
305
+ // First, the failed cases of the check this run revises (lesson-notes.ts rule 4).
306
+ let revised = [];
307
+ if (runId !== undefined) {
308
+ try {
309
+ revised = revisedFailures(store, runId);
310
+ }
311
+ catch {
312
+ revised = [];
313
+ }
314
+ }
315
+ const all = [...new Set([...revised, ...tracked])];
316
+ const learn = learnRequest(all);
317
+ return { learn, leftOut: all.length - learn.length };
272
318
  }
@@ -1,4 +1,5 @@
1
1
  import { type CheckReport } from "./contracts/release-check.js";
2
+ import { type CheckLoadRecord } from "./check-load.js";
2
3
  import { INFRA_RERUNS_PER_CANDIDATE, INFRA_RERUNS_PER_INCIDENT, type FailureClassName } from "./release.js";
3
4
  import type { Store } from "./store.js";
4
5
  export type FailureClass = FailureClassName;
@@ -20,12 +21,16 @@ export type CheckFacts = {
20
21
  failure?: string;
21
22
  };
22
23
  coverage?: string | undefined;
24
+ /** The approved limit of the command the receipt names (receipt.command.timeoutMs): a load record must be for it. */
25
+ grantMs?: number | undefined;
23
26
  };
24
27
  /**
25
- * One failed check, classified. `facts` come from the plane's sealed receipt; `report` (the check's own report, sealed
26
- * when valid) only ever picks among the classes that block. Null when the check passed.
28
+ * One failed check, classified. `facts` come from the plane's sealed receipt and `load` from the plane's own sealed load
29
+ * record (check-load.ts); `report` (the check's own report, sealed when valid) only ever picks among the classes that
30
+ * block — a timeout is excused only when the report names nothing else that failed, and only by the plane's own facts:
31
+ * its load readings cut the budget, and the check ran into its limit by the plane's own clock. Null when it passed.
27
32
  */
28
- export declare function classifyCheckFailure(facts: CheckFacts, report: CheckReport | null): ClassifiedFailure | null;
33
+ export declare function classifyCheckFailure(facts: CheckFacts, report: CheckReport | null, load?: CheckLoadRecord | null): ClassifiedFailure | null;
29
34
  /** What passed only on a retry inside a passing check (a real-model journey group, a unit file): counted and reported,
30
35
  * never silently part of a pass. Null when there is no valid report to read it from: unknown, never "no retries". */
31
36
  export declare function retriedInside(report: CheckReport | null): string[] | null;
@@ -45,6 +50,22 @@ export declare const SETUP_REPLAY_RETRY = "the whole check, rerun after the appr
45
50
  * missing program; a receipt that doesn't classify it (an older one) says so, never that it was a launch failure.
46
51
  */
47
52
  export declare function setupReplayWords(first: unknown): string;
53
+ /**
54
+ * An attempt interrupted before its candidate was even loaded (run 3326: the worker stopped under it — "The attempt was
55
+ * stopped before the prepared candidate was loaded."): it ran no check, so it is no check failure of the commit. Only
56
+ * the plane's own records say so, all of them at once: the run ended failed/interrupted, recorded no commit, and has no
57
+ * check attempt (sealed before any check command runs), no recorded check and no check log or receipt. Any attempt that
58
+ * got as far as its check keeps every rule below.
59
+ */
60
+ export declare function ranNoCheck(store: Store, row: {
61
+ id: number;
62
+ outcome: unknown;
63
+ reason: unknown;
64
+ head: unknown;
65
+ checkRow: unknown;
66
+ }): boolean;
67
+ /** The ledger entry the build writes when it refuses a prepared candidate before any process (builder.ts). */
68
+ export declare const NOTHING_STARTED_ACTION = "attempt refused before anything ran";
48
69
  /**
49
70
  * Whether a passing check of a release task stands, given every earlier attempt on the same candidate and signed scope:
50
71
  * each earlier failure must be confirmed infrastructure the release itself reran (at most two), and nothing in the
@@ -5,7 +5,8 @@
5
5
  * deterministic typecheck, build, a failing test, a scripted journey, an upgrade, a required test gone:
6
6
  * nothing tolerated, never rerun to pass; the branch is fixed and the new commit released.
7
7
  * infrastructure independently confirmed by the plane itself: the check never started, this worker lost the
8
- * build, Toolroll's own shutdown stopped it — or, on GitHub, a job that failed before any of the
8
+ * build, Toolroll's own shutdown stopped it, it ran out of time on a machine the plane measured
9
+ * busy enough to cut its budget to half or less (check-load.ts) — or, on GitHub, a job that failed before any of the
9
10
  * workflow's own steps ran. At most one rerun per incident and two per candidate, under the same
10
11
  * approval; a pass after them counts.
11
12
  * unknown exit 1 with no failing test named, an unexplained timeout or signal, no result at all: an error
@@ -20,10 +21,12 @@
20
21
  */
21
22
  import { CHECK_REPORT_CAPTURE, readSealedJson } from "./check-report.js";
22
23
  import { readCheckReport } from "./contracts/release-check.js";
24
+ import { busyMachineTimeout, CHECK_LOAD_CAPTURE, readCheckLoad } from "./check-load.js";
23
25
  import { regateTask } from "./dispose.js";
24
26
  import { INFRA_RERUNS_PER_CANDIDATE, INFRA_RERUNS_PER_INCIDENT } from "./release.js";
25
27
  import { releaseCheckScope } from "./scope.js";
26
- import { verificationEvidence } from "./verification-evidence.js";
28
+ import { isVerificationReceipt, verificationEvidence } from "./verification-evidence.js";
29
+ import { checkAttemptOf } from "./store.js";
27
30
  /** The plane's limits for confirmed infrastructure failures (one rerun per incident, two per candidate). */
28
31
  export { INFRA_RERUNS_PER_CANDIDATE, INFRA_RERUNS_PER_INCIDENT };
29
32
  /** The ledger action that records each rerun the release asked for, and why. */
@@ -35,11 +38,15 @@ const PLANE_INFRA = {
35
38
  "custody-lost": "this worker lost the build before the check could finish",
36
39
  "stopped-by-toolroll": "Toolroll's own shutdown stopped the check before it finished",
37
40
  };
41
+ /** How a timeout on a busy machine reads (its rerun is the release terms' "Toolroll's own machine fails"). */
42
+ const busyWords = (why) => `${why}: Toolroll's own machine, so this commit's check runs again`;
38
43
  /**
39
- * One failed check, classified. `facts` come from the plane's sealed receipt; `report` (the check's own report, sealed
40
- * when valid) only ever picks among the classes that block. Null when the check passed.
44
+ * One failed check, classified. `facts` come from the plane's sealed receipt and `load` from the plane's own sealed load
45
+ * record (check-load.ts); `report` (the check's own report, sealed when valid) only ever picks among the classes that
46
+ * block — a timeout is excused only when the report names nothing else that failed, and only by the plane's own facts:
47
+ * its load readings cut the budget, and the check ran into its limit by the plane's own clock. Null when it passed.
41
48
  */
42
- export function classifyCheckFailure(facts, report) {
49
+ export function classifyCheckFailure(facts, report, load = null) {
43
50
  const result = facts.result;
44
51
  const passedCoverage = facts.coverage === undefined || facts.coverage === "full" || facts.coverage === "composed";
45
52
  if (result.configured && result.ran === true && result.exitCode === 0 && passedCoverage)
@@ -51,8 +58,12 @@ export function classifyCheckFailure(facts, report) {
51
58
  const infra = PLANE_INFRA[failure];
52
59
  if (infra !== undefined)
53
60
  return { class: "infrastructure", why: infra };
54
- if (failure === "timed-out" || failure === "retry-timed-out")
55
- return { class: "unknown", why: "the check timed out, and nothing says why" };
61
+ if (failure === "timed-out" || failure === "retry-timed-out") {
62
+ // The plane stopped it at its limit. With the plane's own record of a cut budget, the machine's fault; otherwise
63
+ // (a normal budget, or no record) a timeout nothing explains, as ever.
64
+ const busy = facts.grantMs === undefined ? null : busyMachineTimeout(load, facts.grantMs, true);
65
+ return busy !== null ? { class: "infrastructure", why: busyWords(busy) } : { class: "unknown", why: "the check timed out, and nothing says why" };
66
+ }
56
67
  if (failure === "baseline-unverifiable")
57
68
  return { class: "unknown", why: "the last release Toolroll recorded couldn't be verified, so what the check owed is unknown and it wasn't run" };
58
69
  if (failure === "no-result")
@@ -65,10 +76,14 @@ export function classifyCheckFailure(facts, report) {
65
76
  if (report === null)
66
77
  return { class: "unknown", why: `the check exited ${result.exitCode ?? "nonzero"} and gave no report naming what failed` };
67
78
  const deterministic = [], agent = [], unexplained = [];
79
+ // Whether everything unexplained is a part that ran out of time (never a signal, never an error no test owned).
80
+ let onlyTimeouts = true;
68
81
  for (const part of report.parts) {
69
82
  if (!part.selected || !part.ran || part.code === null)
70
83
  continue;
71
84
  if (part.timedOut || part.signal != null) {
85
+ if (!part.timedOut || part.signal != null)
86
+ onlyTimeouts = false;
72
87
  unexplained.push(`${part.part} ${part.timedOut ? "timed out" : `was ended by ${part.signal}`}`);
73
88
  continue;
74
89
  }
@@ -78,8 +93,10 @@ export function classifyCheckFailure(facts, report) {
78
93
  for (const file of report.units?.files ?? [])
79
94
  if (file.state === "failed")
80
95
  deterministic.push(`${file.file} failed`);
81
- if ((report.units?.runErrors ?? 0) > 0 && !(report.units?.files ?? []).some(file => file.state === "failed"))
96
+ if ((report.units?.runErrors ?? 0) > 0 && !(report.units?.files ?? []).some(file => file.state === "failed")) {
97
+ onlyTimeouts = false;
82
98
  unexplained.push(`${report.units.runErrors} unit errors no test owned`);
99
+ }
83
100
  for (const run of report.journeys)
84
101
  for (const group of run.groups) {
85
102
  if (group.state !== "failed")
@@ -94,8 +111,14 @@ export function classifyCheckFailure(facts, report) {
94
111
  return { class: "deterministic", why: list(deterministic) };
95
112
  if (agent.length > 0)
96
113
  return { class: "agent-behaviour", why: list(agent) };
97
- if (unexplained.length > 0)
114
+ if (unexplained.length > 0) {
115
+ // Only parts that ran out of time, and the plane's own record says the machine cut the budget and the check ran
116
+ // into its limit: the machine's fault. The report can only withhold this (by naming anything else), never grant it.
117
+ const busy = onlyTimeouts && facts.grantMs !== undefined ? busyMachineTimeout(load, facts.grantMs, false) : null;
118
+ if (busy !== null)
119
+ return { class: "infrastructure", why: busyWords(`${list(unexplained)}; ${busy}`) };
98
120
  return { class: "unknown", why: list(unexplained) };
121
+ }
99
122
  return { class: "unknown", why: `the check exited ${result.exitCode ?? "nonzero"} with no failing test identified` };
100
123
  }
101
124
  /** What passed only on a retry inside a passing check (a real-model journey group, a unit file): counted and reported,
@@ -127,7 +150,10 @@ export function classifyRunCheck(store, root, runId) {
127
150
  return { class: "unknown", why: "the check's receipt names no result" };
128
151
  const sealed = readSealedJson(store, root, runId, CHECK_REPORT_CAPTURE);
129
152
  const parsed = sealed === null ? null : readCheckReport(sealed.value);
130
- return classifyCheckFailure({ result: receipt.result, coverage: receipt.coverage }, parsed?.ok === true ? parsed.value : null);
153
+ // The plane's own load record (check-load.ts): absent on older runs, and then nothing is excused.
154
+ const loadSealed = readSealedJson(store, root, runId, CHECK_LOAD_CAPTURE);
155
+ const grantMs = typeof receipt.command?.timeoutMs === "number" ? receipt.command.timeoutMs : undefined;
156
+ return classifyCheckFailure({ result: receipt.result, coverage: receipt.coverage, grantMs }, parsed?.ok === true ? parsed.value : null, loadSealed === null ? null : readCheckLoad(loadSealed.value));
131
157
  }
132
158
  /**
133
159
  * What passed only on a retry inside a run's check (eval-harness review 1, finding 3): the summary sealed into its
@@ -174,12 +200,59 @@ export function setupReplayWords(first) {
174
200
  }
175
201
  return `${SETUP_REPLAY_RETRY} (its first attempt's failure isn't classified)`;
176
202
  }
177
- /** The task's finished builder runs under `digest`, oldest first, each with whether its check passed. */
203
+ /**
204
+ * An attempt interrupted before its candidate was even loaded (run 3326: the worker stopped under it — "The attempt was
205
+ * stopped before the prepared candidate was loaded."): it ran no check, so it is no check failure of the commit. Only
206
+ * the plane's own records say so, all of them at once: the run ended failed/interrupted, recorded no commit, and has no
207
+ * check attempt (sealed before any check command runs), no recorded check and no check log or receipt. Any attempt that
208
+ * got as far as its check keeps every rule below.
209
+ */
210
+ export function ranNoCheck(store, row) {
211
+ if (row.outcome !== "failed" || (row.reason !== "interrupted" && row.reason !== "no-op") || row.head !== null || row.checkRow !== null)
212
+ return false;
213
+ if (checkAttemptOf(store, row.id) !== null)
214
+ return false;
215
+ if (store.artifactsFor(row.id).some(artifact => artifact.kind === "check-log" || isVerificationReceipt(artifact)))
216
+ return false;
217
+ return row.reason === "interrupted" || refusedBeforeAnything(store, row.id);
218
+ }
219
+ /** The ledger entry the build writes when it refuses a prepared candidate before any process (builder.ts). */
220
+ export const NOTHING_STARTED_ACTION = "attempt refused before anything ran";
221
+ /** Every table a process, an agent or a check of this run writes. */
222
+ const RUN_TRACES = [["artifact", "run"], ["run_process", "run"], ["run_process_summary", "run"], ["run_tool", "run"], ["run_activity", "run"],
223
+ ["check_stamp", "run_id"], ["release_check_attempt", "run"], ["run_note", "run"], ["agent_turn", "run"], ["run_checkpoint", "run"], ["run_decision", "run"]];
224
+ /**
225
+ * An attempt the build refused before anything ran (run 3332: Git couldn't read HEAD while it proved the candidate; the
226
+ * next attempt passed). A failed attempt with reason `no-op` proves nothing alone: an agent's empty result and a check's
227
+ * refusal end that way too. So the build's own ledger entry says so, written only where nothing has run, and the run's
228
+ * records agree: no base stamped, no provider start, session, tokens or commit, and not one row of any process, agent or
229
+ * check. All of them, or it stands as a failure, and a record that can't be read (an older database) never excuses one.
230
+ */
231
+ function refusedBeforeAnything(store, runId) {
232
+ try {
233
+ const said = store.handle.prepare("SELECT COUNT(*) AS n FROM action_ledger WHERE run_id = ? AND action = ? AND outcome = 'refused'").get(runId, NOTHING_STARTED_ACTION)?.["n"];
234
+ if (Number(said) !== 1)
235
+ return false;
236
+ const run = store.handle.prepare("SELECT base_revision, head_revision, provider_started_at, session_id, tokens_in, tokens_out, cost_usd, committed FROM run WHERE id = ?").get(runId);
237
+ if (run === undefined || Object.values(run).some(value => value !== null))
238
+ return false;
239
+ // check_fact is a learning-loops table (eval-hooks.ts): read here in its own literal statement, never through the
240
+ // table list, so no SQL text that could name it is assembled outside its sole writer.
241
+ if (store.handle.prepare("SELECT 1 FROM check_fact WHERE run = ? LIMIT 1").get(runId) !== undefined)
242
+ return false;
243
+ return RUN_TRACES.every(([table, column]) => store.handle.prepare(`SELECT 1 FROM ${table} WHERE ${column} = ? LIMIT 1`).get(runId) === undefined);
244
+ }
245
+ catch {
246
+ return false;
247
+ }
248
+ }
249
+ /** The task's finished builder runs under `digest`, oldest first, each with whether its check passed or never ran. */
178
250
  function builderRuns(store, taskRef, digest) {
179
- return store.handle.prepare(`SELECT r.id, r.outcome, r.scope_digest, r.finished_at, c.status FROM run r LEFT JOIN run_check c ON c.run = r.id
251
+ return store.handle.prepare(`SELECT r.id, r.outcome, r.reason, r.head_revision, r.scope_digest, r.finished_at, c.status, c.run AS check_row FROM run r LEFT JOIN run_check c ON c.run = r.id
180
252
  WHERE r.task_ref = ? AND r.role = 'builder' AND r.finished_at IS NOT NULL AND r.scope_digest = ? ORDER BY r.id`).all(taskRef, digest)
181
253
  .map(row => ({ id: Number(row["id"]), outcome: row["outcome"] === null ? null : String(row["outcome"]), scopeDigest: String(row["scope_digest"]), finishedAt: String(row["finished_at"]),
182
- passed: (row["outcome"] === "built" || row["outcome"] === "no-change") && row["status"] === "passed" }));
254
+ passed: (row["outcome"] === "built" || row["outcome"] === "no-change") && row["status"] === "passed",
255
+ noCheck: ranNoCheck(store, { id: Number(row["id"]), outcome: row["outcome"], reason: row["reason"], head: row["head_revision"] ?? null, checkRow: row["check_row"] ?? null }) }));
183
256
  }
184
257
  /** The reruns the release asked for on this task (each source run once): run id → its class. */
185
258
  function reruns(store, taskId) {
@@ -208,6 +281,9 @@ export function releaseCheckStanding(store, root, input) {
208
281
  for (const one of builderRuns(store, ref.id, digest)) {
209
282
  if (one.id >= input.run || one.passed)
210
283
  continue;
284
+ // Interrupted before its candidate was loaded: no check ran, so there is no check failure to stand against a pass.
285
+ if (one.noCheck)
286
+ continue;
211
287
  const classified = one.outcome === "built" || one.outcome === "no-change" ? classifyRunCheck(store, root, one.id) : { class: "unknown", why: `its build ended ${one.outcome ?? "without an outcome"}` };
212
288
  if (classified === null)
213
289
  continue;
@@ -24,6 +24,10 @@ export type CheckSettingsView = {
24
24
  batch?: {
25
25
  on: boolean;
26
26
  };
27
+ /** Small tasks start right away (fast-start.ts). */
28
+ fast?: {
29
+ on: boolean;
30
+ };
27
31
  said: string | null;
28
32
  problem: string | null;
29
33
  };
@@ -48,7 +48,16 @@ export function checkSettingsHtml(view) {
48
48
  body: view.full === null
49
49
  ? joinHtml([note("Approve one from the command line:"), codeLine(`toolroll verify set --repo … --command "…"`)])
50
50
  : command(view.full) });
51
- return joinHtml([head, level, batchHtml(view), reviewHtml(view), quick, full]);
51
+ return joinHtml([head, level, fastHtml(view), batchHtml(view), reviewHtml(view), quick, full]);
52
+ /** Small tasks: one state, one sentence, and its one change behind a disclosure (the password). */
53
+ function fastHtml(view) {
54
+ if (view.fast === undefined)
55
+ return "";
56
+ const act = view.fast.on ? "Turn off" : "Turn on";
57
+ return settingsSection({ id: "small-tasks", title: "Small tasks start right away", state: html `<span data-small-tasks="${view.fast.on ? "on" : "off"}"> · ${view.fast.on ? "On" : "Off"}</span>`,
58
+ lead: "New task shows the exact terms and Build approves them, with no plan step. It takes a person, and only small, ordinary changes.",
59
+ body: view.canChange ? disclose(act, form("small-tasks-form", "small-tasks", html ``, { label: act, variant: "outline" }, { on: view.fast.on ? "0" : "1" })) : "" });
60
+ }
52
61
  /** Batch checks: one state and one sentence; turning it on restates exactly what happens before the password. */
53
62
  function batchHtml(view) {
54
63
  const batch = view.batch;
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ export declare const CHECK_LOAD_CAPTURE = "release check load v1";
3
+ /** One reading of the machine: its cores and one-minute load (NaN where it isn't measured: Windows reports 0). */
4
+ export type MachineSample = {
5
+ cores: number;
6
+ load: number;
7
+ };
8
+ export declare const machineSample: (platform?: NodeJS.Platform, read?: () => number[], cores?: () => number) => MachineSample;
9
+ /** The check's budget on a quiet machine: three quarters of the cores, at least 2 (release-check.mjs checkBudget). */
10
+ export declare const normalFree: (cores: number) => number;
11
+ /** The check's budget under `load` (release-check.mjs checkBudget's `free`). Unmeasured load budgets from the cores. */
12
+ export declare const freeAt: (cores: number, load: number) => number;
13
+ /** Cut: the budget is at most half its normal size. Unmeasured load is never cut. */
14
+ export declare const budgetCut: (cores: number, load: number) => boolean;
15
+ /** A release check's limit never goes past this, however busy the machine. */
16
+ export declare const CHECK_LIMIT_CEILING_MS: number;
17
+ /** …nor past this many times the approved limit. */
18
+ export declare const CHECK_LIMIT_MAX_FACTOR = 2;
19
+ /**
20
+ * The limit a release check runs under: the approved one, unless the machine's load at its start cuts the check's
21
+ * budget to half or less — then the approved limit times normal/granted, at most twice it and never past the ceiling
22
+ * (an approved limit already at or past the ceiling is never extended). `why` says which, in words.
23
+ */
24
+ export declare function checkLimitFor(grantMs: number, sample: MachineSample): {
25
+ limitMs: number;
26
+ why: string;
27
+ };
28
+ /** When a check ends itself before its limit (release-check.mjs checkDeadline, from its start): src/check-load.test.ts
29
+ * holds the two equal. A check that ran at least this long ran into its own limit. */
30
+ export declare const selfStopAfter: (limitMs: number) => number;
31
+ /** The samples read for the budget the check started with: the check waits up to ten minutes for the load to drop
32
+ * (release-check.mjs), so the least load in its first eleven — before its own load counts — is what it budgeted from at
33
+ * best. The least, so a check that found a quieter moment is never called cut. */
34
+ export declare const CUT_WINDOW_MS: number;
35
+ declare const recordSchema: z.ZodObject<{
36
+ version: z.ZodLiteral<1>;
37
+ cores: z.ZodInt;
38
+ grantMs: z.ZodInt;
39
+ limitMs: z.ZodInt;
40
+ why: z.ZodString;
41
+ startedAt: z.ZodString;
42
+ elapsedMs: z.ZodNullable<z.ZodInt>;
43
+ samples: z.ZodArray<z.ZodObject<{
44
+ atMs: z.ZodInt;
45
+ load: z.ZodNullable<z.ZodNumber>;
46
+ }, z.core.$strict>>;
47
+ }, z.core.$strict>;
48
+ export type CheckLoadRecord = z.infer<typeof recordSchema>;
49
+ /** A sealed record read back, or null when it isn't one (an older run has none: then nothing is excused). */
50
+ export declare function readCheckLoad(value: unknown): CheckLoadRecord | null;
51
+ /**
52
+ * Why a release check that ran out of time did so because the machine was busy — or null. Every fact is the plane's:
53
+ * its load readings in the first eleven minutes (the least of them) cut the check's budget to half or less, and the
54
+ * check's last attempt ran into its own limit (at least `selfStopAfter(limitMs)`), or the plane stopped it at the limit
55
+ * (`stoppedAtLimit`). A check with its normal budget that times out is never excused here: a real hang stays a failure.
56
+ */
57
+ export declare function busyMachineTimeout(record: CheckLoadRecord | null, grantMs: number, stoppedAtLimit: boolean): string | null;
58
+ /**
59
+ * Watches one release check: the limit at its start, the load every 15 s while it runs, and how long its last attempt
60
+ * took. Advisory to the check itself (the limit is a plane value the check is told), and the only source of the record.
61
+ */
62
+ export declare class CheckLoadWatch {
63
+ private readonly grantMs;
64
+ private readonly options;
65
+ private readonly started;
66
+ private readonly samples;
67
+ private readonly timer;
68
+ private readonly cores;
69
+ private attemptStart;
70
+ private elapsed;
71
+ readonly limitMs: number;
72
+ readonly why: string;
73
+ constructor(grantMs: number, options?: {
74
+ sample?: () => MachineSample;
75
+ clock?: () => number;
76
+ everyMs?: number;
77
+ });
78
+ private push;
79
+ /** Around each attempt of the command: the last one's time is the record's. */
80
+ attemptStarted(): void;
81
+ attemptEnded(): void;
82
+ /** Stops the readings and gives the record to seal. */
83
+ finish(): CheckLoadRecord;
84
+ }
85
+ export {};
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The plane's own record of the machine a release check ran on (docs/plans/heavy-jobs.md, layer 1b). The check sizes
3
+ * its budget from the load it finds (scripts/release-check.mjs checkBudget), so on a busy machine it runs on one or two
4
+ * lanes — and ran out of its approved hour twice in one evening (runs 3311 and 3315), which read as an unexplained
5
+ * failure a person had to re-approve.
6
+ *
7
+ * Two plane-side answers, never the check's own words:
8
+ * - the limit: a release check that starts with its budget cut to half its normal size or less gets more time, in
9
+ * proportion to the budget it lost, never more than twice the approved limit nor past a hard ceiling (two hours);
10
+ * - the evidence: the load the plane measured itself (when the check started and every 15 s while it ran), the limit
11
+ * it gave and why, and how long the check ran, sealed beside its receipt (CHECK_LOAD_CAPTURE). check-failure.ts reads
12
+ * only this record — never the check's output or report — to call a timeout under a cut budget the machine's own
13
+ * fault. Nothing the candidate writes can make a failure infrastructure.
14
+ *
15
+ * The budget arithmetic is the plane's copy of the check's (src/check-load.test.ts holds them equal), so the budget the
16
+ * plane judges is the one the check gives itself from the same load.
17
+ */
18
+ import { availableParallelism, loadavg } from "node:os";
19
+ import { z } from "zod";
20
+ export const CHECK_LOAD_CAPTURE = "release check load v1";
21
+ export const machineSample = (platform = process.platform, read = loadavg, cores = availableParallelism) => ({ cores: cores(), load: platform === "win32" ? Number.NaN : read()[0] ?? Number.NaN });
22
+ /** The check's budget on a quiet machine: three quarters of the cores, at least 2 (release-check.mjs checkBudget). */
23
+ export const normalFree = (cores) => Math.max(2, Math.floor(cores * 0.75));
24
+ /** The check's budget under `load` (release-check.mjs checkBudget's `free`). Unmeasured load budgets from the cores. */
25
+ export const freeAt = (cores, load) => Math.max(2, normalFree(cores) - (Number.isFinite(load) && load > 0 ? Math.floor(load) : 0));
26
+ /** Cut: the budget is at most half its normal size. Unmeasured load is never cut. */
27
+ export const budgetCut = (cores, load) => Number.isFinite(load) && freeAt(cores, load) * 2 <= normalFree(cores);
28
+ /** A release check's limit never goes past this, however busy the machine. */
29
+ export const CHECK_LIMIT_CEILING_MS = 120 * 60_000;
30
+ /** …nor past this many times the approved limit. */
31
+ export const CHECK_LIMIT_MAX_FACTOR = 2;
32
+ const minutes = (ms) => `${Math.round(ms / 60_000)} min`;
33
+ /**
34
+ * The limit a release check runs under: the approved one, unless the machine's load at its start cuts the check's
35
+ * budget to half or less — then the approved limit times normal/granted, at most twice it and never past the ceiling
36
+ * (an approved limit already at or past the ceiling is never extended). `why` says which, in words.
37
+ */
38
+ export function checkLimitFor(grantMs, sample) {
39
+ const normal = normalFree(sample.cores), free = freeAt(sample.cores, sample.load);
40
+ if (!budgetCut(sample.cores, sample.load)) {
41
+ return { limitMs: grantMs, why: `the approved ${minutes(grantMs)}: ${Number.isFinite(sample.load) ? `load ${sample.load.toFixed(1)} on ${sample.cores} cores left the check ${free} of its normal ${normal}` : "load isn't measured here"}` };
42
+ }
43
+ const ceiling = Math.max(grantMs, Math.min(grantMs * CHECK_LIMIT_MAX_FACTOR, CHECK_LIMIT_CEILING_MS));
44
+ const limitMs = Math.min(ceiling, Math.round(grantMs * normal / free));
45
+ return { limitMs, why: `the approved ${minutes(grantMs)}${limitMs > grantMs ? `, extended to ${minutes(limitMs)}` : ", not extended (it is at the ceiling)"}: load ${sample.load.toFixed(1)} on ${sample.cores} cores left the check ${free} of its normal ${normal}, so it gets time in proportion, at most ${minutes(ceiling)}` };
46
+ }
47
+ /** When a check ends itself before its limit (release-check.mjs checkDeadline, from its start): src/check-load.test.ts
48
+ * holds the two equal. A check that ran at least this long ran into its own limit. */
49
+ export const selfStopAfter = (limitMs) => limitMs - Math.min(Math.max(3 * 60_000, Math.round(limitMs / 10)), Math.round(limitMs / 2));
50
+ /** The samples read for the budget the check started with: the check waits up to ten minutes for the load to drop
51
+ * (release-check.mjs), so the least load in its first eleven — before its own load counts — is what it budgeted from at
52
+ * best. The least, so a check that found a quieter moment is never called cut. */
53
+ export const CUT_WINDOW_MS = 11 * 60_000;
54
+ const SAMPLE_EVERY_MS = 15_000;
55
+ const MAX_SAMPLES = 1000;
56
+ const recordSchema = z.strictObject({
57
+ version: z.literal(1),
58
+ cores: z.int().min(1).max(4096),
59
+ grantMs: z.int().min(1),
60
+ limitMs: z.int().min(1),
61
+ why: z.string().max(1000),
62
+ startedAt: z.string().max(40),
63
+ /** How long the check's last attempt ran, by the plane's clock (null: it never started). */
64
+ elapsedMs: z.int().min(0).nullable(),
65
+ /** Each reading, `atMs` after the check started; null where load isn't measured. */
66
+ samples: z.array(z.strictObject({ atMs: z.int().min(0), load: z.number().min(0).nullable() })).max(MAX_SAMPLES),
67
+ });
68
+ /** A sealed record read back, or null when it isn't one (an older run has none: then nothing is excused). */
69
+ export function readCheckLoad(value) {
70
+ const read = recordSchema.safeParse(value);
71
+ if (!read.success)
72
+ return null;
73
+ const record = read.data;
74
+ // A limit the plane could never have given is no record at all.
75
+ const ceiling = Math.max(record.grantMs, Math.min(record.grantMs * CHECK_LIMIT_MAX_FACTOR, CHECK_LIMIT_CEILING_MS));
76
+ return record.limitMs < record.grantMs || record.limitMs > ceiling ? null : record;
77
+ }
78
+ /**
79
+ * Why a release check that ran out of time did so because the machine was busy — or null. Every fact is the plane's:
80
+ * its load readings in the first eleven minutes (the least of them) cut the check's budget to half or less, and the
81
+ * check's last attempt ran into its own limit (at least `selfStopAfter(limitMs)`), or the plane stopped it at the limit
82
+ * (`stoppedAtLimit`). A check with its normal budget that times out is never excused here: a real hang stays a failure.
83
+ */
84
+ export function busyMachineTimeout(record, grantMs, stoppedAtLimit) {
85
+ if (record === null || record.grantMs !== grantMs)
86
+ return null;
87
+ const early = record.samples.filter(one => one.atMs <= CUT_WINDOW_MS && one.load !== null).map(one => one.load);
88
+ if (early.length === 0)
89
+ return null;
90
+ const least = Math.min(...early);
91
+ if (!budgetCut(record.cores, least))
92
+ return null;
93
+ if (!stoppedAtLimit && (record.elapsedMs === null || record.elapsedMs < selfStopAfter(record.limitMs)))
94
+ return null;
95
+ return `the machine was busy: its load stayed at ${least.toFixed(1)} or more on ${record.cores} cores as the check started, cutting its budget to ${freeAt(record.cores, least)} of its normal ${normalFree(record.cores)} cores, and it ran out of its ${minutes(record.limitMs)}`;
96
+ }
97
+ /**
98
+ * Watches one release check: the limit at its start, the load every 15 s while it runs, and how long its last attempt
99
+ * took. Advisory to the check itself (the limit is a plane value the check is told), and the only source of the record.
100
+ */
101
+ export class CheckLoadWatch {
102
+ grantMs;
103
+ options;
104
+ started;
105
+ samples = [];
106
+ timer;
107
+ cores;
108
+ attemptStart = null;
109
+ elapsed = null;
110
+ limitMs;
111
+ why;
112
+ constructor(grantMs, options = {}) {
113
+ this.grantMs = grantMs;
114
+ this.options = options;
115
+ const sample = options.sample ?? (() => machineSample());
116
+ const clock = options.clock ?? Date.now;
117
+ this.started = clock();
118
+ const first = sample();
119
+ this.cores = first.cores;
120
+ ({ limitMs: this.limitMs, why: this.why } = checkLimitFor(grantMs, first));
121
+ this.push(0, first.load);
122
+ const every = options.everyMs ?? SAMPLE_EVERY_MS;
123
+ this.timer = every > 0 ? setInterval(() => { try {
124
+ this.push(clock() - this.started, sample().load);
125
+ }
126
+ catch { /* a missed reading */ } }, every) : null;
127
+ this.timer?.unref?.();
128
+ }
129
+ push(atMs, load) {
130
+ if (this.samples.length >= MAX_SAMPLES) {
131
+ if (this.timer !== null)
132
+ clearInterval(this.timer);
133
+ return;
134
+ }
135
+ this.samples.push({ atMs: Math.max(0, Math.round(atMs)), load: Number.isFinite(load) && load >= 0 ? Math.round(load * 100) / 100 : null });
136
+ }
137
+ /** Around each attempt of the command: the last one's time is the record's. */
138
+ attemptStarted() { this.attemptStart = (this.options.clock ?? Date.now)(); }
139
+ attemptEnded() { if (this.attemptStart !== null)
140
+ this.elapsed = Math.max(0, Math.round((this.options.clock ?? Date.now)() - this.attemptStart)); }
141
+ /** Stops the readings and gives the record to seal. */
142
+ finish() {
143
+ if (this.timer !== null)
144
+ clearInterval(this.timer);
145
+ return { version: 1, cores: this.cores, grantMs: this.grantMs, limitMs: this.limitMs, why: this.why, startedAt: new Date(this.started).toISOString(), elapsedMs: this.elapsed, samples: [...this.samples] };
146
+ }
147
+ }