toolroll 0.9.92 → 0.9.94

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 (341) hide show
  1. package/CHANGELOG.md +87 -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 +4 -0
  5. package/dist/agent-fence.js +4 -1
  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 -3
  12. package/dist/assignment.js +16 -3
  13. package/dist/boot-identity.d.ts +2 -1
  14. package/dist/boot-identity.js +6 -4
  15. package/dist/bounded-spawn.d.ts +57 -0
  16. package/dist/bounded-spawn.js +220 -0
  17. package/dist/browser/terminal.js +42 -42
  18. package/dist/browser/workspace.css +2 -2
  19. package/dist/browser/workspace.js +26 -26
  20. package/dist/browser-crew.js +2 -2
  21. package/dist/browser-shell.d.ts +1 -1
  22. package/dist/browser-shell.js +2 -2
  23. package/dist/browser-workspace.d.ts +48 -7
  24. package/dist/browser-workspace.js +3 -3
  25. package/dist/builder-restart.d.ts +21 -0
  26. package/dist/builder-restart.js +50 -0
  27. package/dist/builder.d.ts +4 -0
  28. package/dist/builder.js +73 -25
  29. package/dist/chat-acceptance.js +1 -1
  30. package/dist/chat-actions.d.ts +12 -2
  31. package/dist/chat-actions.js +25 -2
  32. package/dist/chat-channel.js +11 -0
  33. package/dist/chat-decide.d.ts +7 -2
  34. package/dist/chat-decide.js +37 -9
  35. package/dist/chat-quiet.js +3 -3
  36. package/dist/chat-voice.d.ts +1 -1
  37. package/dist/chat-voice.js +2 -2
  38. package/dist/check-facts.d.ts +126 -0
  39. package/dist/check-facts.js +318 -0
  40. package/dist/check-failure.d.ts +22 -3
  41. package/dist/check-failure.js +57 -13
  42. package/dist/check-levels-ui.d.ts +4 -0
  43. package/dist/check-levels-ui.js +10 -1
  44. package/dist/check-load.d.ts +85 -0
  45. package/dist/check-load.js +147 -0
  46. package/dist/check-only-standing.d.ts +36 -0
  47. package/dist/check-only-standing.js +39 -0
  48. package/dist/check-only.d.ts +15 -0
  49. package/dist/check-only.js +99 -0
  50. package/dist/check-report.d.ts +7 -0
  51. package/dist/check-report.js +4 -3
  52. package/dist/check-stamp.d.ts +46 -0
  53. package/dist/check-stamp.js +93 -0
  54. package/dist/checkout-cleanup.d.ts +36 -5
  55. package/dist/checkout-cleanup.js +166 -14
  56. package/dist/cli-help.d.ts +4 -1
  57. package/dist/cli-help.js +47 -30
  58. package/dist/cli.js +16 -1
  59. package/dist/code-extract.d.ts +44 -0
  60. package/dist/code-extract.js +88 -0
  61. package/dist/code-map-acts.d.ts +24 -0
  62. package/dist/code-map-acts.js +53 -0
  63. package/dist/code-map-admission.d.ts +52 -0
  64. package/dist/code-map-admission.js +295 -0
  65. package/dist/code-map-build.d.ts +63 -0
  66. package/dist/code-map-build.js +415 -0
  67. package/dist/code-map-cli.d.ts +153 -0
  68. package/dist/code-map-cli.js +103 -0
  69. package/dist/code-map-main.d.ts +1 -0
  70. package/dist/code-map-main.js +48 -0
  71. package/dist/code-map-read.d.ts +106 -0
  72. package/dist/code-map-read.js +171 -0
  73. package/dist/code-map-report.d.ts +37 -0
  74. package/dist/code-map-report.js +95 -0
  75. package/dist/code-map-shape.d.ts +172 -0
  76. package/dist/code-map-shape.js +327 -0
  77. package/dist/code-map-store.d.ts +136 -0
  78. package/dist/code-map-store.js +233 -0
  79. package/dist/coding-provider.d.ts +2 -0
  80. package/dist/coding-provider.js +3 -2
  81. package/dist/coding-update.js +3 -2
  82. package/dist/coding-workspace.js +4 -4
  83. package/dist/console-chrome.js +2 -2
  84. package/dist/containment.d.ts +5 -0
  85. package/dist/containment.js +14 -1
  86. package/dist/contracts/chat-actions.d.ts +9 -0
  87. package/dist/contracts/chat-actions.js +2 -0
  88. package/dist/contracts/cli.d.ts +52 -0
  89. package/dist/contracts/cli.js +4 -0
  90. package/dist/contracts/console-api.d.ts +12 -5
  91. package/dist/contracts/console-api.js +12 -5
  92. package/dist/contracts/gateway-tools.d.ts +17 -0
  93. package/dist/contracts/gateway-tools.js +3 -1
  94. package/dist/contracts/lead-tools.d.ts +61 -0
  95. package/dist/contracts/lead-tools.js +10 -1
  96. package/dist/contracts/project-knowledge.d.ts +38 -5
  97. package/dist/contracts/project-knowledge.js +11 -5
  98. package/dist/contracts/project-memory.d.ts +3 -1
  99. package/dist/contracts/project-memory.js +3 -2
  100. package/dist/contracts/release-check.d.ts +25 -1
  101. package/dist/contracts/release-check.js +19 -0
  102. package/dist/contracts/storage.d.ts +14 -0
  103. package/dist/contracts/storage.js +3 -1
  104. package/dist/contracts/store-json.d.ts +2 -0
  105. package/dist/contracts/youiyoux-request.d.ts +1 -1
  106. package/dist/contracts/youiyoux-request.js +1 -1
  107. package/dist/desktop-access.js +6 -2
  108. package/dist/desktop-update.d.ts +2 -2
  109. package/dist/desktop-update.js +13 -9
  110. package/dist/dispatch.js +18 -15
  111. package/dist/dispose.js +8 -2
  112. package/dist/easy-start.js +2 -3
  113. package/dist/eval-hooks.d.ts +55 -0
  114. package/dist/eval-hooks.js +227 -4
  115. package/dist/evals.js +2 -1
  116. package/dist/fast-start.d.ts +136 -0
  117. package/dist/fast-start.js +431 -0
  118. package/dist/fast-terms.d.ts +9 -0
  119. package/dist/fast-terms.js +23 -0
  120. package/dist/fenced-check-coverage.d.ts +62 -0
  121. package/dist/fenced-check-coverage.js +54 -0
  122. package/dist/first-run.js +1 -1
  123. package/dist/flaky-ci.d.ts +31 -0
  124. package/dist/flaky-ci.js +149 -0
  125. package/dist/flaky-cli.d.ts +60 -0
  126. package/dist/flaky-cli.js +60 -0
  127. package/dist/flaky.d.ts +83 -0
  128. package/dist/flaky.js +264 -0
  129. package/dist/flow-card-task.js +3 -0
  130. package/dist/flow-triggers.d.ts +1 -1
  131. package/dist/flow-triggers.js +1 -1
  132. package/dist/github-import.d.ts +62 -0
  133. package/dist/github-import.js +146 -0
  134. package/dist/guides.js +15 -10
  135. package/dist/invoke.d.ts +5 -1
  136. package/dist/invoke.js +49 -14
  137. package/dist/job-object-helper.ps1 +10 -3
  138. package/dist/keymap.d.ts +41 -0
  139. package/dist/keymap.js +51 -0
  140. package/dist/knowledge-cli.js +1 -1
  141. package/dist/knowledge-ui.d.ts +21 -4
  142. package/dist/knowledge-ui.js +75 -7
  143. package/dist/laptop-background.d.ts +16 -0
  144. package/dist/laptop-background.js +108 -2
  145. package/dist/lead-context.js +1 -1
  146. package/dist/lead-contract.d.ts +1 -1
  147. package/dist/lead-contract.js +2 -2
  148. package/dist/lead-progress.js +3 -0
  149. package/dist/lead-status.d.ts +2 -0
  150. package/dist/lead-status.js +6 -2
  151. package/dist/lead-tools.js +51 -2
  152. package/dist/lead.js +1 -1
  153. package/dist/lesson-notes.d.ts +81 -0
  154. package/dist/lesson-notes.js +336 -0
  155. package/dist/limits-ui.js +1 -1
  156. package/dist/local-owner.d.ts +1 -1
  157. package/dist/local-owner.js +11 -0
  158. package/dist/machine-check.d.ts +16 -0
  159. package/dist/machine-check.js +26 -0
  160. package/dist/machine-identity.d.ts +75 -0
  161. package/dist/machine-identity.js +170 -0
  162. package/dist/mcp-core.js +11 -1
  163. package/dist/memory-cli.d.ts +300 -1
  164. package/dist/memory-cli.js +62 -9
  165. package/dist/memory-pass.d.ts +16 -0
  166. package/dist/memory-pass.js +67 -3
  167. package/dist/memory-tidy.d.ts +181 -0
  168. package/dist/memory-tidy.js +523 -0
  169. package/dist/memory-use.d.ts +35 -0
  170. package/dist/memory-use.js +109 -0
  171. package/dist/merge-checklist.d.ts +55 -0
  172. package/dist/merge-checklist.js +182 -0
  173. package/dist/needs-you.d.ts +32 -13
  174. package/dist/needs-you.js +108 -49
  175. package/dist/nightly-github.d.ts +2 -0
  176. package/dist/nightly-github.js +4 -4
  177. package/dist/operate-remote-arguments.js +6 -1
  178. package/dist/operate-remote.js +3 -1
  179. package/dist/operate.d.ts +6 -1
  180. package/dist/operate.js +540 -70
  181. package/dist/phase-routing.js +2 -2
  182. package/dist/plain-schema.d.ts +23 -0
  183. package/dist/plain-schema.js +94 -0
  184. package/dist/plain-words.d.ts +20 -0
  185. package/dist/plain-words.js +66 -0
  186. package/dist/planner.js +2 -1
  187. package/dist/process-liveness.d.ts +2 -0
  188. package/dist/process-liveness.js +61 -17
  189. package/dist/project-delete-ui.d.ts +7 -2
  190. package/dist/project-delete-ui.js +12 -1
  191. package/dist/project-delete.js +13 -2
  192. package/dist/project-knowledge.d.ts +18 -11
  193. package/dist/project-knowledge.js +39 -9
  194. package/dist/project-learning.d.ts +2 -1
  195. package/dist/project-learning.js +15 -45
  196. package/dist/project-memory.d.ts +5 -0
  197. package/dist/project-memory.js +22 -2
  198. package/dist/proof.js +30 -5
  199. package/dist/provider-auth.js +1 -1
  200. package/dist/publish.js +9 -1
  201. package/dist/pull-request-flow.js +5 -0
  202. package/dist/push.d.ts +26 -3
  203. package/dist/push.js +55 -15
  204. package/dist/release-adapters.d.ts +9 -0
  205. package/dist/release-adapters.js +87 -1
  206. package/dist/release-approval.d.ts +191 -0
  207. package/dist/release-approval.js +382 -0
  208. package/dist/release-auto.js +26 -17
  209. package/dist/release-card.d.ts +74 -6
  210. package/dist/release-card.js +179 -23
  211. package/dist/release-carry.d.ts +7 -2
  212. package/dist/release-carry.js +39 -9
  213. package/dist/release-cli.d.ts +4 -0
  214. package/dist/release-cli.js +38 -23
  215. package/dist/release-coverage.js +1 -1
  216. package/dist/release-next.d.ts +48 -0
  217. package/dist/release-next.js +161 -0
  218. package/dist/release.d.ts +50 -1
  219. package/dist/release.js +118 -11
  220. package/dist/remote-destination.d.ts +36 -0
  221. package/dist/remote-destination.js +217 -0
  222. package/dist/remote-main.d.ts +48 -0
  223. package/dist/remote-main.js +84 -5
  224. package/dist/repository-context.d.ts +3 -2
  225. package/dist/repository-context.js +112 -70
  226. package/dist/restart-certification.d.ts +1 -0
  227. package/dist/restart-certification.js +8 -6
  228. package/dist/result-acts.d.ts +29 -21
  229. package/dist/result-acts.js +36 -25
  230. package/dist/result-follow-ups.d.ts +2 -2
  231. package/dist/result-follow-ups.js +18 -4
  232. package/dist/result-review.d.ts +1 -1
  233. package/dist/route-change-check.d.ts +4 -0
  234. package/dist/route-change-check.js +13 -0
  235. package/dist/route-change.d.ts +22 -0
  236. package/dist/route-change.js +89 -0
  237. package/dist/route-hooks.js +3 -2
  238. package/dist/route-results.d.ts +6 -1
  239. package/dist/route-results.js +14 -5
  240. package/dist/run-closeout.d.ts +1 -1
  241. package/dist/run-closeout.js +7 -5
  242. package/dist/runner-process.js +3 -2
  243. package/dist/runner.js +2 -1
  244. package/dist/scope.d.ts +6 -1
  245. package/dist/scope.js +22 -9
  246. package/dist/serve.d.ts +1 -1
  247. package/dist/serve.js +24 -18
  248. package/dist/server/chat.js +14 -12
  249. package/dist/server/chrome.d.ts +1 -1
  250. package/dist/server/chrome.js +26 -35
  251. package/dist/server/console.css +23 -0
  252. package/dist/server/guards.js +4 -0
  253. package/dist/server/home.d.ts +6 -0
  254. package/dist/server/home.js +21 -13
  255. package/dist/server/http.js +1 -1
  256. package/dist/server/kit.js +1 -1
  257. package/dist/server/merge-ready-view.d.ts +10 -0
  258. package/dist/server/merge-ready-view.js +30 -0
  259. package/dist/server/pages-assets.d.ts +1 -0
  260. package/dist/server/pages-assets.js +8 -5
  261. package/dist/server/pages.js +103 -82
  262. package/dist/server/render-chat.js +2 -2
  263. package/dist/server/render-code-map.d.ts +30 -0
  264. package/dist/server/render-code-map.js +137 -0
  265. package/dist/server/render-flaky.d.ts +20 -0
  266. package/dist/server/render-flaky.js +59 -0
  267. package/dist/server/render-history.js +1 -1
  268. package/dist/server/render-new-task.d.ts +26 -1
  269. package/dist/server/render-new-task.js +30 -5
  270. package/dist/server/render-pages.d.ts +1 -175
  271. package/dist/server/render-pages.js +38 -313
  272. package/dist/server/render-projects.d.ts +2 -1
  273. package/dist/server/render-projects.js +9 -7
  274. package/dist/server/render-release.d.ts +19 -0
  275. package/dist/server/render-release.js +84 -0
  276. package/dist/server/render-results.d.ts +2 -2
  277. package/dist/server/render-results.js +71 -73
  278. package/dist/server/render-tasks.d.ts +13 -2
  279. package/dist/server/render-tasks.js +156 -139
  280. package/dist/server/retired-routes.d.ts +17 -0
  281. package/dist/server/retired-routes.js +53 -0
  282. package/dist/server/route-table.d.ts +4 -1
  283. package/dist/server/route-table.js +52 -13
  284. package/dist/server/settings.js +129 -22
  285. package/dist/server/task-views.js +61 -26
  286. package/dist/server/tasks.d.ts +17 -6
  287. package/dist/server/tasks.js +273 -338
  288. package/dist/server/welcome.js +1 -1
  289. package/dist/settings-groups-ui.js +1 -1
  290. package/dist/setup-guide.d.ts +2 -2
  291. package/dist/stop-proof.d.ts +67 -0
  292. package/dist/stop-proof.js +295 -0
  293. package/dist/storage-sweep-process.js +1 -1
  294. package/dist/storage-sweep.d.ts +5 -3
  295. package/dist/storage-sweep.js +30 -9
  296. package/dist/storage-ui.js +28 -17
  297. package/dist/storage.d.ts +27 -2
  298. package/dist/storage.js +39 -2
  299. package/dist/store.d.ts +148 -34
  300. package/dist/store.js +626 -177
  301. package/dist/surface.js +46 -5
  302. package/dist/sweep-quarantine.d.ts +1 -0
  303. package/dist/sweep-quarantine.js +6 -4
  304. package/dist/tamper-scan.d.ts +4 -0
  305. package/dist/tamper-scan.js +90 -0
  306. package/dist/task-aliases.d.ts +5 -0
  307. package/dist/task-aliases.js +9 -0
  308. package/dist/task-control.js +4 -1
  309. package/dist/task-outcome-cli.d.ts +2 -0
  310. package/dist/task-outcome-cli.js +36 -17
  311. package/dist/task-sizing.d.ts +5 -0
  312. package/dist/task-sizing.js +2 -0
  313. package/dist/task-source.d.ts +7 -0
  314. package/dist/task-source.js +14 -0
  315. package/dist/task-status.d.ts +9 -0
  316. package/dist/task-status.js +40 -49
  317. package/dist/teardown-git.d.ts +16 -0
  318. package/dist/teardown-git.js +63 -5
  319. package/dist/telegram-progress.js +5 -3
  320. package/dist/telegram-status.js +6 -4
  321. package/dist/test-infra.d.ts +8 -0
  322. package/dist/test-infra.js +18 -0
  323. package/dist/tidy.js +1 -1
  324. package/dist/toolroll-update-ui.js +2 -2
  325. package/dist/toolroll-update.js +18 -5
  326. package/dist/tui-contract.d.ts +24 -0
  327. package/dist/tui-source.js +67 -8
  328. package/dist/tui.js +51 -51
  329. package/dist/vanished-retry.d.ts +30 -0
  330. package/dist/vanished-retry.js +174 -0
  331. package/dist/work-index.d.ts +2 -0
  332. package/dist/work-index.js +82 -70
  333. package/dist/work-summary.d.ts +2 -2
  334. package/dist/work-summary.js +2 -0
  335. package/dist/workspace-ui.d.ts +4 -2
  336. package/dist/workspace-ui.js +28 -22
  337. package/dist/worktree.d.ts +12 -3
  338. package/dist/worktree.js +68 -13
  339. package/package.json +1 -1
  340. package/dist/server/render-next.d.ts +0 -63
  341. package/dist/server/render-next.js +0 -109
@@ -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,20 @@ 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;
48
67
  /**
49
68
  * Whether a passing check of a release task stands, given every earlier attempt on the same candidate and signed scope:
50
69
  * 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,27 @@ 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.head !== null || row.checkRow !== null)
212
+ return false;
213
+ if (checkAttemptOf(store, row.id) !== null)
214
+ return false;
215
+ return !store.artifactsFor(row.id).some(artifact => artifact.kind === "check-log" || isVerificationReceipt(artifact));
216
+ }
217
+ /** The task's finished builder runs under `digest`, oldest first, each with whether its check passed or never ran. */
178
218
  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
219
+ 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
220
  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
221
  .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" }));
222
+ passed: (row["outcome"] === "built" || row["outcome"] === "no-change") && row["status"] === "passed",
223
+ noCheck: ranNoCheck(store, { id: Number(row["id"]), outcome: row["outcome"], reason: row["reason"], head: row["head_revision"] ?? null, checkRow: row["check_row"] ?? null }) }));
183
224
  }
184
225
  /** The reruns the release asked for on this task (each source run once): run id → its class. */
185
226
  function reruns(store, taskId) {
@@ -208,6 +249,9 @@ export function releaseCheckStanding(store, root, input) {
208
249
  for (const one of builderRuns(store, ref.id, digest)) {
209
250
  if (one.id >= input.run || one.passed)
210
251
  continue;
252
+ // Interrupted before its candidate was loaded: no check ran, so there is no check failure to stand against a pass.
253
+ if (one.noCheck)
254
+ continue;
211
255
  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
256
  if (classified === null)
213
257
  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
+ }
@@ -0,0 +1,36 @@
1
+ import type { CheckAccess } from "./check-access.js";
2
+ import type { Store } from "./store.js";
3
+ export type CheckAuthorization = {
4
+ id: number;
5
+ taskRef: number;
6
+ run: number;
7
+ candidate: string;
8
+ commandDigest: string;
9
+ level: "quick" | "full";
10
+ access: CheckAccess;
11
+ scopeDigest: string;
12
+ expiresAt: string;
13
+ usedRequest: number | null;
14
+ };
15
+ export declare function readAuthorization(row: Record<string, unknown>): CheckAuthorization;
16
+ /** The approval an authorization stands on: the plan's seal, on these exact bytes, still standing as dispatch reads it. */
17
+ export declare function approvedScopeStands(store: Store, taskRef: number, scopeDigest: string, now: Date): boolean;
18
+ /** The plain reason an authorization doesn't stand, or null: this exact commit and command, on the result it was
19
+ * written for, under the same standing approval, not put away. */
20
+ export declare function authorizationProblem(store: Store, auth: CheckAuthorization, input: {
21
+ head: string;
22
+ digest: string;
23
+ }, now: Date): string | null;
24
+ /** At the run: the used authorization this request claimed. */
25
+ export declare function recheckStanding(store: Store, input: {
26
+ request: number;
27
+ runId: number;
28
+ head: string;
29
+ digest: string;
30
+ }): {
31
+ ok: true;
32
+ access: CheckAccess;
33
+ } | {
34
+ ok: false;
35
+ problem: string;
36
+ };
@@ -0,0 +1,39 @@
1
+ /** Whether a check-only authorization stands for one automatic recheck (check-only.ts), read at every step. Kept apart
2
+ * from the sweep so the follow-up runner reads it without loading the road that asks. */
3
+ import { authorityStandsSql } from "./approval-authority.js";
4
+ import { checkAccessOf } from "./check-access.js";
5
+ import { refArchived } from "./task-archive.js";
6
+ export function readAuthorization(row) {
7
+ return { id: Number(row["id"]), taskRef: Number(row["task_ref"]), run: Number(row["run"]), candidate: String(row["candidate"]), commandDigest: String(row["command_digest"]),
8
+ level: row["level"] === "quick" ? "quick" : "full", access: checkAccessOf(row["check_access"]), scopeDigest: String(row["scope_digest"]), expiresAt: String(row["expires_at"]),
9
+ usedRequest: row["used_request"] === null || row["used_request"] === undefined ? null : Number(row["used_request"]) };
10
+ }
11
+ /** The approval an authorization stands on: the plan's seal, on these exact bytes, still standing as dispatch reads it. */
12
+ export function approvedScopeStands(store, taskRef, scopeDigest, now) {
13
+ const stamp = now.toISOString();
14
+ return store.handle.prepare(`SELECT 1 AS hit FROM task_scope JOIN task_ref ON task_ref.id = ? AND task_scope.task_id = task_ref.external_id
15
+ WHERE task_scope.digest = ? AND task_scope.approved_digest = task_scope.digest AND task_scope.approved_at IS NOT NULL
16
+ AND ${authorityStandsSql("task_scope", "task_ref.repo", "?")}`).get(taskRef, scopeDigest, stamp, stamp, stamp, stamp) !== undefined;
17
+ }
18
+ /** The plain reason an authorization doesn't stand, or null: this exact commit and command, on the result it was
19
+ * written for, under the same standing approval, not put away. */
20
+ export function authorizationProblem(store, auth, input, now) {
21
+ if (auth.candidate !== input.head)
22
+ return "The commit changed.";
23
+ if (auth.commandDigest !== input.digest)
24
+ return "The approved check changed.";
25
+ if (refArchived(store, auth.taskRef))
26
+ return "This task is put away.";
27
+ if (!approvedScopeStands(store, auth.taskRef, auth.scopeDigest, now))
28
+ return "Its approval no longer stands.";
29
+ return null;
30
+ }
31
+ /** At the run: the used authorization this request claimed. */
32
+ export function recheckStanding(store, input) {
33
+ const row = store.handle.prepare("SELECT * FROM check_authorization WHERE used_request = ? AND run = ?").get(input.request, input.runId);
34
+ if (row === undefined)
35
+ return { ok: false, problem: "Its authorization couldn't be read." };
36
+ const auth = readAuthorization(row);
37
+ const problem = authorizationProblem(store, auth, input, new Date());
38
+ return problem === null ? { ok: true, access: auth.access } : { ok: false, problem };
39
+ }
@@ -0,0 +1,15 @@
1
+ import type { Store } from "./store.js";
2
+ /** How long a recheck may still start after its build finished. */
3
+ export declare const CHECK_ONLY_WINDOW_MS: number;
4
+ export declare const RECHECK_ACTOR = "Toolroll";
5
+ /**
6
+ * Written only from the existing approval of this exact commit: the seal this run was admitted under, still standing,
7
+ * on the bytes this run built. Null when anything is missing, and the person's Run checks stays the only road.
8
+ */
9
+ export declare function authorizeCheckOnly(store: Store, runId: number, now: Date): number | null;
10
+ /** Claim one authorization and ask for its check, in one transaction. Null: nothing was asked. */
11
+ export declare function claimRecheck(store: Store, authorization: number, now: Date): {
12
+ request: number;
13
+ } | null;
14
+ /** Each sweep: a standing authorization whose result has no readable verdict asks for its one check. */
15
+ export declare function requestMissingRechecks(store: Store, root: string, now: Date): number;