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,942 @@
1
+ // Consumers for the root-released native cloud authoring producers (see the
2
+ // root plan20-contracts document): the private native library (personas,
3
+ // scenarios, evaluations), native world declarations bound to a registered
4
+ // descriptor revision, and effect-free apply-plan review. None of these
5
+ // provision, quote, reserve funding or execute anything. Producer source exists
6
+ // but is not deployed or qualified, so these calls are wiring-ready, not
7
+ // verified. Definitions are validated by the service; the client only bounds
8
+ // size and shape and never invents fields.
9
+ import { api } from '../auth.js';
10
+ import { creditApprovalPolicy } from './credit-approval.js';
11
+ import { CliError } from '../http.js';
12
+ import { ENDING_REASONS, SHA256_HEX } from './contracts.js';
13
+ import { registrationKey } from './registration.js';
14
+ export const LIBRARY_KINDS = ['persona', 'scenario', 'evaluation'];
15
+ export const LIBRARY_PARAMETERS_SCHEMA = 'firedrill.native-library-parameters.v1';
16
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17
+ const RESPONSE_LIMIT = 4 * 1024 * 1024;
18
+ /** Matches the producer's aggregate document ceiling; larger definitions are refused before upload. */
19
+ const DEFINITION_LIMIT = 2 * 1024 * 1024;
20
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
21
+ const unconfirmed = (what) => new CliError('native_response_unconfirmed', `The ${what} response could not be confirmed. Nothing local changed; rerun with the same request key to read the saved result.`);
22
+ const scoped = (scope, signal) => ({ actor: scope.owner, workspace: scope.workspace, signal, maximumResponseBytes: RESPONSE_LIMIT });
23
+ function id(value, label) {
24
+ if (typeof value !== 'string' || !UUID.test(value))
25
+ throw new CliError('arguments_invalid', `Use the ${label} id exactly as Firedrill printed it. Nothing was submitted.`, 2);
26
+ return value.toLowerCase();
27
+ }
28
+ function revisionNumber(value) {
29
+ if (!Number.isSafeInteger(value) || value < 0 || value > 2147483646) {
30
+ throw new CliError('native_revision_invalid', 'Use the current revision number (0 for a new resource). Nothing was submitted.', 2);
31
+ }
32
+ return value;
33
+ }
34
+ function definitionObject(value, label) {
35
+ if (!object(value))
36
+ throw new CliError('request_file_invalid', `The ${label} definition must be a JSON object. Nothing was submitted.`, 2);
37
+ if (Buffer.byteLength(JSON.stringify(value)) > DEFINITION_LIMIT) {
38
+ throw new CliError('request_file_invalid', `The ${label} definition exceeds ${DEFINITION_LIMIT} bytes. Nothing was submitted.`, 2);
39
+ }
40
+ return value;
41
+ }
42
+ function page(raw, row, what) {
43
+ if (!object(raw) || typeof raw.count !== 'number' || !Array.isArray(raw.results))
44
+ throw unconfirmed(what);
45
+ return { count: raw.count, page: typeof raw.page === 'number' ? raw.page : null,
46
+ next: typeof raw.next === 'string' ? raw.next : null, previous: typeof raw.previous === 'string' ? raw.previous : null,
47
+ results: raw.results.map(row) };
48
+ }
49
+ function pageQuery(value) {
50
+ const number = value ?? 1;
51
+ if (!Number.isSafeInteger(number) || number < 1)
52
+ throw new CliError('native_page_invalid', 'Use a page number of 1 or more.', 2);
53
+ return `page=${number}`;
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Library.
57
+ /** Root's typed parameter declaration (1-16 items, each with exactly its seven fields), or null. */
58
+ function parameterSchema(value) {
59
+ if (value === null)
60
+ return true;
61
+ if (!object(value) || Object.keys(value).sort().join(',') !== 'parameters,schema' || value.schema !== LIBRARY_PARAMETERS_SCHEMA
62
+ || !Array.isArray(value.parameters) || value.parameters.length < 1 || value.parameters.length > 16)
63
+ return false;
64
+ const names = new Set();
65
+ return value.parameters.every((item) => {
66
+ if (!object(item) || Object.keys(item).sort().join(',') !== 'default,maximum,minimum,name,path,required,type'
67
+ || typeof item.name !== 'string' || !item.name || names.has(item.name) || typeof item.required !== 'boolean'
68
+ || (item.type !== 'string' && item.type !== 'integer') || !Array.isArray(item.path) || item.path.length < 1 || item.path.length > 2
69
+ || typeof item.path[0] !== 'string' || (item.path.length === 2 && !Number.isSafeInteger(item.path[1]))
70
+ || !Number.isSafeInteger(item.maximum) || item.maximum < 0
71
+ || (item.type === 'string' ? item.minimum !== null : !Number.isSafeInteger(item.minimum) || item.minimum > item.maximum)
72
+ || (item.required ? item.default !== null
73
+ : item.type === 'string' ? typeof item.default !== 'string' : !Number.isSafeInteger(item.default)))
74
+ return false;
75
+ names.add(item.name);
76
+ return true;
77
+ });
78
+ }
79
+ /** A bound copy's exact values and the source version they were fixed from, or null. */
80
+ function parameterBinding(value) {
81
+ return value === null || (object(value) && Object.keys(value).sort().join(',') === 'declaration_digest,schema,source_digest,source_revision_id,values'
82
+ && value.schema === LIBRARY_PARAMETERS_SCHEMA && typeof value.declaration_digest === 'string' && SHA256_HEX.test(value.declaration_digest)
83
+ && typeof value.source_revision_id === 'string' && UUID.test(value.source_revision_id)
84
+ && typeof value.source_digest === 'string' && SHA256_HEX.test(value.source_digest) && object(value.values)
85
+ && Object.values(value.values).every((one) => typeof one === 'string' || Number.isSafeInteger(one)));
86
+ }
87
+ export function libraryRow(withDefinition) {
88
+ return (value) => {
89
+ if (!object(value) || typeof value.id !== 'string' || !UUID.test(value.id) || typeof value.resource_id !== 'string'
90
+ || !UUID.test(value.resource_id) || value.origin !== 'native_library' || !LIBRARY_KINDS.includes(value.kind)
91
+ || typeof value.name !== 'string' || typeof value.revision !== 'number' || !Number.isSafeInteger(value.revision)
92
+ || typeof value.digest !== 'string' || !SHA256_HEX.test(value.digest) || (value.state !== 'draft' && value.state !== 'archived')
93
+ || typeof value.current !== 'boolean' || typeof value.created_at !== 'string'
94
+ || (value.forked_from !== null && (typeof value.forked_from !== 'string' || !UUID.test(value.forked_from)))
95
+ || typeof value.owner_workspace_id !== 'string' || !UUID.test(value.owner_workspace_id)
96
+ || typeof value.can_edit !== 'boolean' || typeof value.can_use !== 'boolean'
97
+ || (withDefinition && (!object(value.definition) || !parameterSchema(value.parameter_schema) || !parameterBinding(value.parameter_binding)))
98
+ || (!withDefinition && ('parameter_schema' in value || 'parameter_binding' in value)))
99
+ throw unconfirmed('library');
100
+ return value;
101
+ };
102
+ }
103
+ /** Whether this version belongs to another workspace and is visible here only through a sharing grant. */
104
+ export const sharedHere = (scope, row) => row.owner_workspace_id.toLowerCase() !== scope.workspace.toLowerCase();
105
+ /** Lists current library resources (newest first). Archived resources only when asked. */
106
+ export async function listLibrary(scope, input) {
107
+ const params = new URLSearchParams(pageQuery(input.page));
108
+ if (input.kind !== undefined) {
109
+ if (!LIBRARY_KINDS.includes(input.kind))
110
+ throw new CliError('arguments_invalid', `Use --kind ${LIBRARY_KINDS.join('|')}.`, 2);
111
+ params.set('kind', input.kind);
112
+ }
113
+ params.set('state', input.archived ? 'archived' : 'draft');
114
+ if (input.search !== undefined) {
115
+ if ([...input.search].length > 120)
116
+ throw new CliError('arguments_invalid', 'Use a search prefix of up to 120 characters.', 2);
117
+ params.set('q', input.search);
118
+ }
119
+ const raw = await api(`/api/workspaces/${scope.workspace}/native-library?${params}`, scoped(scope, input.signal));
120
+ return page(raw, libraryRow(false), 'library');
121
+ }
122
+ /** Reads one exact library version with its definition. */
123
+ export async function getLibraryRevision(scope, input) {
124
+ const raw = await api(`/api/workspaces/${scope.workspace}/native-library/revisions/${id(input.revisionId, 'library version')}`, scoped(scope, input.signal));
125
+ return libraryRow(true)(raw);
126
+ }
127
+ /** Lists a library resource's immutable versions, newest first. */
128
+ export async function libraryHistory(scope, input) {
129
+ const raw = await api(`/api/workspaces/${scope.workspace}/native-library/${id(input.resourceId, 'library resource')}/revisions?${pageQuery(input.page)}`, scoped(scope, input.signal));
130
+ return page(raw, libraryRow(false), 'library history');
131
+ }
132
+ /**
133
+ * Records one library change as a new immutable version:
134
+ * - save without resourceId creates a resource (expectedRevision 0);
135
+ * - save/archive/restore with resourceId changes it at expectedRevision;
136
+ * - fork copies an exact version (forkFrom) into a new private resource with the given definition.
137
+ * The request key makes a retry replay the same change instead of creating another.
138
+ * parameterSchema applies to save and fork only: omitted keeps the existing (or
139
+ * forked) declaration, null removes it, and an object declares new parameters.
140
+ */
141
+ export async function changeLibrary(scope, input) {
142
+ if (!LIBRARY_KINDS.includes(input.kind))
143
+ throw new CliError('arguments_invalid', `Use --kind ${LIBRARY_KINDS.join('|')}. Nothing was submitted.`, 2);
144
+ const expected = revisionNumber(input.expectedRevision);
145
+ const resource = input.resourceId === undefined ? null : id(input.resourceId, 'library resource');
146
+ const replacing = input.action === 'save' || input.action === 'fork';
147
+ if (input.action === 'fork' ? resource !== null || input.forkFrom === undefined || expected !== 0 : input.forkFrom !== undefined)
148
+ throw new CliError('arguments_invalid', 'Fork an exact library version into a new resource with revision 0. Nothing was submitted.', 2);
149
+ if ((input.action === 'archive' || input.action === 'restore') && (resource === null || input.definition !== undefined))
150
+ throw new CliError('arguments_invalid', 'Archive or restore an existing resource without a definition. Nothing was submitted.', 2);
151
+ if (input.action === 'save' && resource === null && expected !== 0)
152
+ throw new CliError('native_revision_invalid', 'A new library resource starts at revision 0. Nothing was submitted.', 2);
153
+ if (input.parameterSchema !== undefined && !replacing) {
154
+ throw new CliError('arguments_invalid', 'Declare or remove parameters only when saving or forking a definition. Nothing was submitted.', 2);
155
+ }
156
+ if (input.parameterSchema !== undefined && input.parameterSchema !== null && !parameterSchema(input.parameterSchema)) {
157
+ throw new CliError('request_file_invalid', `Parameters must be {"schema": "${LIBRARY_PARAMETERS_SCHEMA}", "parameters": [...]} with 1-16 items, each with exactly `
158
+ + 'name, path, type (string or integer), minimum, maximum, required and default. Nothing was submitted.', 2);
159
+ }
160
+ const requestKey = registrationKey(input.requestKey);
161
+ const body = {
162
+ request_key: requestKey, action: input.action, expected_revision: expected, kind: input.kind,
163
+ definition: replacing ? definitionObject(input.definition, input.kind) : null,
164
+ fork_from: input.action === 'fork' ? id(input.forkFrom, 'library version') : null,
165
+ ...input.parameterSchema === undefined ? {} : { parameter_schema: input.parameterSchema },
166
+ };
167
+ const path = resource === null ? `/api/workspaces/${scope.workspace}/native-library`
168
+ : `/api/workspaces/${scope.workspace}/native-library/${resource}/revisions`;
169
+ const row = libraryRow(true)(await api(path, { method: 'POST', body, ...scoped(scope, input.signal) }));
170
+ if (row.kind !== input.kind || (resource !== null && row.resource_id !== resource)
171
+ || row.state !== (input.action === 'archive' ? 'archived' : 'draft'))
172
+ throw unconfirmed('library change');
173
+ return { ...row, request_key: requestKey };
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // Native worlds.
177
+ export function worldRow(withDefinition) {
178
+ return (value) => {
179
+ if (!object(value) || typeof value.id !== 'string' || !UUID.test(value.id) || typeof value.world_id !== 'string' || !UUID.test(value.world_id)
180
+ || typeof value.revision !== 'number' || !Number.isSafeInteger(value.revision) || value.revision < 1
181
+ || typeof value.native_descriptor_id !== 'string' || !UUID.test(value.native_descriptor_id)
182
+ || typeof value.definition_digest !== 'string' || !SHA256_HEX.test(value.definition_digest)
183
+ || typeof value.created_at !== 'string' || value.state !== 'draft' || value.execution_started !== false
184
+ || (withDefinition && !object(value.definition)))
185
+ throw unconfirmed('world');
186
+ return value;
187
+ };
188
+ }
189
+ /**
190
+ * Saves a native world declaration bound to an exact registered descriptor
191
+ * revision. Without worldId it creates the world (expectedRevision 0);
192
+ * with worldId it records the next revision. Nothing is provisioned or run.
193
+ */
194
+ export async function saveNativeWorld(scope, input) {
195
+ const expected = revisionNumber(input.expectedRevision);
196
+ const world = input.worldId === undefined ? null : id(input.worldId, 'world');
197
+ if (world === null && expected !== 0)
198
+ throw new CliError('native_revision_invalid', 'A new world starts at revision 0. Nothing was submitted.', 2);
199
+ const requestKey = registrationKey(input.requestKey);
200
+ const body = {
201
+ request_key: requestKey, agent_id: id(input.agentId, 'agent'), native_descriptor_id: id(input.descriptorRevisionId, 'descriptor revision'),
202
+ expected_revision: expected, definition: definitionObject(input.definition, 'world'),
203
+ };
204
+ const path = world === null ? `/api/workspaces/${scope.workspace}/native-worlds`
205
+ : `/api/workspaces/${scope.workspace}/worlds/${world}/native-revisions`;
206
+ const row = worldRow(true)(await api(path, { method: 'POST', body, ...scoped(scope, input.signal) }));
207
+ if ((world !== null && row.world_id !== world) || row.revision !== expected + 1 || row.native_descriptor_id !== body.native_descriptor_id)
208
+ throw unconfirmed('world change');
209
+ return { ...row, request_key: requestKey };
210
+ }
211
+ /** Lists a native world's declaration revisions, newest first. */
212
+ export async function worldHistory(scope, input) {
213
+ const raw = await api(`/api/workspaces/${scope.workspace}/worlds/${id(input.worldId, 'world')}/native-revisions?${pageQuery(input.page)}`, scoped(scope, input.signal));
214
+ return page(raw, worldRow(false), 'world history');
215
+ }
216
+ /** Reads one exact native world revision with its definition. */
217
+ export async function getWorldRevision(scope, input) {
218
+ const raw = await api(`/api/workspaces/${scope.workspace}/worlds/${id(input.worldId, 'world')}/native-revisions/${id(input.revisionId, 'world revision')}`, scoped(scope, input.signal));
219
+ return worldRow(true)(raw);
220
+ }
221
+ /** The newest revision of a native world (what `plan --world` reviews by default). */
222
+ export async function latestWorldRevision(scope, input) {
223
+ const history = await worldHistory(scope, { worldId: input.worldId, page: 1, signal: input.signal });
224
+ const newest = history.results[0];
225
+ if (!newest)
226
+ throw new CliError('native_world_empty', 'This world has no saved declaration yet. Save one with firedrill world save.', 2);
227
+ return newest;
228
+ }
229
+ // ---------------------------------------------------------------------------
230
+ // Apply-plan review (effect-free).
231
+ function planRow(value) {
232
+ if (!object(value) || typeof value.id !== 'string' || !UUID.test(value.id) || typeof value.world_revision_id !== 'string'
233
+ || !UUID.test(value.world_revision_id) || typeof value.review_digest !== 'string' || !SHA256_HEX.test(value.review_digest)
234
+ || typeof value.expires_at !== 'string' || typeof value.expired !== 'boolean' || !object(value.review)
235
+ || value.review.schema !== 'firedrill.native-apply-review.v1' || !Array.isArray(value.review.gaps)
236
+ || !Array.isArray(value.review.real_dependency_grants) || !Array.isArray(value.review.required_gates))
237
+ throw unconfirmed('plan review');
238
+ return value;
239
+ }
240
+ /**
241
+ * Creates an effect-free review of one exact world revision. The digest pins
242
+ * the revision the caller read; a changed world is refused by the service.
243
+ */
244
+ export async function createApplyPlan(scope, input) {
245
+ if (!SHA256_HEX.test(input.worldDigest))
246
+ throw new CliError('arguments_invalid', 'Use the world digest exactly as Firedrill printed it. Nothing was submitted.', 2);
247
+ const requestKey = registrationKey(input.requestKey);
248
+ const revision = id(input.worldRevisionId, 'world revision');
249
+ const row = planRow(await api(`/api/workspaces/${scope.workspace}/native-apply-plans`, {
250
+ method: 'POST', body: { request_key: requestKey, world_revision_id: revision, world_digest: input.worldDigest }, ...scoped(scope, input.signal),
251
+ }));
252
+ if (row.world_revision_id !== revision)
253
+ throw unconfirmed('plan review');
254
+ return { ...row, request_key: requestKey };
255
+ }
256
+ /** Reads a saved plan review (including whether it expired). */
257
+ export async function getApplyPlan(scope, input) {
258
+ return planRow(await api(`/api/workspaces/${scope.workspace}/native-apply-plans/${id(input.planId, 'plan')}`, scoped(scope, input.signal)));
259
+ }
260
+ const safeUnits = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
261
+ /** A sanity bound on quoted cloud-work components; which components exist is root's to say. */
262
+ const MAX_FUNDING_COMPONENTS = 32;
263
+ function fundingRow(value, planId, reviewDigest) {
264
+ const invalid = () => unconfirmed('cloud-work estimate');
265
+ if (!object(value) || value.schema !== 'firedrill.native-funding-bundle.v1' || value.reserves_credits !== false
266
+ || typeof value.digest !== 'string' || !SHA256_HEX.test(value.digest) || !safeUnits(value.maximum_units)
267
+ || typeof value.denomination !== 'string' || !/^[\x20-\x7e]{1,32}$/.test(value.denomination)
268
+ || !safeUnits(value.credit_scale) || value.credit_scale < 1 || typeof value.expires_at !== 'string'
269
+ || !Number.isFinite(Date.parse(value.expires_at)) || !Array.isArray(value.quotes) || value.quotes.length < 1
270
+ // Root's quoted components are its own list (it changes with billing); only bounded here, never enumerated.
271
+ || value.quotes.length > MAX_FUNDING_COMPONENTS)
272
+ throw invalid();
273
+ const bundle = value;
274
+ let total = 0;
275
+ const quotes = bundle.quotes.map((quote) => {
276
+ const scope = object(quote) ? quote.scope : null;
277
+ if (!object(quote) || typeof quote.id !== 'string' || !UUID.test(quote.id) || !object(scope)
278
+ || scope.plan_id !== planId || scope.review_digest !== reviewDigest || scope.local_target_compute_charged !== false
279
+ || typeof scope.component !== 'string' || !/^[a-z_]{1,32}$/.test(scope.component)
280
+ || !safeUnits(quote.maximum_units) || !safeUnits(quote.estimated_units)
281
+ || quote.denomination !== bundle.denomination || quote.credit_scale !== bundle.credit_scale)
282
+ throw invalid();
283
+ total += quote.maximum_units;
284
+ // Participant quotes always freeze their admission limits; no other component carries them.
285
+ const limits = object(scope.participant_limits) ? scope.participant_limits : null;
286
+ const participant_limits = limits && safeUnits(limits.max_steps) && safeUnits(limits.max_input_bytes_per_step)
287
+ && safeUnits(limits.max_output_tokens_per_step) && typeof limits.model === 'string' && /^[\x20-\x7e]{1,128}$/.test(limits.model)
288
+ ? { max_steps: limits.max_steps, max_input_bytes_per_step: limits.max_input_bytes_per_step,
289
+ max_output_tokens_per_step: limits.max_output_tokens_per_step, model: limits.model } : null;
290
+ if ((scope.component === 'participants') !== (participant_limits !== null) || (!participant_limits && scope.participant_limits !== undefined))
291
+ throw invalid();
292
+ // Funding v3: isolated custom-tool quotes freeze their run, cleanup and resource ceilings; no other component carries them.
293
+ const isolated = object(scope.isolated_limits) ? scope.isolated_limits : null;
294
+ const resources = isolated && object(isolated.resources) ? isolated.resources : null;
295
+ const positive = (value) => safeUnits(value) && value >= 1;
296
+ const isolated_limits = isolated && resources && positive(isolated.duration_seconds) && safeUnits(isolated.cleanup_seconds)
297
+ && positive(resources.cpu_units) && positive(resources.memory_mb) && positive(resources.disk_gb)
298
+ ? { duration_seconds: isolated.duration_seconds, cleanup_seconds: isolated.cleanup_seconds,
299
+ cpu_units: resources.cpu_units, memory_mb: resources.memory_mb, disk_gb: resources.disk_gb } : null;
300
+ if ((scope.component === 'isolated_runtime') !== (isolated_limits !== null) || (!isolated_limits && scope.isolated_limits !== undefined))
301
+ throw invalid();
302
+ return { ...creditApprovalPolicy(quote), id: quote.id, component: scope.component, maximum_units: quote.maximum_units, estimated_units: quote.estimated_units,
303
+ participant_limits, isolated_limits };
304
+ });
305
+ if (total !== bundle.maximum_units || new Set(quotes.map((quote) => quote.component)).size !== quotes.length)
306
+ throw invalid();
307
+ // Both reservation fields or neither; the initial hold is exactly the baseline's estimate.
308
+ const policy = bundle.reservation_policy ?? null, initial = bundle.initial_reservation_units ?? null;
309
+ if ((policy === null) !== (initial === null) || (policy !== null && policy !== 'baseline_then_admitted_drills')
310
+ || (initial !== null && (!safeUnits(initial) || initial > bundle.maximum_units
311
+ || initial !== quotes.filter((quote) => quote.component === 'baseline').reduce((sum, quote) => sum + quote.estimated_units, 0))))
312
+ throw invalid();
313
+ return { digest: bundle.digest, maximum_units: bundle.maximum_units, denomination: bundle.denomination,
314
+ credit_scale: bundle.credit_scale, expires_at: bundle.expires_at, quotes,
315
+ reservation_policy: policy, initial_reservation_units: initial };
316
+ }
317
+ /** Bounded cloud-work estimate for an exact, current, gap-free plan review. Reserves nothing. */
318
+ export async function estimatePlanFunding(scope, input) {
319
+ const planId = id(input.planId, 'plan');
320
+ if (!SHA256_HEX.test(input.reviewDigest))
321
+ throw new CliError('arguments_invalid', 'Use the review digest exactly as Firedrill printed it. Nothing was requested.', 2);
322
+ const requestKey = registrationKey(input.requestKey);
323
+ const raw = await api(`/api/workspaces/${scope.workspace}/native-apply-plans/${planId}/funding`, {
324
+ method: 'POST', body: { request_key: requestKey, review_digest: input.reviewDigest }, ...scoped(scope, input.signal),
325
+ });
326
+ return { ...fundingRow(raw, planId, input.reviewDigest), request_key: requestKey };
327
+ }
328
+ export const NATIVE_EXECUTION_STATES = ['offered', 'accepted', 'provisioning', 'starting', 'ready', 'executing', 'paused',
329
+ 'reconciling', 'draining', 'rejected', 'expired', 'completed', 'failed', 'cancelled'];
330
+ export const NATIVE_OBSERVATION_KINDS = ['driver.started', 'conversation.observed', 'action.observed', 'state.observed',
331
+ 'driver.completed', 'driver.failed', 'driver.cancelled'];
332
+ const STATE_NAME = /^[a-z][a-z_]{0,23}$/;
333
+ const uuidField = (value) => typeof value === 'string' && UUID.test(value);
334
+ const nullableUuid = (value) => value === null || uuidField(value);
335
+ const count = (value, min = 0) => typeof value === 'number' && Number.isSafeInteger(value) && value >= min;
336
+ const time = (value) => typeof value === 'string' && Number.isFinite(Date.parse(value));
337
+ function nativeCampaignRow(detail) {
338
+ return (value) => {
339
+ if (!object(value) || !uuidField(value.campaign_id) || !uuidField(value.world_revision_id) || !uuidField(value.descriptor_id)
340
+ || !count(value.planned_drills) || !count(value.allocated_drills) || typeof value.state !== 'string' || !STATE_NAME.test(value.state)
341
+ || !time(value.created_at) || !time(value.updated_at))
342
+ throw unconfirmed('native campaign');
343
+ if (detail && (!count(value.scenario_count) || !count(value.repetitions, 1) || !count(value.max_parallel, 1)
344
+ || !['unknown', 'per_drill', 'serialized'].includes(value.reset)))
345
+ throw unconfirmed('native campaign');
346
+ return value;
347
+ };
348
+ }
349
+ function slotRow(campaignId) {
350
+ return (value) => {
351
+ if (!object(value) || !uuidField(value.slot_id) || value.campaign_id !== campaignId || !uuidField(value.scenario_id)
352
+ || !count(value.position, 1) || !count(value.repetition, 1) || typeof value.state !== 'string' || !STATE_NAME.test(value.state)
353
+ || !nullableUuid(value.assignment_id) || !nullableUuid(value.drill_id)
354
+ || (value.assignment_id === null) !== (value.drill_id === null))
355
+ throw unconfirmed('drill slot');
356
+ return value;
357
+ };
358
+ }
359
+ function executionRow(detail) {
360
+ return (value) => {
361
+ if (!object(value) || !uuidField(value.assignment_id) || !uuidField(value.drill_id) || !uuidField(value.device_id)
362
+ || !uuidField(value.descriptor_id) || !uuidField(value.world_revision_id)
363
+ || !NATIVE_EXECUTION_STATES.includes(value.state) || !time(value.created_at)
364
+ || (value.accepted_at !== null && !time(value.accepted_at)) || (value.terminal_at !== null && !time(value.terminal_at))
365
+ || (detail && (!count(value.retained_observation_count) || !count(value.retained_artifact_count))))
366
+ throw unconfirmed('native execution');
367
+ return value;
368
+ };
369
+ }
370
+ const observationRow = (value) => {
371
+ if (!object(value) || typeof value.event_id !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(value.event_id)
372
+ || !count(value.sequence, 1) || !NATIVE_OBSERVATION_KINDS.includes(value.kind)
373
+ || !count(value.elapsed_ms) || !uuidField(value.artifact_id))
374
+ throw unconfirmed('observation');
375
+ return value;
376
+ };
377
+ const artifactRow = (value) => {
378
+ if (!object(value) || !uuidField(value.artifact_id) || typeof value.role !== 'string' || !/^[a-z][a-z._-]{0,63}$/.test(value.role)
379
+ || typeof value.media_kind !== 'string' || value.media_kind.length > 128 || !count(value.size)
380
+ || typeof value.sha256 !== 'string' || !SHA256_HEX.test(value.sha256) || !time(value.committed_at))
381
+ throw unconfirmed('evidence record');
382
+ return value;
383
+ };
384
+ /** Query string from defined values; page numbers go through the shared page check. */
385
+ function readQuery(page, input) {
386
+ const params = new URLSearchParams(pageQuery(page));
387
+ for (const [key, value] of Object.entries(input))
388
+ if (value !== undefined)
389
+ params.set(key, String(value));
390
+ return params.toString();
391
+ }
392
+ function stateFilter(value, allowed) {
393
+ if (value === undefined)
394
+ return undefined;
395
+ if (!STATE_NAME.test(value) || (allowed && !allowed.includes(value))) {
396
+ throw new CliError('arguments_invalid', allowed ? `Use --status ${allowed.join('|')}.` : 'Use a lowercase state name.', 2);
397
+ }
398
+ return value;
399
+ }
400
+ /** Native campaigns in the workspace, oldest first. */
401
+ export async function listNativeCampaigns(scope, input) {
402
+ const query = readQuery(input.page, { state: stateFilter(input.state),
403
+ world_revision_id: input.worldRevisionId === undefined ? undefined : id(input.worldRevisionId, 'world revision') });
404
+ return page(await api(`/api/workspaces/${scope.workspace}/native/campaigns?${query}`, scoped(scope, input.signal)), nativeCampaignRow(false), 'native campaigns');
405
+ }
406
+ /** One native campaign with its declared execution shape. A CliError with httpStatus 404 means it is not a native campaign here. */
407
+ export async function getNativeCampaign(scope, input) {
408
+ const campaignId = id(input.campaignId, 'campaign');
409
+ const row = nativeCampaignRow(true)(await api(`/api/workspaces/${scope.workspace}/native/campaigns/${campaignId}`, scoped(scope, input.signal)));
410
+ if (row.campaign_id !== campaignId)
411
+ throw unconfirmed('native campaign');
412
+ return row;
413
+ }
414
+ /** Allocated drill slots in position order (100 per page). */
415
+ export async function listNativeCampaignSlots(scope, input) {
416
+ const campaignId = id(input.campaignId, 'campaign');
417
+ const query = readQuery(input.page, { page_size: 100, state: stateFilter(input.state),
418
+ scenario_id: input.scenarioId === undefined ? undefined : id(input.scenarioId, 'scenario') });
419
+ return page(await api(`/api/workspaces/${scope.workspace}/native/campaigns/${campaignId}/slots?${query}`, scoped(scope, input.signal)), slotRow(campaignId), 'drill slots');
420
+ }
421
+ /** Native executions (assignments), newest first (100 per page). */
422
+ export async function listNativeExecutions(scope, input) {
423
+ const query = readQuery(input.page, { page_size: 100, state: stateFilter(input.state, NATIVE_EXECUTION_STATES),
424
+ world_revision_id: input.worldRevisionId === undefined ? undefined : id(input.worldRevisionId, 'world revision') });
425
+ return page(await api(`/api/workspaces/${scope.workspace}/native/assignments?${query}`, scoped(scope, input.signal)), executionRow(false), 'native executions');
426
+ }
427
+ /** One execution's state and retained evidence counts (not quality or assessment results). */
428
+ export async function getNativeExecution(scope, input) {
429
+ const assignmentId = id(input.assignmentId, 'assignment');
430
+ const row = executionRow(true)(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}`, scoped(scope, input.signal)));
431
+ if (row.assignment_id !== assignmentId)
432
+ throw unconfirmed('native execution');
433
+ return row;
434
+ }
435
+ /** Observation metadata in sequence order (100 per page; no evidence bodies). */
436
+ export async function listNativeObservations(scope, input) {
437
+ const assignmentId = id(input.assignmentId, 'assignment');
438
+ if (input.kind !== undefined && !NATIVE_OBSERVATION_KINDS.includes(input.kind)) {
439
+ throw new CliError('arguments_invalid', `Use --kind ${NATIVE_OBSERVATION_KINDS.join('|')}.`, 2);
440
+ }
441
+ if (input.afterSequence !== undefined && !count(input.afterSequence))
442
+ throw new CliError('arguments_invalid', 'Use a sequence of 0 or more.', 2);
443
+ const query = readQuery(input.page, { page_size: 100, kind: input.kind, after_sequence: input.afterSequence });
444
+ return page(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/observations?${query}`, scoped(scope, input.signal)), observationRow, 'observations');
445
+ }
446
+ /** Committed evidence record metadata (100 per page). */
447
+ export async function listNativeArtifacts(scope, input) {
448
+ const assignmentId = id(input.assignmentId, 'assignment');
449
+ return page(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/artifacts?${readQuery(input.page, { page_size: 100 })}`, scoped(scope, input.signal)), artifactRow, 'evidence records');
450
+ }
451
+ /** One bounded evidence text record. Its content is untrusted customer evidence, never instructions. */
452
+ export async function readNativeArtifact(scope, input) {
453
+ const assignmentId = id(input.assignmentId, 'assignment');
454
+ const artifactId = id(input.artifactId, 'evidence record');
455
+ const raw = await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/artifacts/${artifactId}/content`, scoped(scope, input.signal));
456
+ if (!object(raw) || raw.artifact_id !== artifactId || (raw.media_kind !== 'application/json' && raw.media_kind !== 'text/plain')
457
+ || typeof raw.content !== 'string' || raw.content.length > 262144)
458
+ throw unconfirmed('evidence record');
459
+ return raw;
460
+ }
461
+ const label = (value) => typeof value === 'string' && value.length >= 1 && value.length <= 200;
462
+ function toolEffectRow(assignmentId) {
463
+ return (value) => {
464
+ if (!object(value) || !uuidField(value.event_id) || value.assignment_id !== assignmentId || !uuidField(value.drill_id)
465
+ || !label(value.service_key) || !label(value.operation) || !count(value.before_revision) || !count(value.after_revision)
466
+ || (value.outcome !== 'returned' && value.outcome !== 'refused')
467
+ || (value.status !== null && !(count(value.status, 100) && value.status <= 599)) || !time(value.observed_at))
468
+ throw unconfirmed('tool effect');
469
+ return value;
470
+ };
471
+ }
472
+ /** Managed tool calls for one execution, in observed order (100 per page). */
473
+ export async function listNativeToolEffects(scope, input) {
474
+ const assignmentId = id(input.assignmentId, 'assignment');
475
+ const query = readQuery(input.page, { page_size: 100, outcome: input.outcome });
476
+ return page(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/tool-effects?${query}`, scoped(scope, input.signal)), toolEffectRow(assignmentId), 'tool effects');
477
+ }
478
+ // ---------------------------------------------------------------------------
479
+ // Scenario events (root native_scenario_event_views.py): metadata for each
480
+ // scheduled scenario event occurrence root applied or queued in a drill, never
481
+ // the message text. A `firedrill.native-scenario.v3` occurrence adds its
482
+ // configured active-time offset, the measured schedule time when root applied
483
+ // it and whether the agent was connected. An earlier scenario's event reports
484
+ // occurrence 0, no timed fields and the agent present.
485
+ export const SCENARIO_EVENT_KINDS = ['user_message', 'dependency_unavailable', 'dependency_restored'];
486
+ export const SCENARIO_EVENT_STATES = ['pending', 'applied', 'consumed'];
487
+ const SCENARIO_EVENT_KEYS = ['after_active_ms', 'after_turn', 'causal_action_id', 'causal_conversation_id', 'continuation_until',
488
+ 'created_at', 'dependency', 'event_id', 'generation', 'kind', 'observed_active_ms', 'observed_completed_turns', 'occurrence',
489
+ 'position', 'receipt_digest', 'scenario_id', 'state', 'target_present'].join(',');
490
+ const ACTIVE_LIMIT_MS = 86_400_000;
491
+ function scenarioEventRow(value) {
492
+ const nullable = (item, check) => item === null || check(item);
493
+ if (!object(value) || Object.keys(value).sort().join(',') !== SCENARIO_EVENT_KEYS
494
+ || !uuidField(value.event_id) || !uuidField(value.scenario_id) || !count(value.generation, 1)
495
+ || !count(value.position) || value.position > 63 || !count(value.occurrence) || value.occurrence > 63
496
+ || !nullable(value.after_active_ms, item => count(item) && item <= ACTIVE_LIMIT_MS)
497
+ || !nullable(value.observed_active_ms, item => count(item)) || typeof value.target_present !== 'boolean'
498
+ || !nullable(value.continuation_until, time)
499
+ || !SCENARIO_EVENT_KINDS.includes(value.kind) || !SCENARIO_EVENT_STATES.includes(value.state)
500
+ || typeof value.dependency !== 'string' || value.dependency.length > 120 || (value.kind === 'user_message') !== (value.dependency === '')
501
+ || !count(value.after_turn) || value.after_turn > 1000
502
+ || !count(value.observed_completed_turns) || value.observed_completed_turns > 1000
503
+ || !nullable(value.causal_action_id, uuidField) || !nullable(value.causal_conversation_id, uuidField)
504
+ || typeof value.receipt_digest !== 'string' || !SHA256_HEX.test(value.receipt_digest) || !time(value.created_at)
505
+ // Only a timed event repeats; a timed occurrence carries the measured time it was applied at.
506
+ || (value.occurrence !== 0 && value.after_active_ms === null)
507
+ || (value.after_active_ms !== null && value.observed_active_ms === null)
508
+ // The agent is away only inside an approved continuation, which always names its cutoff.
509
+ || value.target_present !== (value.continuation_until === null)
510
+ || (!value.target_present && value.observed_active_ms === null))
511
+ throw unconfirmed('scenario event');
512
+ return value;
513
+ }
514
+ /** A drill's scenario event occurrences in applied order (100 per page). */
515
+ export async function listNativeScenarioEvents(scope, input) {
516
+ const assignmentId = id(input.assignmentId, 'assignment');
517
+ const query = readQuery(input.page, { page_size: 100 });
518
+ return page(await api(`/api/workspaces/${scope.workspace}/native-executions/${assignmentId}/scenario-events?${query}`, scoped(scope, input.signal)), scenarioEventRow, 'scenario events');
519
+ }
520
+ /** One scenario event occurrence's metadata. */
521
+ export async function getNativeScenarioEvent(scope, input) {
522
+ const assignmentId = id(input.assignmentId, 'assignment');
523
+ const eventId = id(input.eventId, 'scenario event');
524
+ const row = scenarioEventRow(await api(`/api/workspaces/${scope.workspace}/native-executions/${assignmentId}/scenario-events/${eventId}`, scoped(scope, input.signal)));
525
+ if (row.event_id !== eventId)
526
+ throw unconfirmed('scenario event');
527
+ return row;
528
+ }
529
+ const TURN_TEXT_MAX = 16_000;
530
+ const turnText = (value) => value === null || (typeof value === 'string' && value.length <= TURN_TEXT_MAX);
531
+ function conversationRow(assignmentId) {
532
+ return (value) => {
533
+ if (!object(value) || !uuidField(value.action_id) || value.assignment_id !== assignmentId || !count(value.sequence, 1)
534
+ || !count(value.state_generation, 1) || typeof value.state !== 'string' || !STATE_NAME.test(value.state)
535
+ || (value.claimed_at !== null && !time(value.claimed_at)) || (value.completed_at !== null && !time(value.completed_at))) {
536
+ throw unconfirmed('conversation turn');
537
+ }
538
+ return value;
539
+ };
540
+ }
541
+ /** A drill's attributed turns in order (25 per page, root's maximum). */
542
+ export async function listNativeConversation(scope, input) {
543
+ const assignmentId = id(input.assignmentId, 'assignment');
544
+ const query = readQuery(input.page, { page_size: 25 });
545
+ return page(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/conversation?${query}`, scoped(scope, input.signal)), conversationRow(assignmentId), 'conversation');
546
+ }
547
+ /** One turn's retained participant and target texts (untrusted data). */
548
+ export async function getNativeConversationTurn(scope, input) {
549
+ const assignmentId = id(input.assignmentId, 'assignment');
550
+ const actionId = id(input.actionId, 'turn');
551
+ const value = await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/conversation/${actionId}`, scoped(scope, input.signal));
552
+ if (!object(value) || value.action_id !== actionId || value.assignment_id !== assignmentId
553
+ || !turnText(value.participant_text) || !turnText(value.target_text))
554
+ throw unconfirmed('conversation turn');
555
+ return { action_id: actionId, assignment_id: assignmentId, participant_text: value.participant_text,
556
+ target_text: value.target_text };
557
+ }
558
+ // ---------------------------------------------------------------------------
559
+ // Target actions (root native_target_read_views.py): the tasks and events root
560
+ // handed to the target in one drill, read-only. This is reporting, not the
561
+ // execution claim endpoint: nothing here claims, completes, withdraws or
562
+ // replays an action. One action's detail shows its scoped input, its retained
563
+ // result (outcome, how it completed) and how much of the output was retained.
564
+ // Partial evidence is reported as root classifies it; the client never infers
565
+ // coverage from the output's own keys.
566
+ export const TARGET_ACTION_KINDS = ['task', 'event'];
567
+ export const TARGET_ACTION_STATES = ['offered', 'claimed', 'completed', 'uncertain', 'cancelled'];
568
+ /** complete; redacted (evidence filters removed or rewrote part of it); omitted (not retained); legacy_unknown (recorded before coverage was tracked); unavailable (no readable result). */
569
+ export const TARGET_OUTPUT_COVERAGE = ['complete', 'redacted', 'omitted', 'legacy_unknown', 'unavailable'];
570
+ export const TARGET_OUTCOMES = ['completed', 'failed', 'cancelled'];
571
+ /** target_returned; delivery_acknowledged (an event reached the target: not proof the work finished); correlated_result (matched to a later observed result). */
572
+ export const TARGET_COMPLETIONS = ['target_returned', 'delivery_acknowledged', 'correlated_result'];
573
+ const TARGET_UNAVAILABLE = ['not_recorded', 'evidence_unavailable'];
574
+ const TARGET_ACTION_PAGE = 25;
575
+ const TARGET_ACTION_KEYS = ['action_id', 'assignment_id', 'claimed_at', 'completed_at', 'delivery_note', 'kind', 'sequence', 'state'];
576
+ const TARGET_ACTION_LIST_KEYS = [...TARGET_ACTION_KEYS].sort().join(',');
577
+ const TARGET_ACTION_DETAIL_KEYS = [...TARGET_ACTION_KEYS, 'completion', 'input', 'input_available', 'outcome', 'output',
578
+ 'output_available', 'output_coverage', 'provenance', 'unavailable_reason'].sort().join(',');
579
+ const among = (values, value) => typeof value === 'string' && values.includes(value);
580
+ function targetActionRow(assignmentId, detail) {
581
+ return (value) => {
582
+ if (!object(value) || Object.keys(value).sort().join(',') !== (detail ? TARGET_ACTION_DETAIL_KEYS : TARGET_ACTION_LIST_KEYS)
583
+ || !uuidField(value.action_id) || value.assignment_id !== assignmentId || !among(TARGET_ACTION_KINDS, value.kind)
584
+ || !count(value.sequence, 1) || !among(TARGET_ACTION_STATES, value.state)
585
+ || (value.claimed_at !== null && !time(value.claimed_at)) || (value.completed_at !== null && !time(value.completed_at))
586
+ || (value.delivery_note !== null && value.delivery_note !== 'device_declared_not_delivered'))
587
+ throw unconfirmed('target action');
588
+ if (!detail)
589
+ return value;
590
+ // A readable result carries its outcome and completion and no unavailable reason; without one, root says why.
591
+ const result = value.output_coverage !== 'unavailable';
592
+ if (typeof value.input_available !== 'boolean' || value.input_available !== (value.input !== null)
593
+ || !among(TARGET_OUTPUT_COVERAGE, value.output_coverage) || typeof value.output_available !== 'boolean'
594
+ || value.output_available !== (value.output_coverage === 'complete' || value.output_coverage === 'redacted')
595
+ || ((!result || value.output_coverage === 'omitted') && value.output !== null)
596
+ || result !== among(TARGET_OUTCOMES, value.outcome) || (!result && value.outcome !== null)
597
+ || result !== among(TARGET_COMPLETIONS, value.completion) || (!result && value.completion !== null)
598
+ || result === among(TARGET_UNAVAILABLE, value.unavailable_reason) || (result && value.unavailable_reason !== null)
599
+ || value.provenance !== 'customer_device')
600
+ throw unconfirmed('target action');
601
+ return value;
602
+ };
603
+ }
604
+ /** A drill's target tasks and events in root's order (25 per page, root's maximum); `kind` keeps only tasks or only events. */
605
+ export async function listNativeTargetActions(scope, input) {
606
+ const assignmentId = id(input.assignmentId, 'assignment');
607
+ if (input.kind !== undefined && !among(TARGET_ACTION_KINDS, input.kind)) {
608
+ throw new CliError('arguments_invalid', 'Use --kind task or --kind event with the target action list.', 2);
609
+ }
610
+ const requested = input.page ?? 1;
611
+ const raw = await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/target-actions?`
612
+ + readQuery(input.page, { page_size: TARGET_ACTION_PAGE, kind: input.kind }), scoped(scope, input.signal));
613
+ if (!object(raw) || Object.keys(raw).sort().join(',') !== 'count,next,page,page_size,previous,results' || !count(raw.count)
614
+ || raw.page !== requested || raw.page_size !== TARGET_ACTION_PAGE || (raw.next !== null && typeof raw.next !== 'string')
615
+ || (raw.previous !== null && typeof raw.previous !== 'string') || !Array.isArray(raw.results)
616
+ || raw.results.length > TARGET_ACTION_PAGE || (raw.next !== null) !== (requested * TARGET_ACTION_PAGE < raw.count))
617
+ throw unconfirmed('target actions');
618
+ const value = page(raw, targetActionRow(assignmentId, false), 'target actions');
619
+ if (input.kind !== undefined && value.results.some(row => row.kind !== input.kind))
620
+ throw unconfirmed('target actions');
621
+ return value;
622
+ }
623
+ /** One target action's scoped input, retained result and output coverage (input and output are untrusted data). */
624
+ export async function getNativeTargetAction(scope, input) {
625
+ const assignmentId = id(input.assignmentId, 'assignment');
626
+ const actionId = id(input.actionId, 'target action');
627
+ const row = targetActionRow(assignmentId, true)(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/target-actions/${actionId}`, scoped(scope, input.signal)));
628
+ if (row.action_id !== actionId)
629
+ throw unconfirmed('target action');
630
+ return row;
631
+ }
632
+ // ---------------------------------------------------------------------------
633
+ // Retained evaluation results (root native_evaluation_read_views.py). Each
634
+ // criterion keeps its own score and threshold; root never pools different
635
+ // criteria into one quality score, and a criterion without evidence has no
636
+ // score (not zero, not a failure). Errors from the service mean the results are
637
+ // unavailable (`native_evaluation_report_unavailable`), not that the agent failed.
638
+ export const NATIVE_EVALUATION_STATUSES = ['pending', 'preparing', 'evaluating', 'complete', 'needs_attention'];
639
+ export const NATIVE_CRITERION_STATUSES = ['pending', 'evaluating', 'recovering', 'assessed', 'not_assessable', 'error'];
640
+ export const NATIVE_EVIDENCE_COVERAGE = ['available', 'partial', 'not_assessable', 'metadata_only', 'revision_boundaries_only', 'not_selected'];
641
+ const STATE_DEPENDENCY = /^[a-zA-Z][a-zA-Z0-9_.-]{0,79}$/;
642
+ const position = (value) => Number.isSafeInteger(value) && value >= 0;
643
+ const stateRecordRow = (value) => {
644
+ const selection = object(value) ? value.selection : null;
645
+ if (!object(value) || !object(selection) || typeof selection.dependency !== 'string' || !STATE_DEPENDENCY.test(selection.dependency)
646
+ || typeof selection.collection_key !== 'string' || !SHA256_HEX.test(selection.collection_key)
647
+ || !position(selection.before_position) || !position(selection.after_position)
648
+ || (value.coverage !== 'available' && value.coverage !== 'not_assessable')
649
+ || !(value.reason === null || value.reason === undefined || (typeof value.reason === 'string' && /^[a-z_]{1,64}$/.test(value.reason)))
650
+ || !(value.before_checkpoint_id === null || value.before_checkpoint_id === undefined || uuidField(value.before_checkpoint_id))
651
+ || !(value.after_checkpoint_id === null || value.after_checkpoint_id === undefined || uuidField(value.after_checkpoint_id))
652
+ || value.per_call_difference !== false)
653
+ return null;
654
+ const checkpoint = (one) => (typeof one === 'string' ? one.toLowerCase() : null);
655
+ return {
656
+ selection: { dependency: selection.dependency, collection_key: selection.collection_key,
657
+ before_position: selection.before_position, after_position: selection.after_position },
658
+ coverage: value.coverage, reason: typeof value.reason === 'string' ? value.reason : null,
659
+ before_checkpoint_id: checkpoint(value.before_checkpoint_id), after_checkpoint_id: checkpoint(value.after_checkpoint_id),
660
+ per_call_difference: false,
661
+ };
662
+ };
663
+ const CHECK_TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
664
+ const exactCheckRow = (value) => {
665
+ if (!object(value) || typeof value.key !== 'string' || !CHECK_TOKEN.test(value.key) || typeof value.source !== 'string' || !CHECK_TOKEN.test(value.source)
666
+ || !(value.service === null || (typeof value.service === 'string' && CHECK_TOKEN.test(value.service)))
667
+ || !(value.operation === null || (typeof value.operation === 'string' && CHECK_TOKEN.test(value.operation)))
668
+ || !['eq', 'gte', 'lte'].includes(value.comparison) || !Number.isSafeInteger(value.expected) || value.expected < 0
669
+ || !(value.observed === null || (Number.isSafeInteger(value.observed) && value.observed >= 0))
670
+ || !(value.passed === null || typeof value.passed === 'boolean') || (value.passed === null) !== (value.observed === null)
671
+ || value.coverage !== (value.observed === null ? 'not_assessable' : 'available')
672
+ || !(value.reason === null || (typeof value.reason === 'string' && /^[a-z_]{1,64}$/.test(value.reason))))
673
+ return null;
674
+ return { key: value.key, source: value.source, service: value.service, operation: value.operation,
675
+ comparison: value.comparison, expected: value.expected, observed: value.observed,
676
+ passed: value.passed, coverage: value.coverage, reason: value.reason };
677
+ };
678
+ const policyRow = (value) => {
679
+ if (!object(value) || !uuidField(value.revision_id) || !uuidField(value.policy_id) || typeof value.name !== 'string'
680
+ || value.name.length < 1 || value.name.length > 1000 || !Number.isSafeInteger(value.version) || value.version < 1
681
+ || typeof value.sha256 !== 'string' || !SHA256_HEX.test(value.sha256) || typeof value.text_sha256 !== 'string'
682
+ || !SHA256_HEX.test(value.text_sha256) || !Array.isArray(value.paragraphs) || value.paragraphs.length > 20_000
683
+ || !value.paragraphs.every((one) => typeof one === 'string' && /^p[0-9]{1,11}$/.test(one)) || value.scope !== 'evaluator_only')
684
+ return null;
685
+ return { revision_id: value.revision_id.toLowerCase(), policy_id: value.policy_id.toLowerCase(), name: value.name, version: value.version,
686
+ sha256: value.sha256, text_sha256: value.text_sha256, paragraphs: value.paragraphs, scope: 'evaluator_only' };
687
+ };
688
+ const score = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100;
689
+ const COVERAGE_KEY = /^[a-z][a-z_]{0,63}$/;
690
+ function evaluationReportRow(assignmentId) {
691
+ return (value) => {
692
+ // An older root sends neither coverage field; both read as not known.
693
+ const executionCoverage = object(value) ? value.execution_coverage ?? null : null;
694
+ const endingReason = object(value) ? value.ending_reason ?? null : null;
695
+ if (!object(value) || value.assignment_id !== assignmentId || !nullableUuid(value.basis_id)
696
+ || !NATIVE_EVALUATION_STATUSES.includes(value.status)
697
+ || ![null, 'complete', 'partial'].includes(executionCoverage)
698
+ || !(endingReason === null || ENDING_REASONS.includes(endingReason))
699
+ || (executionCoverage === 'partial' && value.status !== 'needs_attention')
700
+ || (value.criteria_total !== null && !count(value.criteria_total))
701
+ || ![value.assessed, value.criteria_met, value.below_threshold, value.not_assessable, value.pending, value.errors].every(one => count(one))
702
+ || value.quality_score !== null || (value.exact_check !== null && typeof value.exact_check !== 'boolean') || !object(value.coverage)
703
+ || Object.keys(value.coverage).length > 32
704
+ || !Object.entries(value.coverage).every(([key, one]) => COVERAGE_KEY.test(key)
705
+ && NATIVE_EVIDENCE_COVERAGE.includes(one)))
706
+ throw unconfirmed('evaluation report');
707
+ const row = { ...value, execution_coverage: executionCoverage, ending_reason: endingReason };
708
+ // Root's counts partition the criteria; an inconsistent report is not shown as a result. With no criteria
709
+ // the report is either not prepared yet or, after a cleanup-only end, `needs_attention` with nothing evaluated.
710
+ const partitioned = row.criteria_total === null
711
+ ? row.assessed + row.not_assessable + row.pending + row.errors + row.criteria_met + row.below_threshold === 0
712
+ && (row.status === 'needs_attention'
713
+ || ((row.status === 'pending' || row.status === 'preparing') && (row.status === 'pending') === (row.basis_id === null)))
714
+ : row.basis_id !== null && row.assessed + row.not_assessable + row.pending + row.errors === row.criteria_total
715
+ && row.criteria_met + row.below_threshold === row.assessed;
716
+ if (!partitioned)
717
+ throw unconfirmed('evaluation report');
718
+ return row;
719
+ };
720
+ }
721
+ /** One criterion result, exactly as root reports it (also used for a reassessment's criteria). */
722
+ export const criterionRow = (value) => {
723
+ if (!object(value) || !uuidField(value.criterion_id) || typeof value.name !== 'string' || value.name.length < 1 || value.name.length > 1000
724
+ || !NATIVE_CRITERION_STATUSES.includes(value.status)
725
+ || !['pending', 'assessable', 'not_assessable'].includes(value.coverage)
726
+ || (value.quality_score !== null && !score(value.quality_score)) || !score(value.threshold)
727
+ || (value.threshold_met !== null && typeof value.threshold_met !== 'boolean')
728
+ || (value.exact_check !== null && typeof value.exact_check !== 'boolean'))
729
+ throw unconfirmed('criterion result');
730
+ // Evaluation v2 fields; an older platform sends none of them, which reads as a v1 rubric.
731
+ const dimension = value.dimension ?? null;
732
+ const checks = value.exact_checks ?? [];
733
+ const policies = value.company_policies ?? [];
734
+ // Absent stays absent: an older platform, or a criterion that declared no state records.
735
+ const records = value.state_records ?? [];
736
+ const memory = value.memory ?? null, style = value.style ?? null;
737
+ if (!(dimension === null || (typeof dimension === 'string' && /^[a-z_]{1,64}$/.test(dimension)))
738
+ || !Array.isArray(checks) || checks.length > 32 || !Array.isArray(policies) || policies.length > 100
739
+ || !Array.isArray(records) || records.length > 16
740
+ || !(memory === null || object(memory)) || !(style === null || object(style)))
741
+ throw unconfirmed('criterion result');
742
+ const exactChecks = checks.map(exactCheckRow);
743
+ const companyPolicies = policies.map(policyRow);
744
+ const stateRecords = records.map(stateRecordRow);
745
+ if (exactChecks.some((one) => one === null) || companyPolicies.some((one) => one === null) || stateRecords.some((one) => one === null)
746
+ || new Set(exactChecks.map((one) => one?.key)).size !== exactChecks.length)
747
+ throw unconfirmed('criterion result');
748
+ // Root's summary of the checks: null without checks or when one could not be checked, else whether all passed.
749
+ const summary = !exactChecks.length || exactChecks.some((one) => one?.passed === null) ? null : exactChecks.every((one) => one?.passed === true);
750
+ if (value.exact_check !== summary)
751
+ throw unconfirmed('criterion result');
752
+ const row = { ...value, dimension, exact_checks: exactChecks, company_policies: companyPolicies, state_records: stateRecords, memory, style };
753
+ // Only an assessed criterion carries a score, and its threshold result follows from that score.
754
+ const consistent = row.status === 'assessed'
755
+ ? row.coverage === 'assessable' && row.quality_score !== null && row.threshold_met === (row.quality_score >= row.threshold)
756
+ : row.quality_score === null && row.threshold_met === null && row.coverage === (row.status === 'not_assessable' ? 'not_assessable' : 'pending');
757
+ if (!consistent)
758
+ throw unconfirmed('criterion result');
759
+ return { ...row, criterion_id: row.criterion_id.toLowerCase() };
760
+ };
761
+ /** One drill's evaluation report. Takes no query parameters (root refuses filters on this route). */
762
+ export async function getNativeEvaluationReport(scope, input) {
763
+ const assignmentId = id(input.assignmentId, 'assignment');
764
+ const raw = await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/evaluation-report`, scoped(scope, input.signal));
765
+ return evaluationReportRow(assignmentId)(object(raw) && typeof raw.assignment_id === 'string' ? { ...raw, assignment_id: raw.assignment_id.toLowerCase() } : raw);
766
+ }
767
+ /** Per-criterion results in the evaluation's criterion order (100 per page; root holds at most 256). */
768
+ export async function listNativeEvaluationCriteria(scope, input) {
769
+ const assignmentId = id(input.assignmentId, 'assignment');
770
+ return page(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/evaluation-criteria?${readQuery(input.page, { page_size: 100 })}`, scoped(scope, input.signal)), criterionRow, 'criterion results');
771
+ }
772
+ export async function getNativeCampaignProgress(scope, input) {
773
+ const campaignId = id(input.campaignId, 'campaign');
774
+ const raw = await api(`/api/workspaces/${scope.workspace}/native/campaigns/${campaignId}/report`, scoped(scope, input.signal));
775
+ const rows = object(raw) && Array.isArray(raw.execution_counts) ? raw.execution_counts : null;
776
+ const valid = object(raw) && rows !== null && typeof raw.campaign_id === 'string' && raw.campaign_id.toLowerCase() === campaignId
777
+ && count(raw.planned_drills) && count(raw.allocated_drills) && count(raw.unallocated_drills)
778
+ && rows.length <= 64 && rows.every(row => object(row) && typeof row.state === 'string' && STATE_NAME.test(row.state) && count(row.count, 1));
779
+ if (!valid)
780
+ throw unconfirmed('campaign report');
781
+ const report = raw;
782
+ const executions = rows.map(row => ({ state: row.state, count: row.count }));
783
+ // Allocated and unallocated partition the planned drills, and every allocated slot is counted once.
784
+ if (report.allocated_drills + report.unallocated_drills !== report.planned_drills
785
+ || new Set(executions.map(row => row.state)).size !== executions.length
786
+ || executions.reduce((sum, row) => sum + row.count, 0) !== report.allocated_drills)
787
+ throw unconfirmed('campaign report');
788
+ return { campaign_id: campaignId, planned_drills: report.planned_drills, allocated_drills: report.allocated_drills,
789
+ unallocated_drills: report.unallocated_drills, execution_counts: executions };
790
+ }
791
+ const COHORT_DIGEST = /^[A-Za-z0-9:_-]{1,128}$/;
792
+ const cohortRow = (value) => {
793
+ if (!object(value) || typeof value.cohort_digest !== 'string' || !COHORT_DIGEST.test(value.cohort_digest) || !uuidField(value.criterion_id)
794
+ || typeof value.criterion_name !== 'string' || value.criterion_name.length < 1 || value.criterion_name.length > 1000 || !score(value.threshold)
795
+ || ![value.assessed, value.threshold_met, value.below_threshold, value.not_assessable].every(one => count(one))
796
+ || ![value.average_quality, value.minimum_quality, value.maximum_quality].every(one => one === null || score(one)))
797
+ throw unconfirmed('quality cohort');
798
+ const row = value;
799
+ // Only a scored drill has a threshold result; figures exist exactly when some drill was scored,
800
+ // and the average lies between the extremes.
801
+ const scored = row.average_quality !== null && row.minimum_quality !== null && row.maximum_quality !== null;
802
+ const unscored = row.average_quality === null && row.minimum_quality === null && row.maximum_quality === null;
803
+ if (row.threshold_met + row.below_threshold !== row.assessed || (row.assessed > 0
804
+ // A database average can differ from the extremes in the last bits, hence the tolerance.
805
+ ? !scored || row.minimum_quality - row.average_quality > 1e-6 || row.average_quality - row.maximum_quality > 1e-6
806
+ : !unscored))
807
+ throw unconfirmed('quality cohort');
808
+ return { ...row, criterion_id: row.criterion_id.toLowerCase() };
809
+ };
810
+ /** A campaign's per-criterion quality cohorts, by criterion then cohort (25 per page, as root pages them). */
811
+ export async function listNativeCampaignQuality(scope, input) {
812
+ const campaignId = id(input.campaignId, 'campaign');
813
+ return page(await api(`/api/workspaces/${scope.workspace}/native/campaigns/${campaignId}/quality?${readQuery(input.page, { page_size: 25 })}`, scoped(scope, input.signal)), cohortRow, 'campaign quality');
814
+ }
815
+ /** One criterion's result. */
816
+ export async function getNativeEvaluationCriterion(scope, input) {
817
+ const assignmentId = id(input.assignmentId, 'assignment');
818
+ const criterionId = id(input.criterionId, 'criterion');
819
+ const row = criterionRow(await api(`/api/workspaces/${scope.workspace}/native/assignments/${assignmentId}/evaluation-criteria/${criterionId}`, scoped(scope, input.signal)));
820
+ if (row.criterion_id !== criterionId)
821
+ throw unconfirmed('criterion result');
822
+ return row;
823
+ }
824
+ /**
825
+ * Request cancellation of the caller's native campaign. The same request key
826
+ * replays the original receipt; a finished campaign is refused (409).
827
+ */
828
+ export async function cancelNativeCampaign(scope, input) {
829
+ const campaignId = id(input.campaignId, 'campaign');
830
+ const requestKey = id(input.requestKey, 'request key');
831
+ const raw = await api(`/api/workspaces/${scope.workspace}/native/campaigns/${campaignId}/cancel`, { method: 'POST', body: { request_key: requestKey }, ...scoped(scope, input.signal) });
832
+ if (!object(raw) || raw.campaign_id !== campaignId || raw.request_key !== requestKey || raw.accepted !== true
833
+ || typeof raw.cleanup_complete !== 'boolean')
834
+ throw unconfirmed('campaign cancellation');
835
+ return raw;
836
+ }
837
+ // ---------------------------------------------------------------------------
838
+ // Apply admission and campaign creation (root native_apply_views.py and
839
+ // native_campaign_views.py). Approval reserves the quoted cloud-work credits
840
+ // and queues preparation; queued or provisioning is not ready and never
841
+ // permission to start a target. A campaign allocates drill slots only after
842
+ // preparation is ready, one campaign per preparation; it starts no target either.
843
+ export const NATIVE_APPLY_STATES = ['accepted', 'queued', 'provisioning', 'reconciling', 'ready', 'failed', 'cancelled'];
844
+ function applyOperationRow(value) {
845
+ const capacity = object(value) ? value.capacity : undefined;
846
+ if (!object(value) || !uuidField(value.id) || !uuidField(value.plan_id) || !uuidField(value.world_revision_id)
847
+ || !uuidField(value.request_key) || typeof value.review_digest !== 'string' || !SHA256_HEX.test(value.review_digest)
848
+ || !NATIVE_APPLY_STATES.includes(value.state) || !count(value.state_revision, 1)
849
+ || !time(value.created_at) || !time(value.updated_at) || !(value.finished_at === null || time(value.finished_at))
850
+ || typeof value.failure_code !== 'string' || value.failure_code.length > 64
851
+ || typeof value.can_request_prepared_cancellation !== 'boolean'
852
+ || value.can_request_prepared_cancellation && value.state !== 'ready'
853
+ || value.can_request_unstarted_cancellation !== undefined && typeof value.can_request_unstarted_cancellation !== 'boolean'
854
+ || value.can_request_unstarted_cancellation === true && value.state !== 'queued'
855
+ || !(capacity === null || (object(capacity) && typeof capacity.state === 'string' && STATE_NAME.test(capacity.state)
856
+ && typeof capacity.reason === 'string' && capacity.reason.length <= 200 && typeof capacity.capacity_held === 'boolean'
857
+ && typeof capacity.retryable === 'boolean')))
858
+ throw unconfirmed('preparation');
859
+ return { ...value, id: value.id.toLowerCase(), request_key: value.request_key.toLowerCase(),
860
+ plan_id: value.plan_id.toLowerCase(), can_request_unstarted_cancellation: value.can_request_unstarted_cancellation === true };
861
+ }
862
+ /**
863
+ * Approve the exact plan review and budget: root reserves the quoted credits
864
+ * and queues preparation atomically (201 new, 200 replay of the same key and body).
865
+ */
866
+ export async function applyNativePlan(scope, input) {
867
+ const planId = id(input.planId, 'plan');
868
+ const requestKey = id(input.requestKey, 'request key');
869
+ if (!SHA256_HEX.test(input.reviewDigest) || !SHA256_HEX.test(input.budgetDigest) || input.quoteIds.length < 1 || input.quoteIds.length > 5) {
870
+ throw new CliError('arguments_invalid', 'The plan review or cloud-work estimate is incomplete. Nothing was applied.', 2);
871
+ }
872
+ const quoteIds = input.quoteIds.map((quote) => id(quote, 'quote'));
873
+ const row = applyOperationRow(await api(`/api/workspaces/${scope.workspace}/native-apply-operations`, {
874
+ method: 'POST', body: { request_key: requestKey, plan_id: planId, review_digest: input.reviewDigest, quote_ids: quoteIds,
875
+ budget_digest: input.budgetDigest, credit_approval: input.creditApproval === true }, ...scoped(scope, input.signal),
876
+ }));
877
+ if (row.request_key !== requestKey || row.plan_id !== planId || row.review_digest !== input.reviewDigest)
878
+ throw unconfirmed('preparation');
879
+ return row;
880
+ }
881
+ /** One preparation by id, with its current state. */
882
+ export async function getNativeApplyOperation(scope, input) {
883
+ const operationId = id(input.operationId, 'preparation');
884
+ const row = applyOperationRow(await api(`/api/workspaces/${scope.workspace}/native-apply-operations/${operationId}`, scoped(scope, input.signal)));
885
+ if (row.id !== operationId)
886
+ throw unconfirmed('preparation');
887
+ return row;
888
+ }
889
+ /**
890
+ * Cancel a ready preparation that has no campaign. One cancellation exists per
891
+ * preparation: the same key and review replay the original receipt, a
892
+ * different key is refused (409 idempotency_conflict).
893
+ */
894
+ export async function cancelPreparedApply(scope, input) {
895
+ const operationId = id(input.operationId, 'preparation');
896
+ const requestKey = id(input.requestKey, 'request key');
897
+ if (!SHA256_HEX.test(input.reviewDigest))
898
+ throw new CliError('arguments_invalid', 'The plan review is incomplete. Nothing was cancelled.', 2);
899
+ const raw = await api(`/api/workspaces/${scope.workspace}/native-apply-operations/${operationId}/cancel-prepared`, {
900
+ method: 'POST', body: { request_key: requestKey, review_digest: input.reviewDigest }, ...scoped(scope, input.signal),
901
+ });
902
+ if (!object(raw) || Object.keys(raw).sort().join(',') !== 'operation_id,physical_cleanup,receipt_digest,state' || raw.operation_id !== operationId
903
+ || raw.state !== 'cancelled' || typeof raw.receipt_digest !== 'string' || !SHA256_HEX.test(raw.receipt_digest)
904
+ || raw.physical_cleanup !== 'retained')
905
+ throw unconfirmed('prepared-world cancellation');
906
+ return raw;
907
+ }
908
+ /** Cancel only an eligible queued preparation, with the original review and one retained cancellation key. */
909
+ export async function cancelUnstartedApply(scope, input) {
910
+ const operationId = id(input.operationId, 'preparation');
911
+ const requestKey = id(input.requestKey, 'request key');
912
+ if (!SHA256_HEX.test(input.reviewDigest))
913
+ throw new CliError('arguments_invalid', 'The plan review is incomplete. Nothing was cancelled.', 2);
914
+ const raw = await api(`/api/workspaces/${scope.workspace}/native-apply-operations/${operationId}/cancel-unstarted`, {
915
+ method: 'POST', body: { request_key: requestKey, review_digest: input.reviewDigest }, ...scoped(scope, input.signal),
916
+ });
917
+ if (!object(raw) || Object.keys(raw).sort().join(',') !== 'operation_id,physical_cleanup,receipt_digest,state' || raw.operation_id !== operationId
918
+ || raw.state !== 'cancelled' || typeof raw.receipt_digest !== 'string' || !SHA256_HEX.test(raw.receipt_digest)
919
+ || raw.physical_cleanup !== 'retained')
920
+ throw unconfirmed('queued preparation cancellation');
921
+ return raw;
922
+ }
923
+ /** The caller's preparation submitted under `requestKey`, or null when none was retained. */
924
+ export async function findNativeApplyOperation(scope, input) {
925
+ const requestKey = id(input.requestKey, 'request key');
926
+ const rows = page(await api(`/api/workspaces/${scope.workspace}/native-apply-operations?request_key=${requestKey}&page=1&page_size=1`, scoped(scope, input.signal)), applyOperationRow, 'preparations');
927
+ const row = rows.results[0];
928
+ if (row && row.request_key !== requestKey)
929
+ throw unconfirmed('preparations');
930
+ return row ?? null;
931
+ }
932
+ /**
933
+ * Create the one campaign for a ready preparation: allocates its drill slots
934
+ * without starting any target (201 new, 200 replay of the same key).
935
+ */
936
+ export async function createNativeCampaign(scope, input) {
937
+ const operationId = id(input.operationId, 'preparation');
938
+ const requestKey = id(input.requestKey, 'request key');
939
+ return nativeCampaignRow(true)(await api(`/api/workspaces/${scope.workspace}/native/campaigns`, {
940
+ method: 'POST', body: { operation_id: operationId, request_key: requestKey }, ...scoped(scope, input.signal),
941
+ }));
942
+ }