toolroll 0.5.0

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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
@@ -0,0 +1,803 @@
1
+ // Assignment tracking grants no repair authority or attempts.
2
+ import { originalTaskBase, focusedTestCommandSupported } from "./observations.js";
3
+ /**
4
+ * The build disposition service (Parity II Phase 2, v4 Q2 / v6 W1): the
5
+ * operations that actually END an attempt — sealing parks, accepting
6
+ * completions behind the completion fence, recording failures with their
7
+ * strikes and holds, opening publication intents — extracted from tick's
8
+ * and the standalone build command's finalizers into ONE place, so the
9
+ * held-session coordinator can end a run through exactly the machinery
10
+ * every other road uses. The two historical roads differ deliberately
11
+ * (the standalone command completes no task, strikes nothing, publishes
12
+ * nothing), and the policy record says so explicitly instead of forking
13
+ * the logic: behavior on both is byte-identical to what the callers
14
+ * inlined before.
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { completeFenced, finalizeFailureFenced, finalizeMalformedFenced, finalizeParkFenced, interruptIfStopped, release, } from "./claim.js";
18
+ import { bodyHashOf, publicationBody } from "./publish.js";
19
+ import { modeTermsFromJson } from "./modes.js";
20
+ import { readVerifiedArtifact, writeEvidenceFile } from "./evidence.js";
21
+ import {} from "./proof.js";
22
+ import { verificationEvidence, failedVerificationEvidence } from "./verification-evidence.js";
23
+ import { classifyGateFailure, describeGateFailure, gateFailureSummary } from "./gate-failure.js";
24
+ import { propose, approve } from "./scope.js";
25
+ import { headWithin } from "./names.js";
26
+ /** The reasons tick classifies as the attempt itself breaking. */
27
+ const TICK_FAILURE_REASONS = new Set([
28
+ "agent",
29
+ "agent-reported",
30
+ "no-op",
31
+ "moved-head",
32
+ "moved-branch",
33
+ "timeout",
34
+ "git",
35
+ "commit-failure",
36
+ "provider-init",
37
+ "setup",
38
+ "revision-brief",
39
+ "stopped",
40
+ // Phase 3 (C1): the gateway's typed refusals consume the task strike
41
+ // budget like every other infrastructure failure — the pre-claim skip
42
+ // keeps the NORMAL road from ever reaching here; these arms bound the
43
+ // races.
44
+ "provider-unattested",
45
+ "provider-protocol",
46
+ ]);
47
+ /** The standalone road's historical "broke" list — narrower, deliberately. */
48
+ const STANDALONE_BROKE_REASONS = new Set([
49
+ "agent",
50
+ "agent-reported",
51
+ "no-op",
52
+ "moved-head",
53
+ "timeout",
54
+ "git",
55
+ ]);
56
+ /** Compatibility for historical integrations: finished work is handed to the
57
+ * lead. No model review is requested and no signed history is rewritten. */
58
+ export function maybeRequestAutoReview(_store, _repo, _runId, _committed, _noChange, _now) { }
59
+ export function disposeBuildOutcome(context, result) {
60
+ // Stop and every terminal side effect compete under the same SQLite
61
+ // write lock. A separate preliminary transaction leaves a completion gap.
62
+ return context.store.transact(() => disposeBuildOutcomeLocked(context, result));
63
+ }
64
+ function disposeBuildOutcomeLocked(context, result) {
65
+ const { store, policy, leaseId, runId, taskId, taskRef, runner, repo, branch, origin, provider, model, worktreePath, clock } = context;
66
+ // THE STOP FENCE at settlement (v52), before any arm below and inside
67
+ // its own transaction: a stop recorded against this attempt (or the
68
+ // ancestor it runs under) before this instant WINS — a late success is
69
+ // never accepted as done, a park is never sealed, a failure never takes
70
+ // a strike. The attempt ends as interrupted with its work preserved (a
71
+ // commit it made stays on the branch as a reviewable artifact), its
72
+ // stop settles, and the task waits under the stop's own hold. The
73
+ // already-sealed revision endings are exempt exactly as below: their
74
+ // finalizer fenced the stop itself, inside its own transaction.
75
+ if (result.ok || (result.reason !== "plan-revised" && result.reason !== "plan-revision-blocked")) {
76
+ const stopped = store.transact(() => {
77
+ const run = store.getRun(runId);
78
+ const fenceLease = leaseId ?? run?.leaseId;
79
+ if (run === null || run.outcome !== null || fenceLease === undefined)
80
+ return null;
81
+ return interruptIfStopped(store, {
82
+ leaseId: fenceLease,
83
+ runId,
84
+ taskId,
85
+ ...(result.ok && result.parked === undefined ? { committed: result.committed } : {}),
86
+ now: clock(),
87
+ });
88
+ });
89
+ if (stopped !== null)
90
+ return { kind: "stopped", stopRun: stopped.stopRun, requeued: stopped.requeued };
91
+ }
92
+ if (result.ok && result.parked !== undefined) {
93
+ if (leaseId === undefined)
94
+ return { kind: "park-fenced" };
95
+ const sealed = finalizeParkFenced(store, {
96
+ leaseId,
97
+ runId,
98
+ taskId,
99
+ decision: result.parked.decision,
100
+ artifactIds: result.parked.artifactIds,
101
+ now: clock(),
102
+ });
103
+ if (!sealed.ok)
104
+ return { kind: "park-fenced" };
105
+ // A park is the system working, and it ends any failure streak — on the
106
+ // loop's road. The standalone command never touched strikes.
107
+ if (policy === "tick")
108
+ store.resetStrikes(taskRef);
109
+ return { kind: "parked", decisionId: sealed.decisionId };
110
+ }
111
+ if (result.ok) {
112
+ if (policy === "continuation") {
113
+ // The taskless success (v4 Q7 + v5 P5): run finished, claim released,
114
+ // publication intent under the SAME completion latch the ordinary
115
+ // road holds — and the parent task untouched in state, strikes,
116
+ // holds, and derived stats. All one transaction.
117
+ const sealed = store.transact(() => {
118
+ const latchOpen = store.mirrorAllowsCompletion(taskId);
119
+ store.finishRun(runId, {
120
+ outcome: result.noChange === true ? "no-change" : "built",
121
+ ...(result.noChange === true ? { reason: "handoff" } : {}),
122
+ committed: result.committed,
123
+ now: clock(),
124
+ });
125
+ if (leaseId !== undefined)
126
+ release(store, leaseId, clock());
127
+ if (!latchOpen)
128
+ return { disowned: true };
129
+ if (result.noChange !== true && result.committed) {
130
+ const grant = store.publicationGrantFor(repo);
131
+ const headSha = store.getRun(runId)?.headRevision ?? null;
132
+ if (grant !== null &&
133
+ headSha !== null &&
134
+ headWithin(branch, grant.headPrefix) &&
135
+ (grant.selector === "all" || origin === "ours")) {
136
+ const intentId = store.createPublicationIntent({
137
+ run: runId,
138
+ taskRef,
139
+ githubRepo: grant.githubRepo,
140
+ remote: grant.remote,
141
+ base: grant.base,
142
+ head: branch,
143
+ headSha,
144
+ bodyHash: "",
145
+ draft: grant.draft,
146
+ }, clock());
147
+ const publication = store.publicationForRun(runId);
148
+ if (publication !== null) {
149
+ store.handle
150
+ .prepare("UPDATE publication SET body_hash = ? WHERE id = ?")
151
+ .run(bodyHashOf(publicationBody(store, publication)), intentId);
152
+ }
153
+ }
154
+ }
155
+ return { disowned: false };
156
+ });
157
+ store.clearQuota(runner, provider, model ?? "");
158
+ if (sealed.disowned)
159
+ return { kind: "disowned" };
160
+ return { kind: "built", committed: result.committed, noChange: result.noChange === true };
161
+ }
162
+ if (policy === "standalone") {
163
+ store.finishRun(runId, {
164
+ outcome: result.noChange === true ? "no-change" : "built",
165
+ ...(result.noChange === true ? { reason: "handoff" } : {}),
166
+ committed: result.committed,
167
+ now: clock(),
168
+ });
169
+ return { kind: "built", committed: result.committed, noChange: result.noChange === true };
170
+ }
171
+ if (leaseId === undefined)
172
+ return { kind: "built-fenced" };
173
+ // The completion has to be *accepted*, not assumed — one transaction
174
+ // around the fenced release, the run's outcome, and the publication
175
+ // intent, so "done" and "this must reach a PR" cannot come apart.
176
+ const sealed = store.transact(() => {
177
+ const fence = completeFenced(store, leaseId, "done", clock());
178
+ if (!fence.ok)
179
+ return fence;
180
+ // The disowned arm (external dispatch, v4 §24): the tracker closed
181
+ // this mirror while it was being built.
182
+ if (fence.arm === "disowned") {
183
+ store.finishRun(runId, { outcome: "failed", reason: "external-closed", committed: result.committed, now: clock() });
184
+ store.enqueueNotification({
185
+ source: { run: runId },
186
+ dedupeKey: `run:${runId}:external-closed`,
187
+ kind: "external-closed",
188
+ subject: `${taskId}: the tracker closed this while it was being built`,
189
+ body: `The branch ${branch} is kept as evidence; nothing is published. Reopen the tracker item and \`toolroll task reopen ${taskId}\` if the work should continue.`,
190
+ }, clock());
191
+ return fence;
192
+ }
193
+ store.finishRun(runId, {
194
+ outcome: result.noChange === true ? "no-change" : "built",
195
+ ...(result.noChange === true ? { reason: "handoff" } : {}),
196
+ committed: result.committed,
197
+ now: clock(),
198
+ });
199
+ // A stated no-change publishes nothing.
200
+ if (result.noChange !== true && result.committed) {
201
+ const grant = store.publicationGrantFor(repo);
202
+ const headSha = store.getRun(runId)?.headRevision ?? null;
203
+ if (grant !== null &&
204
+ headSha !== null &&
205
+ headWithin(branch, grant.headPrefix) &&
206
+ (grant.selector === "all" || origin === "ours")) {
207
+ const intentId = store.createPublicationIntent({
208
+ run: runId,
209
+ taskRef,
210
+ githubRepo: grant.githubRepo,
211
+ remote: grant.remote,
212
+ base: grant.base,
213
+ head: branch,
214
+ headSha,
215
+ bodyHash: "",
216
+ draft: grant.draft,
217
+ }, clock());
218
+ // The body's identity is computed from the rows this very
219
+ // transaction made durable — reproducible after any crash.
220
+ const publication = store.publicationForRun(runId);
221
+ if (publication !== null) {
222
+ store.handle
223
+ .prepare("UPDATE publication SET body_hash = ? WHERE id = ?")
224
+ .run(bodyHashOf(publicationBody(store, publication)), intentId);
225
+ }
226
+ }
227
+ }
228
+ return fence;
229
+ });
230
+ if (sealed.ok && sealed.arm === "disowned")
231
+ return { kind: "disowned" };
232
+ if (sealed.ok) {
233
+ // A concluded success ends the failure streak and its backoff — and
234
+ // proves the credential, clearing any quota stamp.
235
+ store.resetStrikes(taskRef);
236
+ store.clearQuota(runner, provider, model ?? "");
237
+ return { kind: "built", committed: result.committed, noChange: result.noChange === true };
238
+ }
239
+ store.transact(() => {
240
+ store.finishRun(runId, { outcome: "failed", reason: "fenced", committed: result.committed, now: clock() });
241
+ store.enqueueNotification({
242
+ source: { run: runId },
243
+ dedupeKey: `run:${runId}:fenced`,
244
+ kind: "build-fenced",
245
+ subject: `${taskId}: completed, but the lease was gone`,
246
+ body: `The commit exists on ${branch}, but the world moved past this lease before the completion was accepted. Look before anything reuses it.`,
247
+ }, clock());
248
+ });
249
+ return { kind: "built-fenced" };
250
+ }
251
+ // ---- refusals and failures -----------------------------------------------
252
+ // THE ALREADY-SEALED ENDINGS (adaptive execution plans). Every other arm
253
+ // below ends the attempt itself — releases the claim, writes the run's
254
+ // outcome, counts what it costs. These two arrive with all of that
255
+ // already done: `finalizeRevisionFenced` released the lease, appended the
256
+ // ledger row, placed the hold when one was owed, finished the run, and
257
+ // paged, all inside ONE fenced transaction — the planner road's shape,
258
+ // where claim.ts owns the ending and dispose only reports it.
259
+ //
260
+ // So this branch exists to do NOTHING, deliberately, and it is placed
261
+ // above the policy arms so that every road — tick, standalone, held,
262
+ // continuation — skips them alike.
263
+ //
264
+ // Falling through instead would be wrong twice over. `release()` would
265
+ // survive it (an already-released lease whose `released_by` is
266
+ // 'released' reads as this same lease's duplicate hand-back, not a
267
+ // fence), but `finishRun` overwrites unconditionally, so the sealed
268
+ // `finished_at` would be restamped by a second, later writer; and the
269
+ // bottom of the chain returns `invariant`, which means "the dispatcher
270
+ // broke a rule it was supposed to uphold" — the opposite of what
271
+ // happened here, where the machine worked exactly as designed.
272
+ if (result.reason === "plan-revised" || result.reason === "plan-revision-blocked") {
273
+ return { kind: "skipped", reason: result.reason };
274
+ }
275
+ if (policy === "continuation") {
276
+ // The taskless failure (v4 Q7): the run says what happened, the claim
277
+ // releases — NO strikes, NO holds, NO done→failed demotion; three
278
+ // failed continuations still leave the parent exactly as it finished.
279
+ const outcome = result.reason === "fenced" ? "refused" : "failed";
280
+ store.finishRun(runId, { outcome, reason: result.reason, now: clock() });
281
+ if (leaseId !== undefined)
282
+ release(store, leaseId, clock());
283
+ return { kind: "recorded", outcome };
284
+ }
285
+ if (policy === "standalone") {
286
+ if (result.reason === "malformed-decision" && leaseId !== undefined) {
287
+ const sealed = finalizeMalformedFenced(store, {
288
+ leaseId,
289
+ runId,
290
+ taskId,
291
+ problems: result.problems ?? [],
292
+ now: clock(),
293
+ });
294
+ return { kind: "malformed", sealed: sealed.ok };
295
+ }
296
+ const outcome = STANDALONE_BROKE_REASONS.has(result.reason) ? "failed" : "refused";
297
+ store.finishRun(runId, { outcome, reason: result.reason, now: clock() });
298
+ return { kind: "recorded", outcome };
299
+ }
300
+ // The attended refusal family (v28 sweep): typed, no strike, release and
301
+ // move on — the pre-claim gates make every one a rare race, and the
302
+ // invariant arm they used to fall through is for BUGS, not races.
303
+ if (result.reason === "attended-only" ||
304
+ result.reason === "attended-held" ||
305
+ result.reason === "stale-authorization" ||
306
+ result.reason === "session-cap" ||
307
+ result.reason === "run-held") {
308
+ if (leaseId !== undefined)
309
+ release(store, leaseId, clock());
310
+ store.finishRun(runId, { outcome: "refused", reason: result.reason, now: clock() });
311
+ return { kind: "skipped", reason: result.reason };
312
+ }
313
+ if (result.reason === "unapproved" || result.reason === "scope-changed") {
314
+ // Approval drifted between the prefilter and the builder's own gate.
315
+ if (leaseId !== undefined)
316
+ release(store, leaseId, clock());
317
+ store.finishRun(runId, { outcome: "refused", reason: result.reason, now: clock() });
318
+ return { kind: "skipped", reason: result.reason };
319
+ }
320
+ if (result.reason === "fenced") {
321
+ // The lease did not survive the build. Nothing is ours to release.
322
+ store.finishRun(runId, { outcome: "refused", reason: "fenced", now: clock() });
323
+ return { kind: "fenced" };
324
+ }
325
+ if (result.reason === "malformed-decision") {
326
+ if (leaseId === undefined)
327
+ return { kind: "malformed", sealed: false };
328
+ const sealed = finalizeMalformedFenced(store, {
329
+ leaseId,
330
+ runId,
331
+ taskId,
332
+ problems: result.problems ?? [],
333
+ now: clock(),
334
+ });
335
+ return { kind: "malformed", sealed: sealed.ok };
336
+ }
337
+ if (TICK_FAILURE_REASONS.has(result.reason)) {
338
+ // One fenced transaction decides what the failure means. The
339
+ // classification trusts only what the machine itself observed.
340
+ const failureClass = result.reason === "agent-reported"
341
+ ? "agent-reported"
342
+ : result.reason === "no-op" || result.reason === "moved-head" || result.reason === "moved-branch"
343
+ ? "no-op"
344
+ : result.reason === "timeout" || result.reason === "git" || result.reason === "provider-init" || result.reason === "setup" || result.reason === "stopped" || result.reason === "provider-unattested" || result.reason === "provider-protocol"
345
+ ? "retryable-infra"
346
+ : result.reason === "commit-failure"
347
+ ? "commit-failure"
348
+ : "unknown";
349
+ // The attempt's ending, in words, on the run itself: the class is what
350
+ // the strikes count, the message is what a person reads (an agent's
351
+ // own verdict was already recorded by the builder under the same key).
352
+ if (result.reason !== "agent-reported" && result.message.trim() !== "") {
353
+ store.recordOutcomeFacts(runId, { handoff: result.message });
354
+ }
355
+ if (leaseId === undefined) {
356
+ store.finishRun(runId, { outcome: "failed", reason: result.reason, now: clock() });
357
+ return { kind: "failed", failureClass, disposition: null, strikes: null, sealed: false };
358
+ }
359
+ const sealed = finalizeFailureFenced(store, {
360
+ leaseId,
361
+ runId,
362
+ taskId,
363
+ failureClass,
364
+ message: result.message,
365
+ worktree: worktreePath,
366
+ now: clock(),
367
+ });
368
+ return {
369
+ kind: "failed",
370
+ failureClass,
371
+ disposition: sealed.ok ? sealed.disposition : null,
372
+ strikes: sealed.ok && "strikes" in sealed ? sealed.strikes : null,
373
+ sealed: sealed.ok,
374
+ };
375
+ }
376
+ // no-claim, not-yours, not-leased, protected-branch, wrong-branch:
377
+ // invariants the dispatcher was supposed to uphold.
378
+ if (leaseId !== undefined)
379
+ release(store, leaseId, clock());
380
+ store.finishRun(runId, { outcome: "refused", reason: result.reason, now: clock() });
381
+ if (result.reason === "stale-approval") {
382
+ // A deterministic refusal (setup review): the approval no longer
383
+ // matches the routing, and nothing about the next pass changes that.
384
+ // Retrying every pass wrote a thousand refused runs in minutes. So:
385
+ // release, record, HOLD the task under a backoff the approval door
386
+ // lifts, and page once per approval — the operator re-approves (or
387
+ // reconfigures the build agent) and the hold goes with the yes.
388
+ if (leaseId !== undefined)
389
+ release(store, leaseId, clock());
390
+ store.finishRun(runId, { outcome: "refused", reason: "stale-approval", now: clock() });
391
+ holdStaleApproval(store, { taskRef, taskId, message: result.message }, clock());
392
+ return { kind: "skipped", reason: "stale-approval" };
393
+ }
394
+ return { kind: "invariant", reason: result.reason };
395
+ }
396
+ /**
397
+ * The stale-approval HOLD, shared by the post-build disposition above and
398
+ * the tick's pre-admission refusal (v48 integrity): a pre-routing row
399
+ * whose sealed profile no longer matches what would resolve is refused
400
+ * BEFORE any run row exists — held under the backoff the approval door
401
+ * lifts, paged once per approval — so the fail-closed admission never
402
+ * turns into a refusal every pass.
403
+ */
404
+ export function holdStaleApproval(store, args, now) {
405
+ const scope = store.getScope(args.taskId);
406
+ store.holdOwned({
407
+ taskRef: args.taskRef,
408
+ ownerKind: "backoff",
409
+ ownerId: `stale:${args.taskRef}`,
410
+ reason: "stale-approval — the approval no longer matches how builds are routed; approve the scope again on its page",
411
+ until: new Date(now.getTime() + 6 * 60 * 60_000),
412
+ }, now);
413
+ store.enqueueNotification({
414
+ source: { taskRef: args.taskRef },
415
+ dedupeKey: `stale-approval:${args.taskRef}:${scope?.approvedDigest ?? "none"}`,
416
+ kind: "stale-approval",
417
+ pushClass: "attention",
418
+ link: `/t/${encodeURIComponent(args.taskId)}`,
419
+ subject: `${args.taskId}: its approval no longer matches the build routing — approve it again`,
420
+ body: `${args.message}\nNothing runs on it until the scope is approved again; the hold lifts with the yes.`,
421
+ }, now);
422
+ }
423
+ // ---- the bounded repair loop (v40, evidence-review-v1) --------------------
424
+ /** Whether a run's proof reasons name a LIE about the signed terms — an
425
+ * altered criterion statement, or an overclaimed changed path — rather
426
+ * than a gap in the work. The exact phrases `adjudicate()` uses for those
427
+ * two rules, and no others; the integrity stop's own predicate. */
428
+ function isIntegrityRefutation(reasons) {
429
+ return reasons.some(one => one.includes("was signed as") || one.includes("not in the sealed diff"));
430
+ }
431
+ /** Strict shrink: `to` is a proper subset of `from` — gnhf's no-progress
432
+ * rule (docs/DESIGN.md:229), extended: an attempt that does not strictly
433
+ * shrink the unresolved-criterion set counts as a failure. */
434
+ function strictlyShrunk(from, to) {
435
+ if (to.length >= from.length)
436
+ return false;
437
+ const fromSet = new Set(from);
438
+ return to.every(id => fromSet.has(id));
439
+ }
440
+ /**
441
+ * The chain's happy exit (v40): the instant one of its own attempts
442
+ * reaches `verified` or `attested`, the chain is done. Checked at every
443
+ * completed build's own verdict-save point — not only after a review —
444
+ * since a review can never UPGRADE a verdict past what `adjudicate`
445
+ * already found (foldReview's monotonicity), so only the structural save
446
+ * can ever produce one of these two words.
447
+ */
448
+ export function maybeSettleRepairChain(store, taskId, verdict, now) {
449
+ if (verdict !== "verified" && verdict !== "attested")
450
+ return;
451
+ const chain = store.repairChainForDraft(taskId);
452
+ if (chain === null || chain.outcome !== "drafted")
453
+ return;
454
+ store.settleRepairChain(chain.id, "resolved", now);
455
+ }
456
+ /** Read the sealed receipt, log and candidate inventory behind a failed gate
457
+ * and say whether the failure is one no repair attempt could fix. An
458
+ * unreadable or truncated inventory leaves the failure repairable. */
459
+ function unrelatedGateFailure(store, evidenceRoot, failure, runId) {
460
+ const receipt = JSON.parse(failure.receipt);
461
+ let changed = null;
462
+ const stat = store.artifactsFor(runId).find(a => a.kind === "diff-stat");
463
+ if (stat && !stat.truncated && stat.captureStatus === "ok") {
464
+ const read = readVerifiedArtifact(evidenceRoot, stat);
465
+ if (read.ok) {
466
+ try {
467
+ const inventory = JSON.parse(read.content.toString("utf8"));
468
+ if (inventory.filesTruncated === false && Array.isArray(inventory.files)) {
469
+ changed = inventory.files.map(one => one.path).filter((path) => typeof path === "string");
470
+ }
471
+ }
472
+ catch {
473
+ changed = null;
474
+ }
475
+ }
476
+ }
477
+ const classified = classifyGateFailure(receipt.result, failure.log, changed, receipt.command?.timeoutMs ?? null);
478
+ return classified.kind === "repairable" ? null : classified;
479
+ }
480
+ export function regateTask(store, taskId, now, approval) {
481
+ const ref = store.lookupRef(taskId);
482
+ if (ref === null)
483
+ return { ok: false, reason: "no-task", message: `no task ${taskId}` };
484
+ const last = store.lastBuilderRun(ref.id);
485
+ if (last === null || !last.headRevision)
486
+ return { ok: false, reason: "no-attempt", message: `${taskId} has no finished attempt to rerun the check on` };
487
+ const verdict = store.proofVerdictFor(last.id);
488
+ if (verdict !== null && (verdict.verdict === "verified" || verdict.verdict === "attested"))
489
+ return { ok: false, reason: "accepted-result", message: `${taskId}'s last result already reads ${verdict.verdict}; there is nothing to rerun` };
490
+ if (store.proofAcceptance(last.id) !== null)
491
+ return { ok: false, reason: "accepted-result", message: `${taskId}'s last result was accepted by an operator` };
492
+ // Rejected: the proof was refuted, or the machine never got a passing
493
+ // gate (a timed-out check reads "short", not "refuted"). A short verdict
494
+ // whose gate passed is a result awaiting review, not one to rerun.
495
+ const machinePassed = verdict?.machineVerdict === "verified" || verdict?.machineVerdict === "attested";
496
+ if (verdict === null || !(verdict.verdict === "refuted" || (verdict.verdict === "short" && !machinePassed))) {
497
+ return { ok: false, reason: "not-rejected", message: `${taskId}'s last result is still being assessed; wait for it or stop it` };
498
+ }
499
+ // A pushed or opened branch is revised through its pull request, never
500
+ // rerun underneath it; a live claim means an attempt is still running.
501
+ const publication = store.publicationForRun(last.id);
502
+ if (publication !== null && (publication.state === "pushed" || publication.state === "opened"))
503
+ return { ok: false, reason: "published", message: `${taskId}'s last result is published; revise it through its pull request` };
504
+ if (store.hasLiveClaim(ref.id, now))
505
+ return { ok: false, reason: "claimed", message: `a runner holds ${taskId} right now — wait for the attempt to end, or stop it` };
506
+ const scope = store.getScope(taskId);
507
+ if (scope === null)
508
+ return { ok: false, reason: "no-attempt", message: `${taskId} has no scope` };
509
+ const head = last.headRevision;
510
+ const actor = approval.kind === "operator" ? approval.name : `mode ${approval.name}`;
511
+ return store.transact(() => {
512
+ const proposed = propose(store, {
513
+ taskId, goal: scope.goal, outOfScope: scope.outOfScope, touches: scope.touches, budgetMicrousd: scope.budgetMicrousd,
514
+ acceptance: scope.acceptance, qualityMode: scope.qualityMode ?? "default", riskLevel: scope.riskLevel ?? "routine",
515
+ candidate: head, now,
516
+ });
517
+ let sealed = false;
518
+ if (approval.kind === "operator")
519
+ sealed = approve(store, taskId, approval.name, now, proposed.digest, approval.token).ok;
520
+ else
521
+ sealed = store.sealScopeApproval(taskId, actor, now, {}, { kind: "mode", modeDigest: approval.modeDigest });
522
+ if (!sealed)
523
+ return { ok: false, reason: "not-approved", message: `${taskId}'s rerun scope could not be approved` };
524
+ // A finished task returns to the queue; one already queued (a park, a
525
+ // stop) simply dispatches with the rerun scope on its next turn.
526
+ if (store.getTask(taskId)?.state !== "queued") {
527
+ const queued = store.requeueTask(taskId, actor, now);
528
+ if (!queued.ok) {
529
+ const why = { claimed: "claimed", "accepted-result": "accepted-result", published: "published" };
530
+ return { ok: false, reason: why[queued.reason] ?? "not-rejected", message: `${taskId} could not be queued again: ${queued.reason}` };
531
+ }
532
+ }
533
+ store.addRunNote(last.id, "Toolroll", `The check will run again on this exact commit (${head.slice(0, 7)}) as a new attempt, ${approval.kind === "operator" ? `on ${approval.name}'s say-so` : "automatically once, because the failure was outside this change"}.`, now);
534
+ return { ok: true, run: last.id, head };
535
+ });
536
+ }
537
+ /**
538
+ * An explicitly requested repair from saved check or historical review findings.
539
+ * Automatic calls are retired. Composes at
540
+ * most one durable revision draft naming exactly the unmet criterion ids,
541
+ * inheriting the source scope and rubric verbatim — or settles the chain
542
+ * at one of its four independent stops (integrity, no-progress, attempts
543
+ * cap; the fourth — existing spend/run rails — is the ordinary tick's own
544
+ * job once a mode-approved draft dispatches as a normal builder run).
545
+ *
546
+ * Never dispatches anything itself: a mode-authorized draft is
547
+ * auto-APPROVED here, but still builds through the ordinary tick, under
548
+ * the ordinary rails and strikes — this function only ever composes a
549
+ * task and, at most, one scope approval.
550
+ */
551
+ export function maybeTriggerRepair(store, repo, evidenceRoot, sourceRunId, verdict, now, cause = "review", automatic = false) {
552
+ if (verdict !== "short" && verdict !== "refuted")
553
+ return { kind: "none" };
554
+ // Findings go to the lead. Explicit repair/revision actions remain available.
555
+ // Historical mode signatures remain intact; no automatic follow-up spends.
556
+ if (automatic)
557
+ return { kind: "none" };
558
+ // One attempt per source run, ever (source_run UNIQUE) — checked first so
559
+ // a re-fired trigger is a silent no-op, never a duplicate.
560
+ if (store.repairChainFor(sourceRunId) !== null)
561
+ return { kind: "none" };
562
+ const run = store.getRun(sourceRunId);
563
+ if (run === null)
564
+ return { kind: "none" };
565
+ const stored = store.proofVerdictFor(sourceRunId);
566
+ if (stored === null)
567
+ return { kind: "none" };
568
+ // THE RERUN STOP (v70): this attempt was a rerun of the same commit — the
569
+ // scope names that commit as its prepared candidate and the commit has
570
+ // been attempted before. The machine has had its one retry; whatever
571
+ // rejected it this time, a failed check or a contradicting review, goes
572
+ // to a person. No repair draft, no third pass, on either road.
573
+ {
574
+ const rerunTask = store.refById(run.taskRef)?.externalId ?? "";
575
+ if (run.headRevision && store.getScope(rerunTask)?.candidate === run.headRevision && store.builderAttemptsAt(run.taskRef, run.headRevision) >= 2) {
576
+ const contradicted = cause === "review";
577
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `regate-failed:${sourceRunId}`, kind: "repair-evidence", pushClass: "attention",
578
+ subject: contradicted ? "The rerun on the same commit was contradicted by its review" : "The project check failed again on the same commit",
579
+ body: `Attempt #${sourceRunId} reran commit ${run.headRevision.slice(0, 7)} and ${contradicted ? "its review contradicted the result" : "the approved check failed again"}. No repair task was filed: decide whether the code or the check is wrong, then run \`toolroll task regate ${rerunTask}\` or re-scope a corrected candidate.`,
580
+ link: `/t/${encodeURIComponent(rerunTask)}` }, now);
581
+ return { kind: "none" };
582
+ }
583
+ }
584
+ const direct = stored.matrix.length > 0 && stored.matrix.every(row => row.assessment !== undefined);
585
+ const observation = cause === "review" && direct && !stored.matrix.some(row => row.review?.judgement === "contradicts") && stored.matrix.some(row => row.review?.judgement === "cannot-tell");
586
+ if (cause === "review" && direct && !stored.matrix.some(row => row.review?.judgement === "contradicts") && !observation)
587
+ return { kind: "none" };
588
+ if (store.applicableStopFor(sourceRunId) !== null || store.activeHolds(run.taskRef, now).length > 0)
589
+ return { kind: "none" };
590
+ let observationGate;
591
+ if (observation) {
592
+ if (store.refForId(run.taskRef)?.repo !== repo)
593
+ return { kind: "none" };
594
+ const missing = stored.matrix.filter(row => row.review?.judgement === "cannot-tell");
595
+ const supported = missing.every(row => row.requiredEvidence.includes("check") && !row.requiredEvidence.includes("screenshot") && !row.requiredEvidence.includes("manual-review")) && focusedTestCommandSupported(store.liveVerifyCommand(repo)?.command ?? "");
596
+ if (!supported) {
597
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `assessment-evidence:${sourceRunId}`, kind: "repair-evidence", pushClass: "attention", subject: "More evidence is needed", body: missing.flatMap(row => row.detail).join("\n"), link: `/r/${sourceRunId}` }, now);
598
+ return { kind: "none" };
599
+ }
600
+ const gate = verificationEvidence(store, evidenceRoot, sourceRunId);
601
+ const originalBase = originalTaskBase(store, store.externalIdFor(run.taskRef));
602
+ if (!gate.ok || !gate.bytes || !originalBase || JSON.parse(gate.bytes).result?.exitCode !== 0 || stored.machineVerdict !== "verified")
603
+ return { kind: "none" };
604
+ observationGate = { digest: gate.digest, originalBase };
605
+ }
606
+ const mode = store.activeMode(repo, now);
607
+ const terms = mode === null ? null : modeTermsFromJson(mode.termsJson);
608
+ let verification;
609
+ if (cause === "verification") {
610
+ // A signed repair mode is required for this automatic producer. The
611
+ // existing review/manual road continues to offer unapproved drafts.
612
+ if (terms?.repairAuto !== true || run.finishedAt === null || !run.headRevision ||
613
+ (run.outcome !== "built" && run.outcome !== "no-change"))
614
+ return { kind: "none" };
615
+ const owner = store.refForId(run.taskRef);
616
+ if (owner === null || owner.repo !== repo || store.applicableStopFor(sourceRunId) !== null ||
617
+ store.activeHolds(run.taskRef, now).length > 0 ||
618
+ (!run.committed && !(run.outcome === "no-change" && store.repairChainForDraft(owner.externalId) !== null)))
619
+ return { kind: "none" };
620
+ const failure = failedVerificationEvidence(store, evidenceRoot, sourceRunId);
621
+ if (failure.kind === "unavailable") {
622
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `repair-evidence:${sourceRunId}`, kind: "repair-evidence", pushClass: "attention", subject: "Repair needs complete evidence", body: failure.problem, link: `/t/${encodeURIComponent(store.refById(run.taskRef)?.externalId ?? "")}` }, now);
623
+ return { kind: "none" };
624
+ }
625
+ if (failure.kind !== "failed")
626
+ return { kind: "none" };
627
+ // A failure the change cannot have caused — the whole gate timing out,
628
+ // or an untouched test's own timeout — is handed to a person, not to
629
+ // another code-writing attempt (2026-09-18: mayhem-spire run 1784
630
+ // drafted a repair for a balance probe the diff never touched).
631
+ const unrelated = unrelatedGateFailure(store, evidenceRoot, failure, sourceRunId);
632
+ if (unrelated !== null) {
633
+ const taskId = store.refById(run.taskRef)?.externalId ?? "";
634
+ // One automatic rerun of the check on the same commit, under the mode
635
+ // that already authorizes automatic repair (a rerun is strictly less
636
+ // than a repair). A second failure on the same commit goes to a person.
637
+ const rerunBefore = run.headRevision ? store.builderAttemptsAt(run.taskRef, run.headRevision) : 0;
638
+ if (mode !== null && terms?.repairAuto === true && run.headRevision && rerunBefore < 2) {
639
+ const rerun = regateTask(store, taskId, now, { kind: "mode", name: mode.name, modeDigest: mode.digest });
640
+ if (rerun.ok) {
641
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `regate:${sourceRunId}`, kind: "repair-evidence",
642
+ subject: unrelated.kind === "gate-timed-out" ? "The project check ran out of time; running it once more" : "A slow test outside this change failed the check; running it once more",
643
+ body: `${gateFailureSummary(unrelated)} The check runs again once on the same commit ${run.headRevision.slice(0, 7)}, with no agent. If it fails again, a person decides.`,
644
+ link: `/t/${encodeURIComponent(taskId)}` }, now);
645
+ return { kind: "none" };
646
+ }
647
+ }
648
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `repair-unrelated:${sourceRunId}`, kind: "repair-evidence", pushClass: "attention",
649
+ subject: unrelated.kind === "gate-timed-out" ? "The project check ran out of time" : "A slow test outside this change failed the project check",
650
+ body: describeGateFailure(unrelated, taskId), link: `/t/${encodeURIComponent(taskId)}` }, now);
651
+ return { kind: "none" };
652
+ }
653
+ verification = { sourceRun: sourceRunId, digest: failure.digest };
654
+ }
655
+ const unresolved = stored.matrix
656
+ .filter(row => cause === "review" && direct ? row.review?.judgement === (observation ? "cannot-tell" : "contradicts") : row.state === "missing" || row.state === "failed")
657
+ .map(row => row.id)
658
+ .sort();
659
+ // The project gate is independent of agent-reported criterion passes.
660
+ // This ledger target is not a new acceptance criterion or a changed verdict.
661
+ if (verification !== undefined && !unresolved.includes("project checks"))
662
+ unresolved.push("project checks");
663
+ unresolved.sort();
664
+ if (unresolved.length === 0)
665
+ return { kind: "none" };
666
+ const brief = store.refById(run.taskRef);
667
+ const ref = brief === null ? null : store.lookupRef(brief.externalId);
668
+ if (ref === null)
669
+ return { kind: "none" };
670
+ // Never repair: a scout task (a report, not a branch — nothing to build
671
+ // back into).
672
+ if (ref.deliverable === "report")
673
+ return { kind: "none" };
674
+ // Never repair: a run already published or holding a merge blocker.
675
+ const publication = store.publicationForRun(sourceRunId);
676
+ if (publication !== null && !(verification !== undefined && publication.state === "intended" && publication.attempts === 0))
677
+ return { kind: "none" };
678
+ // Never repair: a task with an open decision or a pending steering note.
679
+ if (store.decisionsForTask(ref.id).some(d => d.state === "open" || d.state === "expired"))
680
+ return { kind: "none" };
681
+ if (store.pendingSteerCount(ref.id) > 0)
682
+ return { kind: "none" };
683
+ // Never repair: a run whose proof was already accepted.
684
+ if (store.proofAcceptance(sourceRunId) !== null)
685
+ return { kind: "none" };
686
+ const task = store.getTask(ref.externalId);
687
+ const scope = store.getScope(ref.externalId);
688
+ if (task === null || scope === null)
689
+ return { kind: "none" };
690
+ // THE LINEAGE (contract handoff task 2): the chain this task CONTINUES
691
+ // is found through its revision ancestry, not only its own draft row —
692
+ // an annotation or CI detour between two repair attempts keeps the
693
+ // root, the attempts already spent, and the remaining automatic bound.
694
+ // The next attempt number is one past the chain's highest, whichever
695
+ // branch spent it; nothing resets because a person annotated a draft.
696
+ const lineage = store.repairLineageOf(ref.externalId);
697
+ if (lineage.problem !== undefined) {
698
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `repair-lineage:${sourceRunId}`, kind: "repair-lineage", subject: `${ref.externalId}: repair ancestry needs attention`, body: lineage.problem, link: `/t/${encodeURIComponent(ref.externalId)}` }, now);
699
+ return { kind: "stopped", reason: "repair-refused-integrity" };
700
+ }
701
+ const priorChain = lineage.continues;
702
+ const rootTask = lineage.rootTask;
703
+ const attempt = lineage.attempts.reduce((highest, row) => Math.max(highest, row.attempt), 0) + 1;
704
+ const auto = terms?.repairAuto === true;
705
+ const basis = auto ? "mode" : "human";
706
+ const modeDigest = auto && mode !== null ? mode.digest : null;
707
+ /** Settle the row this task continues when it is still open; a stop
708
+ * with no open row to settle records its own settled row instead. */
709
+ const settleOrRecord = (outcome) => {
710
+ if (priorChain !== null && priorChain.outcome === "drafted")
711
+ store.settleRepairChain(priorChain.id, outcome, now);
712
+ else
713
+ store.recordRepairStop({ rootTask, sourceRun: sourceRunId, attempt, basis, modeDigest, unresolved, outcome }, now);
714
+ };
715
+ // THE INTEGRITY STOP (unconditional, both roads): a refutation that lies
716
+ // about the signed terms is never handed back to the same machine
717
+ // unattended — park for a human instead.
718
+ if (isIntegrityRefutation(stored.reasons)) {
719
+ settleOrRecord("integrity-refused");
720
+ return { kind: "stopped", reason: "repair-refused-integrity" };
721
+ }
722
+ // THE NO-PROGRESS STOP (unconditional, both roads): two consecutive
723
+ // attempts that fail to strictly shrink the unresolved set.
724
+ if (priorChain !== null) {
725
+ const history = store.repairChainForRoot(rootTask).map(row => [...row.unresolved].sort());
726
+ const sequence = [...history, unresolved];
727
+ if (sequence.length >= 3) {
728
+ const last = sequence[sequence.length - 1];
729
+ const mid = sequence[sequence.length - 2];
730
+ const first = sequence[sequence.length - 3];
731
+ if (!strictlyShrunk(mid, last) && !strictlyShrunk(first, mid)) {
732
+ settleOrRecord("no-progress");
733
+ return { kind: "stopped", reason: "repair-no-progress" };
734
+ }
735
+ }
736
+ }
737
+ // THE ATTEMPT CAP: bounds only the AUTOMATIC road (a mode's signed
738
+ // repairMaxAttempts) — the default road's loop is already bounded by
739
+ // requiring a fresh human "yes" for every attempt.
740
+ if (auto && terms !== null && attempt > terms.repairMaxAttempts) {
741
+ settleOrRecord("attempts-spent");
742
+ return { kind: "stopped", reason: "repair-attempts-spent" };
743
+ }
744
+ // THE DRAFT: no log content, no new instructions — exactly the unmet
745
+ // ids, their matrix detail sentences, and the reviewer's own
746
+ // contradiction notes.
747
+ const contradictions = store.criterionReviewsFor(sourceRunId).filter(one => one.judgement === "contradicts");
748
+ const unresolvedDetail = stored.matrix.filter(row => unresolved.includes(row.id)).map(row => ({ id: row.id, statement: row.statement, detail: row.detail }));
749
+ const draftBrief = {
750
+ schema: 1,
751
+ kind: observation ? "evidence-observation" : "criterion-repair",
752
+ sourceTask: ref.externalId,
753
+ sourceRun: sourceRunId,
754
+ sourceScopeDigest: scope.digest,
755
+ head: run.headRevision,
756
+ rootTask,
757
+ attempt,
758
+ unresolved: unresolvedDetail,
759
+ reviewerContradictions: contradictions.map(one => ({ id: one.criterionId, author: one.author, note: one.note })),
760
+ ...(verification === undefined ? {} : { verification }),
761
+ ...(observationGate === undefined ? {} : { originalBase: observationGate.originalBase, gateDigest: observationGate.digest }),
762
+ };
763
+ const briefBytes = Buffer.from(JSON.stringify(draftBrief, null, 2), "utf8");
764
+ const key = writeEvidenceFile(evidenceRoot, sourceRunId, `repair-brief-${sourceRunId}-${attempt}.json`, briefBytes);
765
+ // Suffixes survive truncation (the CI-repair rule, verbatim): the prefix
766
+ // gives way, the identity-bearing tail never does.
767
+ const suffix = observation ? `-evidence-${attempt}` : `-fix-${attempt}`;
768
+ const draftId = `${rootTask.slice(0, 64 - suffix.length)}${suffix}`;
769
+ // The draft files through the ONE revision boundary: the source's goal,
770
+ // exclusions, touches, rubric, risk, quality, posture, budget, overrides
771
+ // and pins are read from the SOURCE ROWS inside the seal — the scope
772
+ // read above is only the digest this draft binds to, re-proved there.
773
+ const drafted = store.openRepairDraft({
774
+ source: { task: ref.externalId, run: sourceRunId, scopeDigest: scope.digest },
775
+ brief: { evidenceRoot, key, sha256: createHash("sha256").update(briefBytes).digest("hex"), bytes: briefBytes.length, capture: "machine-authored repair brief (exit 0)" },
776
+ child: {
777
+ id: draftId,
778
+ title: observation ? `Collect missing evidence for ${ref.externalId}` : verification === undefined ? `repair ${ref.externalId}: ${unresolved.length} criteri${unresolved.length === 1 ? "on" : "a"} unmet` : `Fix failed checks for ${ref.externalId}`,
779
+ repair: observation ? `Collect only the missing observations for ${unresolved.join(", ")}. Keep the saved candidate unchanged. The machine runs supported focused test observations and reuses its intact passing gate; a fresh review assesses the added evidence.` : verification === undefined ? `repair exactly the unmet criteria named below; a comment cannot widen the scope. Unmet: ${unresolved.join(", ")}.` : "Diagnose the saved failed project check and repair within the original scope. Preserve the acceptance criteria and verification command; the revision brief binds the exact failure evidence.",
780
+ },
781
+ rootTask,
782
+ attempt,
783
+ basis,
784
+ modeDigest,
785
+ unresolved,
786
+ }, now);
787
+ if (!drafted.ok)
788
+ return { kind: "none" };
789
+ let approved = false;
790
+ if (auto && mode !== null) {
791
+ const draftScope = store.getScope(drafted.id);
792
+ // A scope whose profile could not resolve is unapprovable by the human
793
+ // road; the mode road refuses the same way — the draft stays honestly
794
+ // unapproved rather than sealing an approval nobody could act on.
795
+ if (draftScope?.profileState === "resolved") {
796
+ approved = store.sealScopeApproval(drafted.id, `mode ${mode.name}`, now, {}, { kind: "mode", modeDigest: mode.digest });
797
+ }
798
+ }
799
+ if (observation && !approved)
800
+ store.enqueueNotification({ source: { run: sourceRunId }, dedupeKey: `assessment-evidence:${sourceRunId}`,
801
+ kind: "repair-evidence", pushClass: "attention", subject: "Review evidence collection", body: "A follow-up is ready to collect the missing observations without changing the saved code.", link: `/t/${encodeURIComponent(drafted.id)}` }, now);
802
+ return { kind: "drafted", draftTaskId: drafted.id, attempt, approved };
803
+ }