firedrill-run 2.2.5 → 3.0.1

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 (392) hide show
  1. package/README.md +176 -22
  2. package/bin/firedrill.mjs +29 -5
  3. package/dist/assembly.json +13 -0
  4. package/dist/cli/runtime/candidate_copy.py +395 -0
  5. package/dist/cli/runtime/source_snapshot.py +391 -0
  6. package/dist/cli/src/administration.js +295 -0
  7. package/dist/cli/src/agent-changes.js +496 -0
  8. package/dist/cli/src/assessment-requests.js +67 -0
  9. package/dist/cli/src/auth.js +506 -0
  10. package/dist/cli/src/billing-commerce-contracts.js +1 -0
  11. package/dist/cli/src/billing-usage.js +227 -0
  12. package/dist/cli/src/billing-wallet-contracts.js +1 -0
  13. package/dist/cli/src/billing.js +170 -0
  14. package/dist/cli/src/bootstrap-process-lock.js +136 -0
  15. package/dist/cli/src/browser-links.js +14 -0
  16. package/dist/cli/src/campaign-controls.js +122 -0
  17. package/dist/cli/src/campaign-diagnoses.js +118 -0
  18. package/dist/cli/src/campaign-reports.js +1215 -0
  19. package/dist/cli/src/campaign-world.js +156 -0
  20. package/dist/cli/src/candidate-copy.js +376 -0
  21. package/dist/cli/src/catalog-contracts.js +1 -0
  22. package/dist/cli/src/ci-access-contracts.js +1 -0
  23. package/dist/cli/src/ci-configuration-contracts.js +1 -0
  24. package/dist/cli/src/ci-coverage.js +64 -0
  25. package/dist/cli/src/ci-delivery-contracts.js +1 -0
  26. package/dist/cli/src/ci-result-contracts.js +1 -0
  27. package/dist/cli/src/ci-run-contracts.js +1 -0
  28. package/dist/cli/src/ci-setup-proposals.js +52 -0
  29. package/dist/cli/src/company-policies.js +229 -0
  30. package/dist/cli/src/completion.js +79 -0
  31. package/dist/cli/src/configuration-intents.js +277 -0
  32. package/dist/cli/src/configuration.js +369 -0
  33. package/dist/cli/src/continuous-simulation.js +277 -0
  34. package/dist/cli/src/coordinated-checkpoints.js +133 -0
  35. package/dist/cli/src/credential-mode.js +139 -0
  36. package/dist/cli/src/credentials.js +331 -0
  37. package/dist/cli/src/credit-format.js +8 -0
  38. package/dist/cli/src/dataset-contracts.js +1 -0
  39. package/dist/cli/src/dataset-export.js +154 -0
  40. package/dist/cli/src/dataset-reads.js +37 -0
  41. package/dist/cli/src/dependency-preparation.js +239 -0
  42. package/dist/cli/src/director-tool-setup.js +104 -0
  43. package/dist/cli/src/driver-review.js +225 -0
  44. package/dist/cli/src/evaluation-authoring.js +315 -0
  45. package/dist/cli/src/evidence-bundles.js +367 -0
  46. package/dist/cli/src/execution-proposals.js +36 -0
  47. package/dist/cli/src/http.js +302 -0
  48. package/dist/cli/src/local-container-boundary.js +38 -0
  49. package/dist/cli/src/local-container-policy.js +201 -0
  50. package/dist/cli/src/local-execution-setup.js +81 -0
  51. package/dist/cli/src/local-source-basis.js +73 -0
  52. package/dist/cli/src/local-source.js +66 -0
  53. package/dist/cli/src/local-value-store.js +91 -0
  54. package/dist/cli/src/local-values.js +163 -0
  55. package/dist/cli/src/login-organizations.js +70 -0
  56. package/dist/cli/src/main.js +1627 -0
  57. package/dist/cli/src/mcp-connection.js +109 -0
  58. package/dist/cli/src/native/bindings.js +137 -0
  59. package/dist/cli/src/native/canonical.js +370 -0
  60. package/dist/cli/src/native/cloud-resources.js +942 -0
  61. package/dist/cli/src/native/companion-channel.js +421 -0
  62. package/dist/cli/src/native/companion-ipc.js +473 -0
  63. package/dist/cli/src/native/contracts.js +218 -0
  64. package/dist/cli/src/native/control-plane.js +1354 -0
  65. package/dist/cli/src/native/credit-approval.js +15 -0
  66. package/dist/cli/src/native/dependency-proxy.js +1006 -0
  67. package/dist/cli/src/native/drill-cleanup.js +115 -0
  68. package/dist/cli/src/native/drill-runner.js +1562 -0
  69. package/dist/cli/src/native/drivers/action-trace.js +210 -0
  70. package/dist/cli/src/native/drivers/function-wrapper.js +152 -0
  71. package/dist/cli/src/native/drivers/http.js +442 -0
  72. package/dist/cli/src/native/drivers/index.js +47 -0
  73. package/dist/cli/src/native/drivers/jsonl-process.js +538 -0
  74. package/dist/cli/src/native/drivers/playwright.js +600 -0
  75. package/dist/cli/src/native/drivers/signed-event.js +244 -0
  76. package/dist/cli/src/native/drivers/types.js +578 -0
  77. package/dist/cli/src/native/evidence-filter.js +255 -0
  78. package/dist/cli/src/native/isolated-access.js +241 -0
  79. package/dist/cli/src/native/library-sharing.js +220 -0
  80. package/dist/cli/src/native/local-run.js +667 -0
  81. package/dist/cli/src/native/local-secrets.js +19 -0
  82. package/dist/cli/src/native/mcp-config.js +123 -0
  83. package/dist/cli/src/native/metadata-requests.js +120 -0
  84. package/dist/cli/src/native/observations.js +1159 -0
  85. package/dist/cli/src/native/owned-groups.js +46 -0
  86. package/dist/cli/src/native/plan.js +594 -0
  87. package/dist/cli/src/native/png.js +159 -0
  88. package/dist/cli/src/native/ports.js +43 -0
  89. package/dist/cli/src/native/readiness.js +139 -0
  90. package/dist/cli/src/native/reassessments.js +206 -0
  91. package/dist/cli/src/native/redaction.js +235 -0
  92. package/dist/cli/src/native/registration.js +155 -0
  93. package/dist/cli/src/native/roots.js +110 -0
  94. package/dist/cli/src/native/run-cleanup.js +199 -0
  95. package/dist/cli/src/native/scratch.js +117 -0
  96. package/dist/cli/src/native/screenshots.js +60 -0
  97. package/dist/cli/src/native/session.js +556 -0
  98. package/dist/cli/src/native/standalone-proxy.js +580 -0
  99. package/dist/cli/src/native/standalone-renewals.js +183 -0
  100. package/dist/cli/src/native/standalone-state.js +193 -0
  101. package/dist/cli/src/native/standalone-tools.js +187 -0
  102. package/dist/cli/src/native/supervisor.js +831 -0
  103. package/dist/cli/src/native/target-auth.js +161 -0
  104. package/dist/cli/src/native/target-state.js +330 -0
  105. package/dist/cli/src/native/tool-reuse.js +364 -0
  106. package/dist/cli/src/native/tool-setups.js +336 -0
  107. package/dist/cli/src/native/world-reuse.js +288 -0
  108. package/dist/cli/src/onboarding.js +23 -0
  109. package/dist/cli/src/priority-requests.js +70 -0
  110. package/dist/cli/src/priority.js +223 -0
  111. package/dist/cli/src/prompts.js +184 -0
  112. package/dist/cli/src/real-operations.js +221 -0
  113. package/dist/cli/src/recipe-intents.js +58 -0
  114. package/dist/cli/src/recorded-context-intents.js +59 -0
  115. package/dist/cli/src/recorded-context.js +248 -0
  116. package/dist/cli/src/recordings.js +69 -0
  117. package/dist/cli/src/registration-guide.js +39 -0
  118. package/dist/cli/src/report-exports.js +213 -0
  119. package/dist/cli/src/request-input.js +47 -0
  120. package/dist/cli/src/resource-lifecycle.js +141 -0
  121. package/dist/cli/src/revision-requests.js +55 -0
  122. package/dist/cli/src/runner-abandon.js +53 -0
  123. package/dist/cli/src/runner-campaign-controls.js +262 -0
  124. package/dist/cli/src/runner-claim-reads.js +36 -0
  125. package/dist/cli/src/runner-completion.js +65 -0
  126. package/dist/cli/src/runner-configuration.js +546 -0
  127. package/dist/cli/src/runner-container.js +794 -0
  128. package/dist/cli/src/runner-evidence-erasure.js +351 -0
  129. package/dist/cli/src/runner-evidence-privacy.js +37 -0
  130. package/dist/cli/src/runner-evidence-retention.js +161 -0
  131. package/dist/cli/src/runner-evidence.js +326 -0
  132. package/dist/cli/src/runner-execute.js +92 -0
  133. package/dist/cli/src/runner-execution-deadline.js +25 -0
  134. package/dist/cli/src/runner-grants.js +307 -0
  135. package/dist/cli/src/runner-identity.js +615 -0
  136. package/dist/cli/src/runner-inputs.js +308 -0
  137. package/dist/cli/src/runner-jobs.js +49 -0
  138. package/dist/cli/src/runner-model-transport.js +392 -0
  139. package/dist/cli/src/runner-ownership.js +332 -0
  140. package/dist/cli/src/runner-process-lock.js +175 -0
  141. package/dist/cli/src/runner-runtime.js +396 -0
  142. package/dist/cli/src/runner-session-channel.js +377 -0
  143. package/dist/cli/src/runner-trial-completion.js +34 -0
  144. package/dist/cli/src/runner-trial-journal.js +358 -0
  145. package/dist/cli/src/runner-trial-live.js +393 -0
  146. package/dist/cli/src/runner-trial-protocol.js +289 -0
  147. package/dist/cli/src/runner-trial-sync.js +110 -0
  148. package/dist/cli/src/runner-trial-transfer.js +107 -0
  149. package/dist/cli/src/runner-voice.js +40 -0
  150. package/dist/cli/src/runner-world-claim.js +21 -0
  151. package/dist/cli/src/runner-world-collector.js +159 -0
  152. package/dist/cli/src/runner-world-command-evidence.js +487 -0
  153. package/dist/cli/src/runner-world-commands.js +268 -0
  154. package/dist/cli/src/runner-world-completion.js +76 -0
  155. package/dist/cli/src/runner-world-erasure.js +95 -0
  156. package/dist/cli/src/runner-world-evidence-sync.js +91 -0
  157. package/dist/cli/src/runner-world-journal.js +510 -0
  158. package/dist/cli/src/runner-world-native-filters.js +134 -0
  159. package/dist/cli/src/runner-world-observations.js +50 -0
  160. package/dist/cli/src/runner-world-session-protocol.js +134 -0
  161. package/dist/cli/src/saved-records.js +40 -0
  162. package/dist/cli/src/scenario-authoring.js +212 -0
  163. package/dist/cli/src/scenario-discovery.js +216 -0
  164. package/dist/cli/src/scenario-paths.js +68 -0
  165. package/dist/cli/src/scenario-requests.js +4 -0
  166. package/dist/cli/src/service-receipts.js +105 -0
  167. package/dist/cli/src/simple/authoring.js +270 -0
  168. package/dist/cli/src/simple/credit-approval.js +16 -0
  169. package/dist/cli/src/simple/help.js +98 -0
  170. package/dist/cli/src/simple/init.js +96 -0
  171. package/dist/cli/src/simple/library-sharing.js +414 -0
  172. package/dist/cli/src/simple/metadata-requests.js +46 -0
  173. package/dist/cli/src/simple/native-report.js +841 -0
  174. package/dist/cli/src/simple/native-tools.js +780 -0
  175. package/dist/cli/src/simple/plan-apply.js +506 -0
  176. package/dist/cli/src/simple/reassessment.js +323 -0
  177. package/dist/cli/src/simple/register.js +123 -0
  178. package/dist/cli/src/simple/run.js +122 -0
  179. package/dist/cli/src/simple/status.js +49 -0
  180. package/dist/cli/src/simple/tool-renewal.js +308 -0
  181. package/dist/cli/src/simple/tool-reuse.js +526 -0
  182. package/dist/cli/src/simple/tool-start-catalog.js +100 -0
  183. package/dist/cli/src/simple/tool-state.js +426 -0
  184. package/dist/cli/src/simple/tools-author.js +222 -0
  185. package/dist/cli/src/simple/watch.js +28 -0
  186. package/dist/cli/src/simple/world-reuse.js +586 -0
  187. package/dist/cli/src/simulation-intents.js +406 -0
  188. package/dist/cli/src/simulation-run.js +1058 -0
  189. package/dist/cli/src/source-batch.js +322 -0
  190. package/dist/cli/src/source-connection.js +451 -0
  191. package/dist/cli/src/source-runtime.js +346 -0
  192. package/dist/cli/src/source-selection-intents.js +75 -0
  193. package/dist/cli/src/source-sets.js +214 -0
  194. package/dist/cli/src/target-models.js +109 -0
  195. package/dist/cli/src/tool-catalog.js +224 -0
  196. package/dist/cli/src/tool-command-contracts.js +1 -0
  197. package/dist/cli/src/tool-command-export.js +245 -0
  198. package/dist/cli/src/tool-connection-contracts.js +1 -0
  199. package/dist/cli/src/tool-connection-reads.js +23 -0
  200. package/dist/cli/src/tool-contributions.js +58 -0
  201. package/dist/cli/src/tool-data.js +116 -0
  202. package/dist/cli/src/tool-datasets.js +176 -0
  203. package/dist/cli/src/tool-group-contracts.js +1 -0
  204. package/dist/cli/src/tool-group-reads.js +26 -0
  205. package/dist/cli/src/tool-group-state.js +147 -0
  206. package/dist/cli/src/tool-groups.js +311 -0
  207. package/dist/cli/src/tool-instances.js +186 -0
  208. package/dist/cli/src/tool-lifecycle-contracts.js +1 -0
  209. package/dist/cli/src/tool-lifecycle-reads.js +30 -0
  210. package/dist/cli/src/tool-lifecycle.js +323 -0
  211. package/dist/cli/src/tool-package-contracts.js +1 -0
  212. package/dist/cli/src/tool-package-reads.js +41 -0
  213. package/dist/cli/src/tool-records.js +53 -0
  214. package/dist/cli/src/tool-runtime.js +96 -0
  215. package/dist/cli/src/tool-state-contracts.js +1 -0
  216. package/dist/cli/src/tool-state-reads.js +31 -0
  217. package/dist/cli/src/tools.js +306 -0
  218. package/dist/cli/src/trial-actions.js +93 -0
  219. package/dist/cli/src/trial-checkpoints.js +133 -0
  220. package/dist/cli/src/trial-checks.js +70 -0
  221. package/dist/cli/src/trial-state.js +197 -0
  222. package/dist/cli/src/vault.js +266 -0
  223. package/dist/cli/src/workspace-registration.js +383 -0
  224. package/dist/cli/src/workspace-team-contracts.js +1 -0
  225. package/dist/cli/src/world-access.js +231 -0
  226. package/dist/cli/src/world-agent.js +4 -0
  227. package/dist/cli/src/world-checkpoint-scopes.js +15 -0
  228. package/dist/cli/src/world-contracts.js +6 -0
  229. package/dist/cli/src/world-creation.js +201 -0
  230. package/dist/cli/src/world-datasets.js +207 -0
  231. package/dist/cli/src/world-inspection.js +257 -0
  232. package/dist/cli/src/world-recipes.js +103 -0
  233. package/dist/cli/src/world-requests.js +241 -0
  234. package/dist/cli/src/world-session-checkpoints.js +196 -0
  235. package/dist/cli/src/world-session-lifecycle.js +207 -0
  236. package/dist/cli/src/world-session-requests.js +215 -0
  237. package/dist/cli/src/world-session-state.js +145 -0
  238. package/dist/cli/src/world-session-voice.js +66 -0
  239. package/dist/cli/src/world-sessions.js +288 -0
  240. package/dist/cli/src/world-stream.js +99 -0
  241. package/dist/cli/src/world-time-report.js +58 -0
  242. package/dist/contracts/agent-change-contracts.js +75 -0
  243. package/dist/contracts/agent-execution-contracts.js +38 -0
  244. package/dist/contracts/application-state-contracts.js +26 -0
  245. package/dist/contracts/assessment-aggregate-contracts.js +15 -0
  246. package/dist/contracts/assessment-contracts.js +74 -0
  247. package/dist/contracts/billing-commerce-contracts.js +107 -0
  248. package/dist/contracts/billing-wallet-contracts.js +43 -0
  249. package/dist/contracts/campaign-lifecycle.js +1 -0
  250. package/dist/contracts/campaign-world-contracts.js +80 -0
  251. package/dist/contracts/catalog-contracts.js +73 -0
  252. package/dist/contracts/ci-access-contracts.js +68 -0
  253. package/dist/contracts/ci-configuration-contracts.js +88 -0
  254. package/dist/contracts/ci-coverage-contracts.js +65 -0
  255. package/dist/contracts/ci-coverage-labels.js +25 -0
  256. package/dist/contracts/ci-delivery-contracts.js +61 -0
  257. package/dist/contracts/ci-result-contracts.js +103 -0
  258. package/dist/contracts/ci-run-contracts.js +51 -0
  259. package/dist/contracts/ci-setup-contracts.js +88 -0
  260. package/dist/contracts/clip-contracts.js +32 -0
  261. package/dist/contracts/comparison-contracts.js +53 -0
  262. package/dist/contracts/composite-contracts.js +34 -0
  263. package/dist/contracts/context-contracts.js +76 -0
  264. package/dist/contracts/continuation-report-contracts.js +1 -0
  265. package/dist/contracts/continuity-requirements.js +8 -0
  266. package/dist/contracts/coordinated-checkpoint-contracts.js +34 -0
  267. package/dist/contracts/dataset-contracts.js +88 -0
  268. package/dist/contracts/dependency-decision-contracts.js +36 -0
  269. package/dist/contracts/dependency-protocol-contracts.js +43 -0
  270. package/dist/contracts/director-execution.js +51 -0
  271. package/dist/contracts/driver-review-contracts.js +99 -0
  272. package/dist/contracts/evidence-contracts.js +65 -0
  273. package/dist/contracts/evidence-coverage-contracts.js +10 -0
  274. package/dist/contracts/export-contracts.js +44 -0
  275. package/dist/contracts/github-directory-contracts.js +96 -0
  276. package/dist/contracts/insight-contracts.js +11 -0
  277. package/dist/contracts/inspection-contracts.js +1 -0
  278. package/dist/contracts/local-claim-contracts.js +39 -0
  279. package/dist/contracts/local-configuration-contracts.js +7 -0
  280. package/dist/contracts/local-evidence-contracts.js +32 -0
  281. package/dist/contracts/local-grant-contracts.js +83 -0
  282. package/dist/contracts/local-input-contracts.js +19 -0
  283. package/dist/contracts/local-runner-contracts.js +42 -0
  284. package/dist/contracts/local-trial-live-contracts.js +16 -0
  285. package/dist/contracts/local-value-source-contracts.js +92 -0
  286. package/dist/contracts/local-world-native-contracts.js +8 -0
  287. package/dist/contracts/mcp-contracts.js +42 -0
  288. package/dist/contracts/media-contracts.js +28 -0
  289. package/dist/contracts/native-check-contracts.js +31 -0
  290. package/dist/contracts/native-identity-contracts.js +52 -0
  291. package/dist/contracts/onboarding-contracts.js +32 -0
  292. package/dist/contracts/performance-contracts.js +85 -0
  293. package/dist/contracts/planning-contracts.js +81 -0
  294. package/dist/contracts/real-operation-contracts.js +30 -0
  295. package/dist/contracts/recommendation-contracts.js +54 -0
  296. package/dist/contracts/report-contracts.js +1 -0
  297. package/dist/contracts/resource-lifecycle-contracts.js +57 -0
  298. package/dist/contracts/revision-contracts.js +12 -0
  299. package/dist/contracts/revision-request-contracts.js +39 -0
  300. package/dist/contracts/saved-record-contracts.js +76 -0
  301. package/dist/contracts/scenario-draft-comparison.js +91 -0
  302. package/dist/contracts/scenario-world-contracts.js +14 -0
  303. package/dist/contracts/service-contracts.js +1 -0
  304. package/dist/contracts/session-command-contracts.js +56 -0
  305. package/dist/contracts/session-contracts.js +65 -0
  306. package/dist/contracts/session-state-contracts.js +67 -0
  307. package/dist/contracts/session-voice-contracts.js +45 -0
  308. package/dist/contracts/source-set-contracts.js +26 -0
  309. package/dist/contracts/source-upload-contracts.js +22 -0
  310. package/dist/contracts/tool-command-contracts.js +57 -0
  311. package/dist/contracts/tool-connection-contracts.js +87 -0
  312. package/dist/contracts/tool-contribution-contracts.js +35 -0
  313. package/dist/contracts/tool-group-contracts.js +53 -0
  314. package/dist/contracts/tool-group-state-contracts.js +63 -0
  315. package/dist/contracts/tool-lifecycle-contracts.js +94 -0
  316. package/dist/contracts/tool-package-contracts.js +91 -0
  317. package/dist/contracts/tool-protocol-contracts.js +71 -0
  318. package/dist/contracts/tool-runtime-billing-contracts.js +28 -0
  319. package/dist/contracts/tool-setup-proposal.js +59 -0
  320. package/dist/contracts/tool-state-contracts.js +79 -0
  321. package/dist/contracts/trial-state-contracts.js +30 -0
  322. package/dist/contracts/typescript/agent-change.js +1 -0
  323. package/dist/contracts/typescript/assessment-aggregate.js +1 -0
  324. package/dist/contracts/typescript/assessment.js +1 -0
  325. package/dist/contracts/typescript/billing-commerce.js +1 -0
  326. package/dist/contracts/typescript/campaigns.js +1 -0
  327. package/dist/contracts/typescript/catalog.js +1 -0
  328. package/dist/contracts/typescript/ci-access.js +1 -0
  329. package/dist/contracts/typescript/ci-configuration.js +1 -0
  330. package/dist/contracts/typescript/ci-delivery.js +1 -0
  331. package/dist/contracts/typescript/ci-result.js +1 -0
  332. package/dist/contracts/typescript/ci-runs.js +1 -0
  333. package/dist/contracts/typescript/clips.js +1 -0
  334. package/dist/contracts/typescript/comparison.js +1 -0
  335. package/dist/contracts/typescript/composite.js +1 -0
  336. package/dist/contracts/typescript/connections.js +1 -0
  337. package/dist/contracts/typescript/context.js +1 -0
  338. package/dist/contracts/typescript/datasets.js +1 -0
  339. package/dist/contracts/typescript/evidence-coverage.js +1 -0
  340. package/dist/contracts/typescript/export.js +1 -0
  341. package/dist/contracts/typescript/github-directory.js +1 -0
  342. package/dist/contracts/typescript/insight.js +1 -0
  343. package/dist/contracts/typescript/inspection.js +1 -0
  344. package/dist/contracts/typescript/intake.js +1 -0
  345. package/dist/contracts/typescript/local-claim.js +1 -0
  346. package/dist/contracts/typescript/local-configuration.js +1 -0
  347. package/dist/contracts/typescript/local-evidence.js +1 -0
  348. package/dist/contracts/typescript/local-grant.js +1 -0
  349. package/dist/contracts/typescript/local-input.js +1 -0
  350. package/dist/contracts/typescript/local-runner.js +1 -0
  351. package/dist/contracts/typescript/local-trial-live.js +1 -0
  352. package/dist/contracts/typescript/local-value-source.js +1 -0
  353. package/dist/contracts/typescript/local-world-native.js +1 -0
  354. package/dist/contracts/typescript/mcp.js +1 -0
  355. package/dist/contracts/typescript/media.js +1 -0
  356. package/dist/contracts/typescript/native-check.js +1 -0
  357. package/dist/contracts/typescript/native-identity.js +1 -0
  358. package/dist/contracts/typescript/onboarding.js +1 -0
  359. package/dist/contracts/typescript/performance.js +1 -0
  360. package/dist/contracts/typescript/planning.js +1 -0
  361. package/dist/contracts/typescript/preparation.js +1 -0
  362. package/dist/contracts/typescript/real-operation.js +1 -0
  363. package/dist/contracts/typescript/recommendation.js +1 -0
  364. package/dist/contracts/typescript/reports.js +1 -0
  365. package/dist/contracts/typescript/resource-lifecycle.js +1 -0
  366. package/dist/contracts/typescript/saved-record.js +1 -0
  367. package/dist/contracts/typescript/scenarios.js +1 -0
  368. package/dist/contracts/typescript/services.js +1 -0
  369. package/dist/contracts/typescript/session-command.js +1 -0
  370. package/dist/contracts/typescript/session-state.js +1 -0
  371. package/dist/contracts/typescript/session-voice.js +1 -0
  372. package/dist/contracts/typescript/session.js +1 -0
  373. package/dist/contracts/typescript/tool-command.js +1 -0
  374. package/dist/contracts/typescript/tool-connection.js +1 -0
  375. package/dist/contracts/typescript/tool-contribution.js +1 -0
  376. package/dist/contracts/typescript/tool-groups.js +1 -0
  377. package/dist/contracts/typescript/tool-lifecycle.js +1 -0
  378. package/dist/contracts/typescript/tool-package.js +1 -0
  379. package/dist/contracts/typescript/tool-state.js +1 -0
  380. package/dist/contracts/typescript/trial-state.js +1 -0
  381. package/dist/contracts/typescript/vault.js +1 -0
  382. package/dist/contracts/typescript/workspace-team.js +1 -0
  383. package/dist/contracts/typescript/world-stream.js +1 -0
  384. package/dist/contracts/workspace-team-contracts.js +55 -0
  385. package/dist/contracts/world-command.js +65 -0
  386. package/dist/contracts/world-lifetime-contracts.js +74 -0
  387. package/dist/contracts/world-recipe-contracts.js +27 -0
  388. package/dist/contracts/world-stream-contracts.js +27 -0
  389. package/dist/package.json +1 -0
  390. package/package.json +16 -16
  391. package/LICENSE.md +0 -23
  392. package/PACKAGING.json +0 -50
@@ -0,0 +1,326 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { lstat, mkdir, open, rename, unlink } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { setTimeout as sleep } from 'node:timers/promises';
6
+ import { api } from './auth.js';
7
+ import { privateDirectory } from './credentials.js';
8
+ import { CliError } from './http.js';
9
+ import { identifier } from './world-contracts.js';
10
+ import { historicalRunnerSigner } from './runner-identity.js';
11
+ import { withLocalJobInputs } from './runner-ownership.js';
12
+ import { executeLocalValidation } from './runner-container.js';
13
+ import { syncLocalCompletion } from './runner-completion.js';
14
+ import { evidenceNeedles } from './runner-evidence-privacy.js';
15
+ import { localEvidenceReceiptMatches } from '../../contracts/local-evidence-contracts.js';
16
+ import { acquireLocalEvidence, assertEvidenceRetentionCurrent, evidenceRetentionSignal, frozenEvidenceRetention, retentionMatches } from './runner-evidence-retention.js';
17
+ const CHUNK = 1024 ** 2, HASH = /^[a-f0-9]{64}$/;
18
+ const fail = () => new CliError('local_evidence_unconfirmed', 'Evidence is not confirmed in the cloud. Its private local journal remains; no target was run again.');
19
+ const canonical = (value) => {
20
+ if (Array.isArray(value))
21
+ return '[' + value.map(canonical).join(',') + ']';
22
+ if (value && typeof value === 'object')
23
+ return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}';
24
+ const result = JSON.stringify(value);
25
+ if (result === undefined)
26
+ throw fail();
27
+ return result;
28
+ };
29
+ const hash = (value) => createHash('sha256').update(value).digest('hex');
30
+ async function folder(owner, operation) {
31
+ const path = join(await privateDirectory(), `evidence-${identifier(owner.owner)}-${identifier(owner.workspace)}-${identifier(operation)}`);
32
+ await mkdir(path, { mode: 0o700 }).catch(error => { if (error.code !== 'EEXIST')
33
+ throw error; });
34
+ const stat = await lstat(path);
35
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.mode & 0o077 || process.getuid && stat.uid !== process.getuid())
36
+ throw fail();
37
+ return path;
38
+ }
39
+ async function persist(directory, value, initial = false) {
40
+ assertEvidenceRetentionCurrent(value.retention);
41
+ const path = join(directory, 'receipt.json'), temporary = path + '.' + randomUUID() + '.tmp';
42
+ const file = await open(initial ? path : temporary, 'wx', 0o600);
43
+ try {
44
+ await file.writeFile(JSON.stringify(value));
45
+ await file.sync();
46
+ }
47
+ finally {
48
+ await file.close();
49
+ }
50
+ if (!initial)
51
+ await rename(temporary, path);
52
+ const parent = await open(directory, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0));
53
+ try {
54
+ await parent.sync();
55
+ }
56
+ finally {
57
+ await parent.close();
58
+ }
59
+ }
60
+ async function ownedFile(path, maximum) {
61
+ const before = await lstat(path);
62
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.mode & 0o077 || before.size > maximum
63
+ || process.getuid && before.uid !== process.getuid())
64
+ throw fail();
65
+ const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
66
+ const actual = await file.stat();
67
+ if (actual.ino !== before.ino || actual.dev !== before.dev || actual.size !== before.size) {
68
+ await file.close();
69
+ throw fail();
70
+ }
71
+ return file;
72
+ }
73
+ async function collect(owner, manifestSha256, observation, read, configuration, retention, signal) {
74
+ signal = evidenceRetentionSignal(retention, signal);
75
+ const directory = await folder(owner, owner.lease);
76
+ const journal = { schema: 'firedrill.local-evidence-journal.v1', owner, manifest_sha256: manifestSha256,
77
+ key: randomUUID(), stage: 'collecting', observation, document: null, retention };
78
+ await persist(directory, journal, true);
79
+ const kind = observation.launch.state === 'exited' && observation.launch.exit_code === 0 && observation.status?.state === 'native_validated'
80
+ ? 'native_validation' : observation.launch.state === 'exited' && observation.launch.exit_code !== 0
81
+ && observation.status?.state === 'failed' && observation.status.operation_attempt_available === true ? 'native_operation_attempt' : null;
82
+ if (!kind) {
83
+ // Preserve actual redacted failure/unknown metadata, never invent an archive
84
+ // or treat it as a passing native validation. Failed-trial sync is separate.
85
+ journal.stage = 'local_retained';
86
+ await persist(directory, journal);
87
+ return journal;
88
+ }
89
+ const exported = await read(kind), blocked = evidenceNeedles(configuration), overlap = blocked.reduce((maximum, value) => Math.max(maximum, value.length), 1);
90
+ if (exported.kind !== kind)
91
+ throw fail();
92
+ const name = kind === 'native_validation' ? 'native' : 'operations';
93
+ const partial = join(directory, name + '.partial'), final = join(directory, kind === 'native_validation' ? 'native.zip' : 'operations.json');
94
+ const file = await open(partial, 'wx', 0o600), digest = createHash('sha256');
95
+ let offset = 0, tail = Buffer.alloc(0), accepted = false;
96
+ try {
97
+ while (offset < exported.receipt.size) {
98
+ signal.throwIfAborted();
99
+ assertEvidenceRetentionCurrent(retention);
100
+ const body = await exported.read(offset, Math.min(16384, exported.receipt.size - offset));
101
+ assertEvidenceRetentionCurrent(retention);
102
+ const scan = Buffer.concat([tail, body]);
103
+ if (blocked.some(value => scan.includes(value)))
104
+ throw new CliError('local_evidence_private_value', 'A private mapped value was found in local evidence. It was not uploaded.');
105
+ tail = Buffer.from(scan.subarray(Math.max(0, scan.length - overlap + 1)));
106
+ digest.update(body);
107
+ let written = 0;
108
+ while (written < body.length) {
109
+ const result = await file.write(body, written, body.length - written);
110
+ if (!result.bytesWritten)
111
+ throw fail();
112
+ written += result.bytesWritten;
113
+ }
114
+ offset += body.length;
115
+ }
116
+ if (offset !== exported.receipt.size || digest.digest('hex') !== exported.receipt.sha256)
117
+ throw fail();
118
+ await file.sync();
119
+ accepted = true;
120
+ }
121
+ finally {
122
+ await file.close();
123
+ if (!accepted)
124
+ await unlink(partial).catch(() => undefined);
125
+ blocked.length = 0;
126
+ tail.fill(0);
127
+ }
128
+ await rename(partial, final);
129
+ journal.document = { kind, receipt: exported.receipt, launch: observation.launch, status: observation.status };
130
+ journal.stage = 'local_retained';
131
+ await persist(directory, journal);
132
+ return journal;
133
+ }
134
+ const endpoint = (owner, key) => `/api/workspaces/${identifier(owner.workspace)}/local-runners/${identifier(owner.device)}/jobs/${identifier(owner.job)}/claims/${identifier(owner.request)}/evidence/${identifier(key)}`;
135
+ function matching(value, journal) {
136
+ if (!localEvidenceReceiptMatches(value, { ...journal.owner, manifestSha256: journal.manifest_sha256 }, journal.key)
137
+ || value.schema !== 'firedrill.local-evidence-receipt.v1' || value.job_id !== journal.owner.job
138
+ || value.transfer_key !== journal.key || value.lease_id !== journal.owner.lease || value.provenance !== 'customer_device'
139
+ || value.cloud_completion_recorded !== false || !Array.isArray(value.chunks)
140
+ || !['not_retained', 'receiving', 'verifying', 'retained'].includes(value.state)
141
+ || value.state !== 'not_retained' && (canonical(value.document) !== canonical(journal.document) || value.request_digest !== hash(canonical(journal.document))))
142
+ throw fail();
143
+ if (value.state !== 'retained' && (!Number.isSafeInteger(Date.parse(value.server_time)) || value.upload_available !== true
144
+ || !Number.isSafeInteger(Date.parse(value.upload_until)) || Date.parse(value.upload_until) <= Date.parse(value.server_time)))
145
+ throw fail();
146
+ }
147
+ async function confirmCloud(journal, receipt, directory) {
148
+ const artifact = receipt.artifact;
149
+ if (!journal.document || receipt.state !== 'retained' || !artifact || identifier(artifact.id) !== artifact.id
150
+ || typeof artifact.generation !== 'string' || !/^[1-9][0-9]*$/.test(artifact.generation)
151
+ || artifact.sha256 !== journal.document.receipt.sha256 || artifact.size !== journal.document.receipt.size)
152
+ throw fail();
153
+ journal.stage = 'cloud_retained';
154
+ journal.cloud = artifact;
155
+ await persist(directory, journal);
156
+ return { state: 'cloud_retained', artifact, provenance: 'customer_device', cloud_completion_recorded: false };
157
+ }
158
+ async function synchronize(journal, signal) {
159
+ signal = evidenceRetentionSignal(journal.retention, signal);
160
+ const owner = journal.owner, directory = await folder(owner, owner.lease);
161
+ if (!journal.document)
162
+ return { state: 'local_metadata_only', cloud_completion_recorded: false };
163
+ if (!owner.evidenceUpload)
164
+ return { state: 'local_only', cloud_completion_recorded: false };
165
+ const auth = { actor: owner.owner, workspace: owner.workspace, signal }, path = endpoint(owner, journal.key);
166
+ if (journal.stage === 'cloud_retained') {
167
+ const receipt = await api(path, auth);
168
+ matching(receipt, journal);
169
+ return confirmCloud(journal, receipt, directory); // Local bytes can be removed after verified cloud retention.
170
+ }
171
+ if (!['native_validation', 'native_operation_attempt'].includes(journal.document.kind))
172
+ throw fail();
173
+ const file = await ownedFile(join(directory, journal.document.kind === 'native_validation' ? 'native.zip' : 'operations.json'), (journal.document.kind === 'native_validation' ? 128 : 8) * CHUNK);
174
+ try {
175
+ const bytes = await file.stat(), digest = createHash('sha256');
176
+ for await (const body of file.createReadStream({ autoClose: false, start: 0 })) {
177
+ signal.throwIfAborted();
178
+ assertEvidenceRetentionCurrent(journal.retention);
179
+ digest.update(body);
180
+ }
181
+ if (bytes.size !== journal.document.receipt.size || digest.digest('hex') !== journal.document.receipt.sha256)
182
+ throw fail();
183
+ let receipt = await api(path, auth);
184
+ matching(receipt, journal);
185
+ const signer = await historicalRunnerSigner(owner, owner.device, signal);
186
+ const send = async (action, fields) => {
187
+ assertEvidenceRetentionCurrent(journal.retention);
188
+ const sent = Date.parse(receipt.server_time);
189
+ if (!Number.isSafeInteger(sent))
190
+ throw fail();
191
+ const proof = await signer.sign({ schema: 'firedrill.local-evidence-write.v1', job_id: owner.job,
192
+ attempt_id: owner.attempt, lease_id: owner.lease, request_key: owner.request, device_key: owner.device,
193
+ native_session_id: signer.nativeSession, transfer_key: journal.key, manifest_sha256: journal.manifest_sha256,
194
+ sent_at_ms: sent, action, ...fields });
195
+ try {
196
+ receipt = await api(path, { ...auth, method: 'POST', body: proof });
197
+ }
198
+ catch {
199
+ receipt = await api(path, auth);
200
+ } // Exact read, no target or write replay.
201
+ matching(receipt, journal);
202
+ };
203
+ if (receipt.state === 'not_retained') {
204
+ journal.stage = 'register_dispatched';
205
+ await persist(directory, journal);
206
+ await send('register', { document: journal.document });
207
+ if (!['receiving', 'verifying', 'retained'].includes(receipt.state))
208
+ throw fail();
209
+ }
210
+ if (!['receiving', 'verifying', 'retained'].includes(receipt.state))
211
+ throw fail();
212
+ journal.stage = 'uploading';
213
+ await persist(directory, journal);
214
+ const buffer = Buffer.alloc(CHUNK), count = Math.ceil(bytes.size / CHUNK);
215
+ for (let index = 0; index < count && receipt.state === 'receiving'; index++) {
216
+ signal.throwIfAborted();
217
+ assertEvidenceRetentionCurrent(journal.retention);
218
+ const size = Math.min(CHUNK, bytes.size - index * CHUNK);
219
+ let received = 0;
220
+ while (received < size) {
221
+ const part = await file.read(buffer, received, size - received, index * CHUNK + received);
222
+ if (!part.bytesRead)
223
+ throw fail();
224
+ received += part.bytesRead;
225
+ }
226
+ const body = buffer.subarray(0, size), checksum = hash(body), current = receipt.chunks.find(item => item.index === index);
227
+ if (current && (current.size !== size || current.sha256 !== checksum))
228
+ throw fail();
229
+ if (!current?.retained)
230
+ await send('chunk', { index, sha256: checksum, data: body.toString('base64') });
231
+ if (!receipt.chunks.some(item => item.index === index && item.retained && item.sha256 === checksum && item.size === size))
232
+ throw fail();
233
+ }
234
+ if (receipt.state === 'receiving' || receipt.state === 'verifying') {
235
+ journal.stage = 'commit_dispatched';
236
+ await persist(directory, journal);
237
+ await send('commit', {});
238
+ }
239
+ while (receipt.state === 'verifying') {
240
+ // Observe the exact retained outbox intent, not invented progress or a
241
+ // repeated commit. The existing job keeper independently maintains access.
242
+ if (receipt.authorization_current !== true)
243
+ throw new CliError('local_evidence_authorization_required', 'The saved evidence needs authorization from this terminal session. Run the same evidence sync command again. No target was run again.');
244
+ await sleep(1000, undefined, { signal });
245
+ receipt = await api(path, auth);
246
+ matching(receipt, journal);
247
+ }
248
+ return confirmCloud(journal, receipt, directory);
249
+ }
250
+ finally {
251
+ await file.close();
252
+ }
253
+ }
254
+ /** Internal full evidence handoff; public execution awaits cloud completion. */
255
+ export async function retainLocalValidation(context, device, job, grantRequest, signal) {
256
+ const progress = {};
257
+ let executionError;
258
+ try {
259
+ await withLocalJobInputs(context, device, job, grantRequest, 'validation', signal, async (owner, inputs, configuration, active) => {
260
+ progress.owner = owner;
261
+ if (owner.purpose !== 'validation')
262
+ throw fail();
263
+ const evidence = await acquireLocalEvidence(owner, owner.lease, frozenEvidenceRetention(owner, inputs.manifestSha256, inputs.manifest.evidence_recovery_until));
264
+ try {
265
+ active = AbortSignal.any([active, evidence.signal]);
266
+ return await executeLocalValidation(owner, inputs, configuration, (current, observation, exported, running) => collect(current, inputs.manifestSha256, observation, exported, configuration, evidence.retention, running), active);
267
+ }
268
+ finally {
269
+ await evidence.close();
270
+ }
271
+ });
272
+ }
273
+ catch (error) {
274
+ executionError = error;
275
+ }
276
+ const claimed = progress.owner;
277
+ if (!claimed)
278
+ throw executionError ?? fail();
279
+ // The keeper has stopped and transient configuration has been cleared. Exact
280
+ // container cleanup is already journaled. Closing facts use a fresh bounded
281
+ // channel even after interruption; this neither renews nor executes work.
282
+ try {
283
+ await syncLocalCompletion(claimed, claimed.lease, AbortSignal.timeout(30000));
284
+ }
285
+ catch (error) {
286
+ throw executionError ?? error;
287
+ }
288
+ if (executionError)
289
+ throw executionError;
290
+ const evidence = await syncLocalEvidence(claimed, claimed.lease, signal);
291
+ const completion = await syncLocalCompletion(claimed, claimed.lease, signal, { wait: true });
292
+ return { evidence, completion };
293
+ }
294
+ /** Resume artifact transfer only. Never call the execution callback again. */
295
+ export async function syncLocalEvidence(scope, operation, signal) {
296
+ const evidence = await acquireLocalEvidence(scope, identifier(operation));
297
+ try {
298
+ return await syncRetainedLocalEvidence(scope, operation, AbortSignal.any([signal, evidence.signal]), evidence.retention);
299
+ }
300
+ finally {
301
+ await evidence.close();
302
+ }
303
+ }
304
+ async function syncRetainedLocalEvidence(scope, operation, signal, retention) {
305
+ const directory = await folder(scope, operation), lock = join(directory, 'sync.lock');
306
+ // The outer process-lifetime evidence lock owns all new transfers. A legacy
307
+ // mkdir lock is ambiguous and is preserved for explicit inspection, not
308
+ // silently treated as a released kernel lock or removed by erasure.
309
+ if (await lstat(lock).then(() => true).catch(error => { if (error.code === 'ENOENT')
310
+ return false; throw error; }))
311
+ throw new CliError('local_evidence_legacy_lock', 'This older evidence transfer has an ambiguous sync.lock. Inspect that exact legacy lock before continuing; it was not removed.');
312
+ const file = await ownedFile(join(directory, 'receipt.json'), 65536);
313
+ let journal;
314
+ try {
315
+ journal = JSON.parse(await file.readFile('utf8'));
316
+ }
317
+ finally {
318
+ await file.close();
319
+ }
320
+ if (journal.schema !== 'firedrill.local-evidence-journal.v1' || !journal.owner || journal.owner.owner !== scope.owner || journal.owner.workspace !== scope.workspace
321
+ || journal.owner.lease !== identifier(operation) || !HASH.test(journal.manifest_sha256)
322
+ || !retentionMatches(journal.retention, retention) || journal.manifest_sha256 !== retention.manifest_sha256
323
+ || !['local_retained', 'register_dispatched', 'uploading', 'commit_dispatched', 'cloud_retained'].includes(journal.stage))
324
+ throw fail();
325
+ return synchronize(journal, signal);
326
+ }
@@ -0,0 +1,92 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { localAssignmentsPage, readLocalClaim } from './runner-claim-reads.js';
3
+ import { CliError } from './http.js';
4
+ import { selectOne } from './prompts.js';
5
+ import { executionPermission, localPermissionPage } from './runner-grants.js';
6
+ import { withRunnerPresence } from './runner-identity.js';
7
+ import { retainLocalValidation } from './runner-evidence.js';
8
+ import { retainLocalTrial } from './runner-trial-journal.js';
9
+ import { retainLocalWorldSession } from './runner-world-collector.js';
10
+ import { selectedWorld } from './world-access.js';
11
+ import { identifier } from './world-contracts.js';
12
+ import { retainedLocalClaim } from './runner-ownership.js';
13
+ const purposeLabel = (value) => value === 'trial' ? 'Simulation drill' : value === 'world_session' ? 'Manual world session' : 'World validation';
14
+ async function choose(label, choices, signal, readPage) {
15
+ let page = 1;
16
+ while (true) {
17
+ const result = await readPage(page);
18
+ const items = result.results.flatMap(row => { const item = choices(row); return item ? [item] : []; });
19
+ if (page > 1)
20
+ items.unshift({ value: 'previous', label: 'Previous page' });
21
+ if (result.next)
22
+ items.push({ value: 'next', label: 'Next page' });
23
+ if (!items.length)
24
+ throw new CliError('local_selection_empty', `No ${label.toLowerCase()} is available for this world and device.`, 2);
25
+ const selected = await selectOne(label, items, signal);
26
+ if (selected === 'previous') {
27
+ page--;
28
+ continue;
29
+ }
30
+ if (selected === 'next') {
31
+ page++;
32
+ continue;
33
+ }
34
+ return identifier(selected);
35
+ }
36
+ }
37
+ /** Execute exactly one already-admitted assignment, never arbitrary host code,
38
+ * a new campaign, an implicit target retry or a cloud fallback.
39
+ */
40
+ export async function executeAssignment(device, job, options, output) {
41
+ device = identifier(device);
42
+ const interactive = !options.json && !!process.stdin.isTTY && !!process.stderr.isTTY;
43
+ const context = await selectedWorld({ workspace: options.workspace, world: options.world,
44
+ interactive, openBrowser: options.openBrowser, signal: options.signal });
45
+ if (!job && interactive)
46
+ job = await choose('Saved local job', row => row.world_id === context.world && row.device_key === device && row.job_state === 'queued' && !row.claim
47
+ ? { value: row.job_id, label: row.world_name, detail: purposeLabel(row.purpose) } : null, options.signal, page => localAssignmentsPage(context, device, { world: context.world, available: true, page }, options.signal));
48
+ if (!job)
49
+ throw new CliError('local_job_required', 'Provide a saved job ID, or run interactively to choose an assignment.', 2);
50
+ job = identifier(job);
51
+ // GET of a fresh lookup identity creates no claim or grant. A prior claim
52
+ // refuses this new request, rather than being taken over or replayed.
53
+ const original = await retainedLocalClaim(context, device, job);
54
+ if (original && ['work_dispatched', 'not_dispatched'].includes(original.stage))
55
+ throw new CliError('local_execution_recovery_required', original.stage === 'not_dispatched' ? 'This job was abandoned before launch. Repeat runner abandon to reconcile its original closing receipt.'
56
+ : 'This job already handed off to a container. Use runner cleanup and evidence sync for its original operation.');
57
+ const offered = await readLocalClaim(context, device, job, original?.request ?? randomUUID(), options.signal);
58
+ if (offered.job_id !== job || offered.world_id !== context.world || offered.device_key !== device
59
+ || !['validation', 'trial', 'world_session'].includes(offered.purpose)
60
+ || (!original && (offered.claim || offered.job_state !== 'queued'))
61
+ || (original && (!offered.claim || offered.claim.request_key !== original.request))) {
62
+ throw new CliError('local_assignment_changed', 'The saved assignment is not available for a first execution. Inspect its exact ownership.');
63
+ }
64
+ if (original && options.grant && options.grant !== original.grantRequest)
65
+ throw new CliError('local_assignment_changed', 'Resume uses the original permission. A different grant cannot replace it.');
66
+ let grantRequest = original?.grantRequest ?? options.grant;
67
+ if (!grantRequest && interactive)
68
+ grantRequest = await choose('Local permission', row => row.id === offered.grant_id && row.state === 'active' && row.device_key === device
69
+ && row.operations.includes(offered.purpose) ? { value: row.request_key, label: 'Use the saved permission', detail: `Expires ${row.expires_at}` } : null, options.signal, page => localPermissionPage(context, { device, page }, options.signal));
70
+ if (!grantRequest)
71
+ throw new CliError('local_grant_required', 'Provide --grant <permission-request-id> or choose the saved permission interactively.', 2);
72
+ const permission = await executionPermission(context, identifier(grantRequest), device, offered.purpose, options.signal);
73
+ if (permission.id !== offered.grant_id)
74
+ throw new CliError('local_assignment_changed', 'This permission does not own the saved assignment.');
75
+ output('local_execution_review', { job_id: job, world_id: context.world, device_key: device, purpose: offered.purpose,
76
+ evidence_upload: permission.document.consent.evidence_upload, limits: permission.document.limits }, `${offered.world_name}: ${purposeLabel(offered.purpose).toLowerCase()} on this device.\nJob: ${job}\n`
77
+ + (permission.document.consent.evidence_upload ? 'Evidence will be retained in this world’s cloud history.'
78
+ : 'Evidence sharing is off. Private files stay local; cloud readiness and scoring require shared evidence.')
79
+ + `\nBefore container handoff, resume with runner execute ${device} ${job} --world ${context.world}, or close with runner abandon ${device} ${job} --world ${context.world}.`);
80
+ if (!options.approve) {
81
+ if (!interactive)
82
+ throw new CliError('local_execution_confirmation_required', 'Use --execute-local to start this exact saved assignment.', 2);
83
+ if (!await selectOne('Start this saved local job?', [{ value: true, label: 'Run locally' },
84
+ { value: false, label: 'Cancel' }], options.signal))
85
+ throw new CliError('canceled', 'No job was claimed or executed.', 130);
86
+ }
87
+ const result = await withRunnerPresence(context, device, options.signal, output, async (active) => offered.purpose === 'trial'
88
+ ? await retainLocalTrial(context, device, job, grantRequest, active, output)
89
+ : offered.purpose === 'world_session' ? await retainLocalWorldSession(context, device, job, grantRequest, active)
90
+ : await retainLocalValidation(context, device, job, grantRequest, active));
91
+ output('local_execution_retained', result, 'Local execution and evidence handoff returned. The saved cloud receipt records its actual outcome.');
92
+ }
@@ -0,0 +1,25 @@
1
+ import { CliError } from './http.js';
2
+ /** Original job authority, not a fresh window each time a pipe is opened. */
3
+ export function localExecutionTimeout(configuration, closingGraceMs = 0) {
4
+ const control = configuration.control;
5
+ if (!Number.isSafeInteger(control.budget_seconds) || control.budget_seconds < 1
6
+ || !Number.isSafeInteger(closingGraceMs) || closingGraceMs < 0)
7
+ throw invalid();
8
+ const deadline = Date.parse(configuration.job_deadline_at);
9
+ if (!Number.isSafeInteger(deadline)
10
+ || control.schema === 'firedrill.trial.v1' && control.job_deadline_at !== configuration.job_deadline_at
11
+ || control.schema === 'firedrill.world-session.v1' && control.expires_at !== configuration.job_deadline_at)
12
+ throw invalid();
13
+ // Validation has no separate deadline field in its native control document.
14
+ // The shared, identity-checked delivery envelope still binds its original
15
+ // absolute window. Bootstrap, Docker setup or host sleep must not restart it.
16
+ const remaining = Math.min(control.budget_seconds * 1000, deadline - Date.now());
17
+ if (remaining <= 0)
18
+ throw new CliError('local_execution_window_ended', 'This execution window ended. Inspect the original run; the target was not restarted.');
19
+ // Native target supervision ends work before this optional host closing
20
+ // window. The extra time only permits its original evidence/cleanup readback.
21
+ return Math.min(2147483647, Math.ceil(remaining) + closingGraceMs);
22
+ }
23
+ function invalid() {
24
+ return new CliError('local_runtime_packet_invalid', 'The native execution deadline does not match the approved job. No target was started.');
25
+ }