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,782 @@
1
+ /**
2
+ * Evidence: what actually happened, captured by the machine (§4, §7).
3
+ *
4
+ * The agent does not choose what counts as evidence — a diff the agent could
5
+ * fabricate is a screenshot of a different crime scene. At park time the
6
+ * builder captures the working state itself, relative to the base revision
7
+ * stamped before the agent spent anything, and records how each capture was
8
+ * made, whether it is complete, and what it hashes to. A truncated capture
9
+ * says truncated; a failed capture stores the failure. Presenting either as
10
+ * the whole story is the one dishonesty this module exists to rule out.
11
+ *
12
+ * Files live under the evidence root — outside every worktree, so an agent
13
+ * cannot pre-write them — created exclusively (`wx`) with mode 0600. The
14
+ * store holds keys relative to that root, never absolute paths.
15
+ */
16
+ import { createHash, randomBytes } from "node:crypto";
17
+ import { closeSync, existsSync, constants, fstatSync, mkdirSync, openSync, readdirSync, readSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
18
+ import { join, sep } from "node:path";
19
+ import { namedPath } from "./names.js";
20
+ import { LIMITS } from "./decision.js";
21
+ import { PLAN_LIMITS } from "./plan.js";
22
+ import { parseReport, REPORT_LIMITS } from "./scout-report.js";
23
+ import { parseProof, PROOF_LIMITS } from "./proof.js";
24
+ import { encodeBaseTreeSnapshot, parseBaseTreeSnapshot } from "./peek.js";
25
+ /** The park mailbox pattern. Each run's actual name carries a nonce; see `mailboxName`. */
26
+ export const MAILBOX_PREFIX = "STANDING-ORDERS-PARK-";
27
+ /** The terminal handoff: how every non-parking attempt says how it ended. */
28
+ export const HANDOFF_PREFIX = "STANDING-ORDERS-DONE-";
29
+ /** The planner's terminal handoff: the proposed scope and the plan document. */
30
+ export const PLAN_PREFIX = "STANDING-ORDERS-PLAN-";
31
+ /** The reviewer's former comment mailbox (v29–evidence-review-v1): the
32
+ * reviewer now replies in its final message and writes nothing, but the
33
+ * pattern stays recognized so a sweep still cleans up a stray file this
34
+ * name left by an older build. */
35
+ export const REVIEW_PREFIX = "STANDING-ORDERS-REVIEW-";
36
+ /** The scout's terminal handoff (v34): the report, its only deliverable. */
37
+ export const REPORT_PREFIX = "STANDING-ORDERS-REPORT-";
38
+ /** The build's proof manifest (Priority 2): optional, and read only after
39
+ * the handoff — a completed attempt's proof, never a parking or failure
40
+ * artifact. */
41
+ export const PROOF_PREFIX = "STANDING-ORDERS-PROOF-";
42
+ export const RUBRIC_PREFIX = "STANDING-ORDERS-RUBRIC-";
43
+ /** The running build's atomic milestone checkpoint (adaptive execution
44
+ * plans): overwritten in place — a temp name, then a rename — rather than
45
+ * created once and unlinked, since progress is reported many times across
46
+ * one attempt. Read, never consumed, by the pulse and by settlement. */
47
+ export const PROGRESS_PREFIX = "STANDING-ORDERS-PROGRESS-";
48
+ /** The running build's bounded, evidence-linked plan-revision proposal
49
+ * (adaptive execution plans): a terminal file like park or proof, written
50
+ * at most once, read once at settlement. */
51
+ export const PROPOSAL_PREFIX = "STANDING-ORDERS-PROPOSAL-";
52
+ export const MAILBOX_SUFFIX = ".json";
53
+ /** v105: left in a run's evidence folder when the retention setting removed its files (retention.ts). */
54
+ export const RETENTION_NOTE = "REMOVED-BY-RETENTION";
55
+ /** Bounds on a claimed screenshot file's own bytes — independent of the
56
+ * proof manifest's byte cap, since these are binary images, not JSON. */
57
+ export const SCREENSHOT_BYTE_CAP = 5 * 1024 * 1024;
58
+ /** Bytes each kind may store. Originals can be any size; the record says what was cut. */
59
+ /** Bound one captured stream to `cap` bytes keeping its beginning and, mostly,
60
+ * its end: a test runner's summary and failure list come last, and that is
61
+ * what a reviewer or a repair needs to read. */
62
+ export const SHORTENED_MARKER = "\n… output shortened; ending follows …\n";
63
+ export function boundStreamHeadTail(value, cap, headShare = 0.2) {
64
+ const bytes = Buffer.from(value, "utf8");
65
+ if (bytes.length <= cap)
66
+ return value;
67
+ const marker = Buffer.from(SHORTENED_MARKER, "utf8");
68
+ // A cut inside a multi-byte character decodes as U+FFFD (three bytes);
69
+ // leave room for one at each edge so the result never exceeds the cap.
70
+ const room = Math.max(0, cap - marker.length - 8);
71
+ const head = Math.floor(room * headShare);
72
+ const tail = room - head;
73
+ return Buffer.concat([bytes.subarray(0, head), marker, bytes.subarray(bytes.length - tail)]).toString("utf8");
74
+ }
75
+ export const EVIDENCE_CAPS = {
76
+ diff: 256 * 1024,
77
+ status: 64 * 1024,
78
+ "park-payload": LIMITS.payload,
79
+ plan: PLAN_LIMITS.document,
80
+ "terminal-diff": 256 * 1024,
81
+ "diff-stat": 32 * 1024,
82
+ // The enveloped base snapshot (live peek): 20k entries of path+sha+size.
83
+ "base-tree": 4 * 1024 * 1024,
84
+ handoff: 32 * 1024,
85
+ "revision-brief": 64 * 1024,
86
+ report: REPORT_LIMITS.payload,
87
+ proof: PROOF_LIMITS.payload,
88
+ // Six bounded streams (three attempts × stdout/stderr) plus the summary.
89
+ "check-log": 160 * 1024,
90
+ screenshot: SCREENSHOT_BYTE_CAP,
91
+ "structured-output": 64 * 1024,
92
+ // The planner's recorded source is bounded BEFORE it is written (the
93
+ // planner-source cap IS this value), so a stored record is never a
94
+ // truncated one: what the record holds is exactly what the planner read.
95
+ "plan-contract": 128 * 1024,
96
+ // The inherited review-context inventory (v51): structured JSON that is
97
+ // never byte-truncated — the capture sheds whole items against its own
98
+ // aggregate content limit (REVIEW_CONTEXT_LIMITS) and this cap only has
99
+ // to hold the JSON-escaped form of that bounded content plus provenance.
100
+ "review-context": 200 * 1024 * 1024,
101
+ };
102
+ export function evidenceRoot(home) {
103
+ // Same continuity rule as the database: evidence recorded under an older
104
+ // name keeps verifying until a new-name root exists.
105
+ return namedPath(home, ["evidence"], { dot: true });
106
+ }
107
+ /**
108
+ * Unpredictable per attempt, so nothing on disk before this build can already
109
+ * be the mailbox: a stale file, however it got there, simply has the wrong
110
+ * name. The agent learns the name from its brief and nowhere else.
111
+ */
112
+ export function mailboxName() {
113
+ return `${MAILBOX_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
114
+ }
115
+ export function looksLikeMailbox(name) {
116
+ return name.startsWith(MAILBOX_PREFIX) && name.endsWith(MAILBOX_SUFFIX);
117
+ }
118
+ /** Every protocol file the sweep must never let an old attempt leave behind.
119
+ * The pre-rename NIGHTORDERS- prefix stays recognized here — a worktree cut
120
+ * down before 2026-08-13 may still hold one, and cleanup has no vintage. */
121
+ export function looksLikeProtocolFile(name) {
122
+ return ((name.startsWith(MAILBOX_PREFIX) ||
123
+ name.startsWith(HANDOFF_PREFIX) ||
124
+ name.startsWith(PLAN_PREFIX) ||
125
+ name.startsWith(REVIEW_PREFIX) ||
126
+ name.startsWith(REPORT_PREFIX) ||
127
+ name.startsWith(PROOF_PREFIX) ||
128
+ name.startsWith(RUBRIC_PREFIX) ||
129
+ name === "STANDING-ORDERS-OBSERVATIONS.json" ||
130
+ name.startsWith(PROGRESS_PREFIX) ||
131
+ name.startsWith(PROPOSAL_PREFIX) ||
132
+ name.startsWith("NIGHTORDERS-")) &&
133
+ name.endsWith(MAILBOX_SUFFIX));
134
+ }
135
+ export function handoffName() {
136
+ return `${HANDOFF_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
137
+ }
138
+ export function planFileName() {
139
+ return `${PLAN_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
140
+ }
141
+ /** The build's optional proof manifest (Priority 2), named exactly like
142
+ * every other protocol file — a nonce the agent learns only from its brief. */
143
+ export function proofFileName() {
144
+ return `${PROOF_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
145
+ }
146
+ export function rubricFileName() {
147
+ return `${RUBRIC_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
148
+ }
149
+ export function reportFileName() {
150
+ return `${REPORT_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
151
+ }
152
+ export function progressFileName() {
153
+ return `${PROGRESS_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
154
+ }
155
+ export function proposalFileName() {
156
+ return `${PROPOSAL_PREFIX}${randomBytes(8).toString("hex")}${MAILBOX_SUFFIX}`;
157
+ }
158
+ /**
159
+ * Read a mailbox without following anything: the path must be a regular file,
160
+ * opened O_NOFOLLOW and measured through its own descriptor — an agent that
161
+ * made the pathname a symlink or FIFO gets a refusal, not a runner reading an
162
+ * unrelated local file into web-visible evidence.
163
+ */
164
+ export function readMailbox(path, cap = LIMITS.payload) {
165
+ let fd;
166
+ try {
167
+ // O_NONBLOCK matters before the type check: opening a FIFO read-only can
168
+ // otherwise wait forever for a writer, outside every provider timeout.
169
+ // Once the descriptor is open we still accept regular files only.
170
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
171
+ }
172
+ catch (error) {
173
+ const code = error.code;
174
+ return {
175
+ ok: false,
176
+ missing: code === "ENOENT",
177
+ problem: code === "ELOOP"
178
+ ? "the mailbox is a symlink, which is not a park — it is a pointer at somebody else's file"
179
+ : `cannot open the mailbox: ${code ?? String(error)}`,
180
+ };
181
+ }
182
+ try {
183
+ const stat = fstatSync(fd);
184
+ if (!stat.isFile()) {
185
+ return { ok: false, missing: false, problem: "the mailbox is not a regular file" };
186
+ }
187
+ if (stat.size > cap) {
188
+ // Oversize is invalid protocol, but it is still emitted evidence.
189
+ // Capture only the bounded prefix from this already no-follow-opened
190
+ // descriptor and carry the exact observed size so callers can retain
191
+ // an honest, capped artifact without allocating attacker-sized input.
192
+ const prefix = Buffer.alloc(cap);
193
+ let offset = 0;
194
+ while (offset < prefix.length) {
195
+ const read = readSync(fd, prefix, offset, prefix.length - offset, offset);
196
+ if (read <= 0)
197
+ break;
198
+ offset += read;
199
+ }
200
+ return {
201
+ ok: false,
202
+ missing: false,
203
+ problem: `the mailbox is over ${cap} bytes`,
204
+ raw: prefix.subarray(0, offset),
205
+ bytesOriginal: Number(stat.size),
206
+ };
207
+ }
208
+ const buffer = Buffer.alloc(Number(stat.size));
209
+ let offset = 0;
210
+ while (offset < buffer.length) {
211
+ const read = readSync(fd, buffer, offset, buffer.length - offset, offset);
212
+ if (read <= 0)
213
+ break;
214
+ offset += read;
215
+ }
216
+ return { ok: true, raw: buffer.subarray(0, offset) };
217
+ }
218
+ finally {
219
+ closeSync(fd);
220
+ }
221
+ }
222
+ export function readVerifiedReport(store, root, taskRef) {
223
+ const artifact = store.latestReportArtifact(taskRef);
224
+ if (artifact === null)
225
+ return null;
226
+ let verified;
227
+ try {
228
+ verified = readVerifiedArtifact(root, artifact);
229
+ }
230
+ catch {
231
+ return { ok: false, run: artifact.run, problem: "the report file could not be read" };
232
+ }
233
+ if (!verified.ok)
234
+ return { ok: false, run: artifact.run, problem: `the report does not verify (${verified.problem})` };
235
+ const parsed = parseReport(verified.content.toString("utf8"));
236
+ if (!parsed.ok)
237
+ return { ok: false, run: artifact.run, problem: "the stored report is not a report this build can read" };
238
+ return { ok: true, run: artifact.run, report: parsed.report };
239
+ }
240
+ /** Verifies this run's proof bytes and format. A handoff that relies on a
241
+ * revision's ancestors must also recheck its saved review-context custody. */
242
+ export function readVerifiedProofForRun(store, root, runId) {
243
+ const artifact = store.artifactsFor(runId).find(one => one.kind === "proof") ?? null;
244
+ if (artifact === null)
245
+ return null;
246
+ let verified;
247
+ try {
248
+ verified = readVerifiedArtifact(root, artifact);
249
+ }
250
+ catch {
251
+ return { ok: false, run: runId, problem: "the proof file could not be read" };
252
+ }
253
+ if (!verified.ok)
254
+ return { ok: false, run: runId, problem: `the proof does not verify (${verified.problem})` };
255
+ const parsed = parseProof(verified.content.toString("utf8"));
256
+ if (!parsed.ok)
257
+ return { ok: false, run: runId, problem: "the stored proof is not a proof this build can read" };
258
+ return { ok: true, run: runId, proof: parsed.proof };
259
+ }
260
+ /**
261
+ * The PNG and JPEG magic bytes — the only two formats a screenshot may
262
+ * claim to be (scope: "no arbitrary binary evidence beyond bounded
263
+ * PNG/JPEG screenshots"). Sniffed from the bytes themselves, never from a
264
+ * claimed extension: an extension is a string an agent chose, a signature
265
+ * is not.
266
+ */
267
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
268
+ const JPEG_SIGNATURE = Buffer.from([0xff, 0xd8, 0xff]);
269
+ export function sniffImageKind(bytes) {
270
+ if (bytes.length >= PNG_SIGNATURE.length && bytes.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE))
271
+ return "png";
272
+ if (bytes.length >= JPEG_SIGNATURE.length && bytes.subarray(0, JPEG_SIGNATURE.length).equals(JPEG_SIGNATURE))
273
+ return "jpeg";
274
+ return null;
275
+ }
276
+ /** Whether bytes claimed as a screenshot actually are a bounded PNG or
277
+ * JPEG — the one check standing between a claimed path and stored,
278
+ * immutable image evidence. */
279
+ export function validateScreenshotBytes(bytes) {
280
+ if (bytes.length === 0)
281
+ return { ok: false, problem: "the file is empty" };
282
+ if (bytes.length > SCREENSHOT_BYTE_CAP) {
283
+ return { ok: false, problem: `the file is over ${SCREENSHOT_BYTE_CAP} bytes` };
284
+ }
285
+ const kind = sniffImageKind(bytes);
286
+ if (kind === null)
287
+ return { ok: false, problem: "the file is not a PNG or JPEG (checked by signature, not extension)" };
288
+ return { ok: true, kind };
289
+ }
290
+ /**
291
+ * Pixel dimensions read straight from the format's own header — never
292
+ * decoded, never trusting a claimed size (v39, screenshot evidence:
293
+ * "meaningful byte size and at least 320 by 200 dimensions"). `null` when
294
+ * the bytes are a valid signature but the header could not be read (a
295
+ * genuinely corrupt or truncated file) — the caller treats that the same
296
+ * as failing the size floor, never as passing it.
297
+ *
298
+ * PNG: the IHDR chunk is fixed at bytes 8-25 for every valid PNG — width
299
+ * and height are the two big-endian uint32s right after it starts.
300
+ *
301
+ * JPEG: scan markers from byte 2. Every marker except the two without a
302
+ * payload (SOI 0xD8, EOI 0xD9) and the RST markers (0xD0-0xD7) carries a
303
+ * two-byte big-endian length; the SOF markers (0xC0-0xCF, excluding the
304
+ * DHT/JPG/DAC markers 0xC4/0xC8/0xCC, which share the range but are not
305
+ * frame headers) hold precision(1) + height(2) + width(2) right after
306
+ * that length field.
307
+ */
308
+ export function imageDimensions(bytes, kind) {
309
+ if (kind === "png") {
310
+ if (bytes.length < 26)
311
+ return null;
312
+ // Bytes 12-15 must literally spell IHDR, or this is not a PNG this
313
+ // reader understands well enough to trust the offsets that follow.
314
+ if (bytes.toString("ascii", 12, 16) !== "IHDR")
315
+ return null;
316
+ const width = bytes.readUInt32BE(16);
317
+ const height = bytes.readUInt32BE(20);
318
+ if (width <= 0 || height <= 0)
319
+ return null;
320
+ return { width, height };
321
+ }
322
+ // JPEG: walk markers looking for a Start Of Frame.
323
+ let offset = 2;
324
+ const SOF_MARKERS = new Set([0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf]);
325
+ while (offset + 1 < bytes.length) {
326
+ if (bytes[offset] !== 0xff)
327
+ return null; // not a marker boundary — malformed
328
+ let marker = bytes[offset + 1];
329
+ offset += 2;
330
+ // Fill bytes (0xFF padding) between markers.
331
+ while (marker === 0xff && offset < bytes.length) {
332
+ marker = bytes[offset];
333
+ offset += 1;
334
+ }
335
+ if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7))
336
+ continue; // no payload
337
+ if (offset + 1 >= bytes.length)
338
+ return null;
339
+ const length = bytes.readUInt16BE(offset);
340
+ if (SOF_MARKERS.has(marker)) {
341
+ if (offset + 7 >= bytes.length)
342
+ return null;
343
+ const height = bytes.readUInt16BE(offset + 3);
344
+ const width = bytes.readUInt16BE(offset + 5);
345
+ if (width <= 0 || height <= 0)
346
+ return null;
347
+ return { width, height };
348
+ }
349
+ if (marker === 0xda)
350
+ return null; // Start Of Scan — image data follows, no SOF was found first
351
+ offset += length;
352
+ }
353
+ return null;
354
+ }
355
+ /**
356
+ * Write one evidence file and record it. The row is written through the
357
+ * store the caller passed — inside whatever transaction the caller holds.
358
+ */
359
+ export function storeEvidence(store, root, runId, kind, name, content, capture, now, options = {}) {
360
+ const cap = EVIDENCE_CAPS[kind];
361
+ const stored = content.subarray(0, cap);
362
+ // Some callers that deliberately summarize/bound a source before storage
363
+ // may still report its true original size. This keeps `truncated` honest
364
+ // even when the already-bounded representation fits beneath this layer's
365
+ // cap (for example a three-attempt verification log).
366
+ const bytesOriginal = Math.max(content.length, options.sourceBytesOriginal ?? content.length);
367
+ const key = writeEvidenceFile(root, runId, name, stored);
368
+ return store.saveArtifact({
369
+ run: runId,
370
+ kind,
371
+ key,
372
+ bytesOriginal,
373
+ bytesStored: stored.length,
374
+ truncated: bytesOriginal > stored.length,
375
+ sha256: createHash("sha256").update(stored).digest("hex"),
376
+ capture,
377
+ ...(options.redacted === true ? { redacted: true } : {}),
378
+ ...(options.captureStatus === undefined ? {} : { captureStatus: options.captureStatus }),
379
+ }, now);
380
+ }
381
+ /**
382
+ * Read an artifact's bytes back, believing nothing until it is proved on the
383
+ * descriptor actually read (§7's honesty, applied to serving). The record's
384
+ * key must be a normalized relative path; the resolved path must live under
385
+ * the root; the open refuses symlinks; the size and hash checks run against
386
+ * the same descriptor the bytes come from — so a pathname swapped between a
387
+ * check and the read buys an attacker nothing. Only a buffer whose SHA-256
388
+ * matches the row is ever returned: unverified bytes never leave this
389
+ * function, which is what lets a caller stream with a clear conscience.
390
+ * Success proves stored-byte integrity only. Callers must independently
391
+ * decide whether a shortened representation or failed capture is usable;
392
+ * a verified failure log is still a failure, not successful evidence.
393
+ */
394
+ // Recovery uses this read boundary for retained receipts and handoffs: inspecting
395
+ // bytes must never reseal a missing artifact or turn a saved failure into a pass.
396
+ export function readVerifiedArtifact(root, artifact) {
397
+ const segments = artifact.key.split("/");
398
+ const wellFormed = segments.length > 0 &&
399
+ segments.every(segment => segment.length > 0 &&
400
+ segment !== "." &&
401
+ segment !== ".." &&
402
+ !segment.includes("\\") &&
403
+ // eslint-disable-next-line no-control-regex
404
+ !/[\u0000-\u001f\u007f:]/.test(segment));
405
+ if (!wellFormed)
406
+ return { ok: false, problem: "the key is not a normalized relative path" };
407
+ const cap = EVIDENCE_CAPS[artifact.kind];
408
+ if (artifact.bytesStored > cap) {
409
+ return { ok: false, problem: "the record claims more bytes than its kind may store" };
410
+ }
411
+ let resolved;
412
+ try {
413
+ resolved = realpathSync(join(root, ...segments));
414
+ const rootReal = realpathSync(root);
415
+ if (!resolved.startsWith(rootReal + sep)) {
416
+ return { ok: false, problem: "the file resolves outside the evidence root" };
417
+ }
418
+ }
419
+ catch {
420
+ if (existsSync(join(root, segments[0], RETENTION_NOTE)))
421
+ return { ok: false, problem: "the file was removed by the retention setting" };
422
+ return { ok: false, problem: "the file is gone or unresolvable" };
423
+ }
424
+ let fd;
425
+ try {
426
+ fd = openSync(resolved, constants.O_RDONLY | constants.O_NOFOLLOW);
427
+ }
428
+ catch {
429
+ return { ok: false, problem: "cannot open the file without following a link" };
430
+ }
431
+ try {
432
+ const stat = fstatSync(fd);
433
+ if (!stat.isFile())
434
+ return { ok: false, problem: "not a regular file" };
435
+ if (Number(stat.size) !== artifact.bytesStored) {
436
+ return { ok: false, problem: "the file's size no longer matches its record" };
437
+ }
438
+ const buffer = Buffer.alloc(artifact.bytesStored);
439
+ let offset = 0;
440
+ while (offset < buffer.length) {
441
+ const read = readSync(fd, buffer, offset, buffer.length - offset, offset);
442
+ if (read <= 0)
443
+ break;
444
+ offset += read;
445
+ }
446
+ if (offset !== buffer.length)
447
+ return { ok: false, problem: "the file ended early" };
448
+ const digest = createHash("sha256").update(buffer).digest("hex");
449
+ if (digest !== artifact.sha256) {
450
+ return { ok: false, problem: "the file no longer hashes to its record" };
451
+ }
452
+ return { ok: true, content: buffer };
453
+ }
454
+ finally {
455
+ closeSync(fd);
456
+ }
457
+ }
458
+ /** The file alone, no row — quarantines use this; they belong to no live run. */
459
+ export function writeEvidenceFile(root, runId, name, content) {
460
+ const dir = join(root, String(runId));
461
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
462
+ writeFileSync(join(dir, name), content, { flag: "wx", mode: 0o600 });
463
+ return `${runId}/${name}`;
464
+ }
465
+ /**
466
+ * Sweep every park-shaped file out of a worktree before the agent runs.
467
+ *
468
+ * A mailbox left by a cut-down attempt is never ingested as a decision — the
469
+ * lease that could have vouched for it is gone — but its bytes may still say
470
+ * what the agent meant, so what can be read safely is kept under quarantine
471
+ * in the current run's evidence directory. What cannot be read safely (a
472
+ * symlink, a FIFO, something oversized) is removed unread.
473
+ */
474
+ export function quarantineMailboxes(worktree, root, runId) {
475
+ let entries;
476
+ try {
477
+ entries = readdirSync(worktree);
478
+ }
479
+ catch {
480
+ return [];
481
+ }
482
+ const swept = [];
483
+ for (const name of entries) {
484
+ if (!looksLikeProtocolFile(name))
485
+ continue;
486
+ const path = join(worktree, name);
487
+ if (root !== null && runId !== null) {
488
+ const read = readMailbox(path);
489
+ if (read.ok) {
490
+ try {
491
+ writeEvidenceFile(root, runId, `quarantine-${randomBytes(4).toString("hex")}-${name}`, read.raw);
492
+ }
493
+ catch {
494
+ // Quarantine is best-effort; removal below is not.
495
+ }
496
+ }
497
+ }
498
+ try {
499
+ unlinkSync(path);
500
+ swept.push(name);
501
+ }
502
+ catch {
503
+ // A mailbox that cannot be removed will fail the freshness check at
504
+ // ingestion anyway: it does not carry this attempt's nonce.
505
+ }
506
+ }
507
+ return swept;
508
+ }
509
+ /**
510
+ * Capture the park's evidence: the complete working state against the base
511
+ * revision, and the porcelain inventory that names what the diff cannot —
512
+ * untracked and staged files included. External diff drivers and textconv
513
+ * are disabled by name: both run repository-configured commands, and this
514
+ * capture must not execute anything the agent may have just written.
515
+ */
516
+ export async function captureParkEvidence(store, git, worktree, baseRevision, root, runId, now) {
517
+ const captures = [
518
+ {
519
+ kind: "diff",
520
+ name: "diff.patch",
521
+ args: baseRevision === null
522
+ ? ["--no-optional-locks", "diff", "--no-ext-diff", "--no-textconv", "--no-color"]
523
+ : ["--no-optional-locks", "diff", "--no-ext-diff", "--no-textconv", "--no-color", baseRevision, "--", "."],
524
+ },
525
+ {
526
+ kind: "status",
527
+ name: "status.txt",
528
+ args: ["--no-optional-locks", "status", "--porcelain=v2", "-z", "--untracked-files=all"],
529
+ },
530
+ ];
531
+ const ids = [];
532
+ for (const { kind, name, args } of captures) {
533
+ const result = await git("git", args, { cwd: worktree });
534
+ const command = `git ${args.filter(a => a !== "--no-optional-locks").join(" ")} (exit ${result.code})`;
535
+ // A failed capture is stored as the failure it is — stderr under the
536
+ // same key, labeled by its exit code — never silently skipped, because
537
+ // "no diff shown" and "diff capture failed" read very differently at 7am.
538
+ const content = Buffer.from(result.code === 0 ? result.stdout : result.stderr, "utf8");
539
+ ids.push(storeEvidence(store, root, runId, kind, name, content, command, now));
540
+ }
541
+ return ids;
542
+ }
543
+ const DIFF_STAT_FILE_LIMIT = 400;
544
+ /**
545
+ * Parse `git diff --numstat -z` output. NUL-delimited, so filenames are never
546
+ * guessed out of patch headers (Codex roadmap review, scope cut A.3): plain
547
+ * entries are one token "adds\tdels\tpath"; renames are "adds\tdels\t" with
548
+ * the two paths as the following tokens. Binary files carry "-" counts,
549
+ * recorded as null — never coerced to zero.
550
+ */
551
+ export function parseNumstat(raw, base, head) {
552
+ const tokens = raw.split("\u0000").filter(token => token.length > 0);
553
+ const files = [];
554
+ let additions = 0;
555
+ let deletions = 0;
556
+ let binaryCount = 0;
557
+ for (let i = 0; i < tokens.length; i += 1) {
558
+ const token = tokens[i];
559
+ const parts = token.split("\t");
560
+ if (parts.length < 3)
561
+ continue; // not a numstat entry this parser knows
562
+ const adds = parts[0] === "-" ? null : Number(parts[0]);
563
+ const dels = parts[1] === "-" ? null : Number(parts[1]);
564
+ if ((adds !== null && !Number.isFinite(adds)) || (dels !== null && !Number.isFinite(dels)))
565
+ continue;
566
+ let path = parts.slice(2).join("\t");
567
+ let renamedFrom;
568
+ if (path === "") {
569
+ // Rename: the next two tokens are the old and new paths.
570
+ renamedFrom = tokens[i + 1];
571
+ path = tokens[i + 2] ?? "";
572
+ i += 2;
573
+ if (path === "")
574
+ continue;
575
+ }
576
+ if (adds === null || dels === null)
577
+ binaryCount += 1;
578
+ additions += adds ?? 0;
579
+ deletions += dels ?? 0;
580
+ files.push({ path, additions: adds, deletions: dels, ...(renamedFrom === undefined ? {} : { renamedFrom }) });
581
+ }
582
+ const kept = files.slice(0, DIFF_STAT_FILE_LIMIT);
583
+ return {
584
+ schema: 1,
585
+ base,
586
+ head,
587
+ fileCount: files.length,
588
+ additions,
589
+ deletions,
590
+ binaryCount,
591
+ files: kept,
592
+ filesTruncated: kept.length < files.length,
593
+ };
594
+ }
595
+ /**
596
+ * Write the handoff artifact (M6.10): the machine's own statement of where
597
+ * a finished run left the world — workspace identity, exact base and head,
598
+ * which answered decisions the brief carried, and a freshness stamp a
599
+ * successor can PROVE against the branch before spending a token on stale
600
+ * context. This is what makes a cold takeover context-bearing rather than
601
+ * repo-only; a provider session is memory, not a freshness proof.
602
+ */
603
+ export function storeHandoffArtifact(store, root, payload, now) {
604
+ return storeEvidence(store, root, payload.runId, "handoff", "handoff.json", Buffer.from(JSON.stringify(payload, null, 2), "utf8"), "machine-authored handoff (exit 0)", now);
605
+ }
606
+ /**
607
+ * High-confidence secret shapes ONLY (audit IV-7): a detector that cries
608
+ * wolf trains people to approve wolves. Each pattern is a format no
609
+ * ordinary source line matches by accident; generic "password=" shapes
610
+ * are deliberately absent — test fixtures would drown the signal.
611
+ */
612
+ export const SECRET_PATTERNS = [
613
+ { name: "private-key", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
614
+ { name: "aws-access-key", pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/ },
615
+ { name: "github-token", pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}\b|github_pat_[A-Za-z0-9_]{22,}/ },
616
+ { name: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
617
+ { name: "npm-auth-token", pattern: /_authToken\s*=\s*[^\s$][^\s]*/ },
618
+ { name: "openai-key", pattern: /\bsk-[A-Za-z0-9_-]{32,}\b/ },
619
+ // v99: the other keys a team pastes most.
620
+ { name: "stripe-key", pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b|\bwhsec_[A-Za-z0-9]{24,}\b/ },
621
+ { name: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/ },
622
+ { name: "gitlab-token", pattern: /\bglpat-[A-Za-z0-9_-]{20,}\b/ },
623
+ { name: "telegram-bot-token", pattern: /\b\d{8,10}:AA[A-Za-z0-9_-]{33}\b/ },
624
+ { name: "discord-bot-token", pattern: /\b[MN][A-Za-z\d]{23,25}\.[\w-]{6}\.[\w-]{27,}\b/ },
625
+ { name: "password-in-url", pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:@/]+:[^\s:@/]{3,}@[^\s/]/i },
626
+ ];
627
+ /** Scan text line by line; every hit names its pattern and line. */
628
+ export function scanForSecrets(text) {
629
+ const hits = [];
630
+ const lines = text.split("\n");
631
+ for (let index = 0; index < lines.length; index += 1) {
632
+ for (const { name, pattern } of SECRET_PATTERNS) {
633
+ if (pattern.test(lines[index])) {
634
+ hits.push({ name, line: index + 1 });
635
+ break;
636
+ }
637
+ }
638
+ }
639
+ return hits;
640
+ }
641
+ /** Replace every hit line with a marker naming what was found — the display copy holds no secret. */
642
+ export function redactSecretLines(text, hits) {
643
+ const flagged = new Map(hits.map(hit => [hit.line, hit.name]));
644
+ return text
645
+ .split("\n")
646
+ .map((line, index) => {
647
+ const name = flagged.get(index + 1);
648
+ return name === undefined ? line : `[redacted: ${name} detected on this line]`;
649
+ })
650
+ .join("\n");
651
+ }
652
+ /**
653
+ * Fit a DiffStat under its cap by shedding FILES, never bytes (audit C-4):
654
+ * a structured artifact cut mid-token is not a smaller stat, it is no stat.
655
+ * Aggregate counts always survive; the file list shrinks until the encoded
656
+ * JSON fits, and filesTruncated says so.
657
+ */
658
+ export function budgetedStatJson(stat) {
659
+ const cap = EVIDENCE_CAPS["diff-stat"];
660
+ let files = stat.files;
661
+ for (;;) {
662
+ const candidate = {
663
+ ...stat,
664
+ files,
665
+ filesTruncated: stat.filesTruncated || files.length < stat.files.length,
666
+ };
667
+ const encoded = Buffer.from(JSON.stringify(candidate), "utf8");
668
+ if (encoded.length <= cap || files.length === 0)
669
+ return encoded;
670
+ files = files.slice(0, Math.floor(files.length / 2));
671
+ }
672
+ }
673
+ /**
674
+ * Capture the TERMINAL diff — the immutable base→head patch of a run that
675
+ * committed, taken before its worktree is released (M5.3; the Codex scope
676
+ * cut is the spec). Two artifacts: the bounded plain patch, and a
677
+ * machine-parsed stat whose filenames came NUL-delimited from git, never
678
+ * from patch headers. Same execution posture as the park capture: external
679
+ * diff drivers and textconv disabled by name, because this must not run
680
+ * anything the agent may have just written. A no-change run stores the
681
+ * explicit zero-stat rather than storing nothing — "no diff" and "capture
682
+ * failed" have to read differently.
683
+ */
684
+ export async function captureTerminalDiff(store, git, worktree, base, head, root, runId, now) {
685
+ const patchArgs = ["--no-optional-locks", "diff", "--no-ext-diff", "--no-textconv", "--no-color", base, head];
686
+ const patch = await git("git", patchArgs, { cwd: worktree });
687
+ const patchCommand = `git ${patchArgs.filter(a => a !== "--no-optional-locks").join(" ")} (exit ${patch.code})`;
688
+ // The secret scan (audit IV-7): a committed credential must not gain a
689
+ // SECOND durable, console-served copy. Hit lines are redacted in the
690
+ // stored artifact, the row says redacted, a page names the branch — and
691
+ // the publication gate refuses to push a redacted run's branch anywhere.
692
+ const rawPatch = patch.code === 0 ? patch.stdout : patch.stderr;
693
+ const hits = patch.code === 0 ? scanForSecrets(rawPatch) : [];
694
+ const patchContent = Buffer.from(hits.length > 0 ? redactSecretLines(rawPatch, hits) : rawPatch, "utf8");
695
+ const diffId = storeEvidence(store, root, runId, "terminal-diff", "terminal-diff.patch", patchContent, patchCommand, now, {
696
+ redacted: hits.length > 0,
697
+ captureStatus: patch.code === 0 ? "ok" : "failed",
698
+ });
699
+ if (hits.length > 0) {
700
+ store.enqueueNotification({
701
+ source: { run: runId },
702
+ dedupeKey: `secret:${runId}`,
703
+ kind: "secret-detected",
704
+ pushClass: "attention",
705
+ link: `/r/${runId}`,
706
+ subject: `possible committed secret: run #${runId}`,
707
+ body: `${hits.length} high-confidence secret shape(s) in the accepted diff (${[...new Set(hits.map(one => one.name))].join(", ")}). ` +
708
+ `The branch holds the real bytes — rewrite it before anything merges. Publication of this run is blocked.`,
709
+ }, now);
710
+ }
711
+ const statArgs = ["--no-optional-locks", "diff", "--numstat", "-z", base, head];
712
+ const stat = await git("git", statArgs, { cwd: worktree });
713
+ const statCommand = `git ${statArgs.filter(a => a !== "--no-optional-locks").join(" ")} (exit ${stat.code})`;
714
+ const statContent = stat.code === 0
715
+ ? budgetedStatJson(parseNumstat(stat.stdout, base, head))
716
+ : Buffer.from(stat.stderr, "utf8");
717
+ const statId = storeEvidence(store, root, runId, "diff-stat", "terminal-diff-stat.json", statContent, statCommand, now, {
718
+ captureStatus: stat.code === 0 ? "ok" : "failed",
719
+ });
720
+ return { diffId, statId };
721
+ }
722
+ /**
723
+ * The live peek's base snapshot (live-peek v3 §1), captured in the same
724
+ * pre-spawn window that computed `base` itself — one `ls-tree` against the
725
+ * project clone (NEVER a worktree), lazy-fetch and replace-refs disabled
726
+ * on argv, the child's environment reduced to an allowlist that contains
727
+ * no GIT_* at all. The parsed listing is round-tripped through the strict
728
+ * snapshot codec: any entry the codec refuses — including paths the UTF-8
729
+ * decode mangled into U+FFFD — refuses the WHOLE snapshot, recorded as a
730
+ * failed capture. Integrity class: the database's own, claimed as nothing
731
+ * more (round-3 finding 42).
732
+ */
733
+ export async function captureBaseTree(store, git, repoRoot, repo, base, root, runId, now) {
734
+ // Fail-soft throughout: a snapshot that cannot capture disables the live
735
+ // peek for this run and NOTHING else — the build must proceed untouched,
736
+ // and an invalid run refuses later at the invocation gateway, typed.
737
+ try {
738
+ return await captureBaseTreeInner(store, git, repoRoot, repo, base, root, runId, now);
739
+ }
740
+ catch {
741
+ return { ok: false };
742
+ }
743
+ }
744
+ async function captureBaseTreeInner(store, git, repoRoot, repo, base, root, runId, now) {
745
+ const args = ["--no-lazy-fetch", "--no-replace-objects", "--no-optional-locks", "ls-tree", "-r", "-l", "-z", base];
746
+ const listed = await git("git", args, { cwd: repoRoot, envAllowlist: ["PATH", "HOME"] });
747
+ const command = `git ls-tree -r -l -z ${base} (exit ${listed.code})`;
748
+ const refuse = () => {
749
+ storeEvidence(store, root, runId, "base-tree", "base-tree.json", Buffer.alloc(0), command, now, {
750
+ captureStatus: "failed",
751
+ });
752
+ return { ok: false };
753
+ };
754
+ if (listed.code !== 0)
755
+ return refuse();
756
+ const entries = [];
757
+ for (const record of listed.stdout.split("\u0000")) {
758
+ if (record === "")
759
+ continue;
760
+ // "<mode> <type> <sha> <size>\t<path>" — size is "-" for gitlinks.
761
+ const tab = record.indexOf("\t");
762
+ if (tab < 0)
763
+ return refuse();
764
+ const head = record.slice(0, tab).trim().split(/\s+/);
765
+ const path = record.slice(tab + 1);
766
+ const [mode, , sha, sizeText] = head;
767
+ if (head.length !== 4 || mode === undefined || sha === undefined || sizeText === undefined)
768
+ return refuse();
769
+ const size = sizeText === "-" ? 0 : Number(sizeText);
770
+ entries.push({ path, mode, sha, size });
771
+ if (entries.length > 20_000)
772
+ return refuse();
773
+ }
774
+ const encoded = encodeBaseTreeSnapshot({ repo, run: runId, base, entries });
775
+ // The strict codec is the gate: what it cannot re-read, nothing stores.
776
+ if (parseBaseTreeSnapshot(encoded) === null)
777
+ return refuse();
778
+ storeEvidence(store, root, runId, "base-tree", "base-tree.json", Buffer.from(encoded, "utf8"), command, now, {
779
+ captureStatus: "ok",
780
+ });
781
+ return { ok: true };
782
+ }