firedrill-run 2.2.5 → 3.0.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 (392) hide show
  1. package/README.md +178 -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 +465 -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 +1622 -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 +61 -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,351 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open, opendir, rmdir, unlink } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { privateDirectory } from './credentials.js';
5
+ import { CliError } from './http.js';
6
+ import { identifier } from './world-contracts.js';
7
+ import { selectOne } from './prompts.js';
8
+ import { localEvidenceContexts, withLocalContainerCompletion } from './runner-container.js';
9
+ import { forgetWorldEvidence, pruneWorldEvidence } from './runner-world-erasure.js';
10
+ import { lockLocalEvidence, privateEvidenceMetadata, readEvidencePolicy, writeEvidencePolicy } from './runner-evidence-retention.js';
11
+ const uuid = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
12
+ const index = '(0|[1-9][0-9]{0,15})';
13
+ const temporary = new RegExp('^(receipt|cloud-receipts)\\.json\\.' + uuid + '\\.tmp$');
14
+ const invalid = () => new CliError('local_evidence_erasure_identity', 'The exact private evidence identity or generated file set could not be confirmed. Unknown files and original cleanup records were not removed.');
15
+ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
16
+ const count = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
17
+ const same = (v, expected) => ['owner', 'workspace', 'device', 'job', 'request', 'lease', 'attempt']
18
+ .every(key => v[key] === expected[key]);
19
+ const MAX_PASS = 1000;
20
+ async function selected(operation, options) {
21
+ operation = identifier(operation);
22
+ let result;
23
+ for await (const row of localEvidenceContexts(operation, options.workspace, undefined, options.signal)) {
24
+ if (result)
25
+ throw invalid();
26
+ result = row;
27
+ }
28
+ if (!result)
29
+ throw invalid();
30
+ return result;
31
+ }
32
+ async function directory(path) {
33
+ const before = await lstat(path, { bigint: true }).catch(error => { if (error.code === 'ENOENT')
34
+ return null; throw error; });
35
+ if (!before)
36
+ return null;
37
+ if (!before.isDirectory() || before.isSymbolicLink() || before.mode & 63n || process.getuid && before.uid !== BigInt(process.getuid()))
38
+ throw invalid();
39
+ const held = await open(path, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | (constants.O_NOFOLLOW ?? 0));
40
+ const actual = await held.stat({ bigint: true });
41
+ if (actual.dev !== before.dev || actual.ino !== before.ino) {
42
+ await held.close();
43
+ throw invalid();
44
+ }
45
+ const check = async () => {
46
+ const current = await lstat(path, { bigint: true });
47
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== actual.dev || current.ino !== actual.ino
48
+ || current.mode & 63n || process.getuid && current.uid !== BigInt(process.getuid()))
49
+ throw invalid();
50
+ };
51
+ return { check, sync: () => held.sync(), close: () => held.close() };
52
+ }
53
+ async function header(path, kind, identity, hasPolicy) {
54
+ const value = await privateEvidenceMetadata(join(path, 'receipt.json')).catch(error => {
55
+ if (error.code === 'ENOENT' && hasPolicy)
56
+ return null;
57
+ throw error;
58
+ });
59
+ if (value === null)
60
+ return null;
61
+ if (!record(value) || value.manifest_sha256 !== identity.manifest_sha256)
62
+ throw invalid();
63
+ if (kind === 'live') {
64
+ if (value.schema !== 'firedrill.local-trial-live-journal.v2' || value.job_id !== identity.job
65
+ || value.lease_id !== identity.lease || value.attempt_id !== identity.attempt
66
+ || !count(value.events) || !count(value.queued) || !count(value.artifact_count))
67
+ throw invalid();
68
+ }
69
+ else if (!record(value.owner) || !same(value.owner, identity) || value.owner.purpose !== identity.purpose
70
+ || value.schema !== (kind === 'validation' ? 'firedrill.local-evidence-journal.v1' : 'firedrill.local-trial-journal.v1')
71
+ || kind === 'trial' && (!count(value.pages) || !count(value.artifact_offset) || !count(value.artifacts_retained)))
72
+ throw invalid();
73
+ return value;
74
+ }
75
+ function maximum(name, kind, saved) {
76
+ const pending = temporary.exec(name);
77
+ if (name === 'receipt.json' || pending && (pending[1] === 'receipt' || kind === 'trial')
78
+ || kind === 'trial' && name === 'cloud-receipts.json')
79
+ return 65536;
80
+ if (kind === 'validation') {
81
+ if (['native.zip', 'native.partial'].includes(name))
82
+ return 128 * 1024 ** 2;
83
+ if (['operations.json', 'operations.partial'].includes(name))
84
+ return 8 * 1024 ** 2;
85
+ throw invalid();
86
+ }
87
+ const artifact = new RegExp('^' + index + '\\.bin(\\.json|\\.partial)?$').exec(name);
88
+ if (artifact) {
89
+ const bound = saved && (kind === 'live' ? saved.artifact_count : saved.artifact_offset);
90
+ if (!count(Number(artifact[1])) || bound !== null && bound !== undefined && (!count(bound) || Number(artifact[1]) > bound))
91
+ throw invalid();
92
+ return artifact[2] === '.json' ? 8192 : 512 * 1024 ** 2;
93
+ }
94
+ const event = new RegExp('^(page|event|queue)-' + index + '\\.json$').exec(name);
95
+ if (event && (kind === 'trial' ? event[1] === 'page' : ['event', 'queue'].includes(event[1]))) {
96
+ const bound = saved && (event[1] === 'page' ? saved.pages : event[1] === 'event' ? saved.events : saved.queued);
97
+ if (!count(Number(event[2])) || bound !== null && bound !== undefined && (!count(bound) || Number(event[2]) > bound))
98
+ throw invalid();
99
+ return event[1] === 'queue' ? 256 : event[1] === 'page' ? 4 * 1024 ** 2 : 2 * 1024 ** 2;
100
+ }
101
+ throw invalid();
102
+ }
103
+ async function privateFile(path, maximumBytes) {
104
+ const before = await lstat(path, { bigint: true });
105
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n || before.mode & 63n || before.size > BigInt(maximumBytes)
106
+ || process.getuid && before.uid !== BigInt(process.getuid()))
107
+ throw invalid();
108
+ const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
109
+ try {
110
+ const actual = await file.stat({ bigint: true });
111
+ if (actual.dev !== before.dev || actual.ino !== before.ino || actual.size !== before.size)
112
+ throw invalid();
113
+ return actual;
114
+ }
115
+ finally {
116
+ await file.close();
117
+ }
118
+ }
119
+ async function erase(context, signal, automatic) {
120
+ if (context.purpose === 'world_session')
121
+ throw invalid();
122
+ const use = await lockLocalEvidence(context, context.operation);
123
+ try {
124
+ return await withLocalContainerCompletion(context, context.operation, context.purpose, async (saved, containerSignal) => {
125
+ signal = AbortSignal.any([signal, use.signal, containerSignal]);
126
+ signal.throwIfAborted();
127
+ use.assertHeld();
128
+ const identity = { owner: context.owner, workspace: context.workspace, device: saved.device, job: saved.job, request: saved.request,
129
+ lease: saved.lease, attempt: saved.attempt, manifest_sha256: saved.manifestSha256, purpose: context.purpose };
130
+ let policy = await readEvidencePolicy(context, context.operation);
131
+ if (policy && (!same(policy, identity) || policy.manifest_sha256 !== identity.manifest_sha256 || policy.purpose !== identity.purpose))
132
+ throw invalid();
133
+ if (policy?.state === 'removed')
134
+ return { state: 'removed', already_removed: true, file_count: policy.removed_files };
135
+ if (automatic && (!policy || policy.expires_at === null))
136
+ throw new CliError('local_evidence_retention_missing', 'Legacy evidence requires explicit forget; no expiry was invented.');
137
+ if (automatic && policy?.state === 'retained' && Date.parse(policy.expires_at) > Date.now())
138
+ return { state: 'kept', already_removed: false, file_count: 0 };
139
+ const root = await privateDirectory(), suffix = [context.owner, context.workspace, context.operation].map(identifier).join('-');
140
+ const targets = identity.purpose === 'validation' ? [{ path: join(root, 'evidence-' + suffix), kind: 'validation' }]
141
+ : [{ path: join(root, 'trial-' + suffix), kind: 'trial' }, { path: join(root, 'trial-live-' + suffix), kind: 'live' }];
142
+ // Validate headers and cloud synchronization before automatic deletion.
143
+ // Explicit forget can discard unsynced bytes only after user consent.
144
+ if (automatic && policy?.state !== 'removing') {
145
+ const primary = await header(targets[0].path, targets[0].kind, identity, true);
146
+ if (!primary || identity.purpose === 'validation' && (primary.stage !== 'cloud_retained' || !record(primary.cloud)
147
+ || !record(primary.document) || !record(primary.document.receipt)
148
+ || primary.cloud.size !== primary.document.receipt.size || primary.cloud.sha256 !== primary.document.receipt.sha256))
149
+ throw new CliError('local_evidence_sync_unresolved', 'Expired evidence has no confirmed cloud retention. Use explicit forget to discard it, or inspect original recovery metadata.');
150
+ if (identity.purpose === 'trial') {
151
+ const sync = await privateEvidenceMetadata(join(targets[0].path, 'cloud-receipts.json'));
152
+ if (primary.stage !== 'local_collected' || !record(sync) || sync.schema !== 'firedrill.local-trial-sync.v2'
153
+ || sync.operation_id !== identity.lease || sync.job_id !== identity.job || sync.manifest_sha256 !== identity.manifest_sha256
154
+ || sync.state !== 'cloud_staged' || sync.pending !== null || sync.reports_retained !== primary.pages
155
+ || sync.artifacts_retained !== primary.artifacts_retained)
156
+ throw new CliError('local_evidence_sync_unresolved', 'Expired drill evidence is not confirmed fully staged in the cloud. It was not automatically discarded.');
157
+ }
158
+ }
159
+ // A durable metadata tombstone prevents any later collector or sync from
160
+ // recreating data, including after an interrupted or legacy erasure.
161
+ const first = !policy;
162
+ policy = policy ?? { ...identity, schema: 'firedrill.local-evidence-policy.v1', expires_at: null, state: 'removing',
163
+ removed_files: 0, removal_started_at: new Date().toISOString(), finished_at: null, directory_cursor: 0, pending_file: null };
164
+ policy = { ...policy, state: 'removing', removal_started_at: policy.removal_started_at ?? new Date().toISOString() };
165
+ // Constant-size parent/header validation only. Individual file validation
166
+ // is bounded below; no full preflight/history rescan precedes each pass.
167
+ for (const target of targets) {
168
+ const held = await directory(target.path);
169
+ if (!held)
170
+ continue;
171
+ try {
172
+ await header(target.path, target.kind, identity, !first);
173
+ await held.check();
174
+ }
175
+ finally {
176
+ await held.close();
177
+ }
178
+ }
179
+ await writeEvidencePolicy(policy, first);
180
+ let removed = 0, pending = false;
181
+ for (let position = policy.directory_cursor; position < targets.length; position++) {
182
+ const target = targets[position];
183
+ const held = await directory(target.path);
184
+ if (!held) {
185
+ if (policy.pending_file)
186
+ throw invalid();
187
+ policy.directory_cursor = position + 1;
188
+ await writeEvidencePolicy(policy);
189
+ continue;
190
+ }
191
+ try {
192
+ const metadata = await header(target.path, target.kind, identity, true);
193
+ const complete = async (intent) => {
194
+ signal.throwIfAborted();
195
+ use.assertHeld();
196
+ await held.check();
197
+ if (intent.directory !== target.kind || intent.name === 'receipt.json')
198
+ throw invalid();
199
+ const path = join(target.path, intent.name), limit = maximum(intent.name, target.kind, metadata);
200
+ if (intent.size > limit)
201
+ throw invalid();
202
+ const actual = await privateFile(path, limit).catch(error => { if (error.code === 'ENOENT')
203
+ return null; throw error; });
204
+ if (actual && (actual.dev.toString() !== intent.device || actual.ino.toString() !== intent.inode || actual.size !== BigInt(intent.size)))
205
+ throw invalid();
206
+ if (actual) {
207
+ signal.throwIfAborted();
208
+ use.assertHeld();
209
+ await held.check();
210
+ const current = await lstat(path, { bigint: true });
211
+ if (current.dev !== actual.dev || current.ino !== actual.ino || current.size !== actual.size || current.isSymbolicLink() || current.nlink !== 1n)
212
+ throw invalid();
213
+ await unlink(path);
214
+ }
215
+ await held.sync();
216
+ if (!Number.isSafeInteger(policy.removed_files + 1))
217
+ throw invalid();
218
+ policy.removed_files++;
219
+ policy.pending_file = null;
220
+ // Durable per-file completion. After a crash, a still-pending exact
221
+ // inode is either unlinked once or its absence is reconciled once.
222
+ await writeEvidencePolicy(policy);
223
+ removed++;
224
+ };
225
+ if (policy.pending_file)
226
+ await complete(policy.pending_file);
227
+ for await (const item of await opendir(target.path, { bufferSize: 32 })) {
228
+ signal.throwIfAborted();
229
+ await held.check();
230
+ if (item.name === 'receipt.json')
231
+ continue; // Keep recovery metadata until every payload is removed.
232
+ if (['sync.lock', 'cloud-sync.lock', 'owner.lock'].includes(item.name))
233
+ throw new CliError('local_evidence_owned', 'An original evidence transfer still owns this operation. Its lock was not removed.');
234
+ if (removed >= MAX_PASS) {
235
+ pending = true;
236
+ break;
237
+ }
238
+ const path = join(target.path, item.name), before = await privateFile(path, maximum(item.name, target.kind, metadata));
239
+ policy.pending_file = { directory: target.kind, name: item.name, device: before.dev.toString(), inode: before.ino.toString(), size: Number(before.size) };
240
+ signal.throwIfAborted();
241
+ use.assertHeld();
242
+ await held.check();
243
+ await writeEvidencePolicy(policy); // Original intent BEFORE unlink.
244
+ await complete(policy.pending_file);
245
+ }
246
+ if (!pending) {
247
+ // Keep the bounded non-secret original summary for closing/hash
248
+ // recovery, not raw event/page/artifact bytes. This never restores
249
+ // raw access: the permanent removal tombstone fences every reader.
250
+ let retainedMetadata = false;
251
+ for await (const item of await opendir(target.path, { bufferSize: 32 })) {
252
+ if (item.name !== 'receipt.json') {
253
+ pending = true;
254
+ break;
255
+ }
256
+ await privateFile(join(target.path, item.name), 65536);
257
+ retainedMetadata = true;
258
+ }
259
+ await held.check();
260
+ await held.sync();
261
+ if (!pending) {
262
+ if (!retainedMetadata)
263
+ await rmdir(target.path);
264
+ policy.directory_cursor = position + 1;
265
+ await writeEvidencePolicy(policy);
266
+ }
267
+ }
268
+ await held.sync();
269
+ }
270
+ finally {
271
+ await held.close();
272
+ }
273
+ if (pending)
274
+ break;
275
+ }
276
+ if (!pending)
277
+ policy = { ...policy, state: 'removed', finished_at: new Date().toISOString() };
278
+ await writeEvidencePolicy(policy);
279
+ return { state: policy.state, already_removed: false, file_count: policy.removed_files };
280
+ });
281
+ }
282
+ finally {
283
+ await use.close();
284
+ }
285
+ }
286
+ export async function forgetLocalEvidence(operation, options, output) {
287
+ const context = await selected(operation, options);
288
+ if (context.purpose === 'world_session')
289
+ return forgetWorldEvidence(operation, options, output);
290
+ if (!options.approve) {
291
+ if (options.json || !process.stdin.isTTY || !process.stderr.isTTY)
292
+ throw new CliError('local_evidence_erasure_consent', 'Use --discard-evidence after reviewing this exact operation. Unsynchronized local evidence cannot be recovered; cloud records are unchanged.', 2);
293
+ if (!await selectOne('Remove private local evidence for ' + context.operation + '?', [{ value: false, label: 'Keep evidence' },
294
+ { value: true, label: 'Remove local evidence', detail: 'Unsynchronized bytes cannot be recovered. Original container cleanup metadata and cloud reports remain.' }], options.signal))
295
+ return output({ state: 'kept', operation_id: context.operation }, 'Private local evidence kept.');
296
+ }
297
+ const receipt = await erase(context, options.signal, false);
298
+ output({ schema: 'firedrill.local-evidence-erasure.v1', operation_id: context.operation, workspace_id: context.workspace,
299
+ purpose: context.purpose, ...receipt, cloud_data_changed: false, filesystem_backup_erasure_attested: false }, receipt.state === 'removing' ? 'A bounded evidence removal pass finished. Repeat this exact forget command to continue. Cloud records are unchanged.'
300
+ : 'Exact private evidence bytes removed. Non-secret original recovery metadata and cloud records are unchanged. Filesystem backups are not covered.');
301
+ }
302
+ export async function inspectLocalEvidence(operation, options, output) {
303
+ const context = await selected(operation, { ...options, approve: false });
304
+ if (context.purpose === 'world_session')
305
+ return output({ operation_id: context.operation, purpose: context.purpose,
306
+ metadata_only: true, cloud_data_changed: false }, 'Manual-world evidence uses its original encrypted journal. Inspect its saved session or exact forget command.');
307
+ const policy = await readEvidencePolicy(context, context.operation);
308
+ output({ operation_id: context.operation, purpose: context.purpose, workspace_id: context.workspace, metadata_only: true,
309
+ policy: policy ? { state: policy.state, expires_at: policy.expires_at, removed_files: policy.removed_files, finished_at: policy.finished_at,
310
+ directory_cursor: policy.directory_cursor, pending_file: policy.pending_file ? { directory: policy.pending_file.directory, name: policy.pending_file.name } : null } : null,
311
+ retention_state: !policy ? 'legacy_policy_missing' : policy.state !== 'retained' ? policy.state : policy.expires_at === null
312
+ ? 'legacy_policy_missing' : Date.parse(policy.expires_at) <= Date.now() ? 'expired' : 'within_policy', cloud_data_changed: false }, !policy ? 'Older private evidence has no frozen retention policy. Raw reads and uploads are disabled. Explicit forget remains available after original cleanup.'
313
+ : `Private evidence: ${policy.state}; expiry ${policy.expires_at ?? 'not established'}. ${policy.state === 'removing' ? 'Continue the same original forget command. ' : ''}This metadata does not establish cloud retention or target outcome.`);
314
+ }
315
+ export async function pruneLocalEvidence(options, output) {
316
+ // Preserve the established encrypted manual-world path, then reconcile only
317
+ // validation/trial originals with finite policy and confirmed cleanup/sync.
318
+ await pruneWorldEvidence(options, output);
319
+ let inspected = 0, removed = 0, kept = 0, deferred = 0;
320
+ for await (const context of localEvidenceContexts(undefined, options.workspace, (operation, code) => {
321
+ deferred++;
322
+ output({ state: 'deferred', operation_id: operation, code, metadata_only: true }, `${operation}: ${code}. Nothing was removed.`);
323
+ }, options.signal)) {
324
+ if (context.purpose === 'world_session')
325
+ continue;
326
+ options.signal.throwIfAborted();
327
+ inspected++;
328
+ try {
329
+ const result = await erase(context, options.signal, true);
330
+ if (result.state === 'kept')
331
+ kept++;
332
+ else if (result.state === 'removed')
333
+ removed++;
334
+ else
335
+ deferred++;
336
+ if (result.state !== 'kept')
337
+ output({ operation_id: context.operation, purpose: context.purpose, ...result, cloud_data_changed: false }, `${context.operation}: ${result.state}. Cloud records and cleanup metadata are unchanged.`);
338
+ }
339
+ catch (error) {
340
+ if (!(error instanceof CliError) && error.code !== 'ENOENT')
341
+ throw error;
342
+ deferred++;
343
+ const code = error instanceof CliError ? error.code : 'local_evidence_sync_unresolved';
344
+ output({ operation_id: context.operation, state: 'deferred', code, metadata_only: true }, `${context.operation}: ${code}. Removal is not confirmed complete. Inspect its original metadata; cloud records are unchanged.`);
345
+ }
346
+ if (inspected % 250 === 0)
347
+ output({ schema: 'firedrill.local-evidence-prune-progress.v1', inspected, removed, kept, deferred }, `Inspected ${inspected} original local receipts using bounded metadata batches.`);
348
+ }
349
+ output({ schema: 'firedrill.local-evidence-prune.v1', inspected, removed, kept, deferred, bounded: true,
350
+ cloud_data_changed: false, filesystem_backup_erasure_attested: false }, `Validation/drill evidence: ${removed} removed, ${kept} within policy, ${deferred} require explicit attention. Interrupted bounded removals can continue with the same original operation.`);
351
+ }
@@ -0,0 +1,37 @@
1
+ import { CliError } from './http.js';
2
+ /** Transient comparison values only. Never persist these buffers in a journal. */
3
+ export function evidenceNeedles(configuration) {
4
+ const values = new Set(), names = configuration.control.secret_variable_names;
5
+ if (!Array.isArray(names) || names.some(name => typeof name !== 'string'))
6
+ throw new CliError('local_evidence_invalid', 'The evidence privacy policy is unavailable.');
7
+ for (const phase of ['build', 'runtime'])
8
+ for (const name of names) {
9
+ const value = configuration.target_phases[phase][String(name)];
10
+ if (value)
11
+ values.add(value);
12
+ }
13
+ for (const item of configuration.model_channels)
14
+ values.add(item.token);
15
+ for (const item of configuration.voice_channels)
16
+ values.add(item.token);
17
+ for (const item of configuration.real_channels) {
18
+ values.add(item.token);
19
+ for (const marker of Object.values(item.markers))
20
+ values.add(marker);
21
+ }
22
+ if (configuration.participant_channel)
23
+ values.add(configuration.participant_channel.token);
24
+ const result = [];
25
+ for (const value of values) {
26
+ const quoted = JSON.stringify(value).slice(1, -1);
27
+ for (const item of [value, quoted, quoted.replace(/[\u007f-\uffff]/g, char => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0')),
28
+ Buffer.from(value).toString('base64')])
29
+ result.push(Buffer.from(item));
30
+ }
31
+ return result.filter(item => item.length);
32
+ }
33
+ export function assertEvidencePrivacy(body, blocked) {
34
+ if (blocked.some(value => body.includes(value)) || /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|\b(?:sk-ant-api\d+-|gh[pousr]_|github_pat_|xox[baprs]-)[A-Za-z0-9_-]{16,}/.test(body.toString('latin1'))) {
35
+ throw new CliError('local_evidence_private_value', 'A private value was found in evidence. It was not uploaded.');
36
+ }
37
+ }
@@ -0,0 +1,161 @@
1
+ import { constants } from 'node:fs';
2
+ import { lstat, open, rename } from 'node:fs/promises';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { join } from 'node:path';
5
+ import { privateDirectory } from './credentials.js';
6
+ import { CliError } from './http.js';
7
+ import { identifier } from './world-contracts.js';
8
+ import { processLifetimeLock } from './runner-process-lock.js';
9
+ const HASH = /^[a-f0-9]{64}$/;
10
+ const fail = () => new CliError('local_evidence_retention_invalid', 'This operation’s private evidence policy could not be confirmed. No evidence was read, uploaded or removed.');
11
+ const missing = () => new CliError('local_evidence_retention_missing', 'This older local evidence has no frozen retention policy. Only metadata inspection and explicit runner evidence forget are available; no new expiry or upload authority was invented.');
12
+ const expired = () => new CliError('local_evidence_expired', 'Local evidence retention ended. Use runner evidence forget for this exact operation. No target is run to reconstruct it.');
13
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
14
+ const date = (value) => typeof value === 'string' && Number.isSafeInteger(Date.parse(value));
15
+ const identity = (value) => {
16
+ for (const key of ['owner', 'workspace', 'device', 'job', 'request', 'lease', 'attempt'])
17
+ identifier(value[key]);
18
+ if (!['validation', 'trial'].includes(value.purpose) || !HASH.test(value.manifest_sha256))
19
+ throw fail();
20
+ return value;
21
+ };
22
+ const same = (left, right) => ['owner', 'workspace', 'device', 'job', 'request', 'lease', 'attempt', 'purpose', 'manifest_sha256']
23
+ .every(key => left[key] === right[key]);
24
+ const prefix = (scope, operation) => [identifier(scope.owner), identifier(scope.workspace), identifier(operation)].join('-');
25
+ const policyPath = async (scope, operation) => join(await privateDirectory(), `evidence-policy-${prefix(scope, operation)}.json`);
26
+ /** Metadata files never contain target evidence, credentials or customer paths. */
27
+ export async function privateEvidenceMetadata(path, maximum = 65536) {
28
+ const before = await lstat(path);
29
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.mode & 0o077 || before.size > maximum
30
+ || process.getuid && before.uid !== process.getuid())
31
+ throw fail();
32
+ const file = await open(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
33
+ try {
34
+ const actual = await file.stat();
35
+ if (actual.dev !== before.dev || actual.ino !== before.ino || actual.size !== before.size)
36
+ throw fail();
37
+ const body = await file.readFile('utf8'), after = await file.stat();
38
+ if (actual.size !== after.size || actual.mtimeMs !== after.mtimeMs || actual.ctimeMs !== after.ctimeMs)
39
+ throw fail();
40
+ return JSON.parse(body);
41
+ }
42
+ catch (error) {
43
+ if (error instanceof CliError)
44
+ throw error;
45
+ throw fail();
46
+ }
47
+ finally {
48
+ await file.close();
49
+ }
50
+ }
51
+ export function frozenEvidenceRetention(owner, manifest, expiresAt) {
52
+ if (!date(expiresAt))
53
+ throw missing();
54
+ const value = { schema: 'firedrill.local-evidence-retention.v1', owner: owner.owner, workspace: owner.workspace,
55
+ device: owner.device, job: owner.job, request: owner.request, lease: owner.lease, attempt: owner.attempt, purpose: owner.purpose,
56
+ manifest_sha256: manifest, expires_at: expiresAt };
57
+ identity(value);
58
+ assertEvidenceRetentionCurrent(value);
59
+ return value;
60
+ }
61
+ export function assertEvidenceRetentionCurrent(value) {
62
+ identity(value);
63
+ if (value.schema !== 'firedrill.local-evidence-retention.v1' || !date(value.expires_at))
64
+ throw missing();
65
+ if (Date.parse(value.expires_at) <= Date.now())
66
+ throw expired();
67
+ }
68
+ export function evidenceRetentionSignal(value, signal) {
69
+ assertEvidenceRetentionCurrent(value);
70
+ return AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, Math.min(2147483647, Date.parse(value.expires_at) - Date.now())))]);
71
+ }
72
+ export function retentionMatches(value, expected) {
73
+ return object(value) && Object.keys(value).sort().join(',') === 'attempt,device,expires_at,job,lease,manifest_sha256,owner,purpose,request,schema,workspace'
74
+ && value.schema === expected.schema && value.expires_at === expected.expires_at && same(value, expected);
75
+ }
76
+ export async function readEvidencePolicy(scope, operation) {
77
+ const path = await policyPath(scope, operation);
78
+ const value = await privateEvidenceMetadata(path).catch(error => { if (error.code === 'ENOENT')
79
+ return null; throw error; });
80
+ if (value === null)
81
+ return null;
82
+ if (!object(value) || Object.keys(value).sort().join(',') !== 'attempt,device,directory_cursor,expires_at,finished_at,job,lease,manifest_sha256,owner,pending_file,purpose,removal_started_at,removed_files,request,schema,state,workspace'
83
+ || value.schema !== 'firedrill.local-evidence-policy.v1' || value.owner !== scope.owner || value.workspace !== scope.workspace || value.lease !== operation
84
+ || !['retained', 'removing', 'removed'].includes(String(value.state)) || value.expires_at !== null && !date(value.expires_at)
85
+ || value.removal_started_at !== null && !date(value.removal_started_at) || value.finished_at !== null && !date(value.finished_at)
86
+ || !Number.isSafeInteger(value.removed_files) || Number(value.removed_files) < 0
87
+ || !Number.isSafeInteger(value.directory_cursor) || Number(value.directory_cursor) < 0
88
+ || Number(value.directory_cursor) > (value.purpose === 'validation' ? 1 : 2)
89
+ || value.pending_file !== null && (!object(value.pending_file)
90
+ || Object.keys(value.pending_file).sort().join(',') !== 'device,directory,inode,name,size'
91
+ || !['validation', 'trial', 'live'].includes(String(value.pending_file.directory))
92
+ || typeof value.pending_file.name !== 'string' || value.pending_file.name.length > 128 || /[\\/\0]/.test(value.pending_file.name)
93
+ || typeof value.pending_file.device !== 'string' || !/^[0-9]{1,32}$/.test(value.pending_file.device)
94
+ || typeof value.pending_file.inode !== 'string' || !/^[0-9]{1,32}$/.test(value.pending_file.inode)
95
+ || !Number.isSafeInteger(value.pending_file.size) || Number(value.pending_file.size) < 0 || Number(value.pending_file.size) > 512 * 1024 ** 2)
96
+ || value.state === 'retained' && (value.expires_at === null || value.removal_started_at !== null || value.finished_at !== null)
97
+ || value.state === 'retained' && (value.pending_file !== null || value.directory_cursor !== 0)
98
+ || value.state === 'removed' && (value.pending_file !== null || value.directory_cursor !== (value.purpose === 'validation' ? 1 : 2))
99
+ || value.state !== 'retained' && !date(value.removal_started_at) || value.state === 'removed' && !date(value.finished_at))
100
+ throw fail();
101
+ identity(value);
102
+ return value;
103
+ }
104
+ export async function writeEvidencePolicy(value, initial = false) {
105
+ identity(value);
106
+ const path = await policyPath(value, value.lease), pending = initial ? path : path + '.' + randomUUID() + '.tmp';
107
+ const file = await open(pending, 'wx', 0o600);
108
+ try {
109
+ await file.writeFile(JSON.stringify(value));
110
+ await file.sync();
111
+ }
112
+ finally {
113
+ await file.close();
114
+ }
115
+ if (!initial)
116
+ await rename(pending, path);
117
+ const parent = await open(await privateDirectory(), constants.O_RDONLY | (constants.O_DIRECTORY ?? 0));
118
+ try {
119
+ await parent.sync();
120
+ }
121
+ finally {
122
+ await parent.close();
123
+ }
124
+ }
125
+ /** Shared kernel ownership across collectors, sync and erasure. Persistent
126
+ * metadata files do not imply ownership after the broker process has exited. */
127
+ export async function lockLocalEvidence(scope, operation) {
128
+ return processLifetimeLock(join(await privateDirectory(), `evidence-use-${prefix(scope, operation)}.lock`));
129
+ }
130
+ export async function acquireLocalEvidence(scope, operation, initialize) {
131
+ const lock = await lockLocalEvidence(scope, operation);
132
+ try {
133
+ let saved = await readEvidencePolicy(scope, operation);
134
+ if (!saved && initialize) {
135
+ if (initialize.owner !== scope.owner || initialize.workspace !== scope.workspace || initialize.lease !== operation)
136
+ throw fail();
137
+ assertEvidenceRetentionCurrent(initialize);
138
+ saved = { ...initialize, schema: 'firedrill.local-evidence-policy.v1', state: 'retained', removed_files: 0,
139
+ removal_started_at: null, finished_at: null, directory_cursor: 0, pending_file: null };
140
+ await writeEvidencePolicy(saved, true);
141
+ }
142
+ if (!saved)
143
+ throw missing();
144
+ if (saved.state !== 'retained')
145
+ throw new CliError('local_evidence_removal_started', 'Private evidence removal was already requested for this operation. Continue the exact forget command; no files were recreated.');
146
+ if (initialize && (!same(saved, initialize) || saved.expires_at !== initialize.expires_at))
147
+ throw fail();
148
+ if (!date(saved.expires_at))
149
+ throw missing();
150
+ const retention = { schema: 'firedrill.local-evidence-retention.v1', owner: saved.owner, workspace: saved.workspace,
151
+ device: saved.device, job: saved.job, request: saved.request, lease: saved.lease, attempt: saved.attempt, purpose: saved.purpose,
152
+ manifest_sha256: saved.manifest_sha256, expires_at: saved.expires_at };
153
+ assertEvidenceRetentionCurrent(retention);
154
+ lock.assertHeld();
155
+ return { retention, signal: lock.signal, assertCurrent: () => { lock.assertHeld(); assertEvidenceRetentionCurrent(retention); }, close: lock.close };
156
+ }
157
+ catch (error) {
158
+ await lock.close();
159
+ throw error;
160
+ }
161
+ }