@zergai/ztc 0.4.0-beta.1 → 0.5.0-beta.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 (718) hide show
  1. package/README.md +398 -45
  2. package/RUST_THIRD_PARTY_LICENSES.txt +1080 -822
  3. package/THIRD_PARTY_NOTICES.md +273 -12
  4. package/bin/zmux-darwin-arm64 +0 -0
  5. package/bin/ztc-audio-darwin-arm64 +0 -0
  6. package/bin/ztc-capture-darwin-arm64 +0 -0
  7. package/bin/ztc-computer-darwin-arm64 +0 -0
  8. package/bin/ztc-llm-darwin-arm64 +0 -0
  9. package/bin/ztc-tts-darwin-arm64 +0 -0
  10. package/bin/ztc-tui-darwin-arm64 +0 -0
  11. package/bin/ztc-zlicky-overlay-darwin-arm64 +0 -0
  12. package/bin/zterm-darwin-arm64 +0 -0
  13. package/dist/App.js +1206 -209
  14. package/dist/agent/agent.d.ts +74 -8
  15. package/dist/agent/agent.js +1073 -184
  16. package/dist/agent/backends/account_rotation.d.ts +4 -0
  17. package/dist/agent/backends/account_rotation.js +8 -2
  18. package/dist/agent/backends/anthropic.d.ts +13 -0
  19. package/dist/agent/backends/anthropic.js +169 -28
  20. package/dist/agent/backends/claude_cli.js +18 -8
  21. package/dist/agent/backends/codex_app_server.d.ts +1 -0
  22. package/dist/agent/backends/codex_app_server.js +55 -3
  23. package/dist/agent/backends/codex_auth.d.ts +1 -0
  24. package/dist/agent/backends/codex_auth.js +67 -13
  25. package/dist/agent/backends/codex_buffered_recovery.d.ts +27 -0
  26. package/dist/agent/backends/codex_buffered_recovery.js +293 -0
  27. package/dist/agent/backends/gemini.js +3 -1
  28. package/dist/agent/backends/http_error.d.ts +26 -0
  29. package/dist/agent/backends/http_error.js +221 -0
  30. package/dist/agent/backends/index.d.ts +9 -2
  31. package/dist/agent/backends/index.js +22 -2
  32. package/dist/agent/backends/openai.d.ts +0 -6
  33. package/dist/agent/backends/openai.js +5 -2
  34. package/dist/agent/backends/openai_compatible.d.ts +5 -4
  35. package/dist/agent/backends/openai_compatible.js +26 -19
  36. package/dist/agent/backends/openai_responses.d.ts +8 -3
  37. package/dist/agent/backends/openai_responses.js +77 -33
  38. package/dist/agent/backends/provider_ids.d.ts +1 -0
  39. package/dist/agent/backends/provider_ids.js +4 -0
  40. package/dist/agent/backends/request_metrics.js +12 -2
  41. package/dist/agent/backends/types.d.ts +109 -6
  42. package/dist/agent/backends/types.js +5 -1
  43. package/dist/agent/backends/zrouter_stream.d.ts +6 -1
  44. package/dist/agent/backends/zrouter_stream.js +160 -42
  45. package/dist/agent/background_registry.d.ts +2 -0
  46. package/dist/agent/background_registry.js +1 -0
  47. package/dist/agent/claim_observation.d.ts +6 -0
  48. package/dist/agent/claim_observation.js +54 -0
  49. package/dist/agent/commands/accounts.js +46 -177
  50. package/dist/agent/commands/attention.d.ts +2 -0
  51. package/dist/agent/commands/attention.js +67 -0
  52. package/dist/agent/commands/auth.js +18 -2
  53. package/dist/agent/commands/board.d.ts +2 -0
  54. package/dist/agent/commands/board.js +169 -0
  55. package/dist/agent/commands/clipboard.js +50 -1
  56. package/dist/agent/commands/compact.js +21 -13
  57. package/dist/agent/commands/config.js +4 -1
  58. package/dist/agent/commands/context.js +6 -1
  59. package/dist/agent/commands/desktop.d.ts +2 -0
  60. package/dist/agent/commands/desktop.js +29 -0
  61. package/dist/agent/commands/effort.js +14 -6
  62. package/dist/agent/commands/environment.d.ts +3 -0
  63. package/dist/agent/commands/environment.js +109 -0
  64. package/dist/agent/commands/evidence.d.ts +2 -0
  65. package/dist/agent/commands/evidence.js +9 -0
  66. package/dist/agent/commands/fork.js +4 -3
  67. package/dist/agent/commands/goal.d.ts +33 -1
  68. package/dist/agent/commands/goal.js +174 -14
  69. package/dist/agent/commands/goal_exit.d.ts +36 -0
  70. package/dist/agent/commands/goal_exit.js +120 -0
  71. package/dist/agent/commands/help.js +3 -1
  72. package/dist/agent/commands/history.js +3 -1
  73. package/dist/agent/commands/inbox.d.ts +2 -0
  74. package/dist/agent/commands/inbox.js +148 -0
  75. package/dist/agent/commands/index.js +16 -1
  76. package/dist/agent/commands/input_mode.js +8 -6
  77. package/dist/agent/commands/job.d.ts +2 -0
  78. package/dist/agent/commands/job.js +93 -0
  79. package/dist/agent/commands/keybindings.js +4 -1
  80. package/dist/agent/commands/label.d.ts +2 -0
  81. package/dist/agent/commands/label.js +77 -0
  82. package/dist/agent/commands/layout.d.ts +2 -0
  83. package/dist/agent/commands/layout.js +20 -0
  84. package/dist/agent/commands/mirror.js +16 -1
  85. package/dist/agent/commands/model.js +12 -2
  86. package/dist/agent/commands/permissions.d.ts +3 -1
  87. package/dist/agent/commands/permissions.js +79 -89
  88. package/dist/agent/commands/plan.js +5 -15
  89. package/dist/agent/commands/profile.js +21 -2
  90. package/dist/agent/commands/provider_selection.js +20 -1
  91. package/dist/agent/commands/rename.js +1 -1
  92. package/dist/agent/commands/resources.d.ts +2 -0
  93. package/dist/agent/commands/resources.js +67 -0
  94. package/dist/agent/commands/rewind.d.ts +3 -0
  95. package/dist/agent/commands/rewind.js +16 -0
  96. package/dist/agent/commands/runbooks.d.ts +2 -0
  97. package/dist/agent/commands/runbooks.js +73 -0
  98. package/dist/agent/commands/runbooks_lazy.d.ts +2 -0
  99. package/dist/agent/commands/runbooks_lazy.js +8 -0
  100. package/dist/agent/commands/sessions.d.ts +1 -1
  101. package/dist/agent/commands/sessions.js +33 -5
  102. package/dist/agent/commands/settings.js +185 -39
  103. package/dist/agent/commands/setup.d.ts +2 -0
  104. package/dist/agent/commands/setup.js +78 -0
  105. package/dist/agent/commands/share.d.ts +2 -0
  106. package/dist/agent/commands/share.js +64 -0
  107. package/dist/agent/commands/status.js +14 -2
  108. package/dist/agent/commands/types.d.ts +99 -6
  109. package/dist/agent/commands/update.js +8 -0
  110. package/dist/agent/commands/usage.d.ts +2 -0
  111. package/dist/agent/commands/usage.js +8 -0
  112. package/dist/agent/factory.d.ts +11 -0
  113. package/dist/agent/factory.js +113 -88
  114. package/dist/agent/loop_policy.d.ts +6 -3
  115. package/dist/agent/loop_policy.js +12 -3
  116. package/dist/agent/measurement_guidance.d.ts +2 -0
  117. package/dist/agent/measurement_guidance.js +5 -0
  118. package/dist/agent/request_encoder.d.ts +8 -1
  119. package/dist/agent/request_encoder.js +95 -23
  120. package/dist/agent/runtime/benchmark_comparison.d.ts +30 -0
  121. package/dist/agent/runtime/benchmark_comparison.js +94 -0
  122. package/dist/agent/runtime/benchmark_contract.d.ts +48 -0
  123. package/dist/agent/runtime/benchmark_contract.js +90 -0
  124. package/dist/agent/runtime/checked_commands.d.ts +32 -0
  125. package/dist/agent/runtime/checked_commands.js +119 -0
  126. package/dist/agent/runtime/command_execution_policy.d.ts +26 -0
  127. package/dist/agent/runtime/command_execution_policy.js +91 -0
  128. package/dist/agent/runtime/command_jobs.d.ts +36 -18
  129. package/dist/agent/runtime/command_jobs.js +151 -13
  130. package/dist/agent/runtime/command_panes.d.ts +59 -0
  131. package/dist/agent/runtime/command_panes.js +398 -0
  132. package/dist/agent/runtime/command_wait.d.ts +19 -0
  133. package/dist/agent/runtime/command_wait.js +167 -0
  134. package/dist/agent/runtime/cpu_benchmark.d.ts +30 -0
  135. package/dist/agent/runtime/cpu_benchmark.js +126 -0
  136. package/dist/agent/runtime/delegated_evidence.d.ts +26 -0
  137. package/dist/agent/runtime/delegated_evidence.js +80 -0
  138. package/dist/agent/runtime/evidence.d.ts +127 -0
  139. package/dist/agent/runtime/evidence.js +468 -0
  140. package/dist/agent/runtime/execution_context.d.ts +7 -0
  141. package/dist/agent/runtime/execution_context.js +39 -3
  142. package/dist/agent/runtime/json_verifier_recipe.d.ts +8 -0
  143. package/dist/agent/runtime/json_verifier_recipe.js +66 -0
  144. package/dist/agent/runtime/measurements.d.ts +110 -0
  145. package/dist/agent/runtime/measurements.js +317 -0
  146. package/dist/agent/runtime/policy.d.ts +4 -0
  147. package/dist/agent/runtime/policy.js +31 -20
  148. package/dist/agent/runtime/provider_native_evidence.d.ts +20 -0
  149. package/dist/agent/runtime/provider_native_evidence.js +86 -0
  150. package/dist/agent/runtime/recovery.d.ts +2 -0
  151. package/dist/agent/runtime/recovery.js +45 -2
  152. package/dist/agent/runtime/remote_broker_transport.d.ts +10 -0
  153. package/dist/agent/runtime/remote_broker_transport.js +84 -0
  154. package/dist/agent/runtime/remote_deletion_receipts.d.ts +3 -0
  155. package/dist/agent/runtime/remote_deletion_receipts.js +43 -0
  156. package/dist/agent/runtime/remote_journal_replay.d.ts +13 -0
  157. package/dist/agent/runtime/remote_journal_replay.js +233 -0
  158. package/dist/agent/runtime/remote_mutation_journal.d.ts +25 -0
  159. package/dist/agent/runtime/remote_mutation_journal.js +208 -0
  160. package/dist/agent/runtime/remote_resource_endpoint.d.ts +7 -0
  161. package/dist/agent/runtime/remote_resource_endpoint.js +6 -0
  162. package/dist/agent/runtime/remote_resource_errors.d.ts +5 -0
  163. package/dist/agent/runtime/remote_resource_errors.js +9 -0
  164. package/dist/agent/runtime/remote_resource_script.d.ts +2 -0
  165. package/dist/agent/runtime/remote_resource_script.js +1009 -0
  166. package/dist/agent/runtime/remote_resources.d.ts +29 -0
  167. package/dist/agent/runtime/remote_resources.js +160 -0
  168. package/dist/agent/runtime/source_verification.d.ts +12 -0
  169. package/dist/agent/runtime/source_verification.js +210 -0
  170. package/dist/agent/runtime/source_verification_contract.d.ts +75 -0
  171. package/dist/agent/runtime/source_verification_contract.js +203 -0
  172. package/dist/agent/runtime/tool_checkpoints.d.ts +11 -0
  173. package/dist/agent/runtime/tool_checkpoints.js +26 -0
  174. package/dist/agent/runtime/tool_effects.d.ts +5 -0
  175. package/dist/agent/runtime/tool_progress.d.ts +12 -0
  176. package/dist/agent/runtime/tool_progress.js +76 -0
  177. package/dist/agent/runtime/tool_runtime.d.ts +16 -1
  178. package/dist/agent/runtime/tool_runtime.js +204 -10
  179. package/dist/agent/runtime/tracing.d.ts +1 -1
  180. package/dist/agent/runtime/verifier_capture.d.ts +9 -0
  181. package/dist/agent/runtime/verifier_capture.js +100 -0
  182. package/dist/agent/runtime/verifier_contract.d.ts +24 -0
  183. package/dist/agent/runtime/verifier_contract.js +41 -0
  184. package/dist/agent/subagent.d.ts +20 -10
  185. package/dist/agent/subagent.js +266 -29
  186. package/dist/agent/tool_response_directory.d.ts +24 -0
  187. package/dist/agent/tool_response_directory.js +61 -0
  188. package/dist/agent/tool_response_source.d.ts +8 -0
  189. package/dist/agent/tool_response_source.js +49 -0
  190. package/dist/agent/tool_response_store.d.ts +75 -4
  191. package/dist/agent/tool_response_store.js +583 -64
  192. package/dist/agent/tools/ask_user.d.ts +3 -1
  193. package/dist/agent/tools/ask_user.js +30 -11
  194. package/dist/agent/tools/background.js +1 -0
  195. package/dist/agent/tools/benchmark_cpu.d.ts +2 -0
  196. package/dist/agent/tools/benchmark_cpu.js +102 -0
  197. package/dist/agent/tools/compare_benchmarks.d.ts +2 -0
  198. package/dist/agent/tools/compare_benchmarks.js +53 -0
  199. package/dist/agent/tools/deferred.js +1 -0
  200. package/dist/agent/tools/delegate.js +18 -2
  201. package/dist/agent/tools/helper_requirements.d.ts +7 -0
  202. package/dist/agent/tools/helper_requirements.js +19 -0
  203. package/dist/agent/tools/index.d.ts +1 -0
  204. package/dist/agent/tools/index.js +3 -0
  205. package/dist/agent/tools/measurement_report.d.ts +22 -0
  206. package/dist/agent/tools/measurement_report.js +190 -0
  207. package/dist/agent/tools/pane.d.ts +0 -7
  208. package/dist/agent/tools/pane.js +5 -4
  209. package/dist/agent/tools/project_context.d.ts +3 -0
  210. package/dist/agent/tools/project_context.js +45 -0
  211. package/dist/agent/tools/recall_tool_response.d.ts +3 -5
  212. package/dist/agent/tools/recall_tool_response.js +19 -29
  213. package/dist/agent/tools/registry.d.ts +1 -0
  214. package/dist/agent/tools/registry.js +23 -0
  215. package/dist/agent/tools/related_sessions.d.ts +2 -0
  216. package/dist/agent/tools/related_sessions.js +156 -0
  217. package/dist/agent/tools/routine.js +13 -3
  218. package/dist/agent/tools/session_evidence.d.ts +3 -0
  219. package/dist/agent/tools/session_evidence.js +72 -0
  220. package/dist/agent/tools/shell.d.ts +4 -3
  221. package/dist/agent/tools/shell.js +174 -47
  222. package/dist/agent/tools/tool_search.js +3 -2
  223. package/dist/agent/tools/types.d.ts +28 -0
  224. package/dist/agent/tools/verify_artifact.d.ts +2 -0
  225. package/dist/agent/tools/verify_artifact.js +60 -0
  226. package/dist/agent/tools/workflow.js +1 -0
  227. package/dist/app/activity.d.ts +6 -1
  228. package/dist/app/activity.js +81 -12
  229. package/dist/app/agent_runner.d.ts +6 -1
  230. package/dist/app/agent_runner.js +34 -19
  231. package/dist/app/command_context.js +106 -10
  232. package/dist/app/controller.d.ts +15 -4
  233. package/dist/app/controller.js +29 -3
  234. package/dist/app/escape_stop.d.ts +11 -0
  235. package/dist/app/escape_stop.js +15 -0
  236. package/dist/app/liveness.d.ts +6 -4
  237. package/dist/app/liveness.js +13 -3
  238. package/dist/app/model_catalog_cache.d.ts +5 -0
  239. package/dist/app/model_catalog_cache.js +138 -0
  240. package/dist/app/ordered_transcript.js +43 -4
  241. package/dist/app/prompt_turn.d.ts +14 -0
  242. package/dist/app/prompt_turn.js +53 -0
  243. package/dist/app/recap.d.ts +3 -0
  244. package/dist/app/recap.js +9 -3
  245. package/dist/app/restart.d.ts +3 -2
  246. package/dist/app/restart.js +22 -7
  247. package/dist/app/rewind.d.ts +3 -3
  248. package/dist/app/rewind.js +3 -3
  249. package/dist/app/selection_completion.d.ts +17 -2
  250. package/dist/app/selection_completion.js +84 -9
  251. package/dist/app/session_manager.d.ts +38 -6
  252. package/dist/app/session_manager.js +263 -60
  253. package/dist/app/session_messages.d.ts +3 -0
  254. package/dist/app/session_messages.js +60 -0
  255. package/dist/app/task_scope_router.d.ts +20 -0
  256. package/dist/app/task_scope_router.js +173 -0
  257. package/dist/app/transcript_window.d.ts +2 -0
  258. package/dist/app/transcript_window.js +4 -0
  259. package/dist/app/workspace_admission_details.d.ts +3 -0
  260. package/dist/app/workspace_admission_details.js +26 -0
  261. package/dist/app/workspace_layout_controller.d.ts +11 -0
  262. package/dist/app/workspace_layout_controller.js +88 -0
  263. package/dist/app/workspace_monitor.d.ts +1 -0
  264. package/dist/app/workspace_monitor.js +216 -0
  265. package/dist/app/workspace_presets.d.ts +29 -0
  266. package/dist/app/workspace_presets.js +192 -0
  267. package/dist/attention/queue.d.ts +37 -0
  268. package/dist/attention/queue.js +183 -0
  269. package/dist/bench/rf_shared.d.ts +2 -0
  270. package/dist/board/binding.d.ts +42 -0
  271. package/dist/board/binding.js +38 -0
  272. package/dist/board/client.d.ts +42 -0
  273. package/dist/board/client.js +60 -0
  274. package/dist/cli.js +1 -1
  275. package/dist/cli_args.js +19 -17
  276. package/dist/cli_main.js +110 -13
  277. package/dist/cli_ratatui.d.ts +183 -16
  278. package/dist/cli_ratatui.js +3989 -1255
  279. package/dist/components/ActivityLine.d.ts +3 -2
  280. package/dist/components/ActivityLine.js +2 -2
  281. package/dist/components/InputArea.d.ts +19 -1
  282. package/dist/components/InputArea.js +264 -190
  283. package/dist/components/MessageList.d.ts +2 -0
  284. package/dist/components/MessageList.js +3 -1
  285. package/dist/components/SingleMessage.d.ts +2 -0
  286. package/dist/components/SingleMessage.js +4 -2
  287. package/dist/components/StatusBar.d.ts +4 -2
  288. package/dist/components/StatusBar.js +3 -1
  289. package/dist/conductor_branch.js +3 -0
  290. package/dist/config/api_retry.d.ts +6 -1
  291. package/dist/config/api_retry.js +19 -1
  292. package/dist/config/defaults.d.ts +2 -1
  293. package/dist/config/defaults.js +9 -2
  294. package/dist/config/message_display.d.ts +1 -0
  295. package/dist/config/message_display.js +17 -2
  296. package/dist/config/permissions.d.ts +21 -0
  297. package/dist/config/permissions.js +89 -0
  298. package/dist/config/resolved.d.ts +2 -2
  299. package/dist/config/types.d.ts +32 -5
  300. package/dist/config.d.ts +38 -4
  301. package/dist/config.js +290 -42
  302. package/dist/context/project_intelligence.d.ts +44 -0
  303. package/dist/context/project_intelligence.js +246 -0
  304. package/dist/desktop/cli_launch.d.ts +8 -0
  305. package/dist/desktop/cli_launch.js +6 -0
  306. package/dist/desktop/cli_shim.d.ts +8 -0
  307. package/dist/desktop/cli_shim.js +120 -0
  308. package/dist/desktop/runtime.d.ts +16 -0
  309. package/dist/desktop/runtime.js +161 -0
  310. package/dist/desktop/worker_launch.d.ts +7 -0
  311. package/dist/desktop/worker_launch.js +7 -0
  312. package/dist/diagnostics/cargo_mutants_selection.d.ts +27 -0
  313. package/dist/diagnostics/cargo_mutants_selection.js +48 -0
  314. package/dist/diagnostics/native_terminal_benchmark.d.ts +58 -0
  315. package/dist/diagnostics/native_terminal_benchmark.js +1247 -0
  316. package/dist/diagnostics/session_evidence_package.d.ts +40 -0
  317. package/dist/diagnostics/session_evidence_package.js +234 -0
  318. package/dist/diagnostics/session_export.d.ts +93 -0
  319. package/dist/diagnostics/session_export.js +634 -0
  320. package/dist/emulation/tool_adapters.js +48 -18
  321. package/dist/emulation/ztc_addendum.d.ts +1 -0
  322. package/dist/emulation/ztc_addendum.js +13 -12
  323. package/dist/environment/service_runner.d.ts +1 -0
  324. package/dist/environment/service_runner.js +68 -0
  325. package/dist/environment/store.d.ts +120 -0
  326. package/dist/environment/store.js +387 -0
  327. package/dist/events.d.ts +40 -1
  328. package/dist/events.js +10 -17
  329. package/dist/goal_contract.d.ts +25 -0
  330. package/dist/goal_contract.js +44 -0
  331. package/dist/headless.d.ts +31 -0
  332. package/dist/headless.js +578 -70
  333. package/dist/headless_cli.js +8 -0
  334. package/dist/headless_cli_args.d.ts +1 -0
  335. package/dist/headless_cli_args.js +1 -0
  336. package/dist/intake/cli.d.ts +1 -0
  337. package/dist/intake/cli.js +33 -0
  338. package/dist/intake/format.d.ts +2 -0
  339. package/dist/intake/format.js +14 -0
  340. package/dist/intake/notifications.d.ts +16 -0
  341. package/dist/intake/notifications.js +132 -0
  342. package/dist/intake/store.d.ts +56 -0
  343. package/dist/intake/store.js +238 -0
  344. package/dist/library.d.ts +8 -0
  345. package/dist/library.js +19 -10
  346. package/dist/mcp/client.d.ts +1 -0
  347. package/dist/mcp/client.js +20 -0
  348. package/dist/modes/builtin/plan.yaml +2 -2
  349. package/dist/modes/handlers.js +1 -1
  350. package/dist/onboarding/first_run.d.ts +3 -0
  351. package/dist/onboarding/first_run.js +33 -25
  352. package/dist/onboarding/setup.d.ts +32 -0
  353. package/dist/onboarding/setup.js +133 -0
  354. package/dist/onboarding/setup_agent_health.d.ts +17 -0
  355. package/dist/onboarding/setup_agent_health.js +79 -0
  356. package/dist/onboarding/setup_environment.d.ts +66 -0
  357. package/dist/onboarding/setup_environment.js +219 -0
  358. package/dist/onboarding/setup_workspace.d.ts +17 -0
  359. package/dist/onboarding/setup_workspace.js +129 -0
  360. package/dist/optimize/adapters/index.d.ts +4 -1
  361. package/dist/optimize/adapters/index.js +7 -0
  362. package/dist/optimize/adapters/kernelbench.js +14 -5
  363. package/dist/optimize/adapters/popcorn.js +3 -2
  364. package/dist/optimize/adapters/profile_benchmark.js +13 -7
  365. package/dist/optimize/adapters/rfbench.js +25 -14
  366. package/dist/optimize/adapters/types.d.ts +3 -0
  367. package/dist/optimize/adapters/zergpod.js +5 -2
  368. package/dist/optimize/baseline.js +10 -21
  369. package/dist/optimize/branch.js +9 -12
  370. package/dist/optimize/combine.js +2 -3
  371. package/dist/optimize/executor.js +34 -48
  372. package/dist/optimize/measurements.d.ts +55 -0
  373. package/dist/optimize/measurements.js +173 -0
  374. package/dist/optimize/results.d.ts +1 -0
  375. package/dist/optimize/results.js +11 -33
  376. package/dist/optimize/tge.js +2 -1
  377. package/dist/panes/initial_input.d.ts +19 -0
  378. package/dist/panes/initial_input.js +206 -0
  379. package/dist/panes/labels.d.ts +16 -0
  380. package/dist/panes/labels.js +42 -0
  381. package/dist/panes/model_catalog.d.ts +3 -3
  382. package/dist/panes/model_catalog.js +28 -4
  383. package/dist/panes/pane_registry.js +6 -2
  384. package/dist/panes/types.d.ts +3 -0
  385. package/dist/panes/ztc_launch.js +19 -0
  386. package/dist/plugin/optimization.d.ts +2 -0
  387. package/dist/plugin/optimization.js +1 -0
  388. package/dist/plugin/runtime.d.ts +2 -0
  389. package/dist/plugin/simulation.d.ts +6 -1
  390. package/dist/plugin/simulation_contract.d.ts +836 -1
  391. package/dist/plugin/simulation_documents.d.ts +22 -0
  392. package/dist/plugin/simulation_documents.js +1 -0
  393. package/dist/plugin/simulation_interventions.d.ts +103 -0
  394. package/dist/plugin/simulation_interventions.js +1 -0
  395. package/dist/plugin/simulation_living.d.ts +1 -0
  396. package/dist/plugin/simulation_living.js +3 -0
  397. package/dist/plugin/simulation_meetings.d.ts +121 -0
  398. package/dist/plugin/simulation_meetings.js +1 -0
  399. package/dist/plugin/simulation_story_review.d.ts +111 -0
  400. package/dist/plugin/simulation_story_review.js +1 -0
  401. package/dist/plugin/simulation_storyteller.d.ts +91 -0
  402. package/dist/plugin/simulation_storyteller.js +1 -0
  403. package/dist/plugin/simulation_structured.d.ts +2 -0
  404. package/dist/plugin/simulation_structured.js +2 -0
  405. package/dist/plugin/simulation_toolkit.d.ts +21 -0
  406. package/dist/plugin/simulation_toolkit.js +24 -0
  407. package/dist/prompts/tool_delegate_task.j2 +1 -1
  408. package/dist/remote-broker/Dockerfile +10 -0
  409. package/dist/remote-broker/archive.py +1227 -0
  410. package/dist/remote-broker/cutover.py +761 -0
  411. package/dist/remote-broker/requirements.txt +5 -0
  412. package/dist/remote-broker/resources.json +6 -0
  413. package/dist/remote-broker/resources.write-staging.example.json +18 -0
  414. package/dist/remote-broker/retained.py +686 -0
  415. package/dist/remote-broker/retained_cleanup.py +550 -0
  416. package/dist/remote-broker/server.py +648 -0
  417. package/dist/remote-broker/worker.py +1007 -0
  418. package/dist/router_auth.d.ts +31 -0
  419. package/dist/router_auth.js +155 -9
  420. package/dist/runbooks/catalog.d.ts +3 -0
  421. package/dist/runbooks/catalog.js +26 -0
  422. package/dist/runbooks/library.d.ts +11 -0
  423. package/dist/runbooks/library.js +95 -0
  424. package/dist/runbooks/package.d.ts +43 -0
  425. package/dist/runbooks/package.js +102 -0
  426. package/dist/session.d.ts +65 -13
  427. package/dist/session.js +548 -43
  428. package/dist/session_handoff.d.ts +25 -0
  429. package/dist/session_handoff.js +322 -0
  430. package/dist/session_handoff_contract.d.ts +89 -0
  431. package/dist/session_handoff_contract.js +179 -0
  432. package/dist/session_media.d.ts +41 -0
  433. package/dist/session_media.js +357 -0
  434. package/dist/session_ownership.d.ts +16 -0
  435. package/dist/session_ownership.js +221 -0
  436. package/dist/session_public.d.ts +10 -2
  437. package/dist/session_public.js +6 -1
  438. package/dist/simulation/dynamic_history.d.ts +42 -0
  439. package/dist/simulation/dynamic_history.js +173 -0
  440. package/dist/simulation/engines/swarm/pane_manager.js +7 -6
  441. package/dist/simulation/inference.d.ts +3 -1
  442. package/dist/simulation/inference.js +19 -1
  443. package/dist/simulation/intervention_generation.d.ts +27 -0
  444. package/dist/simulation/intervention_generation.js +246 -0
  445. package/dist/simulation/intervention_material.d.ts +4 -0
  446. package/dist/simulation/intervention_material.js +81 -0
  447. package/dist/simulation/living/context.d.ts +3130 -0
  448. package/dist/simulation/living/context.js +944 -0
  449. package/dist/simulation/living/definer.d.ts +12 -0
  450. package/dist/simulation/living/definer.js +223 -0
  451. package/dist/simulation/living/definition.d.ts +257 -0
  452. package/dist/simulation/living/definition.js +341 -0
  453. package/dist/simulation/living/frame.d.ts +259 -0
  454. package/dist/simulation/living/frame.js +223 -0
  455. package/dist/simulation/living/identifiers.d.ts +2 -0
  456. package/dist/simulation/living/identifiers.js +12 -0
  457. package/dist/simulation/living/index.d.ts +15 -0
  458. package/dist/simulation/living/index.js +15 -0
  459. package/dist/simulation/living/inference.d.ts +219 -0
  460. package/dist/simulation/living/inference.js +576 -0
  461. package/dist/simulation/living/mechanics.d.ts +54 -0
  462. package/dist/simulation/living/mechanics.js +588 -0
  463. package/dist/simulation/living/model-input.d.ts +25 -0
  464. package/dist/simulation/living/model-input.js +215 -0
  465. package/dist/simulation/living/open-world.d.ts +257 -0
  466. package/dist/simulation/living/open-world.js +102 -0
  467. package/dist/simulation/living/projection.d.ts +7 -0
  468. package/dist/simulation/living/projection.js +31 -0
  469. package/dist/simulation/living/recall.d.ts +17 -0
  470. package/dist/simulation/living/recall.js +25 -0
  471. package/dist/simulation/living/reduce.d.ts +15 -0
  472. package/dist/simulation/living/reduce.js +1853 -0
  473. package/dist/simulation/living/replay.d.ts +6 -0
  474. package/dist/simulation/living/replay.js +22 -0
  475. package/dist/simulation/living/runtime.d.ts +140 -0
  476. package/dist/simulation/living/runtime.js +3020 -0
  477. package/dist/simulation/living/shared-kinds.d.ts +24 -0
  478. package/dist/simulation/living/shared-kinds.js +96 -0
  479. package/dist/simulation/living/types.d.ts +2785 -0
  480. package/dist/simulation/living/types.js +669 -0
  481. package/dist/simulation/llm.d.ts +3 -1
  482. package/dist/simulation/llm.js +90 -30
  483. package/dist/simulation/memory/embedding.d.ts +1 -1
  484. package/dist/simulation/memory/embedding.js +9 -3
  485. package/dist/simulation/memory/filesystem.d.ts +2 -0
  486. package/dist/simulation/memory/filesystem.js +5 -1
  487. package/dist/simulation/memory/filesystem_file_tree.d.ts +4 -1
  488. package/dist/simulation/memory/filesystem_file_tree.js +41 -10
  489. package/dist/simulation/memory/filesystem_invariants.js +1 -2
  490. package/dist/simulation/memory/filesystem_types.d.ts +1 -0
  491. package/dist/simulation/memory/memory.d.ts +2 -0
  492. package/dist/simulation/memory/memory.js +5 -1
  493. package/dist/simulation/orchestration/intent.js +0 -2
  494. package/dist/simulation/orchestration/lifecycle.js +0 -2
  495. package/dist/simulation/plugin_host.d.ts +25 -1
  496. package/dist/simulation/plugin_host.js +334 -17
  497. package/dist/simulation/session_documents.d.ts +20 -0
  498. package/dist/simulation/session_documents.js +62 -0
  499. package/dist/simulation/session_input_validation.d.ts +43 -0
  500. package/dist/simulation/session_input_validation.js +364 -0
  501. package/dist/simulation/session_inputs.d.ts +32 -0
  502. package/dist/simulation/session_inputs.js +154 -0
  503. package/dist/simulation/telemetry.d.ts +11 -14
  504. package/dist/simulation/telemetry.js +177 -54
  505. package/dist/simulation/toolkit/bounded_turns.d.ts +13 -0
  506. package/dist/simulation/toolkit/bounded_turns.js +80 -0
  507. package/dist/simulation/toolkit/dynamic_agents.d.ts +25 -0
  508. package/dist/simulation/toolkit/dynamic_agents.js +218 -0
  509. package/dist/simulation/toolkit/dynamic_digest.d.ts +1 -0
  510. package/dist/simulation/toolkit/dynamic_digest.js +8 -0
  511. package/dist/simulation/toolkit/dynamic_documents.d.ts +7 -0
  512. package/dist/simulation/toolkit/dynamic_documents.js +71 -0
  513. package/dist/simulation/toolkit/dynamic_driver.d.ts +8 -0
  514. package/dist/simulation/toolkit/dynamic_driver.js +269 -0
  515. package/dist/simulation/toolkit/dynamic_game.d.ts +53 -0
  516. package/dist/simulation/toolkit/dynamic_game.js +805 -0
  517. package/dist/simulation/toolkit/dynamic_knowledge.d.ts +15 -0
  518. package/dist/simulation/toolkit/dynamic_knowledge.js +91 -0
  519. package/dist/simulation/toolkit/dynamic_mechanics.d.ts +22 -0
  520. package/dist/simulation/toolkit/dynamic_mechanics.js +910 -0
  521. package/dist/simulation/toolkit/dynamic_meetings.d.ts +239 -0
  522. package/dist/simulation/toolkit/dynamic_meetings.js +350 -0
  523. package/dist/simulation/toolkit/dynamic_progress.d.ts +131 -0
  524. package/dist/simulation/toolkit/dynamic_progress.js +214 -0
  525. package/dist/simulation/toolkit/dynamic_projection.d.ts +9 -0
  526. package/dist/simulation/toolkit/dynamic_projection.js +219 -0
  527. package/dist/simulation/toolkit/dynamic_runtime.d.ts +412 -0
  528. package/dist/simulation/toolkit/dynamic_runtime.js +6536 -0
  529. package/dist/simulation/toolkit/dynamic_state.d.ts +111 -0
  530. package/dist/simulation/toolkit/dynamic_state.js +1967 -0
  531. package/dist/simulation/toolkit/dynamic_story_review.d.ts +508 -0
  532. package/dist/simulation/toolkit/dynamic_story_review.js +333 -0
  533. package/dist/simulation/toolkit/dynamic_storyteller.d.ts +77 -0
  534. package/dist/simulation/toolkit/dynamic_storyteller.js +114 -0
  535. package/dist/simulation/toolkit/dynamic_storyteller_runtime.d.ts +26 -0
  536. package/dist/simulation/toolkit/dynamic_storyteller_runtime.js +126 -0
  537. package/dist/simulation/toolkit/dynamic_world.d.ts +22 -0
  538. package/dist/simulation/toolkit/dynamic_world.js +377 -0
  539. package/dist/simulation/toolkit/structured_output.d.ts +5 -0
  540. package/dist/simulation/toolkit/structured_output.js +321 -0
  541. package/dist/simulation/types/world.d.ts +2 -0
  542. package/dist/simulation/world/artifact_store.js +1 -1
  543. package/dist/simulation/world/memory_manager.d.ts +5 -0
  544. package/dist/simulation/world/memory_manager.js +17 -2
  545. package/dist/simulation/world/state.d.ts +3 -2
  546. package/dist/simulation/world/state.js +15 -5
  547. package/dist/skills/bundled/measurement-validation/SKILL.md +144 -0
  548. package/dist/terminal_attach.d.ts +1 -0
  549. package/dist/terminal_attach.js +113 -0
  550. package/dist/tool_progress.d.ts +3 -0
  551. package/dist/tool_progress.js +27 -0
  552. package/dist/types.d.ts +145 -10
  553. package/dist/types.js +3 -0
  554. package/dist/ui/core/input.d.ts +3 -0
  555. package/dist/ui/core/input_segments.d.ts +18 -1
  556. package/dist/ui/core/input_segments.js +131 -47
  557. package/dist/ui/core/input_state.d.ts +12 -0
  558. package/dist/ui/core/layout_yoga.d.ts +1 -1
  559. package/dist/ui/core/turn_metadata.d.ts +5 -0
  560. package/dist/ui/core/turn_metadata.js +22 -0
  561. package/dist/ui/ink/render.d.ts +2 -1
  562. package/dist/ui/ink/render.js +6 -5
  563. package/dist/ui/ratatui/headless_pane_manager.d.ts +14 -1
  564. package/dist/ui/ratatui/headless_pane_manager.js +293 -104
  565. package/dist/ui/ratatui/index.d.ts +1 -0
  566. package/dist/ui/ratatui/input.d.ts +4 -0
  567. package/dist/ui/ratatui/input.js +15 -0
  568. package/dist/ui/ratatui/ipc_path.d.ts +2 -0
  569. package/dist/ui/ratatui/ipc_path.js +29 -1
  570. package/dist/ui/ratatui/mux_persistence.d.ts +3 -1
  571. package/dist/ui/ratatui/mux_persistence.js +139 -1
  572. package/dist/ui/ratatui/pane_relay.d.ts +1 -1
  573. package/dist/ui/ratatui/pane_relay.js +55 -27
  574. package/dist/ui/ratatui/process.d.ts +17 -1
  575. package/dist/ui/ratatui/process.js +80 -7
  576. package/dist/ui/ratatui/protocol.d.ts +236 -1
  577. package/dist/ui/ratatui/protocol.js +90 -0
  578. package/dist/ui/scrollback.d.ts +2 -1
  579. package/dist/ui/scrollback.js +9 -4
  580. package/dist/ui/stream_typewriter.d.ts +8 -3
  581. package/dist/ui/stream_typewriter.js +30 -1
  582. package/dist/ui/streaming_scrollback.d.ts +1 -0
  583. package/dist/ui/streaming_scrollback.js +1 -1
  584. package/dist/ui/transcript_render_cache.js +10 -0
  585. package/dist/ui/views/activity_line.d.ts +5 -3
  586. package/dist/ui/views/activity_line.js +65 -7
  587. package/dist/ui/views/app.d.ts +6 -3
  588. package/dist/ui/views/app.js +17 -6
  589. package/dist/ui/views/completion_menu.js +10 -5
  590. package/dist/ui/views/input_area.d.ts +7 -3
  591. package/dist/ui/views/input_area.js +15 -9
  592. package/dist/ui/views/message_list.d.ts +4 -1
  593. package/dist/ui/views/message_list.js +89 -19
  594. package/dist/ui/views/queue_panel.d.ts +3 -1
  595. package/dist/ui/views/queue_panel.js +9 -2
  596. package/dist/ui/views/status_bar.d.ts +4 -2
  597. package/dist/ui/views/status_bar.js +28 -13
  598. package/dist/ui/views/theme.d.ts +2 -0
  599. package/dist/ui/views/theme.js +2 -0
  600. package/dist/ui/widgets/color_panel.js +18 -0
  601. package/dist/ui/widgets/form.js +8 -0
  602. package/dist/ui/widgets/log_view.js +7 -0
  603. package/dist/ui/widgets/navigator_panel.js +8 -0
  604. package/dist/ui/widgets/prompt_viewer.js +2 -0
  605. package/dist/ui/widgets/questionnaire.d.ts +2 -0
  606. package/dist/ui/widgets/questionnaire.js +79 -16
  607. package/dist/ui/widgets/resume_panel.d.ts +2 -0
  608. package/dist/ui/widgets/resume_panel.js +26 -15
  609. package/dist/ui/widgets/rewind_panel.d.ts +2 -2
  610. package/dist/ui/widgets/rewind_panel.js +4 -2
  611. package/dist/ui/widgets/select.js +16 -27
  612. package/dist/ui/widgets/settings_panel.js +16 -0
  613. package/dist/ui/widgets/status_line_panel.d.ts +1 -0
  614. package/dist/ui/widgets/status_line_panel.js +30 -4
  615. package/dist/ui/widgets/text_input.d.ts +2 -0
  616. package/dist/ui/widgets/text_input.js +15 -1
  617. package/dist/ui/widgets/types.d.ts +2 -0
  618. package/dist/usage_projection.d.ts +24 -0
  619. package/dist/usage_projection.js +130 -0
  620. package/dist/usage_snapshot.d.ts +36 -0
  621. package/dist/usage_snapshot.js +131 -0
  622. package/dist/utils/assistant_message.d.ts +2 -0
  623. package/dist/utils/assistant_message.js +5 -0
  624. package/dist/utils/atomic_write.d.ts +6 -2
  625. package/dist/utils/atomic_write.js +29 -4
  626. package/dist/utils/checked_shell.d.ts +25 -0
  627. package/dist/utils/checked_shell.js +107 -0
  628. package/dist/utils/compaction.d.ts +4 -1
  629. package/dist/utils/compaction.js +31 -7
  630. package/dist/utils/context.d.ts +4 -4
  631. package/dist/utils/context.js +14 -12
  632. package/dist/utils/deepseek.d.ts +2 -2
  633. package/dist/utils/deepseek.js +13 -5
  634. package/dist/utils/ephemeral.js +4 -0
  635. package/dist/utils/ephemeral_tools.d.ts +18 -0
  636. package/dist/utils/ephemeral_tools.js +97 -0
  637. package/dist/utils/jev_client.d.ts +4 -0
  638. package/dist/utils/jev_client.js +15 -0
  639. package/dist/utils/model_cache.d.ts +15 -0
  640. package/dist/utils/model_cache.js +143 -0
  641. package/dist/utils/models.d.ts +4 -2
  642. package/dist/utils/models.js +12 -16
  643. package/dist/utils/reasoning.d.ts +8 -1
  644. package/dist/utils/reasoning.js +31 -11
  645. package/dist/utils/router_catalog.d.ts +49 -0
  646. package/dist/utils/router_catalog.js +271 -0
  647. package/dist/utils/sandbox.d.ts +9 -0
  648. package/dist/utils/sandbox.js +118 -15
  649. package/dist/utils/shell.d.ts +7 -0
  650. package/dist/utils/shell.js +98 -34
  651. package/dist/utils/subprocess_env.d.ts +3 -0
  652. package/dist/utils/subprocess_env.js +27 -1
  653. package/dist/web/accounts_mirror.d.ts +3 -1
  654. package/dist/web/accounts_mirror.js +13 -9
  655. package/dist/web/child_publication.d.ts +17 -0
  656. package/dist/web/child_publication.js +45 -0
  657. package/dist/web/mirror_authenticator.d.ts +31 -11
  658. package/dist/web/mirror_authenticator.js +67 -17
  659. package/dist/web/mirror_client.d.ts +31 -4
  660. package/dist/web/mirror_client.js +189 -23
  661. package/dist/web/mirror_stream.d.ts +84 -22
  662. package/dist/web/mirror_stream.js +676 -95
  663. package/dist/web/observer_query_redaction.d.ts +6 -0
  664. package/dist/web/observer_query_redaction.js +58 -0
  665. package/dist/web/pane_registry.d.ts +2 -0
  666. package/dist/web/pane_registry.js +86 -22
  667. package/dist/web/tool_image.d.ts +6 -0
  668. package/dist/web/tool_image.js +17 -0
  669. package/dist/web/view_plugin.d.ts +14 -0
  670. package/dist/web/view_plugin.js +23 -1
  671. package/dist/worker/executor.js +31 -12
  672. package/dist/worker/index.d.ts +1 -0
  673. package/dist/worker/toolsets.js +3 -1
  674. package/dist/worker/types.d.ts +12 -1
  675. package/dist/workflows/registry.js +1 -0
  676. package/dist/workflows/registry_handle.d.ts +2 -0
  677. package/dist/workflows/runtime.d.ts +2 -0
  678. package/dist/workflows/runtime.js +1 -0
  679. package/dist/workspace_agent.d.ts +7 -0
  680. package/dist/workspace_agent.js +36 -0
  681. package/dist/workspace_collision.d.ts +76 -0
  682. package/dist/workspace_collision.js +688 -0
  683. package/dist/workspace_layout.d.ts +18 -0
  684. package/dist/workspace_layout.js +121 -0
  685. package/dist/workspace_runtime.d.ts +44 -0
  686. package/dist/workspace_runtime.js +82 -0
  687. package/dist/workspace_session_context.d.ts +62 -0
  688. package/dist/workspace_session_context.js +160 -0
  689. package/dist/zlm-embed.mjs +32518 -9615
  690. package/dist/zlm.js +1 -0
  691. package/node_modules/@zerg/model-contracts/LICENSE.md +375 -0
  692. package/node_modules/@zerg/model-contracts/README.md +90 -0
  693. package/node_modules/@zerg/model-contracts/index.d.ts +20 -0
  694. package/node_modules/@zerg/model-contracts/index.js +223 -0
  695. package/node_modules/@zerg/model-contracts/package.json +8 -0
  696. package/node_modules/glob/node_modules/brace-expansion/index.js +78 -22
  697. package/node_modules/glob/node_modules/brace-expansion/package.json +1 -1
  698. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  699. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  700. package/package.json +45 -24
  701. package/packages/model-contracts/LICENSE.md +375 -0
  702. package/packages/model-contracts/README.md +90 -0
  703. package/packages/model-contracts/index.d.ts +20 -0
  704. package/packages/model-contracts/index.js +223 -0
  705. package/packages/model-contracts/package.json +8 -0
  706. package/remote-broker/Dockerfile +10 -0
  707. package/remote-broker/README.md +308 -0
  708. package/remote-broker/archive.py +1227 -0
  709. package/remote-broker/cutover.py +761 -0
  710. package/remote-broker/requirements.txt +5 -0
  711. package/remote-broker/resources.example.json +6 -0
  712. package/remote-broker/resources.write-staging.example.json +18 -0
  713. package/remote-broker/retained.py +686 -0
  714. package/remote-broker/retained_cleanup.py +550 -0
  715. package/remote-broker/server.py +648 -0
  716. package/scripts/build-remote-broker.mjs +13 -0
  717. package/scripts/ensure-local-packages.mjs +6 -0
  718. package/scripts/node-pty-helpers.mjs +103 -0
@@ -0,0 +1,761 @@
1
+ """Offline operator cutover. Archives evidence; never imports lease authority."""
2
+
3
+ import argparse
4
+ import contextlib
5
+ import fcntl
6
+ import hashlib
7
+ import json
8
+ import logging
9
+ import os
10
+ import re
11
+ import stat
12
+ import tempfile
13
+ from pathlib import Path
14
+ from typing import Any, Iterator, Literal
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field
17
+
18
+ L = logging.getLogger(__name__)
19
+ MAX_FILE = 8 * 1024 * 1024
20
+ MAX_TOTAL = 64 * 1024 * 1024
21
+ UUID = re.compile(r"^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$")
22
+ FENCE = "cutover-fence.json"
23
+
24
+
25
+ class Endpoint(BaseModel):
26
+ model_config = ConfigDict(extra="forbid", strict=True)
27
+ host: str = Field(min_length=1, max_length=253)
28
+ user: str = Field(min_length=1, max_length=100)
29
+ port: int = Field(ge=1, le=65535)
30
+
31
+
32
+ class Resource(BaseModel):
33
+ model_config = ConfigDict(extra="forbid", strict=True)
34
+ id: str = Field(pattern=r"^[a-zA-Z0-9._:-]{1,100}$")
35
+ configured_root: str
36
+ root_directory: str
37
+ endpoint: Endpoint
38
+
39
+
40
+ class StagingView(BaseModel):
41
+ model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
42
+ configured_path: str = Field(min_length=1, max_length=4096)
43
+ directory: str = Field(min_length=1, max_length=4096)
44
+
45
+
46
+ class PolicyResource(BaseModel):
47
+ model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
48
+ id: str = Field(pattern=r"^[a-zA-Z0-9._:-]{1,100}$")
49
+ root: str = Field(min_length=1, max_length=4096)
50
+ endpoint: Endpoint
51
+ write_staging_directory: str | None = Field(default=None, max_length=4096)
52
+
53
+
54
+ class ResourcePolicy(BaseModel):
55
+ # Unknown policy fields fail closed rather than becoming omitted locations.
56
+ model_config = ConfigDict(extra="forbid", strict=True, frozen=True)
57
+ version: Literal[1]
58
+ resources: list[PolicyResource] = Field(min_length=1, max_length=100)
59
+ receipt_archive_index: bool = False
60
+ legacy_write_staging_directories: list[str] = Field(
61
+ default_factory=list, max_length=100
62
+ )
63
+
64
+
65
+ class Authority(BaseModel):
66
+ model_config = ConfigDict(extra="forbid", strict=True)
67
+ state_directory: str
68
+ staging_directory: str
69
+ worker_file: str
70
+ resources: list[Resource] = Field(min_length=1, max_length=100)
71
+ resource_policy_file: str | None = Field(default=None, max_length=4096)
72
+ legacy_default_staging_only: bool = False
73
+ staging_views: list[StagingView] = Field(default_factory=list, max_length=201)
74
+
75
+
76
+ class Mapping(BaseModel):
77
+ model_config = ConfigDict(extra="forbid", strict=True)
78
+ legacy: str
79
+ receiver: str
80
+
81
+
82
+ class Manifest(BaseModel):
83
+ model_config = ConfigDict(extra="forbid", strict=True)
84
+ version: Literal[1]
85
+ clients_stopped: bool
86
+ legacy: Authority
87
+ receiver: Authority
88
+ client_journals: list[str] = Field(min_length=1, max_length=100)
89
+ identity_mapping: list[Mapping] = Field(min_length=1, max_length=100)
90
+
91
+
92
+ class Lease(BaseModel):
93
+ model_config = ConfigDict(extra="allow", strict=True, allow_inf_nan=False)
94
+ root: str
95
+ identity: list[int] = Field(min_length=2, max_length=2)
96
+ ancestors: list[list[int]]
97
+ owner_id: str | None
98
+ generation: int = Field(ge=0)
99
+ expires_at: float | int
100
+
101
+
102
+ class Receipt(BaseModel):
103
+ model_config = ConfigDict(extra="allow", strict=True)
104
+ version: Literal[1]
105
+ id: str
106
+ operation: Literal["write", "delete"]
107
+ status: Literal[
108
+ "pending", "verified", "failed", "unverified", "dry_run", "cancelled"
109
+ ]
110
+ admission_resolved: bool = False
111
+ owner_id: str
112
+ resource_id: str
113
+ endpoint: Endpoint
114
+ configured_root: str
115
+ root_identity: list[int] = Field(min_length=2, max_length=2)
116
+ requested_path: str
117
+ tool_call_id: str | None
118
+ target: dict[str, Any]
119
+
120
+
121
+ class ClientRecord(BaseModel):
122
+ model_config = ConfigDict(extra="forbid", strict=True)
123
+ version: Literal[1]
124
+ id: str
125
+ status: Literal["pending", "resolved", "not_dispatched"]
126
+ operation: Literal["write", "delete"]
127
+ owner_id: str
128
+ resource_id: str
129
+ endpoint: Endpoint
130
+ target: str
131
+ requested_path: str
132
+ tool_call_id: str | None
133
+
134
+
135
+ def canonical(data: Any) -> bytes:
136
+ return json.dumps(
137
+ data, sort_keys=True, separators=(",", ":"), allow_nan=False
138
+ ).encode()
139
+
140
+
141
+ def digest(data: bytes) -> str:
142
+ return hashlib.sha256(data).hexdigest()
143
+
144
+
145
+ def normalized_manifest(manifest: Manifest) -> dict[str, Any]:
146
+ value: dict[str, Any] = manifest.model_dump()
147
+ value["identity_mapping"] = sorted(
148
+ value["identity_mapping"], key=lambda m: m["legacy"]
149
+ )
150
+ value["client_journals"] = sorted(value["client_journals"])
151
+ for side in ["legacy", "receiver"]:
152
+ value[side]["resources"] = sorted(
153
+ value[side]["resources"], key=lambda r: r["id"]
154
+ )
155
+ value[side]["staging_views"] = sorted(
156
+ value[side]["staging_views"], key=lambda view: view["configured_path"]
157
+ )
158
+ return value
159
+
160
+
161
+ def directory(value: str) -> Path:
162
+ path = Path(value)
163
+ if not path.is_absolute() or str(path.resolve(strict=True)) != value:
164
+ raise ValueError(
165
+ "Evidence directories must be existing canonical absolute paths"
166
+ )
167
+ if not stat.S_ISDIR(path.lstat().st_mode):
168
+ raise ValueError("Evidence path is not a directory")
169
+ return path
170
+
171
+
172
+ def read_file(path: Path) -> bytes:
173
+ descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
174
+ try:
175
+ if not stat.S_ISREG(os.fstat(descriptor).st_mode):
176
+ raise ValueError("Evidence must be a regular file")
177
+ with os.fdopen(descriptor, "rb", closefd=False) as stream:
178
+ data = stream.read(MAX_FILE + 1)
179
+ if len(data) > MAX_FILE:
180
+ raise ValueError("Evidence file exceeds the byte bound")
181
+ return data
182
+ finally:
183
+ os.close(descriptor)
184
+
185
+
186
+ def json_data(data: bytes) -> Any:
187
+ try:
188
+ return json.loads(data)
189
+ except (ValueError, UnicodeError) as error:
190
+ raise ValueError(
191
+ "Invalid JSON evidence; restore or reconcile it first"
192
+ ) from error
193
+
194
+
195
+ def entries(path: Path) -> list[Path]:
196
+ result = sorted(path.iterdir())
197
+ if len(result) > 10000:
198
+ raise ValueError(
199
+ "Journal exceeds the entry bound; reviewed maintenance required"
200
+ )
201
+ for item in result:
202
+ if item.suffix != ".json" or UUID.fullmatch(item.stem) is None:
203
+ raise ValueError("Invalid journal evidence entry")
204
+ return result
205
+
206
+
207
+ def sync_directory(path: Path) -> None:
208
+ descriptor = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
209
+ try:
210
+ os.fsync(descriptor)
211
+ finally:
212
+ os.close(descriptor)
213
+
214
+
215
+ def publish(path: Path, data: bytes) -> None:
216
+ """No-overwrite, durable, resumable publication of exact immutable bytes."""
217
+ if os.path.lexists(path):
218
+ if read_file(path) != data:
219
+ raise ValueError(
220
+ "Existing cutover evidence conflicts; preserve it for recovery"
221
+ )
222
+ sync_directory(path.parent)
223
+ return
224
+ descriptor, temporary = tempfile.mkstemp(prefix=".cutover-", dir=path.parent)
225
+ try:
226
+ with os.fdopen(descriptor, "wb") as stream:
227
+ stream.write(data)
228
+ stream.flush()
229
+ os.fsync(stream.fileno())
230
+ os.link(temporary, path)
231
+ sync_directory(path.parent)
232
+ finally:
233
+ os.unlink(temporary)
234
+
235
+
236
+ def make_directory(path: Path) -> None:
237
+ if not path.exists():
238
+ make_directory(path.parent)
239
+ path.mkdir(mode=0o700)
240
+ sync_directory(path.parent)
241
+ directory(str(path))
242
+ sync_directory(path)
243
+
244
+
245
+ @contextlib.contextmanager
246
+ def locked(manifest: Manifest) -> Iterator[None]:
247
+ """Use the same lock as every managed worker, in canonical path order."""
248
+ states = sorted(
249
+ {manifest.legacy.state_directory, manifest.receiver.state_directory}
250
+ )
251
+ if len(states) != 2 or any(
252
+ Path(a).is_relative_to(b) for a in states for b in states if a != b
253
+ ):
254
+ raise ValueError(
255
+ "Legacy and receiver state directories must be distinct and disjoint"
256
+ )
257
+ with contextlib.ExitStack() as stack:
258
+ for value in states:
259
+ state = directory(value)
260
+ descriptor = os.open(
261
+ state / "lock", os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK
262
+ )
263
+ stack.callback(os.close, descriptor)
264
+ if not stat.S_ISREG(os.fstat(descriptor).st_mode):
265
+ raise ValueError("Missing regular broker lock evidence")
266
+ fcntl.flock(descriptor, fcntl.LOCK_EX)
267
+ yield
268
+
269
+
270
+ class Survey:
271
+ def __init__(self, manifest: Manifest, reference: Path) -> None:
272
+ self.manifest = manifest
273
+ self.files: dict[str, bytes] = {}
274
+ self.blockers: list[str] = []
275
+ self.inventory: list[dict[str, Any]] = []
276
+ self.bindings: list[dict[str, Any]] = []
277
+ self.receipts: list[Receipt] = []
278
+ self.staging_bindings: list[dict[str, Any]] = []
279
+ self.reference = read_file(reference)
280
+ if (
281
+ b"os.path.lexists(os.path.join(control, 'cutover-fence.json'))"
282
+ not in self.reference
283
+ ):
284
+ raise ValueError("Reference worker lacks the cutover fence protocol")
285
+
286
+ def capture(self, path: Path, name: str) -> bytes:
287
+ data = read_file(path)
288
+ self.files[name] = data
289
+ if sum(map(len, self.files.values())) > MAX_TOTAL:
290
+ raise ValueError("Cutover evidence exceeds the aggregate byte bound")
291
+ return data
292
+
293
+ def inspect_staging(
294
+ self, side: str, authority: Authority, *, check_contents: bool = True
295
+ ) -> None:
296
+ default = directory(authority.staging_directory)
297
+ if authority.resource_policy_file is None:
298
+ if (
299
+ side == "receiver"
300
+ or not authority.legacy_default_staging_only
301
+ or authority.staging_views
302
+ ):
303
+ self.blockers.append(
304
+ side + ": complete resource policy and staging inventory required"
305
+ )
306
+ info = default.stat()
307
+ self.staging_bindings.append(
308
+ {
309
+ "side": side,
310
+ "configured_path": "legacy-default",
311
+ "directory": str(default),
312
+ "identity": [info.st_dev, info.st_ino],
313
+ "owner_uid": info.st_uid,
314
+ "mode": stat.S_IMODE(info.st_mode),
315
+ }
316
+ )
317
+ if check_contents and any(default.iterdir()):
318
+ self.blockers.append(
319
+ side + ": retained staging objects require confirmed recovery"
320
+ )
321
+ return
322
+ if authority.legacy_default_staging_only:
323
+ raise ValueError(
324
+ "Legacy default-only attestation conflicts with complete policy inventory"
325
+ )
326
+ policy_path = Path(authority.resource_policy_file)
327
+ if not policy_path.is_absolute() or str(
328
+ policy_path.resolve(strict=True)
329
+ ) != str(policy_path):
330
+ raise ValueError(
331
+ "Resource policy must have an existing canonical absolute path"
332
+ )
333
+ policy = ResourcePolicy.model_validate_json(
334
+ self.capture(policy_path, side + "/resource-policy.json")
335
+ )
336
+ resources = {resource.id: resource for resource in authority.resources}
337
+ if len(resources) != len(authority.resources) or len(
338
+ {resource.id for resource in policy.resources}
339
+ ) != len(policy.resources):
340
+ raise ValueError(
341
+ "Resource policy and inventory require unique resource IDs"
342
+ )
343
+ if set(resources) != {resource.id for resource in policy.resources}:
344
+ raise ValueError("Resource policy inventory is incomplete")
345
+ expected = {
346
+ "/data/.ztc-delete-quarantine",
347
+ *policy.legacy_write_staging_directories,
348
+ }
349
+ for resource in policy.resources:
350
+ mapped = resources[resource.id]
351
+ if (
352
+ resource.root != mapped.configured_root
353
+ or resource.endpoint != mapped.endpoint
354
+ ):
355
+ raise ValueError(
356
+ "Resource policy lacks its original reviewed root/endpoint binding"
357
+ )
358
+ if resource.write_staging_directory is not None:
359
+ expected.add(resource.write_staging_directory)
360
+ views = {view.configured_path: view for view in authority.staging_views}
361
+ if len(views) != len(authority.staging_views) or set(views) != expected:
362
+ raise ValueError(
363
+ "Complete current/legacy staging view inventory is required"
364
+ )
365
+ if views["/data/.ztc-delete-quarantine"].directory != str(default):
366
+ raise ValueError(
367
+ "Default staging view conflicts with the authority inventory"
368
+ )
369
+ roots = {
370
+ resource.id: directory(resource.root_directory)
371
+ for resource in authority.resources
372
+ }
373
+ for configured, view in sorted(views.items()):
374
+ if (
375
+ not os.path.isabs(configured)
376
+ or os.path.normpath(configured) != configured
377
+ or "\x00" in configured
378
+ ):
379
+ raise ValueError(
380
+ "Configured staging paths must be canonical absolute paths"
381
+ )
382
+ location = directory(view.directory)
383
+ info = location.stat()
384
+ if info.st_mode & 0o077:
385
+ raise ValueError("Staging views must have private permissions")
386
+ if any(
387
+ Path(configured).is_relative_to(resource.root)
388
+ or Path(resource.root).is_relative_to(configured)
389
+ for resource in policy.resources
390
+ ):
391
+ raise ValueError(
392
+ "Private staging overlaps an original resource policy path"
393
+ )
394
+ if any(
395
+ location.is_relative_to(root)
396
+ or root.is_relative_to(location)
397
+ or (info.st_dev, info.st_ino)
398
+ == (root.stat().st_dev, root.stat().st_ino)
399
+ for root in roots.values()
400
+ ):
401
+ raise ValueError("Private staging views overlap a mapped resource")
402
+ for resource in policy.resources:
403
+ if (
404
+ resource.write_staging_directory == configured
405
+ and roots[resource.id].stat().st_dev != info.st_dev
406
+ ):
407
+ raise ValueError(
408
+ "Configured write staging view must share the resource filesystem"
409
+ )
410
+ self.staging_bindings.append(
411
+ {
412
+ "side": side,
413
+ "configured_path": configured,
414
+ "directory": str(location),
415
+ "identity": [info.st_dev, info.st_ino],
416
+ "owner_uid": info.st_uid,
417
+ "mode": stat.S_IMODE(info.st_mode),
418
+ }
419
+ )
420
+ if check_contents and any(location.iterdir()):
421
+ self.blockers.append(
422
+ side
423
+ + ": retained staging objects require confirmed recovery: "
424
+ + configured
425
+ )
426
+
427
+ def inspect_authority(self, side: str, authority: Authority) -> None:
428
+ state = directory(authority.state_directory)
429
+ self.inspect_staging(side, authority)
430
+ if read_file(Path(authority.worker_file)) != self.reference:
431
+ raise ValueError(
432
+ "Deployed worker differs from the reviewed fence-capable worker"
433
+ )
434
+ resources: dict[str, Resource] = {}
435
+ identities: dict[str, list[int]] = {}
436
+ for resource in authority.resources:
437
+ root = directory(resource.root_directory)
438
+ if (
439
+ not os.path.isabs(resource.configured_root)
440
+ or os.path.normpath(resource.configured_root)
441
+ != resource.configured_root
442
+ ):
443
+ raise ValueError("Configured roots must be canonical absolute paths")
444
+ if resource.id in resources or resource.configured_root in identities:
445
+ raise ValueError("Authority resource IDs and roots must be unique")
446
+ info = root.stat()
447
+ identity = [info.st_dev, info.st_ino]
448
+ resources[resource.id] = resource
449
+ identities[resource.configured_root] = identity
450
+ self.bindings.append(
451
+ {"side": side, **resource.model_dump(), "root_identity": identity}
452
+ )
453
+ lease_data = json_data(
454
+ self.capture(state / "leases.json", side + "/leases.json")
455
+ )
456
+ if not isinstance(lease_data, dict):
457
+ raise ValueError("Invalid lease registry evidence")
458
+ for key, value in lease_data.items():
459
+ lease = Lease.model_validate(value)
460
+ if (
461
+ key != digest(lease.root.encode())
462
+ or identities.get(lease.root) != lease.identity
463
+ ):
464
+ raise ValueError(
465
+ "Lease root or physical identity lacks reviewed mapping"
466
+ )
467
+ # Explicit stop is required even for expired owners; expiry is not quiescence.
468
+ if lease.owner_id is not None:
469
+ self.blockers.append(
470
+ side + ": stop every recorded lease before cutover"
471
+ )
472
+ owners = json_data(self.capture(state / "owners.json", side + "/owners.json"))
473
+ if not isinstance(owners, dict) or any(
474
+ type(v) not in [int, float] for v in owners.values()
475
+ ):
476
+ raise ValueError("Invalid owner-presence evidence")
477
+ canonical(owners) # Reject nonfinite presence timestamps.
478
+ paths = entries(directory(str(state / "receipts")))
479
+ for path in paths:
480
+ receipt = Receipt.model_validate_json(
481
+ self.capture(path, side + "/receipts/" + path.name)
482
+ )
483
+ matched_resource = resources.get(receipt.resource_id)
484
+ if (
485
+ receipt.id != path.stem
486
+ or matched_resource is None
487
+ or receipt.endpoint != matched_resource.endpoint
488
+ or receipt.configured_root != matched_resource.configured_root
489
+ or receipt.root_identity != identities[matched_resource.configured_root]
490
+ ):
491
+ raise ValueError("Receipt identity lacks its original reviewed binding")
492
+ expected_target = os.path.normpath(
493
+ os.path.join(matched_resource.configured_root, receipt.requested_path)
494
+ )
495
+ if (
496
+ os.path.isabs(receipt.requested_path)
497
+ or not receipt.requested_path
498
+ or "\x00" in receipt.requested_path
499
+ or not expected_target.startswith(
500
+ matched_resource.configured_root.rstrip("/") + "/"
501
+ )
502
+ or receipt.target.get("path") != expected_target
503
+ ):
504
+ raise ValueError("Receipt target lacks its original reviewed binding")
505
+ if (
506
+ receipt.status in ["pending", "unverified"]
507
+ or not receipt.admission_resolved
508
+ ):
509
+ self.blockers.append(side + ": unresolved receipt " + receipt.id)
510
+ self.receipts.append(receipt)
511
+ self.inventory.append(
512
+ {"side": side, "receipt_count": len(paths), "lease_count": len(lease_data)}
513
+ )
514
+
515
+ def inspect(self) -> dict[str, Any]:
516
+ if not self.manifest.clients_stopped:
517
+ self.blockers.append(
518
+ "Operator must stop all clients and confirm the complete client inventory"
519
+ )
520
+ for side in ["legacy", "receiver"]:
521
+ authority = (
522
+ self.manifest.legacy if side == "legacy" else self.manifest.receiver
523
+ )
524
+ self.inspect_authority(side, authority)
525
+ mappings = sorted(
526
+ [m.model_dump() for m in self.manifest.identity_mapping],
527
+ key=lambda m: m["legacy"],
528
+ )
529
+ for side in ["legacy", "receiver"]:
530
+ expected = {
531
+ r.id
532
+ for r in (
533
+ self.manifest.legacy if side == "legacy" else self.manifest.receiver
534
+ ).resources
535
+ }
536
+ actual = [m[side] for m in mappings]
537
+ if len(set(actual)) != len(actual) or set(actual) != expected:
538
+ raise ValueError(
539
+ "Identity mapping must cover both authorities exactly once"
540
+ )
541
+ count = 0
542
+ journals = sorted(self.manifest.client_journals)
543
+ if len(set(journals)) != len(journals):
544
+ raise ValueError("Duplicate client journal in reviewed inventory")
545
+ for index, value in enumerate(journals):
546
+ journal = directory(value)
547
+ for path in entries(journal):
548
+ record = ClientRecord.model_validate_json(
549
+ self.capture(path, f"clients/{index}/" + path.name)
550
+ )
551
+ if record.id != path.stem:
552
+ raise ValueError("Invalid client operation identity")
553
+ resources = [
554
+ r
555
+ for a in [self.manifest.legacy, self.manifest.receiver]
556
+ for r in a.resources
557
+ if r.id == record.resource_id and r.endpoint == record.endpoint
558
+ ]
559
+ if len(resources) != 1:
560
+ raise ValueError(
561
+ "Client endpoint lacks its original reviewed mapping"
562
+ )
563
+ expected_target = os.path.normpath(
564
+ os.path.join(resources[0].configured_root, record.requested_path)
565
+ )
566
+ if (
567
+ os.path.isabs(record.requested_path)
568
+ or not record.requested_path
569
+ or "\x00" in record.requested_path
570
+ or not expected_target.startswith(
571
+ resources[0].configured_root.rstrip("/") + "/"
572
+ )
573
+ or record.target != expected_target
574
+ ):
575
+ raise ValueError(
576
+ "Client target lacks its original reviewed mapping"
577
+ )
578
+ count += 1
579
+ if record.status == "pending":
580
+ self.blockers.append("pending client operation " + record.id)
581
+ matches = [
582
+ r
583
+ for r in self.receipts
584
+ if r.id == record.id
585
+ and r.owner_id == record.owner_id
586
+ and r.resource_id == record.resource_id
587
+ and r.endpoint == record.endpoint
588
+ and r.operation == record.operation
589
+ and r.tool_call_id == record.tool_call_id
590
+ and r.requested_path == record.requested_path
591
+ and r.target.get("path") == record.target
592
+ ]
593
+ if record.status == "resolved" and len(matches) != 1:
594
+ self.blockers.append(
595
+ "Original receipt missing or mismatched for client operation "
596
+ + record.id
597
+ )
598
+ if record.status == "not_dispatched" and any(
599
+ r.id == record.id for r in self.receipts
600
+ ):
601
+ self.blockers.append(
602
+ "No-dispatch client evidence conflicts with a recorded receipt "
603
+ + record.id
604
+ )
605
+ self.inventory.append(
606
+ {"side": "clients", "client_count": count, "journal_count": len(journals)}
607
+ )
608
+ # Canonical mapping order is not an ownership grant. Raw journal bytes are retained.
609
+ plan: dict[str, Any] = {
610
+ "version": 1,
611
+ "manifest": normalized_manifest(self.manifest),
612
+ "bindings": sorted(self.bindings, key=lambda b: (b["side"], b["id"])),
613
+ "staging_bindings": sorted(
614
+ self.staging_bindings,
615
+ key=lambda view: (view["side"], view["configured_path"]),
616
+ ),
617
+ "worker_sha256": digest(self.reference),
618
+ "files": {n: digest(d) for n, d in sorted(self.files.items())},
619
+ }
620
+ plan["manifest"]["identity_mapping"] = mappings
621
+ plan["manifest"]["client_journals"] = journals
622
+ self.files["plan.json"] = canonical(plan)
623
+ return {
624
+ "ready": not self.blockers,
625
+ "digest": digest(self.files["plan.json"]),
626
+ "blockers": self.blockers,
627
+ "identity_mapping": mappings,
628
+ "bindings": self.bindings,
629
+ "inventory": self.inventory,
630
+ "staging_bindings": sorted(
631
+ self.staging_bindings,
632
+ key=lambda view: (view["side"], view["configured_path"]),
633
+ ),
634
+ }
635
+
636
+
637
+ def execute(
638
+ operation: str, manifest: Manifest, reference: Path, confirmation: str | None
639
+ ) -> dict[str, Any]:
640
+ with locked(manifest):
641
+ if operation != "preview" and (
642
+ confirmation is None or re.fullmatch(r"[a-f0-9]{64}", confirmation) is None
643
+ ):
644
+ raise ValueError("Explicit preview digest confirmation is required")
645
+ legacy = Path(manifest.legacy.state_directory)
646
+ receiver = Path(manifest.receiver.state_directory)
647
+ fence = canonical({"version": 1, "digest": confirmation})
648
+ archive = receiver / "cutovers" / str(confirmation)
649
+ # Lost activation acknowledgement: do not freeze an already active receiver again.
650
+ if operation == "activate" and not os.path.lexists(receiver / FENCE):
651
+ # Settle a visible unlink left by a lost response before acknowledging it.
652
+ sync_directory(receiver)
653
+ completed = json_data(read_file(archive / "completed.json"))
654
+ plan = json_data(read_file(archive / "plan.json"))
655
+ if (
656
+ completed
657
+ != {"version": 1, "digest": confirmation, "state": "activated"}
658
+ or read_file(legacy / FENCE) != fence
659
+ or digest(read_file(archive / "plan.json")) != confirmation
660
+ or plan["manifest"] != normalized_manifest(manifest)
661
+ ):
662
+ raise ValueError(
663
+ "Activation recovery evidence does not match the confirmed mapping"
664
+ )
665
+ for name, expected in plan["files"].items():
666
+ if digest(read_file(archive / name)) != expected:
667
+ raise ValueError(
668
+ "Archived evidence damaged; preserve it for operator recovery"
669
+ )
670
+ current = Survey(manifest, reference)
671
+ for side, authority in [
672
+ ("legacy", manifest.legacy),
673
+ ("receiver", manifest.receiver),
674
+ ]:
675
+ current.inspect_staging(side, authority, check_contents=False)
676
+ if current.blockers or sorted(
677
+ current.staging_bindings,
678
+ key=lambda view: (view["side"], view["configured_path"]),
679
+ ) != plan.get("staging_bindings"):
680
+ raise ValueError(
681
+ "Activation recovery staging identity or policy inventory changed"
682
+ )
683
+ for name, data in current.files.items():
684
+ if read_file(archive / name) != data:
685
+ raise ValueError("Activation recovery resource policy changed")
686
+ return {"state": "activated", "archive": str(archive)}
687
+ survey = Survey(manifest, reference)
688
+ preview = survey.inspect()
689
+ if operation == "preview":
690
+ return {"preview": preview}
691
+ if confirmation != preview["digest"]:
692
+ raise ValueError(
693
+ "Preview confirmation changed; review current evidence and mapping"
694
+ )
695
+ if not preview["ready"]:
696
+ raise ValueError("Cutover blocked: " + "; ".join(preview["blockers"]))
697
+ if operation == "apply":
698
+ # Freeze the destination first. Every interruption leaves at least
699
+ # that authority closed; neither operation copies or resets leases.
700
+ for state in [receiver, legacy]:
701
+ publish(state / FENCE, fence)
702
+ make_directory(archive)
703
+ for name, data in sorted(survey.files.items()):
704
+ target = archive / name
705
+ make_directory(target.parent)
706
+ publish(target, data)
707
+ publish(
708
+ archive / "prepared.json",
709
+ canonical({"version": 1, "digest": confirmation, "state": "frozen"}),
710
+ )
711
+ return {"state": "frozen", "archive": str(archive)}
712
+ for state in [legacy, receiver]:
713
+ if read_file(state / FENCE) != fence:
714
+ raise ValueError(
715
+ "Cutover fence conflicts; explicit operator recovery required"
716
+ )
717
+ if json_data(read_file(archive / "prepared.json")) != {
718
+ "version": 1,
719
+ "digest": confirmation,
720
+ "state": "frozen",
721
+ }:
722
+ raise ValueError("Cutover archive has no complete preparation receipt")
723
+ for name, data in survey.files.items():
724
+ if read_file(archive / name) != data:
725
+ raise ValueError(
726
+ "Cutover archive differs from original evidence; keep both authorities frozen"
727
+ )
728
+ publish(
729
+ archive / "completed.json",
730
+ canonical({"version": 1, "digest": confirmation, "state": "activated"}),
731
+ )
732
+ os.unlink(receiver / FENCE)
733
+ sync_directory(receiver)
734
+ return {"state": "activated", "archive": str(archive)}
735
+
736
+
737
+ def main() -> None:
738
+ parser = argparse.ArgumentParser(
739
+ description="Preview and confirm remote journal cutover"
740
+ )
741
+ parser.add_argument("operation", choices=["preview", "apply", "activate"])
742
+ parser.add_argument("--manifest", required=True)
743
+ parser.add_argument("--confirm")
744
+ parser.add_argument("--worker-file")
745
+ args = parser.parse_args()
746
+ try:
747
+ manifest = Manifest.model_validate_json(read_file(Path(args.manifest)))
748
+ reference = (
749
+ Path(args.worker_file)
750
+ if args.worker_file
751
+ else Path(__file__).with_name("worker.py")
752
+ )
753
+ result = execute(args.operation, manifest, reference, args.confirm)
754
+ print(json.dumps({"ok": True, **result}, allow_nan=False))
755
+ except (ValueError, OSError) as error:
756
+ print(json.dumps({"ok": False, "error": str(error)}))
757
+ raise SystemExit(2) from error
758
+
759
+
760
+ if __name__ == "__main__":
761
+ main()