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
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Automatic recheck under a check-only authorization (docs/plans/plain-toolroll.md P2.2).
3
+ *
4
+ * When a build finishes, the approval that built its commit writes one authorization: this task, this exact commit, the
5
+ * approved check command's digest, the access the build's own check had, one automatic run, and an expiry. It never
6
+ * seals anything and starts no build and no agent: it asks the follow-up check road (result-follow-ups.ts), which checks
7
+ * the commit out in a fresh folder and runs the approved command, nothing else.
8
+ *
9
+ * The sweep uses it only when the result's verdict is missing or unreadable. A failed, passed or running check needs
10
+ * nothing, and closes it. The claim (used_at, compare-and-swap in the transaction that asks) is the cap: a second worker
11
+ * or a crash after the claim finds it spent, and the need stays with the person. A changed command is a different
12
+ * digest, so the old authorization covers nothing: Run checks on the new command is the person's act.
13
+ */
14
+ import { assignmentChecksForRun } from "./assignment.js";
15
+ import { runCheckLevel, repoOfRun, checkCommandFor } from "./check-levels.js";
16
+ import { approvedScopeStands, authorizationProblem, readAuthorization } from "./check-only-standing.js";
17
+ import { requestFollowUpChecks } from "./result-follow-ups.js";
18
+ import { releaseCheckScope } from "./scope.js";
19
+ import { refArchived } from "./task-archive.js";
20
+ import { approvedCheckStateOf } from "./task-status.js";
21
+ /** How long a recheck may still start after its build finished. */
22
+ export const CHECK_ONLY_WINDOW_MS = 24 * 60 * 60_000;
23
+ export const RECHECK_ACTOR = "Toolroll";
24
+ /**
25
+ * Written only from the existing approval of this exact commit: the seal this run was admitted under, still standing,
26
+ * on the bytes this run built. Null when anything is missing, and the person's Run checks stays the only road.
27
+ */
28
+ export function authorizeCheckOnly(store, runId, now) {
29
+ return store.transact(() => {
30
+ const run = store.getRun(runId);
31
+ if (run === null || run.role !== "builder" || run.outcome !== "built" || run.headRevision === null || !/^[a-f0-9]{40}$/.test(run.headRevision))
32
+ return null;
33
+ const ref = store.refById(run.taskRef);
34
+ if (ref === null || ref.repo === null || refArchived(store, ref.id))
35
+ return null;
36
+ const scope = store.getScope(ref.externalId);
37
+ if (scope === null || scope.approvedDigest !== scope.digest || run.scopeDigest !== scope.digest)
38
+ return null;
39
+ // A release's check has its own road (check-failure.ts), and the nightly's its own.
40
+ if (releaseCheckScope(scope) || scope.releaseTerms != null)
41
+ return null;
42
+ if (!approvedScopeStands(store, ref.id, scope.digest, now))
43
+ return null;
44
+ const level = runCheckLevel(store, runId);
45
+ if (level === "off")
46
+ return null;
47
+ const chosen = checkCommandFor(store, ref.repo, level ?? "full");
48
+ if (chosen.command === null || chosen.level === "off")
49
+ return null;
50
+ const written = store.handle.prepare(`INSERT INTO check_authorization (task_ref, run, candidate, command_digest, level, check_access, scope_digest, written_at, expires_at)
51
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (task_ref, candidate, command_digest) DO NOTHING`)
52
+ .run(ref.id, runId, run.headRevision, chosen.command.digest, chosen.level, store.runCheckAccess(runId), scope.digest, now.toISOString(), new Date(now.getTime() + CHECK_ONLY_WINDOW_MS).toISOString());
53
+ return Number(written.changes) > 0 ? Number(written.lastInsertRowid) : null;
54
+ });
55
+ }
56
+ /** Claim one authorization and ask for its check, in one transaction. Null: nothing was asked. */
57
+ export function claimRecheck(store, authorization, now) {
58
+ return store.transact(() => {
59
+ const row = store.handle.prepare("SELECT * FROM check_authorization WHERE id = ? AND used_at IS NULL AND closed_at IS NULL AND expires_at > ?").get(authorization, now.toISOString());
60
+ if (row === undefined)
61
+ return null;
62
+ const auth = readAuthorization(row);
63
+ const run = store.getRun(auth.run);
64
+ const repo = repoOfRun(store, auth.run);
65
+ if (run === null || repo === null || store.lastBuilderRun(auth.taskRef)?.id !== auth.run || store.hasLiveClaim(auth.taskRef, now))
66
+ return null;
67
+ const live = checkCommandFor(store, repo, auth.level).command;
68
+ if (live === null || authorizationProblem(store, auth, { head: run.headRevision ?? "", digest: live.digest }, now) !== null)
69
+ return null;
70
+ const asked = requestFollowUpChecks(store, { runId: auth.run, level: auth.level, actor: RECHECK_ACTOR, why: "recheck" }, now);
71
+ // An existing request is the person's own: it stands, and this authorization isn't spent on it.
72
+ if (!asked.ok || asked.existing)
73
+ return null;
74
+ const spent = store.handle.prepare("UPDATE check_authorization SET used_at = ?, used_request = ? WHERE id = ? AND used_at IS NULL").run(now.toISOString(), asked.request, auth.id);
75
+ if (Number(spent.changes) !== 1)
76
+ throw new Error("the recheck's authorization moved inside its own transaction");
77
+ return { request: asked.request };
78
+ });
79
+ }
80
+ /** Each sweep: a standing authorization whose result has no readable verdict asks for its one check. */
81
+ export function requestMissingRechecks(store, root, now) {
82
+ const open = store.handle.prepare("SELECT * FROM check_authorization WHERE used_at IS NULL AND closed_at IS NULL AND expires_at > ? ORDER BY id").all(now.toISOString());
83
+ let asked = 0;
84
+ for (const row of open) {
85
+ const auth = readAuthorization(row);
86
+ const run = store.getRun(auth.run);
87
+ if (run === null || run.finishedAt === null)
88
+ continue;
89
+ const state = approvedCheckStateOf(assignmentChecksForRun(store, root, auth.run, now));
90
+ if (state === "not-run" || state === "unreadable") {
91
+ if (claimRecheck(store, auth.id, now) !== null)
92
+ asked++;
93
+ continue;
94
+ }
95
+ if (state !== "running")
96
+ store.handle.prepare("UPDATE check_authorization SET closed_at = ? WHERE id = ? AND used_at IS NULL").run(now.toISOString(), auth.id);
97
+ }
98
+ return asked;
99
+ }
@@ -88,6 +88,8 @@ export type PreparedAttempt = {
88
88
  snapshot: ObligationSnapshot | null;
89
89
  /** The identities the check was asked to report one by one (required ones). */
90
90
  watch: string[];
91
+ /** The exact cases the check was asked about for learning only (docs/plans/learning-loops.md): never gate evidence. */
92
+ learn: string[];
91
93
  };
92
94
  /** A Full check's preparation refused (eval-harness review 2, finding 4): the plane recorded a release whose commit it
93
95
  * can't verify now, so the check's obligations can't be decided. The check isn't run (builder.ts: `baseline-unverifiable`). */
@@ -112,6 +114,11 @@ export declare function prepareCheckAttempt(store: Store, root: string, input: {
112
114
  /** A release's check (signed release terms or a release authority): CI never stands in for its unit suite. Sealed in
113
115
  * the inventory (whose digest the attempt row holds); only an explicit false lets CI stand in. */
114
116
  release?: boolean;
117
+ /** Exact cases asked about for learning only (docs/plans/learning-loops.md): a separate handshake field, at most 200,
118
+ * that never joins `watch`, the inventory or anything the check owes. */
119
+ learn?: readonly string[];
120
+ /** The limit the check runs under when the plane gave it more than the approved one (check-load.ts checkLimitFor). */
121
+ limitMs?: number | undefined;
115
122
  }, now: Date): PreparedAttempt | null;
116
123
  /** Seal one JSON payload as a structured-output artifact under its capture label. */
117
124
  export declare function sealJson(store: Store, root: string, runId: number, name: string, value: unknown, capture: string, now: Date): number;
@@ -21,7 +21,7 @@ import { createHash, randomBytes } from "node:crypto";
21
21
  import { lstatSync, realpathSync } from "node:fs";
22
22
  import { join } from "node:path";
23
23
  import { readVerifiedCommit, verifiedBlobs, verifiedTreeEntries } from "./commit-extract.js";
24
- import { CHECK_PARTS, CHECK_REPORT_MAX_BYTES, CHECK_REPORT_PREFIX, readCheckReport, } from "./contracts/release-check.js";
24
+ import { CHECK_PARTS, CHECK_REPORT_MAX_BYTES, CHECK_REPORT_PREFIX, learnRequest, readCheckReport, } from "./contracts/release-check.js";
25
25
  import { readVerifiedArtifact, storeEvidence } from "./evidence.js";
26
26
  import { releaseBaselineRecord } from "./release-card.js";
27
27
  import { checkAttemptOf, recordCheckAttempt } from "./store.js";
@@ -219,8 +219,9 @@ export function prepareCheckAttempt(store, root, input, now) {
219
219
  // The command's own limit rides along, so the check ends itself (logs and report printed) before the plane stops it.
220
220
  // `reportBytes`: the largest report this plane reads back once sealed. A plane that doesn't say (0.9.77 and older) reads
221
221
  // only 64 KiB, so the check gives it no larger report rather than one that poisons the receipt (scripts/release-check.mjs).
222
- const stdin = JSON.stringify({ nonce, mode, plan, watch: [...(input.watch ?? [])], limitMs: input.command.timeoutMs, reportBytes: CHECK_ARTIFACT_CAP, ...(plan === null ? { owed, ciSubstitute } : {}) });
223
- return { runId: input.runId, nonce, stdin, mode, inventory, context, plan, snapshot, watch: [...(input.watch ?? [])] };
222
+ const learn = learnRequest(input.learn ?? []);
223
+ const stdin = JSON.stringify({ nonce, mode, plan, watch: [...(input.watch ?? [])], limitMs: Number.isSafeInteger(input.limitMs) && input.limitMs >= input.command.timeoutMs ? input.limitMs : input.command.timeoutMs, reportBytes: CHECK_ARTIFACT_CAP, ...(plan === null ? { owed, ciSubstitute } : {}), ...(learn.length > 0 ? { learn } : {}) });
224
+ return { runId: input.runId, nonce, stdin, mode, inventory, context, plan, snapshot, watch: [...(input.watch ?? [])], learn };
224
225
  }
225
226
  /** Seal one JSON payload as a structured-output artifact under its capture label. */
226
227
  export function sealJson(store, root, runId, name, value, capture, now) {
@@ -0,0 +1,46 @@
1
+ import { type CheckAccess } from "./check-access.js";
2
+ export declare const CHECK_STAMP_VERSION = 1;
3
+ /** Every path: the launch had no write or read fence of that kind. */
4
+ export declare const EVERYWHERE = "*";
5
+ /** What the launch's network was: the machine's own, or the fence's, with exactly the hosts it allowed. */
6
+ export type StampNetwork = {
7
+ mode: "machine";
8
+ } | {
9
+ mode: "open" | "allowlist";
10
+ hosts: string[];
11
+ };
12
+ export type CheckStamp = {
13
+ version: typeof CHECK_STAMP_VERSION;
14
+ /** The level the commands ran at, whatever was sealed. */
15
+ level: CheckAccess;
16
+ /** The digest of the whole fence as given to the launch (invoke.ts fenceDigest); null with no fence. */
17
+ policyDigest: string | null;
18
+ /** Where it could write: exact grants, or [EVERYWHERE]. */
19
+ writable: string[];
20
+ /** What it was given to read beyond the fence's denies, or [EVERYWHERE]. */
21
+ readable: string[];
22
+ network: StampNetwork;
23
+ };
24
+ export type StampReading = {
25
+ state: "current";
26
+ stamp: CheckStamp;
27
+ } | {
28
+ state: "unknown";
29
+ why: "missing" | "old" | "unreadable";
30
+ };
31
+ /** A launch with no fence at all. */
32
+ export declare const unfencedStamp: () => CheckStamp;
33
+ export declare const stampDigest: (stamp: CheckStamp) => string;
34
+ /** One saved stamp, exactly its shape or unknown. */
35
+ export declare function readStamp(text: unknown): StampReading;
36
+ /**
37
+ * A run's whole account of its checks: every launch it stamped. Any it can't read makes the whole unknown (one launch of
38
+ * unknown reach is reach unknown); otherwise the widest stands, so a narrow command never hides a wider one. None: unknown.
39
+ */
40
+ export declare function readStamps(texts: readonly unknown[]): StampReading;
41
+ /** Why relying on `reading` for a check that had to stay within `sealed` isn't possible, or null when it can be. */
42
+ export declare function stampRefusal(reading: StampReading, sealed: CheckAccess): string | null;
43
+ /** How a check ran, as a person reads it after the fact; null for a current sandboxed one, which needs no line. */
44
+ export declare function ranWords(reading: StampReading): string | null;
45
+ /** v154: one row per distinct launch of a run's commands (a run's repeats of one launch are one row). */
46
+ export declare const V154_SCHEMA = "\nCREATE TABLE IF NOT EXISTS check_stamp (\n id INTEGER PRIMARY KEY,\n run_id INTEGER NOT NULL,\n digest TEXT NOT NULL,\n stamp TEXT NOT NULL,\n at TEXT NOT NULL,\n UNIQUE (run_id, digest)\n);\n";
@@ -0,0 +1,93 @@
1
+ /**
2
+ * A check's execution stamp (docs/plans/trusted-release-reports.md, H2): what actually ran, written at launch by the code
3
+ * that launched it. Before this, a run's `sandbox_access` was all there was, and a missing or unreadable value read as
4
+ * "sandboxed". That reading is right for one thing only, choosing how a later check may run (narrowest), and wrong for
5
+ * saying how a check DID run. Anything that relies on how a check ran reads a stamp here: a missing, old or damaged one
6
+ * is `unknown`, which refuses where it matters and is never said to be sandboxed.
7
+ *
8
+ * Reading a stamp is strict, and a new shape takes a new version: an older build's stamp is `unknown` to a newer one.
9
+ */
10
+ import { createHash } from "node:crypto";
11
+ import { canonicalJson } from "./canonical-json.js";
12
+ import { isCheckAccess, widerThan } from "./check-access.js";
13
+ export const CHECK_STAMP_VERSION = 1;
14
+ /** Every path: the launch had no write or read fence of that kind. */
15
+ export const EVERYWHERE = "*";
16
+ /** A launch with no fence at all. */
17
+ export const unfencedStamp = () => ({ version: CHECK_STAMP_VERSION, level: "unsandboxed", policyDigest: null, writable: [EVERYWHERE], readable: [EVERYWHERE], network: { mode: "machine" } });
18
+ export const stampDigest = (stamp) => createHash("sha256").update(`toolroll:check-stamp:${canonicalJson(stamp)}`).digest("hex").slice(0, 32);
19
+ const paths = (value) => Array.isArray(value) && value.every(one => typeof one === "string" && one !== "");
20
+ const networkIs = (value) => {
21
+ if (value === null || typeof value !== "object")
22
+ return false;
23
+ const one = value;
24
+ if (one["mode"] === "machine")
25
+ return Object.keys(one).length === 1;
26
+ return (one["mode"] === "open" || one["mode"] === "allowlist") && paths(one["hosts"]) && Object.keys(one).length === 2;
27
+ };
28
+ /** One saved stamp, exactly its shape or unknown. */
29
+ export function readStamp(text) {
30
+ if (typeof text !== "string")
31
+ return { state: "unknown", why: "missing" };
32
+ let value;
33
+ try {
34
+ value = JSON.parse(text);
35
+ }
36
+ catch {
37
+ return { state: "unknown", why: "unreadable" };
38
+ }
39
+ if (value === null || typeof value !== "object" || Array.isArray(value))
40
+ return { state: "unknown", why: "unreadable" };
41
+ const one = value;
42
+ if (one["version"] !== CHECK_STAMP_VERSION)
43
+ return { state: "unknown", why: typeof one["version"] === "number" ? "old" : "unreadable" };
44
+ const digest = one["policyDigest"];
45
+ const whole = isCheckAccess(one["level"]) && (digest === null || (typeof digest === "string" && /^[0-9a-f]{32}$/.test(digest)))
46
+ && paths(one["writable"]) && paths(one["readable"]) && networkIs(one["network"]) && Object.keys(one).length === 6;
47
+ if (!whole)
48
+ return { state: "unknown", why: "unreadable" };
49
+ // No fence and a narrow level can't both be true: a stamp that says so is damaged.
50
+ if (digest === null && one["level"] !== "unsandboxed")
51
+ return { state: "unknown", why: "unreadable" };
52
+ return { state: "current", stamp: value };
53
+ }
54
+ /**
55
+ * A run's whole account of its checks: every launch it stamped. Any it can't read makes the whole unknown (one launch of
56
+ * unknown reach is reach unknown); otherwise the widest stands, so a narrow command never hides a wider one. None: unknown.
57
+ */
58
+ export function readStamps(texts) {
59
+ if (texts.length === 0)
60
+ return { state: "unknown", why: "missing" };
61
+ let widest = null;
62
+ for (const text of texts) {
63
+ const one = readStamp(text);
64
+ if (one.state === "unknown")
65
+ return one;
66
+ if (widest === null || widerThan(one.stamp.level, widest.level))
67
+ widest = one.stamp;
68
+ }
69
+ return { state: "current", stamp: widest };
70
+ }
71
+ /** Why relying on `reading` for a check that had to stay within `sealed` isn't possible, or null when it can be. */
72
+ export function stampRefusal(reading, sealed) {
73
+ if (reading.state === "unknown")
74
+ return reading.why === "old" ? "its check's record is from an older Toolroll, so how it ran can't be shown" : "how its check ran wasn't recorded";
75
+ return widerThan(reading.stamp.level, sealed) ? `its check ran ${reading.stamp.level}, wider than the ${sealed} it was approved for` : null;
76
+ }
77
+ /** How a check ran, as a person reads it after the fact; null for a current sandboxed one, which needs no line. */
78
+ export function ranWords(reading) {
79
+ if (reading.state === "unknown")
80
+ return "How these checks ran wasn't recorded";
81
+ return reading.stamp.level === "sandboxed" ? null : reading.stamp.level === "unsandboxed" ? "Checks ran without a sandbox" : "Checks ran with model and GitHub sign-ins";
82
+ }
83
+ /** v154: one row per distinct launch of a run's commands (a run's repeats of one launch are one row). */
84
+ export const V154_SCHEMA = `
85
+ CREATE TABLE IF NOT EXISTS check_stamp (
86
+ id INTEGER PRIMARY KEY,
87
+ run_id INTEGER NOT NULL,
88
+ digest TEXT NOT NULL,
89
+ stamp TEXT NOT NULL,
90
+ at TEXT NOT NULL,
91
+ UNIQUE (run_id, digest)
92
+ );
93
+ `;
@@ -1,11 +1,13 @@
1
- import { type CheckoutCleanup } from "./storage.js";
2
- import type { Store, WorktreeRow } from "./store.js";
1
+ import { type CheckoutCleanup, type CheckoutIdle } from "./storage.js";
2
+ import { type Store, type WorktreeRow } from "./store.js";
3
3
  import { type WorktreePool } from "./worktree.js";
4
4
  /** A release candidate's checkout holds the build a deploy installs: the newest complete one waits at most a week for it. */
5
5
  export declare const CANDIDATE_KEEP_MS: number;
6
6
  /** The worker's pass removes at most this many; a clean-up by hand has no cap. */
7
7
  export declare const AUTO_MAX = 20;
8
- export type KeepWhy = "in use" | "task not finished" | "waiting for review" | "release candidate" | "has changes" | "has commits" | "unreadable" | "git refused" | "not due yet" | "cleanup is off" | "no task";
8
+ export type KeepWhy = "in use" | "task not finished" | "waiting for review" | "release candidate" | "has changes" | "has commits" | "not on its branch" | "unreadable" | "git refused" | "not due yet" | "cleanup is off" | "no task"
9
+ /** A clean-up by hand leaves it; the next sweep takes it. */
10
+ | "idle";
9
11
  export type CheckoutItem = {
10
12
  path: string;
11
13
  repo: string;
@@ -17,10 +19,13 @@ export type CheckoutItem = {
17
19
  why: KeepWhy | null;
18
20
  /** With "not due yet" or "release candidate": when it may go. */
19
21
  dueAt?: string;
22
+ /** Past the idle limit: when its task last saw any activity. */
23
+ idleSince?: string;
20
24
  };
21
25
  export type CheckoutPlan = {
22
26
  at: string;
23
27
  cleanup: CheckoutCleanup;
28
+ idle: CheckoutIdle;
24
29
  manual: boolean;
25
30
  go: CheckoutItem[];
26
31
  stay: CheckoutItem[];
@@ -30,6 +35,9 @@ export type CheckoutPlan = {
30
35
  freeBytes: number;
31
36
  waitingReview: number;
32
37
  withChanges: number;
38
+ /** What the next sweep's idle cleanup takes. */
39
+ idleCount: number;
40
+ idleBytes: number;
33
41
  };
34
42
  type Status = {
35
43
  keep: KeepWhy | null;
@@ -64,6 +72,21 @@ export declare function finishedCheckouts(store: Store, now: Date, repo?: string
64
72
  taskId: string | null;
65
73
  finishedAt: string;
66
74
  }[];
75
+ /** The newest activity of a task's whole family, and whether something rules idle cleanup out for it. */
76
+ type IdleFact = {
77
+ activeAt: string;
78
+ blocked: boolean;
79
+ };
80
+ export type IdleFacts = {
81
+ byRef: Map<number, IdleFact>;
82
+ byBranch: Map<string, IdleFact>;
83
+ };
84
+ /**
85
+ * When each task family last saw a run, a person's act or a lease, read in one pass. A revision builds on what it
86
+ * revises, so a family is idle together. `blocked`: an open run, an unreleased claim, a release gate, or a task only an
87
+ * external tracker knows, in the family.
88
+ */
89
+ export declare function idleFacts(store: Store): IdleFacts;
67
90
  /** Why a checkout stays as a clean-up by hand reads it (in use, or its task not done with it), before its files; null: it may go. */
68
91
  export declare function checkoutKeepWhy(pool: WorktreePool, row: WorktreeRow, statuses: ReturnType<typeof taskStatuses>, now: Date): KeepWhy | null;
69
92
  /**
@@ -83,11 +106,15 @@ export type CleanResult = {
83
106
  why: KeepWhy;
84
107
  }[];
85
108
  freed: number;
109
+ /** The worker's pass stopped at AUTO_MAX: more may be due, and the sweep comes back an hour later. */
110
+ more?: boolean;
86
111
  };
87
112
  /**
88
113
  * Remove what may go: the worker's pass (`manual` false: under the setting, at most AUTO_MAX, one repository) or a
89
114
  * clean-up by hand (no cap, every repository). Each checkout is asked again right before it goes; each removal is in
90
- * the ledger.
115
+ * the ledger. Idle checkouts go only with `idle`, the storage sweep's child (never the service's own pass, whose git
116
+ * and du must not hold up its heartbeats): each is judged again from a fresh read of its task and its family's
117
+ * activity, `spare` (live work), the OS's list of what runs in the folder, and `proveRecreatable`, inside its reservation.
91
118
  */
92
119
  export declare function cleanCheckouts(store: Store, pool: WorktreePool, now: () => Date, options: {
93
120
  manual: boolean;
@@ -95,6 +122,10 @@ export declare function cleanCheckouts(store: Store, pool: WorktreePool, now: ()
95
122
  repo?: string;
96
123
  only?: ReadonlySet<string>;
97
124
  spare?: (path: string) => boolean;
125
+ /** The storage sweep's child only: idle checkouts go too. `occupied` asks the OS whether any process works in a folder. */
126
+ idle?: {
127
+ occupied: (path: string) => boolean;
128
+ };
98
129
  }): Promise<CleanResult>;
99
130
  export type SlimResult = {
100
131
  slimmed: {
@@ -122,5 +153,5 @@ export declare function discardCheckout(store: Store, pool: WorktreePool, path:
122
153
  message: string;
123
154
  }>;
124
155
  /** "Waiting for review", "Has changes" … as a sentence fragment for a person. */
125
- export declare function whyWords(item: Pick<CheckoutItem, "why" | "dueAt">): string;
156
+ export declare function whyWords(item: Pick<CheckoutItem, "why" | "dueAt" | "idleSince">): string;
126
157
  export {};