@vellumai/assistant 0.4.26 → 0.4.30

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 (1360) hide show
  1. package/.env.example +2 -2
  2. package/AGENTS.md +5 -0
  3. package/ARCHITECTURE.md +207 -105
  4. package/Dockerfile +1 -1
  5. package/README.md +111 -113
  6. package/bun.lock +0 -3
  7. package/docs/architecture/integrations.md +0 -1
  8. package/docs/architecture/memory.md +100 -63
  9. package/docs/error-handling.md +71 -0
  10. package/docs/runbook-trusted-contacts.md +89 -52
  11. package/docs/trusted-contact-access.md +48 -46
  12. package/package.json +3 -3
  13. package/scripts/compare-benchmarks.sh +12 -5
  14. package/scripts/ipc/check-swift-decoder-drift.ts +5 -3
  15. package/scripts/test.sh +89 -5
  16. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +50 -37
  17. package/src/__tests__/access-request-decision.test.ts +0 -1
  18. package/src/__tests__/account-registry.test.ts +1 -1
  19. package/src/__tests__/actor-token-service.test.ts +40 -26
  20. package/src/__tests__/agent-loop-thinking.test.ts +29 -13
  21. package/src/__tests__/agent-loop.test.ts +2 -1
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -1
  23. package/src/__tests__/app-executors.test.ts +7 -17
  24. package/src/__tests__/approval-routes-http.test.ts +2 -2
  25. package/src/__tests__/asset-materialize-tool.test.ts +7 -7
  26. package/src/__tests__/asset-search-tool.test.ts +7 -7
  27. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
  28. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  29. package/src/__tests__/browser-skill-endstate.test.ts +10 -1
  30. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +218 -0
  31. package/src/__tests__/call-controller.test.ts +99 -69
  32. package/src/__tests__/call-start-guardian-guard.test.ts +1 -1
  33. package/src/__tests__/channel-approval-routes.test.ts +157 -114
  34. package/src/__tests__/channel-approval.test.ts +8 -0
  35. package/src/__tests__/channel-approvals.test.ts +39 -1
  36. package/src/__tests__/channel-guardian.test.ts +176 -275
  37. package/src/__tests__/channel-readiness-service.test.ts +6 -2
  38. package/src/__tests__/channel-reply-delivery.test.ts +33 -2
  39. package/src/__tests__/channel-retry-sweep.test.ts +14 -14
  40. package/src/__tests__/checker.test.ts +12 -31
  41. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -1
  42. package/src/__tests__/commit-message-enrichment-service.test.ts +71 -59
  43. package/src/__tests__/compaction.benchmark.test.ts +6 -2
  44. package/src/__tests__/computer-use-tools.test.ts +1 -1
  45. package/src/__tests__/config-schema.test.ts +66 -7
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -29
  47. package/src/__tests__/contacts-tools.test.ts +63 -2
  48. package/src/__tests__/context-overflow-approval.test.ts +141 -0
  49. package/src/__tests__/context-overflow-policy.test.ts +171 -0
  50. package/src/__tests__/context-overflow-reducer.test.ts +533 -0
  51. package/src/__tests__/context-window-manager.test.ts +97 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +38 -46
  53. package/src/__tests__/conversation-pairing.test.ts +2 -2
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +214 -10
  55. package/src/__tests__/conversation-routes.test.ts +4 -7
  56. package/src/__tests__/credential-broker-browser-fill.test.ts +13 -2
  57. package/src/__tests__/credential-security-e2e.test.ts +1 -1
  58. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  59. package/src/__tests__/credential-vault-unit.test.ts +1 -1
  60. package/src/__tests__/credential-vault.test.ts +11 -8
  61. package/src/__tests__/daemon-lifecycle.test.ts +2 -2
  62. package/src/__tests__/daemon-server-session-init.test.ts +6 -6
  63. package/src/__tests__/delete-managed-skill-tool.test.ts +1 -1
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -2
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
  66. package/src/__tests__/emit-signal-routing-intent.test.ts +4 -0
  67. package/src/__tests__/encrypted-store.test.ts +10 -7
  68. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  69. package/src/__tests__/file-edit-tool.test.ts +1 -1
  70. package/src/__tests__/file-read-tool.test.ts +1 -1
  71. package/src/__tests__/file-write-tool.test.ts +1 -1
  72. package/src/__tests__/fixtures/credential-security-fixtures.ts +87 -64
  73. package/src/__tests__/fixtures/media-reuse-fixtures.ts +37 -31
  74. package/src/__tests__/fixtures/mock-signup-server.ts +171 -115
  75. package/src/__tests__/fixtures/proxy-fixtures.ts +39 -39
  76. package/src/__tests__/followup-tools.test.ts +1 -1
  77. package/src/__tests__/gateway-only-guard.test.ts +4 -0
  78. package/src/__tests__/gemini-image-service.test.ts +2 -2
  79. package/src/__tests__/guardian-actions-endpoint.test.ts +543 -1
  80. package/src/__tests__/guardian-control-plane-policy.test.ts +15 -15
  81. package/src/__tests__/guardian-dispatch.test.ts +79 -1
  82. package/src/__tests__/guardian-grant-minting.test.ts +20 -20
  83. package/src/__tests__/guardian-outbound-http.test.ts +1 -2
  84. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -41
  85. package/src/__tests__/guardian-routing-invariants.test.ts +36 -16
  86. package/src/__tests__/guardian-routing-state.test.ts +36 -52
  87. package/src/__tests__/guardian-verification-intent-routing.test.ts +4 -6
  88. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +6 -8
  89. package/src/__tests__/handle-user-message-secret-resume.test.ts +39 -1
  90. package/src/__tests__/handlers-cu-observation-blob.test.ts +21 -10
  91. package/src/__tests__/handlers-telegram-config.test.ts +14 -14
  92. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +23 -2
  93. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  94. package/src/__tests__/headless-browser-navigate.test.ts +1 -1
  95. package/src/__tests__/headless-browser-read-tools.test.ts +1 -1
  96. package/src/__tests__/headless-browser-snapshot.test.ts +1 -1
  97. package/src/__tests__/heartbeat-service.test.ts +45 -2
  98. package/src/__tests__/host-file-edit-tool.test.ts +1 -1
  99. package/src/__tests__/host-file-read-tool.test.ts +1 -1
  100. package/src/__tests__/host-file-write-tool.test.ts +1 -1
  101. package/src/__tests__/host-shell-tool.test.ts +1 -1
  102. package/src/__tests__/inbound-invite-redemption.test.ts +17 -19
  103. package/src/__tests__/ingress-reconcile.test.ts +2 -2
  104. package/src/__tests__/integrations-cli.test.ts +232 -0
  105. package/src/__tests__/intent-routing.test.ts +7 -5
  106. package/src/__tests__/invite-redemption-service.test.ts +5 -4
  107. package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +42 -321
  108. package/src/__tests__/ipc-snapshot.test.ts +32 -31
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  110. package/src/__tests__/mcp-cli.test.ts +136 -57
  111. package/src/__tests__/mcp-client-auth.test.ts +95 -0
  112. package/src/__tests__/media-generate-image.test.ts +2 -2
  113. package/src/__tests__/media-reuse-story.e2e.test.ts +8 -8
  114. package/src/__tests__/memory-regressions.test.ts +6 -6
  115. package/src/__tests__/messaging-send-tool.test.ts +1 -1
  116. package/src/__tests__/migration-cross-version-compatibility.test.ts +1855 -0
  117. package/src/__tests__/migration-export-http.test.ts +540 -0
  118. package/src/__tests__/migration-import-commit-http.test.ts +823 -0
  119. package/src/__tests__/migration-import-preflight-http.test.ts +755 -0
  120. package/src/__tests__/migration-parity-persistence.test.ts +1854 -0
  121. package/src/__tests__/migration-transport.test.ts +904 -0
  122. package/src/__tests__/migration-validate-http.test.ts +698 -0
  123. package/src/__tests__/migration-wizard.test.ts +1289 -0
  124. package/src/__tests__/nl-approval-parser.test.ts +305 -0
  125. package/src/__tests__/non-member-access-request.test.ts +17 -17
  126. package/src/__tests__/notification-decision-strategy.test.ts +110 -2
  127. package/src/__tests__/notification-deep-link.test.ts +18 -0
  128. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  129. package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
  130. package/src/__tests__/oauth2-gateway-transport.test.ts +1 -1
  131. package/src/__tests__/playbook-execution.test.ts +1 -1
  132. package/src/__tests__/playbook-tools.test.ts +1 -1
  133. package/src/__tests__/provider-error-scenarios.test.ts +68 -0
  134. package/src/__tests__/provider-streaming.benchmark.test.ts +3 -1
  135. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  136. package/src/__tests__/qdrant-manager.test.ts +40 -11
  137. package/src/__tests__/rebind-secrets-screen.test.ts +839 -0
  138. package/src/__tests__/recording-handler.test.ts +2 -2
  139. package/src/__tests__/recording-intent-handler.test.ts +3 -3
  140. package/src/__tests__/recording-state-machine.test.ts +2 -2
  141. package/src/__tests__/relay-server.test.ts +507 -228
  142. package/src/__tests__/reminder-store.test.ts +8 -0
  143. package/src/__tests__/reminder.test.ts +8 -0
  144. package/src/__tests__/{resolve-guardian-trust-class.test.ts → resolve-trust-class.test.ts} +11 -17
  145. package/src/__tests__/retry-after-extraction.test.ts +111 -0
  146. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  147. package/src/__tests__/schedule-tools.test.ts +1 -1
  148. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  149. package/src/__tests__/script-proxy-connect-tunnel.test.ts +2 -3
  150. package/src/__tests__/script-proxy-decision-trace.test.ts +2 -2
  151. package/src/__tests__/script-proxy-http-forwarder.test.ts +1 -1
  152. package/src/__tests__/script-proxy-injection-runtime.test.ts +5 -5
  153. package/src/__tests__/script-proxy-mitm-handler.test.ts +4 -4
  154. package/src/__tests__/script-proxy-policy-runtime.test.ts +2 -2
  155. package/src/__tests__/script-proxy-policy.test.ts +2 -2
  156. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
  157. package/src/__tests__/script-proxy-session-manager.test.ts +4 -7
  158. package/src/__tests__/script-proxy-session-runtime.test.ts +1 -6
  159. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  160. package/src/__tests__/secret-scanner-executor.test.ts +2 -2
  161. package/src/__tests__/send-endpoint-busy.test.ts +11 -9
  162. package/src/__tests__/send-notification-tool.test.ts +2 -2
  163. package/src/__tests__/session-abort-tool-results.test.ts +17 -2
  164. package/src/__tests__/session-agent-loop.test.ts +456 -35
  165. package/src/__tests__/session-confirmation-signals.test.ts +3 -2
  166. package/src/__tests__/session-conflict-gate.test.ts +20 -3
  167. package/src/__tests__/session-init.benchmark.test.ts +2 -2
  168. package/src/__tests__/session-load-history-repair.test.ts +7 -7
  169. package/src/__tests__/session-media-retry.test.ts +147 -0
  170. package/src/__tests__/session-pre-run-repair.test.ts +17 -2
  171. package/src/__tests__/session-profile-injection.test.ts +20 -3
  172. package/src/__tests__/session-provider-retry-repair.test.ts +86 -6
  173. package/src/__tests__/session-queue.test.ts +33 -18
  174. package/src/__tests__/session-runtime-assembly.test.ts +147 -1
  175. package/src/__tests__/session-runtime-workspace.test.ts +40 -0
  176. package/src/__tests__/session-slash-known.test.ts +21 -3
  177. package/src/__tests__/session-slash-queue.test.ts +17 -2
  178. package/src/__tests__/session-slash-unknown.test.ts +17 -2
  179. package/src/__tests__/session-surfaces-deselection.test.ts +208 -0
  180. package/src/__tests__/session-workspace-cache-state.test.ts +2 -2
  181. package/src/__tests__/session-workspace-injection.test.ts +17 -2
  182. package/src/__tests__/session-workspace-tool-tracking.test.ts +17 -2
  183. package/src/__tests__/shell-credential-ref.test.ts +1 -1
  184. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  185. package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
  186. package/src/__tests__/skill-feature-flags.test.ts +18 -12
  187. package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
  188. package/src/__tests__/skill-load-tool.test.ts +1 -1
  189. package/src/__tests__/skill-script-runner-host.test.ts +1 -1
  190. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -1
  191. package/src/__tests__/skill-script-runner.test.ts +1 -1
  192. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  193. package/src/__tests__/slack-block-formatting.test.ts +100 -0
  194. package/src/__tests__/slack-inbound-verification.test.ts +346 -0
  195. package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
  196. package/src/__tests__/slack-skill.test.ts +4 -2
  197. package/src/__tests__/starter-task-flow.test.ts +0 -1
  198. package/src/__tests__/subagent-tools.test.ts +3 -3
  199. package/src/__tests__/swarm-recursion.test.ts +1 -1
  200. package/src/__tests__/swarm-session-integration.test.ts +1 -1
  201. package/src/__tests__/swarm-tool.test.ts +1 -1
  202. package/src/__tests__/task-management-tools.test.ts +1 -1
  203. package/src/__tests__/task-tools.test.ts +1 -1
  204. package/src/__tests__/terminal-tools.test.ts +1 -1
  205. package/src/__tests__/test-support/browser-skill-harness.ts +39 -27
  206. package/src/__tests__/test-support/computer-use-skill-harness.ts +14 -14
  207. package/src/__tests__/tool-approval-handler.test.ts +15 -15
  208. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  209. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  210. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  211. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  212. package/src/__tests__/tool-executor.test.ts +23 -182
  213. package/src/__tests__/tool-grant-request-escalation.test.ts +11 -11
  214. package/src/__tests__/tool-permission-simulate-handler.test.ts +4 -4
  215. package/src/__tests__/transfer-progress-screen.test.ts +1180 -0
  216. package/src/__tests__/trust-context-guards.test.ts +25 -29
  217. package/src/__tests__/trusted-contact-approval-notifier.test.ts +23 -21
  218. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +37 -40
  219. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +29 -25
  220. package/src/__tests__/trusted-contact-multichannel.test.ts +25 -24
  221. package/src/__tests__/trusted-contact-verification.test.ts +64 -76
  222. package/src/__tests__/turn-commit.test.ts +18 -18
  223. package/src/__tests__/twilio-provider.test.ts +7 -7
  224. package/src/__tests__/validation-results-screen.test.ts +1107 -0
  225. package/src/__tests__/view-image-tool.test.ts +1 -1
  226. package/src/__tests__/voice-invite-redemption.test.ts +4 -3
  227. package/src/__tests__/voice-scoped-grant-consumer.test.ts +12 -12
  228. package/src/__tests__/voice-session-bridge.test.ts +24 -24
  229. package/src/agent/attachments.ts +3 -1
  230. package/src/agent/loop.ts +13 -13
  231. package/src/agent/message-types.ts +13 -7
  232. package/src/amazon/cart.ts +59 -32
  233. package/src/amazon/checkout.ts +25 -14
  234. package/src/amazon/client.ts +61 -58
  235. package/src/amazon/product-details.ts +3 -3
  236. package/src/amazon/request-extractor.ts +46 -31
  237. package/src/amazon/search.ts +6 -4
  238. package/src/amazon/session.ts +33 -24
  239. package/src/approvals/AGENTS.md +26 -0
  240. package/src/approvals/approval-primitive.ts +87 -64
  241. package/src/approvals/guardian-decision-primitive.ts +172 -81
  242. package/src/approvals/guardian-request-resolvers.ts +262 -155
  243. package/src/autonomy/autonomy-resolver.ts +7 -5
  244. package/src/autonomy/autonomy-store.ts +34 -19
  245. package/src/autonomy/disposition-mapper.ts +5 -5
  246. package/src/autonomy/index.ts +6 -6
  247. package/src/autonomy/types.ts +7 -3
  248. package/src/browser-extension-relay/client.ts +50 -19
  249. package/src/browser-extension-relay/protocol.ts +11 -11
  250. package/src/browser-extension-relay/server.ts +45 -20
  251. package/src/bundler/app-bundler.ts +75 -50
  252. package/src/bundler/bundle-scanner.ts +145 -41
  253. package/src/bundler/bundle-signer.ts +16 -14
  254. package/src/bundler/signature-verifier.ts +36 -33
  255. package/src/calls/call-constants.ts +10 -3
  256. package/src/calls/call-controller.ts +473 -214
  257. package/src/calls/call-conversation-messages.ts +25 -15
  258. package/src/calls/call-domain.ts +401 -148
  259. package/src/calls/call-pointer-message-composer.ts +26 -21
  260. package/src/calls/call-pointer-messages.ts +52 -28
  261. package/src/calls/call-recovery.ts +53 -37
  262. package/src/calls/call-state-machine.ts +37 -7
  263. package/src/calls/call-state.ts +35 -13
  264. package/src/calls/call-store.ts +165 -77
  265. package/src/calls/elevenlabs-client.ts +39 -20
  266. package/src/calls/guardian-action-sweep.ts +42 -24
  267. package/src/calls/guardian-dispatch.ts +79 -56
  268. package/src/calls/guardian-question-copy.ts +28 -23
  269. package/src/calls/relay-server.ts +1149 -532
  270. package/src/calls/speaker-identification.ts +21 -15
  271. package/src/calls/twilio-config.ts +34 -17
  272. package/src/calls/twilio-provider.ts +108 -55
  273. package/src/calls/twilio-rest.ts +212 -100
  274. package/src/calls/twilio-routes.ts +165 -92
  275. package/src/calls/types.ts +55 -7
  276. package/src/calls/voice-quality.ts +6 -4
  277. package/src/calls/voice-session-bridge.ts +181 -133
  278. package/src/channels/config.ts +18 -14
  279. package/src/channels/types.ts +38 -10
  280. package/src/cli/amazon.ts +333 -227
  281. package/src/cli/config-commands.ts +236 -146
  282. package/src/cli/core-commands.ts +403 -329
  283. package/src/cli/email-guardrails.ts +38 -19
  284. package/src/cli/email.ts +207 -153
  285. package/src/cli/influencer.ts +58 -56
  286. package/src/cli/integrations.ts +306 -0
  287. package/src/cli/ipc-client.ts +24 -19
  288. package/src/cli/map.ts +176 -129
  289. package/src/cli/mcp.ts +260 -152
  290. package/src/cli/sequence.ts +165 -107
  291. package/src/cli/twitter.ts +302 -218
  292. package/src/cli.ts +418 -279
  293. package/src/commands/cc-command-registry.ts +52 -27
  294. package/src/config/agent-schema.ts +217 -134
  295. package/src/config/assistant-feature-flags.ts +23 -18
  296. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +19 -0
  297. package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
  298. package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
  299. package/src/config/bundled-skills/app-builder/tools/app-create.ts +7 -4
  300. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +6 -3
  301. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +7 -4
  302. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +6 -3
  303. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +6 -3
  304. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +7 -4
  305. package/src/config/bundled-skills/app-builder/tools/app-list.ts +6 -3
  306. package/src/config/bundled-skills/app-builder/tools/app-query.ts +6 -3
  307. package/src/config/bundled-skills/app-builder/tools/app-update.ts +6 -3
  308. package/src/config/bundled-skills/browser/TOOLS.json +59 -2
  309. package/src/config/bundled-skills/browser/tools/browser-click.ts +5 -2
  310. package/src/config/bundled-skills/browser/tools/browser-close.ts +5 -2
  311. package/src/config/bundled-skills/browser/tools/browser-extract.ts +5 -2
  312. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +5 -2
  313. package/src/config/bundled-skills/browser/tools/browser-hover.ts +5 -2
  314. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +5 -2
  315. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +5 -2
  316. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +5 -2
  317. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +5 -2
  318. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +5 -2
  319. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +5 -2
  320. package/src/config/bundled-skills/browser/tools/browser-type.ts +5 -2
  321. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +13 -6
  322. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +5 -2
  323. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
  324. package/src/config/bundled-skills/claude-code/TOOLS.json +4 -0
  325. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +5 -2
  326. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  327. package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
  328. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +6 -3
  329. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +6 -3
  330. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +10 -3
  331. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +6 -3
  332. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +6 -3
  333. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +6 -3
  334. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +10 -3
  335. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +6 -3
  336. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +10 -3
  337. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +10 -3
  338. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +6 -3
  339. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +6 -3
  340. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +6 -3
  341. package/src/config/bundled-skills/configure-settings/SKILL.md +28 -14
  342. package/src/config/bundled-skills/contacts/SKILL.md +453 -15
  343. package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
  344. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +79 -20
  345. package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -18
  346. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +64 -19
  347. package/src/config/bundled-skills/document/TOOLS.json +8 -0
  348. package/src/config/bundled-skills/document/tools/document-create.ts +5 -2
  349. package/src/config/bundled-skills/document/tools/document-update.ts +5 -2
  350. package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -7
  351. package/src/config/bundled-skills/email-setup/SKILL.md +12 -9
  352. package/src/config/bundled-skills/followups/TOOLS.json +12 -0
  353. package/src/config/bundled-skills/followups/tools/followup-create.ts +5 -2
  354. package/src/config/bundled-skills/followups/tools/followup-list.ts +5 -2
  355. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +5 -2
  356. package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
  357. package/src/config/bundled-skills/google-calendar/calendar-client.ts +44 -32
  358. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +11 -5
  359. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +13 -7
  360. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +11 -5
  361. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +13 -7
  362. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +28 -12
  363. package/src/config/bundled-skills/google-calendar/tools/shared.ts +6 -4
  364. package/src/config/bundled-skills/google-calendar/types.ts +3 -3
  365. package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +88 -33
  366. package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
  367. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +48 -25
  368. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
  369. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +60 -35
  370. package/src/config/bundled-skills/mcp-setup/SKILL.md +75 -0
  371. package/src/config/bundled-skills/media-processing/SKILL.md +55 -15
  372. package/src/config/bundled-skills/media-processing/TOOLS.json +48 -2
  373. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +12 -10
  374. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +34 -19
  375. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +82 -66
  376. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +148 -0
  377. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +1 -1
  378. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +8 -3
  379. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +117 -53
  380. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +273 -0
  381. package/src/config/bundled-skills/media-processing/services/preprocess.ts +185 -97
  382. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +32 -27
  383. package/src/config/bundled-skills/media-processing/services/reduce.ts +101 -24
  384. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +121 -55
  385. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +58 -24
  386. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +198 -92
  387. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +98 -70
  388. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +59 -19
  389. package/src/config/bundled-skills/media-processing/tools/media-status.ts +26 -10
  390. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +29 -14
  391. package/src/config/bundled-skills/messaging/SKILL.md +7 -5
  392. package/src/config/bundled-skills/messaging/TOOLS.json +232 -186
  393. package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +31 -13
  394. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +16 -10
  395. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +18 -9
  396. package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +23 -16
  397. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +28 -12
  398. package/src/config/bundled-skills/messaging/tools/gmail-filters.ts +41 -21
  399. package/src/config/bundled-skills/messaging/tools/gmail-follow-up.ts +44 -23
  400. package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +73 -33
  401. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +15 -9
  402. package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +22 -14
  403. package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +99 -50
  404. package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +14 -8
  405. package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +63 -44
  406. package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +90 -46
  407. package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +43 -22
  408. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +15 -9
  409. package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +51 -22
  410. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +62 -26
  411. package/src/config/bundled-skills/messaging/tools/gmail-vacation.ts +34 -19
  412. package/src/config/bundled-skills/messaging/tools/google-contacts.ts +32 -16
  413. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +10 -4
  414. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +91 -47
  415. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +21 -9
  416. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +9 -3
  417. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +30 -17
  418. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +10 -4
  419. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +14 -6
  420. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +16 -5
  421. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +63 -36
  422. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +10 -4
  423. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +30 -12
  424. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +48 -29
  425. package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +20 -6
  426. package/src/config/bundled-skills/messaging/tools/send-notification.ts +1 -1
  427. package/src/config/bundled-skills/messaging/tools/sequence-analytics.ts +59 -22
  428. package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +13 -7
  429. package/src/config/bundled-skills/messaging/tools/sequence-create.ts +27 -12
  430. package/src/config/bundled-skills/messaging/tools/sequence-delete.ts +14 -6
  431. package/src/config/bundled-skills/messaging/tools/sequence-enroll.ts +30 -11
  432. package/src/config/bundled-skills/messaging/tools/sequence-enrollment-list.ts +16 -8
  433. package/src/config/bundled-skills/messaging/tools/sequence-get.ts +31 -13
  434. package/src/config/bundled-skills/messaging/tools/sequence-import.ts +38 -22
  435. package/src/config/bundled-skills/messaging/tools/sequence-list.ts +16 -7
  436. package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +29 -10
  437. package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +16 -8
  438. package/src/config/bundled-skills/messaging/tools/sequence-update.ts +35 -16
  439. package/src/config/bundled-skills/messaging/tools/shared.ts +26 -12
  440. package/src/config/bundled-skills/notifications/SKILL.md +3 -2
  441. package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
  442. package/src/config/bundled-skills/notifications/tools/send-notification.ts +69 -34
  443. package/src/config/bundled-skills/notifications/tools/shared.ts +1 -1
  444. package/src/config/bundled-skills/phone-calls/SKILL.md +46 -48
  445. package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
  446. package/src/config/bundled-skills/phone-calls/tools/call-end.ts +1 -1
  447. package/src/config/bundled-skills/phone-calls/tools/call-start.ts +1 -1
  448. package/src/config/bundled-skills/phone-calls/tools/call-status.ts +1 -1
  449. package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
  450. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +91 -51
  451. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +30 -16
  452. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +66 -27
  453. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +89 -42
  454. package/src/config/bundled-skills/public-ingress/SKILL.md +26 -19
  455. package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
  456. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +5 -2
  457. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +5 -2
  458. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +5 -2
  459. package/src/config/bundled-skills/schedule/SKILL.md +33 -15
  460. package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
  461. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +5 -2
  462. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +5 -2
  463. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +5 -2
  464. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +5 -2
  465. package/src/config/bundled-skills/screen-recording/SKILL.md +11 -3
  466. package/src/config/bundled-skills/self-upgrade/SKILL.md +9 -8
  467. package/src/config/bundled-skills/slack/SKILL.md +30 -1
  468. package/src/config/bundled-skills/slack/TOOLS.json +122 -17
  469. package/src/config/bundled-skills/slack/tools/shared.ts +7 -5
  470. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +11 -5
  471. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +11 -5
  472. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
  473. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +46 -16
  474. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +11 -5
  475. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +28 -0
  476. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +12 -6
  477. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
  478. package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
  479. package/src/config/bundled-skills/sms-setup/SKILL.md +5 -8
  480. package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
  481. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +5 -2
  482. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +5 -2
  483. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +5 -2
  484. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +5 -2
  485. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +5 -2
  486. package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
  487. package/src/config/bundled-skills/tasks/tools/task-delete.ts +5 -2
  488. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +5 -2
  489. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +5 -2
  490. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +5 -2
  491. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +5 -2
  492. package/src/config/bundled-skills/tasks/tools/task-list.ts +5 -2
  493. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +5 -2
  494. package/src/config/bundled-skills/tasks/tools/task-run.ts +5 -2
  495. package/src/config/bundled-skills/tasks/tools/task-save.ts +5 -2
  496. package/src/config/bundled-skills/telegram-setup/SKILL.md +7 -8
  497. package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
  498. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +232 -127
  499. package/src/config/bundled-skills/twilio-setup/SKILL.md +7 -12
  500. package/src/config/bundled-skills/twitter/SKILL.md +19 -2
  501. package/src/config/bundled-skills/voice-setup/SKILL.md +5 -5
  502. package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
  503. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +5 -2
  504. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +5 -2
  505. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +5 -2
  506. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +5 -2
  507. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +5 -2
  508. package/src/config/bundled-skills/weather/TOOLS.json +4 -0
  509. package/src/config/bundled-skills/weather/tools/get-weather.ts +5 -2
  510. package/src/config/bundled-tool-registry.ts +2 -0
  511. package/src/config/calls-schema.ts +108 -63
  512. package/src/config/channel-permission-profiles.ts +155 -0
  513. package/src/config/computer-use-prompt.ts +7 -7
  514. package/src/config/core-schema.ts +239 -155
  515. package/src/config/defaults.ts +2 -2
  516. package/src/config/elevenlabs-schema.ts +15 -15
  517. package/src/config/env-registry.ts +33 -33
  518. package/src/config/env.ts +4 -1
  519. package/src/config/feature-flag-registry.json +31 -7
  520. package/src/config/loader.ts +118 -58
  521. package/src/config/mcp-schema.ts +29 -15
  522. package/src/config/memory-schema.ts +434 -229
  523. package/src/config/notifications-schema.ts +4 -4
  524. package/src/config/sandbox-schema.ts +2 -2
  525. package/src/config/schema.ts +12 -2
  526. package/src/config/skill-state.ts +27 -15
  527. package/src/config/skills-schema.ts +72 -23
  528. package/src/config/skills.ts +303 -143
  529. package/src/config/system-prompt.ts +25 -6
  530. package/src/config/types.ts +1 -1
  531. package/src/config/update-bulletin-format.ts +3 -3
  532. package/src/config/update-bulletin-state.ts +15 -6
  533. package/src/config/update-bulletin-template-path.ts +8 -4
  534. package/src/config/update-bulletin.ts +33 -14
  535. package/src/config/user-reference.ts +8 -8
  536. package/src/contacts/contact-events.ts +21 -0
  537. package/src/contacts/contact-store.ts +813 -100
  538. package/src/contacts/contacts-write.ts +287 -0
  539. package/src/contacts/index.ts +13 -4
  540. package/src/contacts/startup-migration.ts +21 -0
  541. package/src/contacts/types.ts +73 -2
  542. package/src/context/token-estimator.ts +54 -31
  543. package/src/context/tool-result-truncation.ts +41 -7
  544. package/src/context/window-manager.ts +225 -120
  545. package/src/daemon/approval-generators.ts +83 -55
  546. package/src/daemon/approved-devices-store.ts +33 -20
  547. package/src/daemon/assistant-attachments.ts +157 -101
  548. package/src/daemon/auth-manager.ts +17 -15
  549. package/src/daemon/classifier.ts +117 -46
  550. package/src/daemon/computer-use-session.ts +316 -187
  551. package/src/daemon/config-watcher.ts +91 -44
  552. package/src/daemon/connection-policy.ts +18 -10
  553. package/src/daemon/context-overflow-approval.ts +48 -0
  554. package/src/daemon/context-overflow-policy.ts +50 -0
  555. package/src/daemon/context-overflow-reducer.ts +300 -0
  556. package/src/daemon/daemon-control.ts +79 -51
  557. package/src/daemon/date-context.ts +119 -69
  558. package/src/daemon/dictation-profile-store.ts +94 -48
  559. package/src/daemon/dictation-text-processing.ts +33 -12
  560. package/src/daemon/doordash-steps.ts +92 -49
  561. package/src/daemon/guardian-action-generators.ts +62 -46
  562. package/src/daemon/guardian-verification-intent.ts +35 -19
  563. package/src/daemon/handlers/apps.ts +258 -113
  564. package/src/daemon/handlers/avatar.ts +20 -15
  565. package/src/daemon/handlers/computer-use.ts +82 -39
  566. package/src/daemon/handlers/config-channels.ts +146 -69
  567. package/src/daemon/handlers/config-heartbeat.ts +114 -59
  568. package/src/daemon/handlers/config-inbox.ts +213 -160
  569. package/src/daemon/handlers/config-ingress.ts +127 -55
  570. package/src/daemon/handlers/config-integrations.ts +145 -88
  571. package/src/daemon/handlers/config-model.ts +58 -22
  572. package/src/daemon/handlers/config-platform.ts +40 -16
  573. package/src/daemon/handlers/config-scheduling.ts +109 -48
  574. package/src/daemon/handlers/config-slack-channel.ts +67 -35
  575. package/src/daemon/handlers/config-slack.ts +21 -20
  576. package/src/daemon/handlers/config-telegram.ts +100 -70
  577. package/src/daemon/handlers/config-tools.ts +103 -55
  578. package/src/daemon/handlers/config-trust.ts +50 -20
  579. package/src/daemon/handlers/config.ts +72 -24
  580. package/src/daemon/handlers/contacts.ts +163 -0
  581. package/src/daemon/handlers/diagnostics.ts +90 -48
  582. package/src/daemon/handlers/documents.ts +74 -46
  583. package/src/daemon/handlers/guardian-actions.ts +57 -77
  584. package/src/daemon/handlers/home-base.ts +19 -16
  585. package/src/daemon/handlers/identity.ts +65 -45
  586. package/src/daemon/handlers/index.ts +78 -54
  587. package/src/daemon/handlers/misc.ts +664 -234
  588. package/src/daemon/handlers/navigate-settings.ts +14 -11
  589. package/src/daemon/handlers/oauth-connect.ts +48 -35
  590. package/src/daemon/handlers/open-bundle-handler.ts +31 -24
  591. package/src/daemon/handlers/pairing.ts +51 -25
  592. package/src/daemon/handlers/publish.ts +55 -33
  593. package/src/daemon/handlers/recording.ts +378 -162
  594. package/src/daemon/handlers/sessions.ts +922 -423
  595. package/src/daemon/handlers/shared.ts +202 -117
  596. package/src/daemon/handlers/signing.ts +25 -6
  597. package/src/daemon/handlers/subagents.ts +117 -56
  598. package/src/daemon/handlers/twitter-auth.ts +70 -49
  599. package/src/daemon/handlers/work-items.ts +264 -112
  600. package/src/daemon/handlers/workspace-files.ts +27 -20
  601. package/src/daemon/handlers.ts +2 -2
  602. package/src/daemon/history-repair.ts +16 -15
  603. package/src/daemon/identity-helpers.ts +4 -4
  604. package/src/daemon/install-cli-launchers.ts +33 -22
  605. package/src/daemon/ipc-blob-store.ts +38 -24
  606. package/src/daemon/ipc-contract/apps.ts +61 -50
  607. package/src/daemon/ipc-contract/computer-use.ts +47 -37
  608. package/src/daemon/ipc-contract/contacts.ts +69 -0
  609. package/src/daemon/ipc-contract/diagnostics.ts +14 -14
  610. package/src/daemon/ipc-contract/documents.ts +8 -8
  611. package/src/daemon/ipc-contract/guardian-actions.ts +4 -4
  612. package/src/daemon/ipc-contract/inbox.ts +12 -71
  613. package/src/daemon/ipc-contract/integrations.ts +57 -44
  614. package/src/daemon/ipc-contract/memory.ts +3 -5
  615. package/src/daemon/ipc-contract/messages.ts +95 -69
  616. package/src/daemon/ipc-contract/notifications.ts +10 -6
  617. package/src/daemon/ipc-contract/pairing.ts +8 -8
  618. package/src/daemon/ipc-contract/schedules.ts +20 -20
  619. package/src/daemon/ipc-contract/sessions.ts +89 -57
  620. package/src/daemon/ipc-contract/settings.ts +12 -7
  621. package/src/daemon/ipc-contract/shared.ts +9 -7
  622. package/src/daemon/ipc-contract/skills.ts +46 -26
  623. package/src/daemon/ipc-contract/subagents.ts +9 -9
  624. package/src/daemon/ipc-contract/surfaces.ts +0 -1
  625. package/src/daemon/ipc-contract/trust.ts +11 -11
  626. package/src/daemon/ipc-contract/work-items.ts +33 -28
  627. package/src/daemon/ipc-contract/workspace.ts +28 -21
  628. package/src/daemon/ipc-contract-inventory.json +10 -4
  629. package/src/daemon/ipc-contract-inventory.ts +29 -26
  630. package/src/daemon/ipc-contract.ts +111 -44
  631. package/src/daemon/ipc-handler.ts +27 -19
  632. package/src/daemon/ipc-protocol.ts +22 -12
  633. package/src/daemon/ipc-validate.ts +91 -46
  634. package/src/daemon/lifecycle.ts +39 -3
  635. package/src/daemon/main.ts +10 -8
  636. package/src/daemon/media-visibility-policy.ts +3 -1
  637. package/src/daemon/pairing-store.ts +72 -40
  638. package/src/daemon/providers-setup.ts +35 -25
  639. package/src/daemon/recording-executor.ts +37 -30
  640. package/src/daemon/recording-intent-fallback.ts +58 -28
  641. package/src/daemon/recording-intent.ts +71 -61
  642. package/src/daemon/ride-shotgun-handler.ts +201 -121
  643. package/src/daemon/seed-files.ts +28 -17
  644. package/src/daemon/server.ts +23 -14
  645. package/src/daemon/session-agent-loop-handlers.ts +270 -135
  646. package/src/daemon/session-agent-loop.ts +796 -253
  647. package/src/daemon/session-attachments.ts +109 -40
  648. package/src/daemon/session-conflict-gate.ts +72 -28
  649. package/src/daemon/session-dynamic-profile.ts +36 -22
  650. package/src/daemon/session-error.ts +68 -45
  651. package/src/daemon/session-evictor.ts +17 -10
  652. package/src/daemon/session-history.ts +201 -89
  653. package/src/daemon/session-lifecycle.ts +80 -44
  654. package/src/daemon/session-media-retry.ts +104 -42
  655. package/src/daemon/session-memory.ts +77 -55
  656. package/src/daemon/session-messaging.ts +261 -111
  657. package/src/daemon/session-notifiers.ts +57 -45
  658. package/src/daemon/session-process.ts +370 -154
  659. package/src/daemon/session-queue-manager.ts +30 -13
  660. package/src/daemon/session-runtime-assembly.ts +61 -15
  661. package/src/daemon/session-skill-tools.ts +84 -36
  662. package/src/daemon/session-slash.ts +178 -113
  663. package/src/daemon/session-surfaces.ts +498 -212
  664. package/src/daemon/session-tool-setup.ts +24 -16
  665. package/src/daemon/session-usage.ts +26 -13
  666. package/src/daemon/session-workspace.ts +7 -4
  667. package/src/daemon/session.ts +18 -19
  668. package/src/daemon/shutdown-handlers.ts +36 -33
  669. package/src/daemon/tls-certs.ts +90 -57
  670. package/src/daemon/tool-side-effects.ts +97 -65
  671. package/src/daemon/trace-emitter.ts +8 -7
  672. package/src/daemon/video-thumbnail.ts +55 -25
  673. package/src/daemon/watch-handler.ts +164 -86
  674. package/src/email/provider.ts +1 -1
  675. package/src/email/providers/agentmail.ts +87 -45
  676. package/src/email/providers/index.ts +19 -14
  677. package/src/email/service.ts +52 -24
  678. package/src/email/types.ts +2 -2
  679. package/src/errors.ts +1 -1
  680. package/src/events/bus.ts +30 -10
  681. package/src/events/domain-events.ts +20 -13
  682. package/src/events/index.ts +6 -6
  683. package/src/events/tool-audit-listener.ts +34 -20
  684. package/src/events/tool-domain-event-publisher.ts +22 -20
  685. package/src/events/tool-metrics-listener.ts +26 -21
  686. package/src/events/tool-notification-listener.ts +5 -5
  687. package/src/events/tool-profiling-listener.ts +33 -23
  688. package/src/events/tool-trace-listener.ts +70 -46
  689. package/src/export/formatter.ts +38 -32
  690. package/src/followups/followup-store.ts +43 -36
  691. package/src/followups/index.ts +2 -2
  692. package/src/followups/types.ts +1 -1
  693. package/src/gallery/default-gallery.ts +37 -34
  694. package/src/gallery/gallery-manifest.ts +9 -9
  695. package/src/heartbeat/heartbeat-service.ts +59 -37
  696. package/src/home-base/app-link-store.ts +14 -12
  697. package/src/home-base/bootstrap.ts +14 -8
  698. package/src/home-base/prebuilt/seed.ts +34 -26
  699. package/src/home-base/prebuilt-home-base-updater.ts +14 -8
  700. package/src/hooks/cli.ts +56 -43
  701. package/src/hooks/config.ts +27 -14
  702. package/src/hooks/discovery.ts +53 -33
  703. package/src/hooks/manager.ts +50 -26
  704. package/src/hooks/runner.ts +35 -29
  705. package/src/hooks/templates.ts +38 -15
  706. package/src/hooks/types.ts +13 -13
  707. package/src/inbound/platform-callback-registration.ts +21 -15
  708. package/src/inbound/public-ingress-urls.ts +9 -6
  709. package/src/index.ts +20 -19
  710. package/src/influencer/client.ts +261 -117
  711. package/src/instrument.ts +3 -1
  712. package/src/logfire.ts +64 -39
  713. package/src/mcp/client.ts +107 -55
  714. package/src/mcp/manager.ts +45 -18
  715. package/src/mcp/mcp-oauth-provider.ts +114 -62
  716. package/src/media/gemini-image-service.ts +75 -23
  717. package/src/memory/account-store.ts +16 -9
  718. package/src/memory/admin.ts +87 -57
  719. package/src/memory/app-git-service.ts +77 -47
  720. package/src/memory/app-store.ts +148 -78
  721. package/src/memory/attachments-store.ts +123 -53
  722. package/src/memory/canonical-guardian-store.ts +190 -48
  723. package/src/memory/channel-delivery-store.ts +5 -5
  724. package/src/memory/channel-guardian-store.ts +31 -16
  725. package/src/memory/checkpoints.ts +14 -7
  726. package/src/memory/clarification-resolver.ts +219 -104
  727. package/src/memory/conflict-intent.ts +74 -23
  728. package/src/memory/conflict-policy.ts +20 -7
  729. package/src/memory/conflict-store.ts +144 -94
  730. package/src/memory/contradiction-checker.ts +257 -132
  731. package/src/memory/conversation-attention-store.ts +74 -32
  732. package/src/memory/conversation-bootstrap.ts +28 -0
  733. package/src/memory/conversation-crud.ts +12 -5
  734. package/src/memory/conversation-display-order-migration.ts +7 -7
  735. package/src/memory/conversation-key-store.ts +18 -13
  736. package/src/memory/conversation-queries.ts +130 -52
  737. package/src/memory/conversation-store.ts +43 -26
  738. package/src/memory/conversation-title-service.ts +89 -66
  739. package/src/memory/db-init.ts +94 -2
  740. package/src/memory/db.ts +10 -3
  741. package/src/memory/delivery-channels.ts +12 -6
  742. package/src/memory/delivery-crud.ts +26 -12
  743. package/src/memory/delivery-status.ts +19 -16
  744. package/src/memory/embedding-backend.ts +205 -77
  745. package/src/memory/embedding-gemini.ts +23 -10
  746. package/src/memory/embedding-local.ts +89 -44
  747. package/src/memory/embedding-ollama.ts +25 -13
  748. package/src/memory/embedding-openai.ts +20 -11
  749. package/src/memory/embedding-runtime-manager.ts +163 -90
  750. package/src/memory/entity-extractor.ts +185 -123
  751. package/src/memory/external-conversation-store.ts +30 -12
  752. package/src/memory/fingerprint.ts +2 -2
  753. package/src/memory/fts-reconciler.ts +57 -28
  754. package/src/memory/guardian-action-store.ts +162 -100
  755. package/src/memory/guardian-approvals.ts +63 -129
  756. package/src/memory/guardian-rate-limits.ts +20 -9
  757. package/src/memory/guardian-verification.ts +82 -35
  758. package/src/memory/indexer.ts +96 -55
  759. package/src/memory/{ingress-invite-store.ts → invite-store.ts} +28 -169
  760. package/src/memory/items-extractor.ts +313 -157
  761. package/src/memory/job-handlers/backfill.ts +116 -63
  762. package/src/memory/job-handlers/cleanup.ts +64 -41
  763. package/src/memory/job-handlers/conflict.ts +90 -49
  764. package/src/memory/job-handlers/embedding.ts +32 -17
  765. package/src/memory/job-handlers/extraction.ts +58 -33
  766. package/src/memory/job-handlers/index-maintenance.ts +31 -17
  767. package/src/memory/job-handlers/media-processing.ts +65 -24
  768. package/src/memory/job-handlers/summarization.ts +186 -128
  769. package/src/memory/job-utils.ts +100 -57
  770. package/src/memory/jobs-store.ts +235 -142
  771. package/src/memory/jobs-worker.ts +167 -83
  772. package/src/memory/llm-request-log-store.ts +13 -11
  773. package/src/memory/llm-usage-store.ts +35 -26
  774. package/src/memory/media-store.ts +151 -44
  775. package/src/memory/message-content.ts +28 -18
  776. package/src/memory/migrations/001-job-deferrals.ts +11 -5
  777. package/src/memory/migrations/002-tool-invocations-fk.ts +14 -6
  778. package/src/memory/migrations/003-memory-fts-backfill.ts +11 -5
  779. package/src/memory/migrations/004-entity-relation-dedup.ts +17 -11
  780. package/src/memory/migrations/005-fingerprint-scope-unique.ts +36 -21
  781. package/src/memory/migrations/006-scope-salted-fingerprints.ts +35 -20
  782. package/src/memory/migrations/007-assistant-id-to-self.ts +40 -27
  783. package/src/memory/migrations/008-remove-assistant-id-columns.ts +58 -36
  784. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +36 -22
  785. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +21 -11
  786. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +30 -15
  787. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +4 -2
  788. package/src/memory/migrations/013-guardian-action-tables.ts +29 -11
  789. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +35 -21
  790. package/src/memory/migrations/015-drop-active-search-index.ts +17 -11
  791. package/src/memory/migrations/016-memory-segments-indexes.ts +7 -3
  792. package/src/memory/migrations/017-memory-items-indexes.ts +4 -2
  793. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -5
  794. package/src/memory/migrations/019-notification-tables-schema-migration.ts +34 -20
  795. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +87 -53
  796. package/src/memory/migrations/021-conversation-status-indexes.ts +7 -3
  797. package/src/memory/migrations/022-add-origin-interface.ts +4 -2
  798. package/src/memory/migrations/023-memory-item-sources-indexes.ts +4 -2
  799. package/src/memory/migrations/024-embedding-vector-blob.ts +34 -18
  800. package/src/memory/migrations/025-messages-fts-backfill.ts +11 -5
  801. package/src/memory/migrations/026-guardian-verification-sessions.ts +80 -14
  802. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +42 -26
  803. package/src/memory/migrations/027-notification-delivery-pairing-columns.ts +22 -8
  804. package/src/memory/migrations/027a-guardian-bootstrap-token.ts +11 -3
  805. package/src/memory/migrations/028-call-session-mode.ts +13 -3
  806. package/src/memory/migrations/028-notification-delivery-client-ack.ts +22 -8
  807. package/src/memory/migrations/029-channel-inbound-delivered-segments.ts +7 -3
  808. package/src/memory/migrations/030-guardian-action-followup.ts +46 -8
  809. package/src/memory/migrations/030-guardian-verification-purpose.ts +4 -2
  810. package/src/memory/migrations/031-conversations-thread-type-index.ts +4 -2
  811. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +4 -2
  812. package/src/memory/migrations/032-notification-delivery-thread-decision.ts +22 -8
  813. package/src/memory/migrations/033-scoped-approval-grants.ts +1 -1
  814. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +15 -3
  815. package/src/memory/migrations/035-guardian-action-supersession.ts +15 -3
  816. package/src/memory/migrations/036-normalize-phone-identities.ts +101 -87
  817. package/src/memory/migrations/037-voice-invite-columns.ts +22 -4
  818. package/src/memory/migrations/038-actor-token-records.ts +5 -9
  819. package/src/memory/migrations/039-actor-refresh-token-records.ts +7 -13
  820. package/src/memory/migrations/100-core-tables.ts +1 -1
  821. package/src/memory/migrations/101-watchers-and-logs.ts +1 -1
  822. package/src/memory/migrations/103-complex-migrations.ts +9 -9
  823. package/src/memory/migrations/104-core-indexes.ts +188 -64
  824. package/src/memory/migrations/105-contacts-and-triage.ts +28 -10
  825. package/src/memory/migrations/106-call-sessions.ts +58 -16
  826. package/src/memory/migrations/107-followups.ts +16 -6
  827. package/src/memory/migrations/108-tasks-and-work-items.ts +43 -11
  828. package/src/memory/migrations/109-external-conversation-bindings.ts +11 -5
  829. package/src/memory/migrations/110-channel-guardian.ts +48 -10
  830. package/src/memory/migrations/111-media-assets.ts +52 -18
  831. package/src/memory/migrations/112-assistant-inbox.ts +32 -12
  832. package/src/memory/migrations/113-late-migrations.ts +12 -12
  833. package/src/memory/migrations/114-notifications.ts +28 -12
  834. package/src/memory/migrations/115-sequences.ts +10 -4
  835. package/src/memory/migrations/116-messages-fts.ts +1 -1
  836. package/src/memory/migrations/117-conversation-attention.ts +16 -6
  837. package/src/memory/migrations/118-reminder-routing-intent.ts +7 -3
  838. package/src/memory/migrations/119-schema-indexes-and-columns.ts +35 -15
  839. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +36 -17
  840. package/src/memory/migrations/121-canonical-guardian-requests.ts +25 -9
  841. package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +11 -3
  842. package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +4 -2
  843. package/src/memory/migrations/124-voice-invite-display-metadata.ts +15 -3
  844. package/src/memory/migrations/125-guardian-principal-id-columns.ts +22 -4
  845. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +174 -126
  846. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +58 -42
  847. package/src/memory/migrations/128-contacts-role-principal.ts +26 -0
  848. package/src/memory/migrations/129-contact-channels-access-fields.ts +105 -0
  849. package/src/memory/migrations/130-contact-channels-type-ext-chat-id-index.ts +15 -0
  850. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +134 -0
  851. package/src/memory/migrations/132-contacts-assistant-id.ts +21 -0
  852. package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
  853. package/src/memory/migrations/index.ts +83 -73
  854. package/src/memory/migrations/registry.ts +53 -37
  855. package/src/memory/migrations/validate-migration-state.ts +73 -46
  856. package/src/memory/profile-compiler.ts +58 -24
  857. package/src/memory/published-pages-store.ts +12 -16
  858. package/src/memory/qdrant-circuit-breaker.ts +28 -20
  859. package/src/memory/qdrant-client.ts +99 -63
  860. package/src/memory/qdrant-manager.ts +89 -57
  861. package/src/memory/query-builder.ts +9 -7
  862. package/src/memory/raw-query.ts +63 -14
  863. package/src/memory/recall-cache.ts +15 -8
  864. package/src/memory/retrieval-budget.ts +0 -1
  865. package/src/memory/retriever.ts +385 -192
  866. package/src/memory/schema-migration.ts +1 -1
  867. package/src/memory/schema.ts +56 -56
  868. package/src/memory/scoped-approval-grants.ts +99 -45
  869. package/src/memory/search/entity.ts +102 -40
  870. package/src/memory/search/formatting.ts +70 -52
  871. package/src/memory/search/lexical.ts +82 -43
  872. package/src/memory/search/ranking.ts +103 -39
  873. package/src/memory/search/semantic.ts +59 -35
  874. package/src/memory/search/types.ts +8 -8
  875. package/src/memory/segmenter.ts +20 -12
  876. package/src/memory/shared-app-links-store.ts +21 -16
  877. package/src/memory/slack-thread-store.ts +187 -0
  878. package/src/memory/task-memory-cleanup.ts +18 -8
  879. package/src/memory/tool-usage-store.ts +27 -19
  880. package/src/memory/validation.ts +4 -2
  881. package/src/messaging/activity-analyzer.ts +7 -7
  882. package/src/messaging/draft-store.ts +13 -10
  883. package/src/messaging/email-classifier.ts +73 -37
  884. package/src/messaging/index.ts +3 -3
  885. package/src/messaging/outreach-classifier.ts +76 -38
  886. package/src/messaging/provider-types.ts +2 -4
  887. package/src/messaging/provider.ts +37 -8
  888. package/src/messaging/providers/gmail/adapter.ts +183 -66
  889. package/src/messaging/providers/gmail/client.ts +3 -1
  890. package/src/messaging/providers/gmail/mime-builder.ts +21 -19
  891. package/src/messaging/providers/gmail/people-client.ts +22 -9
  892. package/src/messaging/providers/gmail/types.ts +6 -6
  893. package/src/messaging/providers/slack/adapter.ts +93 -43
  894. package/src/messaging/providers/slack/client.ts +165 -48
  895. package/src/messaging/providers/slack/types.ts +10 -0
  896. package/src/messaging/providers/sms/adapter.ts +76 -40
  897. package/src/messaging/providers/sms/client.ts +4 -4
  898. package/src/messaging/providers/telegram-bot/adapter.ts +52 -30
  899. package/src/messaging/providers/telegram-bot/client.ts +7 -7
  900. package/src/messaging/providers/whatsapp/adapter.ts +58 -31
  901. package/src/messaging/providers/whatsapp/client.ts +4 -4
  902. package/src/messaging/registry.ts +9 -5
  903. package/src/messaging/style-analyzer.ts +69 -39
  904. package/src/messaging/thread-summarizer.ts +101 -53
  905. package/src/messaging/triage-engine.ts +111 -82
  906. package/src/messaging/types.ts +10 -10
  907. package/src/migrations/config-merge.ts +18 -10
  908. package/src/migrations/data-layout.ts +35 -22
  909. package/src/migrations/data-merge.ts +17 -7
  910. package/src/migrations/hooks-merge.ts +43 -16
  911. package/src/migrations/index.ts +6 -6
  912. package/src/migrations/log.ts +9 -5
  913. package/src/migrations/skills-merge.ts +17 -7
  914. package/src/migrations/workspace-layout.ts +39 -25
  915. package/src/notifications/AGENTS.md +5 -0
  916. package/src/notifications/adapters/macos.ts +21 -14
  917. package/src/notifications/adapters/slack.ts +90 -0
  918. package/src/notifications/adapters/sms.ts +28 -15
  919. package/src/notifications/adapters/telegram.ts +24 -15
  920. package/src/notifications/broadcaster.ts +108 -52
  921. package/src/notifications/conversation-pairing.ts +64 -29
  922. package/src/notifications/copy-composer.ts +165 -95
  923. package/src/notifications/decision-engine.ts +353 -147
  924. package/src/notifications/decisions-store.ts +26 -10
  925. package/src/notifications/deliveries-store.ts +23 -13
  926. package/src/notifications/destination-resolver.ts +83 -24
  927. package/src/notifications/deterministic-checks.ts +78 -27
  928. package/src/notifications/emit-signal.ts +95 -41
  929. package/src/notifications/events-store.ts +13 -7
  930. package/src/notifications/guardian-question-mode.ts +125 -75
  931. package/src/notifications/preference-extractor.ts +85 -53
  932. package/src/notifications/preference-summary.ts +31 -18
  933. package/src/notifications/preferences-store.ts +29 -18
  934. package/src/notifications/runtime-dispatch.ts +22 -12
  935. package/src/notifications/signal.ts +4 -4
  936. package/src/notifications/thread-candidates.ts +59 -23
  937. package/src/notifications/thread-seed-composer.ts +45 -27
  938. package/src/notifications/types.ts +19 -10
  939. package/src/oauth/connect-orchestrator.ts +105 -54
  940. package/src/oauth/connect-types.ts +3 -3
  941. package/src/oauth/provider-profiles.ts +102 -59
  942. package/src/oauth/scope-policy.ts +5 -2
  943. package/src/oauth/token-persistence.ts +58 -24
  944. package/src/outbound-proxy/certs.ts +284 -0
  945. package/src/outbound-proxy/config.ts +94 -0
  946. package/src/outbound-proxy/connect-tunnel.ts +84 -0
  947. package/src/outbound-proxy/health.ts +62 -0
  948. package/src/outbound-proxy/host-pattern-match.ts +67 -0
  949. package/src/outbound-proxy/http-forwarder.ts +162 -0
  950. package/src/outbound-proxy/index.ts +80 -0
  951. package/src/outbound-proxy/logging.ts +193 -0
  952. package/src/outbound-proxy/mitm-handler.ts +292 -0
  953. package/src/outbound-proxy/policy.ts +172 -0
  954. package/src/outbound-proxy/router.ts +64 -0
  955. package/src/outbound-proxy/server.ts +145 -0
  956. package/src/outbound-proxy/types.ts +150 -0
  957. package/src/permissions/checker.ts +481 -189
  958. package/src/permissions/defaults.ts +135 -108
  959. package/src/permissions/prompter.ts +53 -27
  960. package/src/permissions/secret-prompter.ts +21 -15
  961. package/src/permissions/shell-identity.ts +47 -16
  962. package/src/permissions/trust-store.ts +185 -73
  963. package/src/permissions/types.ts +22 -12
  964. package/src/permissions/workspace-policy.ts +47 -38
  965. package/src/playbooks/index.ts +10 -2
  966. package/src/playbooks/playbook-compiler.ts +30 -24
  967. package/src/playbooks/types.ts +11 -8
  968. package/src/providers/anthropic/client.ts +328 -168
  969. package/src/providers/failover.ts +57 -22
  970. package/src/providers/fireworks/client.ts +9 -5
  971. package/src/providers/gemini/client.ts +61 -39
  972. package/src/providers/model-intents.ts +40 -33
  973. package/src/providers/ollama/client.ts +7 -7
  974. package/src/providers/openai/client.ts +109 -68
  975. package/src/providers/openrouter/client.ts +9 -5
  976. package/src/providers/provider-send-message.ts +59 -27
  977. package/src/providers/ratelimit.ts +25 -8
  978. package/src/providers/registry.ts +86 -38
  979. package/src/providers/retry.ts +93 -37
  980. package/src/providers/stream-timeout.ts +5 -3
  981. package/src/providers/types.ts +7 -6
  982. package/src/runtime/AGENTS.md +42 -0
  983. package/src/runtime/access-request-helper.ts +118 -68
  984. package/src/runtime/actor-refresh-token-store.ts +21 -16
  985. package/src/runtime/actor-token-store.ts +25 -18
  986. package/src/runtime/actor-trust-resolver.ts +191 -80
  987. package/src/runtime/approval-conversation-turn.ts +39 -26
  988. package/src/runtime/approval-message-composer.ts +116 -84
  989. package/src/runtime/assistant-event-hub.ts +25 -6
  990. package/src/runtime/assistant-event.ts +4 -4
  991. package/src/runtime/assistant-scope.ts +1 -1
  992. package/src/runtime/auth/__tests__/guard-tests.test.ts +36 -14
  993. package/src/runtime/auth/context.ts +8 -7
  994. package/src/runtime/auth/credential-service.ts +60 -38
  995. package/src/runtime/auth/external-assistant-id.ts +16 -8
  996. package/src/runtime/auth/index.ts +23 -16
  997. package/src/runtime/auth/require-bound-guardian.ts +44 -0
  998. package/src/runtime/auth/route-policy.ts +166 -104
  999. package/src/runtime/auth/scopes.ts +22 -29
  1000. package/src/runtime/auth/subject.ts +19 -13
  1001. package/src/runtime/auth/token-service.ts +3 -3
  1002. package/src/runtime/auth/types.ts +23 -23
  1003. package/src/runtime/channel-approval-parser.ts +37 -14
  1004. package/src/runtime/channel-approval-types.ts +30 -4
  1005. package/src/runtime/channel-approvals.ts +49 -23
  1006. package/src/runtime/channel-guardian-service.ts +144 -103
  1007. package/src/runtime/channel-invite-transport.ts +5 -3
  1008. package/src/runtime/channel-invite-transports/telegram.ts +16 -10
  1009. package/src/runtime/channel-invite-transports/voice.ts +7 -7
  1010. package/src/runtime/channel-readiness-service.ts +139 -90
  1011. package/src/runtime/channel-readiness-types.ts +4 -2
  1012. package/src/runtime/channel-reply-delivery.ts +83 -14
  1013. package/src/runtime/channel-retry-sweep.ts +111 -62
  1014. package/src/runtime/confirmation-request-guardian-bridge.ts +73 -54
  1015. package/src/runtime/gateway-client.ts +122 -55
  1016. package/src/runtime/gateway-internal-client.ts +86 -0
  1017. package/src/runtime/guardian-action-conversation-turn.ts +34 -18
  1018. package/src/runtime/guardian-action-followup-executor.ts +115 -45
  1019. package/src/runtime/guardian-action-grant-minter.ts +40 -24
  1020. package/src/runtime/guardian-action-message-composer.ts +105 -84
  1021. package/src/runtime/guardian-action-service.ts +127 -0
  1022. package/src/runtime/guardian-decision-types.ts +28 -13
  1023. package/src/runtime/guardian-outbound-actions.ts +9 -0
  1024. package/src/runtime/guardian-reply-router.ts +274 -145
  1025. package/src/runtime/guardian-vellum-migration.ts +38 -24
  1026. package/src/runtime/guardian-verification-templates.ts +24 -12
  1027. package/src/runtime/http-router.ts +175 -0
  1028. package/src/runtime/http-server.ts +913 -680
  1029. package/src/runtime/http-types.ts +2 -2
  1030. package/src/runtime/invite-redemption-service.ts +211 -134
  1031. package/src/runtime/invite-redemption-templates.ts +18 -11
  1032. package/src/runtime/{ingress-service.ts → invite-service.ts} +92 -151
  1033. package/src/runtime/local-actor-identity.ts +73 -55
  1034. package/src/runtime/middleware/auth.ts +25 -14
  1035. package/src/runtime/middleware/error-handler.ts +15 -11
  1036. package/src/runtime/middleware/rate-limiter.ts +23 -17
  1037. package/src/runtime/middleware/request-logger.ts +4 -4
  1038. package/src/runtime/middleware/twilio-validation.ts +29 -20
  1039. package/src/runtime/migrations/migration-transport.ts +575 -0
  1040. package/src/runtime/migrations/migration-wizard.ts +715 -0
  1041. package/src/runtime/migrations/rebind-secrets-screen.ts +351 -0
  1042. package/src/runtime/migrations/transfer-progress-screen.ts +321 -0
  1043. package/src/runtime/migrations/validation-results-screen.ts +467 -0
  1044. package/src/runtime/migrations/vbundle-builder.ts +295 -0
  1045. package/src/runtime/migrations/vbundle-import-analyzer.ts +212 -0
  1046. package/src/runtime/migrations/vbundle-importer.ts +339 -0
  1047. package/src/runtime/migrations/vbundle-validator.ts +356 -0
  1048. package/src/runtime/nl-approval-parser.ts +138 -0
  1049. package/src/runtime/pending-interactions.ts +16 -7
  1050. package/src/runtime/routes/access-request-decision.ts +73 -52
  1051. package/src/runtime/routes/app-routes.ts +56 -38
  1052. package/src/runtime/routes/approval-routes.ts +144 -92
  1053. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +930 -0
  1054. package/src/runtime/routes/approval-strategies/guardian-legacy-fallback-strategy.ts +82 -0
  1055. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +151 -0
  1056. package/src/runtime/routes/attachment-routes.ts +59 -48
  1057. package/src/runtime/routes/brain-graph-routes.ts +85 -69
  1058. package/src/runtime/routes/call-routes.ts +79 -38
  1059. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +10 -10
  1060. package/src/runtime/routes/channel-delivery-routes.ts +19 -14
  1061. package/src/runtime/routes/channel-guardian-routes.ts +3 -3
  1062. package/src/runtime/routes/channel-inbound-routes.ts +2 -2
  1063. package/src/runtime/routes/channel-readiness-routes.ts +12 -6
  1064. package/src/runtime/routes/channel-route-shared.ts +67 -25
  1065. package/src/runtime/routes/channel-routes.ts +4 -6
  1066. package/src/runtime/routes/contact-routes.ts +374 -17
  1067. package/src/runtime/routes/conversation-attention-routes.ts +57 -28
  1068. package/src/runtime/routes/conversation-routes.ts +321 -174
  1069. package/src/runtime/routes/debug-routes.ts +14 -10
  1070. package/src/runtime/routes/events-routes.ts +90 -57
  1071. package/src/runtime/routes/global-search-routes.ts +266 -0
  1072. package/src/runtime/routes/guardian-action-routes.ts +112 -113
  1073. package/src/runtime/routes/guardian-approval-interception.ts +325 -874
  1074. package/src/runtime/routes/guardian-approval-prompt.ts +40 -24
  1075. package/src/runtime/routes/guardian-approval-reply-helpers.ts +135 -0
  1076. package/src/runtime/routes/guardian-bootstrap-routes.ts +55 -36
  1077. package/src/runtime/routes/guardian-expiry-sweep.ts +63 -37
  1078. package/src/runtime/routes/guardian-refresh-routes.ts +40 -19
  1079. package/src/runtime/routes/identity-routes.ts +71 -42
  1080. package/src/runtime/routes/inbound-conversation.ts +17 -11
  1081. package/src/runtime/routes/inbound-message-handler.ts +305 -1459
  1082. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +880 -0
  1083. package/src/runtime/routes/inbound-stages/background-dispatch.ts +600 -0
  1084. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +214 -0
  1085. package/src/runtime/routes/inbound-stages/edit-intercept.ts +116 -0
  1086. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +167 -0
  1087. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +185 -0
  1088. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +132 -0
  1089. package/src/runtime/routes/inbound-stages/verification-intercept.ts +340 -0
  1090. package/src/runtime/routes/integration-routes.ts +60 -21
  1091. package/src/runtime/routes/invite-routes.ts +140 -0
  1092. package/src/runtime/routes/migration-routes.ts +434 -0
  1093. package/src/runtime/routes/pairing-routes.ts +157 -79
  1094. package/src/runtime/routes/secret-routes.ts +6 -2
  1095. package/src/runtime/routes/twilio-routes.ts +443 -249
  1096. package/src/runtime/slack-block-formatting.ts +176 -0
  1097. package/src/runtime/tool-grant-request-helper.ts +36 -27
  1098. package/src/runtime/{guardian-context-resolver.ts → trust-context-resolver.ts} +29 -41
  1099. package/src/schedule/integration-status.ts +44 -9
  1100. package/src/schedule/recurrence-engine.ts +47 -24
  1101. package/src/schedule/recurrence-types.ts +12 -7
  1102. package/src/schedule/schedule-store.ts +166 -83
  1103. package/src/schedule/scheduler.ts +37 -24
  1104. package/src/security/encrypted-store.ts +68 -38
  1105. package/src/security/keychain.ts +183 -120
  1106. package/src/security/oauth-callback-registry.ts +3 -3
  1107. package/src/security/oauth2.ts +226 -138
  1108. package/src/security/redaction.ts +24 -24
  1109. package/src/security/secret-allowlist.ts +46 -21
  1110. package/src/security/secret-ingress.ts +15 -7
  1111. package/src/security/secret-scanner.ts +193 -104
  1112. package/src/security/secure-keys.ts +9 -3
  1113. package/src/security/token-manager.ts +99 -40
  1114. package/src/security/tool-approval-digest.ts +3 -3
  1115. package/src/sequence/analytics.ts +52 -27
  1116. package/src/sequence/engine.ts +135 -72
  1117. package/src/sequence/guardrails.ts +32 -20
  1118. package/src/sequence/importer.ts +75 -37
  1119. package/src/sequence/reply-matcher.ts +36 -18
  1120. package/src/sequence/store.ts +137 -75
  1121. package/src/sequence/types.ts +30 -16
  1122. package/src/services/published-app-updater.ts +26 -16
  1123. package/src/services/vercel-deploy.ts +19 -15
  1124. package/src/skills/active-skill-tools.ts +3 -3
  1125. package/src/skills/clawhub.ts +178 -90
  1126. package/src/skills/include-graph.ts +24 -17
  1127. package/src/skills/managed-store.ts +89 -42
  1128. package/src/skills/path-classifier.ts +10 -10
  1129. package/src/skills/remote-skill-policy.ts +31 -22
  1130. package/src/skills/slash-commands.ts +36 -30
  1131. package/src/skills/tool-manifest.ts +60 -31
  1132. package/src/skills/version-hash.ts +25 -15
  1133. package/src/slack/slack-webhook.ts +19 -15
  1134. package/src/subagent/index.ts +4 -8
  1135. package/src/subagent/manager.ts +119 -69
  1136. package/src/subagent/types.ts +9 -12
  1137. package/src/swarm/backend-claude-code.ts +124 -45
  1138. package/src/swarm/checkpoint.ts +36 -16
  1139. package/src/swarm/graph-utils.ts +1 -3
  1140. package/src/swarm/index.ts +38 -19
  1141. package/src/swarm/limits.ts +13 -4
  1142. package/src/swarm/orchestrator.ts +108 -57
  1143. package/src/swarm/plan-validator.ts +23 -17
  1144. package/src/swarm/router-planner.ts +51 -22
  1145. package/src/swarm/router-prompts.ts +4 -1
  1146. package/src/swarm/synthesizer.ts +26 -18
  1147. package/src/swarm/types.ts +14 -4
  1148. package/src/swarm/worker-backend.ts +36 -26
  1149. package/src/swarm/worker-prompts.ts +13 -9
  1150. package/src/swarm/worker-runner.ts +40 -34
  1151. package/src/tasks/candidate-store.ts +14 -6
  1152. package/src/tasks/ephemeral-permissions.ts +9 -5
  1153. package/src/tasks/task-compiler.ts +41 -38
  1154. package/src/tasks/task-runner.ts +54 -26
  1155. package/src/tasks/task-scheduler.ts +1 -1
  1156. package/src/tasks/task-store.ts +20 -7
  1157. package/src/tasks/tool-sanitizer.ts +3 -3
  1158. package/src/tools/apps/definitions.ts +23 -15
  1159. package/src/tools/apps/executors.ts +122 -40
  1160. package/src/tools/apps/open-proxy.ts +5 -5
  1161. package/src/tools/apps/registry.ts +2 -2
  1162. package/src/tools/assets/materialize.ts +59 -41
  1163. package/src/tools/assets/search.ts +86 -48
  1164. package/src/tools/browser/api-map.ts +52 -36
  1165. package/src/tools/browser/auth-cache.ts +21 -18
  1166. package/src/tools/browser/auth-detector.ts +43 -28
  1167. package/src/tools/browser/auto-navigate.ts +149 -68
  1168. package/src/tools/browser/browser-execution.ts +9 -3
  1169. package/src/tools/browser/headless-browser.ts +287 -150
  1170. package/src/tools/browser/jit-auth.ts +37 -21
  1171. package/src/tools/browser/network-recorder.ts +138 -56
  1172. package/src/tools/browser/recording-store.ts +22 -15
  1173. package/src/tools/browser/runtime-check.ts +8 -5
  1174. package/src/tools/browser/x-auto-navigate.ts +88 -47
  1175. package/src/tools/calls/call-end.ts +10 -7
  1176. package/src/tools/calls/call-start.ts +30 -20
  1177. package/src/tools/calls/call-status.ts +8 -5
  1178. package/src/tools/claude-code/claude-code.ts +301 -165
  1179. package/src/tools/computer-use/definitions.ts +175 -130
  1180. package/src/tools/computer-use/registry.ts +2 -2
  1181. package/src/tools/computer-use/request-computer-control.ts +21 -13
  1182. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  1183. package/src/tools/credentials/account-registry.ts +52 -35
  1184. package/src/tools/credentials/broker-types.ts +1 -1
  1185. package/src/tools/credentials/broker.ts +97 -55
  1186. package/src/tools/credentials/domain-policy.ts +5 -2
  1187. package/src/tools/credentials/host-pattern-match.ts +15 -8
  1188. package/src/tools/credentials/metadata-store.ts +93 -43
  1189. package/src/tools/credentials/policy-types.ts +5 -2
  1190. package/src/tools/credentials/policy-validate.ts +21 -14
  1191. package/src/tools/credentials/post-connect-hooks.ts +18 -7
  1192. package/src/tools/credentials/resolve.ts +11 -10
  1193. package/src/tools/credentials/selection.ts +30 -25
  1194. package/src/tools/credentials/tool-policy.ts +5 -2
  1195. package/src/tools/credentials/vault.ts +538 -185
  1196. package/src/tools/document/document-tool.ts +23 -17
  1197. package/src/tools/document/editor-template.ts +12 -7
  1198. package/src/tools/execution-target.ts +13 -10
  1199. package/src/tools/execution-timeout.ts +6 -5
  1200. package/src/tools/executor.ts +141 -74
  1201. package/src/tools/filesystem/edit.ts +82 -45
  1202. package/src/tools/filesystem/fuzzy-match.ts +70 -32
  1203. package/src/tools/filesystem/read.ts +46 -28
  1204. package/src/tools/filesystem/view-image.ts +86 -42
  1205. package/src/tools/filesystem/write.ts +53 -32
  1206. package/src/tools/followups/followup_create.ts +43 -17
  1207. package/src/tools/followups/followup_list.ts +28 -13
  1208. package/src/tools/followups/followup_resolve.ts +9 -6
  1209. package/src/tools/guardian-control-plane-policy.ts +15 -14
  1210. package/src/tools/host-filesystem/edit.ts +77 -42
  1211. package/src/tools/host-filesystem/read.ts +52 -33
  1212. package/src/tools/host-filesystem/write.ts +50 -29
  1213. package/src/tools/host-terminal/host-shell.ts +97 -61
  1214. package/src/tools/mcp/mcp-tool-factory.ts +21 -14
  1215. package/src/tools/memory/definitions.ts +60 -28
  1216. package/src/tools/memory/handlers.ts +149 -77
  1217. package/src/tools/memory/register.ts +39 -16
  1218. package/src/tools/network/__tests__/web-search.test.ts +236 -177
  1219. package/src/tools/network/domain-normalize.ts +13 -9
  1220. package/src/tools/network/script-proxy/__tests__/logging.test.ts +193 -123
  1221. package/src/tools/network/script-proxy/__tests__/policy.test.ts +225 -127
  1222. package/src/tools/network/script-proxy/index.ts +1 -17
  1223. package/src/tools/network/script-proxy/session-manager.ts +178 -86
  1224. package/src/tools/network/url-safety.ts +56 -34
  1225. package/src/tools/network/web-fetch.ts +273 -155
  1226. package/src/tools/network/web-search.ts +166 -81
  1227. package/src/tools/permission-checker.ts +24 -25
  1228. package/src/tools/policy-context.ts +8 -5
  1229. package/src/tools/registry.ts +73 -46
  1230. package/src/tools/reminder/reminder-store.ts +65 -44
  1231. package/src/tools/reminder/reminder.ts +76 -35
  1232. package/src/tools/schedule/create.ts +44 -21
  1233. package/src/tools/schedule/delete.ts +8 -5
  1234. package/src/tools/schedule/list.ts +39 -19
  1235. package/src/tools/schedule/update.ts +49 -26
  1236. package/src/tools/secret-detection-handler.ts +130 -49
  1237. package/src/tools/sensitive-output-placeholders.ts +15 -8
  1238. package/src/tools/shared/filesystem/edit-engine.ts +45 -14
  1239. package/src/tools/shared/filesystem/errors.ts +18 -18
  1240. package/src/tools/shared/filesystem/file-ops-service.ts +59 -32
  1241. package/src/tools/shared/filesystem/format-diff.ts +21 -11
  1242. package/src/tools/shared/filesystem/path-policy.ts +17 -13
  1243. package/src/tools/shared/filesystem/size-guard.ts +8 -4
  1244. package/src/tools/shared/filesystem/types.ts +2 -2
  1245. package/src/tools/shared/shell-output.ts +4 -3
  1246. package/src/tools/side-effects.ts +36 -28
  1247. package/src/tools/skills/delete-managed.ts +30 -17
  1248. package/src/tools/skills/load.ts +88 -46
  1249. package/src/tools/skills/sandbox-runner.ts +62 -46
  1250. package/src/tools/skills/scaffold-managed.ts +98 -48
  1251. package/src/tools/skills/script-contract.ts +5 -2
  1252. package/src/tools/skills/skill-script-runner.ts +29 -13
  1253. package/src/tools/skills/skill-tool-factory.ts +20 -10
  1254. package/src/tools/subagent/abort.ts +10 -4
  1255. package/src/tools/subagent/message.ts +14 -8
  1256. package/src/tools/subagent/read.ts +20 -11
  1257. package/src/tools/subagent/spawn.ts +14 -6
  1258. package/src/tools/subagent/status.ts +7 -4
  1259. package/src/tools/swarm/delegate.ts +75 -49
  1260. package/src/tools/system/avatar-generator.ts +46 -33
  1261. package/src/tools/system/navigate-settings.ts +29 -19
  1262. package/src/tools/system/open-system-settings.ts +30 -20
  1263. package/src/tools/system/request-permission.ts +59 -44
  1264. package/src/tools/system/version.ts +27 -16
  1265. package/src/tools/system/voice-config.ts +116 -53
  1266. package/src/tools/tasks/index.ts +8 -8
  1267. package/src/tools/tasks/task-delete.ts +61 -22
  1268. package/src/tools/tasks/task-list.ts +23 -11
  1269. package/src/tools/tasks/task-run.ts +41 -16
  1270. package/src/tools/tasks/task-save.ts +27 -10
  1271. package/src/tools/tasks/work-item-enqueue.ts +114 -48
  1272. package/src/tools/tasks/work-item-list.ts +20 -10
  1273. package/src/tools/tasks/work-item-remove.ts +49 -15
  1274. package/src/tools/tasks/work-item-run.ts +34 -13
  1275. package/src/tools/tasks/work-item-update.ts +84 -31
  1276. package/src/tools/terminal/backends/native.ts +64 -35
  1277. package/src/tools/terminal/backends/types.ts +6 -2
  1278. package/src/tools/terminal/parser.ts +200 -125
  1279. package/src/tools/terminal/safe-env.ts +27 -21
  1280. package/src/tools/terminal/sandbox-diagnostics.ts +31 -13
  1281. package/src/tools/terminal/sandbox.ts +10 -6
  1282. package/src/tools/terminal/shell.ts +134 -68
  1283. package/src/tools/tool-approval-handler.ts +239 -140
  1284. package/src/tools/types.ts +79 -22
  1285. package/src/tools/ui-surface/definitions.ts +124 -89
  1286. package/src/tools/ui-surface/registry.ts +2 -2
  1287. package/src/tools/watch/screen-watch.ts +50 -32
  1288. package/src/tools/watch/watch-state.ts +41 -15
  1289. package/src/tools/watcher/create.ts +37 -15
  1290. package/src/tools/watcher/delete.ts +9 -6
  1291. package/src/tools/watcher/digest.ts +10 -6
  1292. package/src/tools/watcher/list.ts +37 -14
  1293. package/src/tools/watcher/update.ts +33 -18
  1294. package/src/tools/weather/service.ts +331 -174
  1295. package/src/twitter/client.ts +261 -138
  1296. package/src/twitter/oauth-client.ts +17 -13
  1297. package/src/twitter/router.ts +51 -23
  1298. package/src/twitter/session.ts +27 -18
  1299. package/src/types/qrcode.d.ts +6 -3
  1300. package/src/usage/actors.ts +16 -16
  1301. package/src/usage/types.ts +3 -3
  1302. package/src/util/bundled-asset.ts +10 -6
  1303. package/src/util/canonicalize-identity.ts +11 -4
  1304. package/src/util/clipboard.ts +7 -7
  1305. package/src/util/content-id.ts +3 -3
  1306. package/src/util/debounce.ts +3 -2
  1307. package/src/util/diff.ts +55 -33
  1308. package/src/util/errors.ts +31 -27
  1309. package/src/util/fs.ts +8 -2
  1310. package/src/util/log-redact.ts +12 -12
  1311. package/src/util/logger.ts +112 -51
  1312. package/src/util/network-info.ts +13 -5
  1313. package/src/util/object.ts +4 -2
  1314. package/src/util/phone.ts +4 -4
  1315. package/src/util/platform.ts +80 -58
  1316. package/src/util/pricing.ts +49 -31
  1317. package/src/util/retry.ts +39 -7
  1318. package/src/util/row-mapper.ts +7 -4
  1319. package/src/util/silently.ts +7 -4
  1320. package/src/util/spawn.ts +48 -0
  1321. package/src/util/spinner.ts +9 -7
  1322. package/src/util/time.ts +16 -3
  1323. package/src/util/truncate.ts +1 -1
  1324. package/src/util/voice-code.ts +6 -4
  1325. package/src/util/xml.ts +5 -1
  1326. package/src/version.ts +12 -8
  1327. package/src/watcher/engine.ts +71 -44
  1328. package/src/watcher/provider-registry.ts +1 -1
  1329. package/src/watcher/providers/github.ts +40 -23
  1330. package/src/watcher/providers/gmail.ts +59 -38
  1331. package/src/watcher/providers/google-calendar.ts +62 -48
  1332. package/src/watcher/providers/linear.ts +219 -150
  1333. package/src/watcher/providers/slack.ts +125 -29
  1334. package/src/watcher/watcher-store.ts +75 -55
  1335. package/src/work-items/work-item-runner.ts +62 -29
  1336. package/src/work-items/work-item-store.ts +137 -47
  1337. package/src/workspace/commit-message-enrichment-service.ts +65 -25
  1338. package/src/workspace/commit-message-provider.ts +14 -12
  1339. package/src/workspace/git-service.ts +355 -239
  1340. package/src/workspace/heartbeat-service.ts +74 -37
  1341. package/src/workspace/provider-commit-message-generator.ts +95 -70
  1342. package/src/workspace/top-level-renderer.ts +10 -8
  1343. package/src/workspace/top-level-scanner.ts +9 -3
  1344. package/src/workspace/turn-commit.ts +63 -36
  1345. package/src/__tests__/ingress-member-store.test.ts +0 -294
  1346. package/src/__tests__/script-proxy-router.test.ts +0 -215
  1347. package/src/config/bundled-skills/trusted-contacts/SKILL.md +0 -372
  1348. package/src/memory/guardian-bindings.ts +0 -158
  1349. package/src/memory/ingress-member-store.ts +0 -352
  1350. package/src/runtime/routes/ingress-routes.ts +0 -229
  1351. package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -77
  1352. package/src/tools/network/script-proxy/certs.ts +0 -7
  1353. package/src/tools/network/script-proxy/connect-tunnel.ts +0 -1
  1354. package/src/tools/network/script-proxy/http-forwarder.ts +0 -2
  1355. package/src/tools/network/script-proxy/logging.ts +0 -12
  1356. package/src/tools/network/script-proxy/mitm-handler.ts +0 -2
  1357. package/src/tools/network/script-proxy/policy.ts +0 -4
  1358. package/src/tools/network/script-proxy/router.ts +0 -2
  1359. package/src/tools/network/script-proxy/server.ts +0 -5
  1360. package/src/tools/network/script-proxy/types.ts +0 -19
@@ -0,0 +1,1855 @@
1
+ /**
2
+ * Cross-version compatibility tests and failure diagnostics for migration
3
+ * endpoints.
4
+ *
5
+ * Tests cover:
6
+ * - Schema version compatibility: bundles with different schema_version values
7
+ * can be validated and imported when otherwise structurally valid
8
+ * - Future/unknown schema versions: produce clear, actionable errors when
9
+ * structural changes break parsing
10
+ * - Missing or malformed version fields: fail with descriptive diagnostics
11
+ * - Round-trip: export -> validate -> import-preflight -> import produces
12
+ * consistent results
13
+ * - Partial failure scenarios: import fails mid-write, write errors surface
14
+ * partial reports
15
+ * - Edge cases: empty bundles, very large file entries, duplicate paths,
16
+ * corrupted checksums, empty file entries
17
+ * - Diagnostic quality: every failure mode includes a machine-readable error
18
+ * code and a human-readable message
19
+ */
20
+ import { createHash } from "node:crypto";
21
+ import {
22
+ mkdirSync,
23
+ mkdtempSync,
24
+ readdirSync,
25
+ readFileSync,
26
+ realpathSync,
27
+ rmSync,
28
+ unlinkSync,
29
+ writeFileSync,
30
+ } from "node:fs";
31
+ import { tmpdir } from "node:os";
32
+ import { join } from "node:path";
33
+ import { gzipSync } from "node:zlib";
34
+ import {
35
+ afterAll,
36
+ afterEach,
37
+ beforeAll,
38
+ beforeEach,
39
+ describe,
40
+ expect,
41
+ mock,
42
+ test,
43
+ } from "bun:test";
44
+
45
+ /** Convert a Uint8Array to an ArrayBuffer for BodyInit compatibility. */
46
+ function toArrayBuffer(data: Uint8Array): ArrayBuffer {
47
+ return data.buffer.slice(
48
+ data.byteOffset,
49
+ data.byteOffset + data.byteLength,
50
+ ) as ArrayBuffer;
51
+ }
52
+
53
+ const testDir = realpathSync(
54
+ mkdtempSync(join(tmpdir(), "migration-cross-version-test-")),
55
+ );
56
+ const testDbDir = join(testDir, "db");
57
+ const testDbPath = join(testDbDir, "assistant.db");
58
+ const testConfigPath = join(testDir, "config.json");
59
+
60
+ mock.module("../util/platform.js", () => ({
61
+ getRootDir: () => testDir,
62
+ getDataDir: () => testDir,
63
+ getWorkspaceConfigPath: () => testConfigPath,
64
+ isMacOS: () => process.platform === "darwin",
65
+ isLinux: () => process.platform === "linux",
66
+ isWindows: () => process.platform === "win32",
67
+ getSocketPath: () => join(testDir, "test.sock"),
68
+ getPidPath: () => join(testDir, "test.pid"),
69
+ getDbPath: () => testDbPath,
70
+ getLogPath: () => join(testDir, "test.log"),
71
+ ensureDataDir: () => {},
72
+ }));
73
+
74
+ mock.module("../util/logger.js", () => ({
75
+ getLogger: () =>
76
+ new Proxy({} as Record<string, unknown>, {
77
+ get: () => () => {},
78
+ }),
79
+ }));
80
+
81
+ mock.module("../config/loader.js", () => ({
82
+ getConfig: () => ({
83
+ ui: {},
84
+ model: "test",
85
+ provider: "test",
86
+ apiKeys: {},
87
+ memory: { enabled: false },
88
+ rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
89
+ secretDetection: { enabled: false },
90
+ sandbox: { enabled: false },
91
+ }),
92
+ }));
93
+
94
+ mock.module("../config/env.js", () => ({
95
+ isHttpAuthDisabled: () => true,
96
+ hasUngatedHttpAuthDisabled: () => false,
97
+ getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
98
+ getGatewayPort: () => 7830,
99
+ getRuntimeHttpPort: () => 7821,
100
+ getRuntimeHttpHost: () => "127.0.0.1",
101
+ getRuntimeProxyBearerToken: () => undefined,
102
+ getRuntimeGatewayOriginSecret: () => undefined,
103
+ getIngressPublicBaseUrl: () => undefined,
104
+ setIngressPublicBaseUrl: () => {},
105
+ }));
106
+
107
+ import { buildVBundle } from "../runtime/migrations/vbundle-builder.js";
108
+ import {
109
+ analyzeImport,
110
+ DefaultPathResolver,
111
+ } from "../runtime/migrations/vbundle-import-analyzer.js";
112
+ import { commitImport } from "../runtime/migrations/vbundle-importer.js";
113
+ import { validateVBundle } from "../runtime/migrations/vbundle-validator.js";
114
+ import {
115
+ handleMigrationExport,
116
+ handleMigrationImport,
117
+ handleMigrationImportPreflight,
118
+ handleMigrationValidate,
119
+ } from "../runtime/routes/migration-routes.js";
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Test fixture data
123
+ // ---------------------------------------------------------------------------
124
+
125
+ const EXISTING_DB_DATA = new Uint8Array([
126
+ 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74,
127
+ 0x20, 0x33, 0x00,
128
+ ]);
129
+ const EXISTING_CONFIG = { provider: "anthropic", model: "test-model" };
130
+
131
+ beforeAll(() => {
132
+ mkdirSync(testDbDir, { recursive: true });
133
+ writeFileSync(testDbPath, EXISTING_DB_DATA);
134
+ writeFileSync(testConfigPath, JSON.stringify(EXISTING_CONFIG, null, 2));
135
+ });
136
+
137
+ afterAll(() => {
138
+ try {
139
+ rmSync(testDir, { recursive: true });
140
+ } catch {
141
+ /* best effort */
142
+ }
143
+ });
144
+
145
+ // Restore test files before each test
146
+ beforeEach(() => {
147
+ mkdirSync(testDbDir, { recursive: true });
148
+ writeFileSync(testDbPath, EXISTING_DB_DATA);
149
+ writeFileSync(testConfigPath, JSON.stringify(EXISTING_CONFIG, null, 2));
150
+ });
151
+
152
+ // Clean up backup files after each test
153
+ afterEach(() => {
154
+ try {
155
+ for (const dir of [testDbDir, testDir]) {
156
+ const entries = readdirSync(dir);
157
+ for (const entry of entries) {
158
+ if (entry.includes(".backup-")) {
159
+ unlinkSync(join(dir, entry));
160
+ }
161
+ }
162
+ }
163
+ } catch {
164
+ /* best effort */
165
+ }
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Tar archive builder helpers
170
+ // ---------------------------------------------------------------------------
171
+
172
+ const BLOCK_SIZE = 512;
173
+
174
+ function padToBlock(data: Uint8Array): Uint8Array {
175
+ const remainder = data.length % BLOCK_SIZE;
176
+ if (remainder === 0) return data;
177
+ const padded = new Uint8Array(data.length + (BLOCK_SIZE - remainder));
178
+ padded.set(data);
179
+ return padded;
180
+ }
181
+
182
+ function writeOctal(
183
+ buf: Uint8Array,
184
+ offset: number,
185
+ length: number,
186
+ value: number,
187
+ ): void {
188
+ const str = value.toString(8).padStart(length - 1, "0");
189
+ for (let i = 0; i < str.length; i++) {
190
+ buf[offset + i] = str.charCodeAt(i);
191
+ }
192
+ buf[offset + length - 1] = 0;
193
+ }
194
+
195
+ function computeHeaderChecksum(header: Uint8Array): number {
196
+ let sum = 0;
197
+ for (let i = 0; i < 512; i++) {
198
+ if (i >= 148 && i < 156) {
199
+ sum += 0x20;
200
+ } else {
201
+ sum += header[i];
202
+ }
203
+ }
204
+ return sum;
205
+ }
206
+
207
+ function createTarEntry(name: string, data: Uint8Array): Uint8Array {
208
+ const header = new Uint8Array(BLOCK_SIZE);
209
+ const encoder = new TextEncoder();
210
+
211
+ const nameBytes = encoder.encode(name);
212
+ header.set(nameBytes.subarray(0, 100), 0);
213
+
214
+ writeOctal(header, 100, 8, 0o644);
215
+ writeOctal(header, 108, 8, 0);
216
+ writeOctal(header, 116, 8, 0);
217
+ writeOctal(header, 124, 12, data.length);
218
+ writeOctal(header, 136, 12, Math.floor(Date.now() / 1000));
219
+ header[156] = "0".charCodeAt(0);
220
+
221
+ const magic = encoder.encode("ustar\0");
222
+ header.set(magic, 257);
223
+ header[263] = "0".charCodeAt(0);
224
+ header[264] = "0".charCodeAt(0);
225
+
226
+ const checksum = computeHeaderChecksum(header);
227
+ writeOctal(header, 148, 7, checksum);
228
+ header[155] = 0x20;
229
+
230
+ const paddedData = padToBlock(data);
231
+ const result = new Uint8Array(header.length + paddedData.length);
232
+ result.set(header, 0);
233
+ result.set(paddedData, header.length);
234
+ return result;
235
+ }
236
+
237
+ function createTarArchive(
238
+ entries: Array<{ name: string; data: Uint8Array }>,
239
+ ): Uint8Array {
240
+ const parts: Uint8Array[] = [];
241
+ for (const entry of entries) {
242
+ parts.push(createTarEntry(entry.name, entry.data));
243
+ }
244
+ parts.push(new Uint8Array(BLOCK_SIZE * 2));
245
+
246
+ const totalLength = parts.reduce((sum, p) => sum + p.length, 0);
247
+ const result = new Uint8Array(totalLength);
248
+ let offset = 0;
249
+ for (const part of parts) {
250
+ result.set(part, offset);
251
+ offset += part.length;
252
+ }
253
+ return result;
254
+ }
255
+
256
+ function sha256Hex(data: Uint8Array | string): string {
257
+ return createHash("sha256").update(data).digest("hex");
258
+ }
259
+
260
+ function canonicalizeJson(obj: unknown): string {
261
+ return JSON.stringify(obj, (_key, value) => {
262
+ if (value && typeof value === "object" && !Array.isArray(value)) {
263
+ const sorted: Record<string, unknown> = {};
264
+ for (const k of Object.keys(value as Record<string, unknown>).sort()) {
265
+ sorted[k] = (value as Record<string, unknown>)[k];
266
+ }
267
+ return sorted;
268
+ }
269
+ return value;
270
+ });
271
+ }
272
+
273
+ interface VBundleFile {
274
+ path: string;
275
+ data: Uint8Array;
276
+ }
277
+
278
+ function createValidVBundle(
279
+ files?: VBundleFile[],
280
+ overrides?: Partial<{
281
+ schema_version: string;
282
+ source: string;
283
+ description: string;
284
+ }>,
285
+ ): Uint8Array {
286
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
287
+ const bundleFiles = files ?? [{ path: "data/db/assistant.db", data: dbData }];
288
+
289
+ const fileEntries = bundleFiles.map((f) => ({
290
+ path: f.path,
291
+ sha256: sha256Hex(f.data),
292
+ size: f.data.length,
293
+ }));
294
+
295
+ const manifestWithoutChecksum = {
296
+ schema_version: overrides?.schema_version ?? "1.0",
297
+ created_at: new Date().toISOString(),
298
+ source: overrides?.source ?? "test",
299
+ description: overrides?.description ?? "Test bundle",
300
+ files: fileEntries,
301
+ };
302
+
303
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
304
+ const manifest = {
305
+ ...manifestWithoutChecksum,
306
+ manifest_sha256: manifestSha256,
307
+ };
308
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
309
+
310
+ const tarEntries = [
311
+ { name: "manifest.json", data: manifestData },
312
+ ...bundleFiles.map((f) => ({ name: f.path, data: f.data })),
313
+ ];
314
+
315
+ const tar = createTarArchive(tarEntries);
316
+ return gzipSync(tar);
317
+ }
318
+
319
+ /**
320
+ * Create a vbundle with a raw manifest object (bypassing normal checksumming).
321
+ * Useful for testing malformed/unusual manifests.
322
+ */
323
+ function createVBundleWithRawManifest(
324
+ manifestObj: Record<string, unknown>,
325
+ archiveFiles: Array<{ name: string; data: Uint8Array }>,
326
+ ): Uint8Array {
327
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifestObj));
328
+ const tarEntries = [
329
+ { name: "manifest.json", data: manifestData },
330
+ ...archiveFiles,
331
+ ];
332
+ const tar = createTarArchive(tarEntries);
333
+ return gzipSync(tar);
334
+ }
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // Response types
338
+ // ---------------------------------------------------------------------------
339
+
340
+ interface ValidationResponse {
341
+ is_valid: boolean;
342
+ errors: Array<{ code: string; message: string; path?: string }>;
343
+ manifest?: Record<string, unknown>;
344
+ }
345
+
346
+ interface ImportDryRunResponse {
347
+ can_import: boolean;
348
+ summary?: {
349
+ total_files: number;
350
+ files_to_create: number;
351
+ files_to_overwrite: number;
352
+ files_unchanged: number;
353
+ files_to_skip: number;
354
+ };
355
+ files?: Array<{
356
+ path: string;
357
+ action: string;
358
+ bundle_size: number;
359
+ current_size: number | null;
360
+ bundle_sha256: string;
361
+ current_sha256: string | null;
362
+ }>;
363
+ conflicts?: Array<{ code: string; message: string; path?: string }>;
364
+ manifest?: Record<string, unknown>;
365
+ validation?: {
366
+ is_valid: boolean;
367
+ errors: Array<{ code: string; message: string; path?: string }>;
368
+ };
369
+ }
370
+
371
+ interface ImportCommitResponse {
372
+ success: boolean;
373
+ reason?: string;
374
+ message?: string;
375
+ summary?: {
376
+ total_files: number;
377
+ files_created: number;
378
+ files_overwritten: number;
379
+ files_skipped: number;
380
+ backups_created: number;
381
+ };
382
+ files?: Array<{
383
+ path: string;
384
+ disk_path: string;
385
+ action: string;
386
+ size: number;
387
+ sha256: string;
388
+ backup_path: string | null;
389
+ }>;
390
+ manifest?: Record<string, unknown>;
391
+ warnings?: string[];
392
+ errors?: Array<{ code: string; message: string; path?: string }>;
393
+ partial_report?: ImportCommitResponse;
394
+ }
395
+
396
+ // ═══════════════════════════════════════════════════════════════════════════
397
+ // 1. Schema version compatibility
398
+ // ═══════════════════════════════════════════════════════════════════════════
399
+
400
+ describe("schema version compatibility", () => {
401
+ test("bundle with schema_version 1.0 validates successfully", () => {
402
+ const vbundle = createValidVBundle(undefined, {
403
+ schema_version: "1.0",
404
+ });
405
+ const result = validateVBundle(vbundle);
406
+
407
+ expect(result.is_valid).toBe(true);
408
+ expect(result.manifest?.schema_version).toBe("1.0");
409
+ });
410
+
411
+ test("bundle with schema_version 2.0 validates successfully (forward compat)", () => {
412
+ // A structurally valid bundle with a future version should still pass
413
+ // validation because the schema_version field is a string and the
414
+ // archive structure is unchanged.
415
+ const vbundle = createValidVBundle(undefined, {
416
+ schema_version: "2.0",
417
+ });
418
+ const result = validateVBundle(vbundle);
419
+
420
+ expect(result.is_valid).toBe(true);
421
+ expect(result.manifest?.schema_version).toBe("2.0");
422
+ });
423
+
424
+ test("bundle with schema_version 99.0 validates when structurally valid", () => {
425
+ const vbundle = createValidVBundle(undefined, {
426
+ schema_version: "99.0",
427
+ });
428
+ const result = validateVBundle(vbundle);
429
+
430
+ expect(result.is_valid).toBe(true);
431
+ expect(result.manifest?.schema_version).toBe("99.0");
432
+ });
433
+
434
+ test("bundle with arbitrary version string validates when structurally valid", () => {
435
+ const vbundle = createValidVBundle(undefined, {
436
+ schema_version: "alpha-preview-3",
437
+ });
438
+ const result = validateVBundle(vbundle);
439
+
440
+ expect(result.is_valid).toBe(true);
441
+ expect(result.manifest?.schema_version).toBe("alpha-preview-3");
442
+ });
443
+
444
+ test("future-versioned bundle can be imported successfully", () => {
445
+ const newDbData = new Uint8Array([0xfa, 0xce, 0xb0, 0x0c]);
446
+ const vbundle = createValidVBundle(
447
+ [{ path: "data/db/assistant.db", data: newDbData }],
448
+ { schema_version: "3.0" },
449
+ );
450
+
451
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
452
+ const result = commitImport({
453
+ archiveData: vbundle,
454
+ pathResolver: resolver,
455
+ });
456
+
457
+ expect(result.ok).toBe(true);
458
+ if (result.ok) {
459
+ expect(result.report.manifest.schema_version).toBe("3.0");
460
+ expect(result.report.success).toBe(true);
461
+ }
462
+ });
463
+
464
+ test("future-versioned bundle shows correct version in preflight report", () => {
465
+ const vbundle = createValidVBundle(undefined, {
466
+ schema_version: "5.0-beta",
467
+ });
468
+
469
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
470
+ const validationResult = validateVBundle(vbundle);
471
+ expect(validationResult.manifest).toBeDefined();
472
+
473
+ const report = analyzeImport({
474
+ manifest: validationResult.manifest!,
475
+ pathResolver: resolver,
476
+ });
477
+
478
+ expect(report.manifest.schema_version).toBe("5.0-beta");
479
+ expect(report.can_import).toBe(true);
480
+ });
481
+ });
482
+
483
+ // ═══════════════════════════════════════════════════════════════════════════
484
+ // 2. Missing or malformed version fields
485
+ // ═══════════════════════════════════════════════════════════════════════════
486
+
487
+ describe("missing or malformed version fields", () => {
488
+ test("missing schema_version produces MANIFEST_SCHEMA_ERROR", () => {
489
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
490
+ const manifestObj = {
491
+ // schema_version intentionally omitted
492
+ created_at: new Date().toISOString(),
493
+ files: [
494
+ {
495
+ path: "data/db/assistant.db",
496
+ sha256: sha256Hex(dbData),
497
+ size: dbData.length,
498
+ },
499
+ ],
500
+ manifest_sha256: "placeholder",
501
+ };
502
+
503
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
504
+ { name: "data/db/assistant.db", data: dbData },
505
+ ]);
506
+ const result = validateVBundle(vbundle);
507
+
508
+ expect(result.is_valid).toBe(false);
509
+ const schemaError = result.errors.find(
510
+ (e) => e.code === "MANIFEST_SCHEMA_ERROR",
511
+ );
512
+ expect(schemaError).toBeDefined();
513
+ expect(schemaError!.message).toContain("schema_version");
514
+ });
515
+
516
+ test("numeric schema_version (instead of string) produces schema error", () => {
517
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
518
+ const manifestObj = {
519
+ schema_version: 1, // should be a string
520
+ created_at: new Date().toISOString(),
521
+ files: [
522
+ {
523
+ path: "data/db/assistant.db",
524
+ sha256: sha256Hex(dbData),
525
+ size: dbData.length,
526
+ },
527
+ ],
528
+ manifest_sha256: "placeholder",
529
+ };
530
+
531
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
532
+ { name: "data/db/assistant.db", data: dbData },
533
+ ]);
534
+ const result = validateVBundle(vbundle);
535
+
536
+ expect(result.is_valid).toBe(false);
537
+ expect(result.errors.some((e) => e.code === "MANIFEST_SCHEMA_ERROR")).toBe(
538
+ true,
539
+ );
540
+ });
541
+
542
+ test("null schema_version produces schema error", () => {
543
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
544
+ const manifestObj = {
545
+ schema_version: null,
546
+ created_at: new Date().toISOString(),
547
+ files: [
548
+ {
549
+ path: "data/db/assistant.db",
550
+ sha256: sha256Hex(dbData),
551
+ size: dbData.length,
552
+ },
553
+ ],
554
+ manifest_sha256: "placeholder",
555
+ };
556
+
557
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
558
+ { name: "data/db/assistant.db", data: dbData },
559
+ ]);
560
+ const result = validateVBundle(vbundle);
561
+
562
+ expect(result.is_valid).toBe(false);
563
+ expect(result.errors.some((e) => e.code === "MANIFEST_SCHEMA_ERROR")).toBe(
564
+ true,
565
+ );
566
+ });
567
+
568
+ test("empty string schema_version passes validation (empty strings are valid)", () => {
569
+ // The Zod schema accepts any string, including empty. An empty string
570
+ // is technically a valid schema_version value, even if unconventional.
571
+ const vbundle = createValidVBundle(undefined, { schema_version: "" });
572
+ const result = validateVBundle(vbundle);
573
+
574
+ expect(result.is_valid).toBe(true);
575
+ expect(result.manifest?.schema_version).toBe("");
576
+ });
577
+
578
+ test("missing created_at produces MANIFEST_SCHEMA_ERROR", () => {
579
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
580
+ const manifestObj = {
581
+ schema_version: "1.0",
582
+ // created_at intentionally omitted
583
+ files: [
584
+ {
585
+ path: "data/db/assistant.db",
586
+ sha256: sha256Hex(dbData),
587
+ size: dbData.length,
588
+ },
589
+ ],
590
+ manifest_sha256: "placeholder",
591
+ };
592
+
593
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
594
+ { name: "data/db/assistant.db", data: dbData },
595
+ ]);
596
+ const result = validateVBundle(vbundle);
597
+
598
+ expect(result.is_valid).toBe(false);
599
+ const error = result.errors.find((e) => e.code === "MANIFEST_SCHEMA_ERROR");
600
+ expect(error).toBeDefined();
601
+ expect(error!.message).toContain("created_at");
602
+ });
603
+
604
+ test("missing files array produces MANIFEST_SCHEMA_ERROR", () => {
605
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
606
+ const manifestObj = {
607
+ schema_version: "1.0",
608
+ created_at: new Date().toISOString(),
609
+ // files intentionally omitted
610
+ manifest_sha256: "placeholder",
611
+ };
612
+
613
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
614
+ { name: "data/db/assistant.db", data: dbData },
615
+ ]);
616
+ const result = validateVBundle(vbundle);
617
+
618
+ expect(result.is_valid).toBe(false);
619
+ expect(result.errors.some((e) => e.code === "MANIFEST_SCHEMA_ERROR")).toBe(
620
+ true,
621
+ );
622
+ });
623
+
624
+ test("missing manifest_sha256 produces MANIFEST_SCHEMA_ERROR", () => {
625
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
626
+ const manifestObj = {
627
+ schema_version: "1.0",
628
+ created_at: new Date().toISOString(),
629
+ files: [
630
+ {
631
+ path: "data/db/assistant.db",
632
+ sha256: sha256Hex(dbData),
633
+ size: dbData.length,
634
+ },
635
+ ],
636
+ // manifest_sha256 intentionally omitted
637
+ };
638
+
639
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
640
+ { name: "data/db/assistant.db", data: dbData },
641
+ ]);
642
+ const result = validateVBundle(vbundle);
643
+
644
+ expect(result.is_valid).toBe(false);
645
+ expect(result.errors.some((e) => e.code === "MANIFEST_SCHEMA_ERROR")).toBe(
646
+ true,
647
+ );
648
+ });
649
+
650
+ test("file entry missing sha256 produces MANIFEST_SCHEMA_ERROR", () => {
651
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
652
+ const manifestObj = {
653
+ schema_version: "1.0",
654
+ created_at: new Date().toISOString(),
655
+ files: [
656
+ {
657
+ path: "data/db/assistant.db",
658
+ // sha256 intentionally omitted
659
+ size: dbData.length,
660
+ },
661
+ ],
662
+ manifest_sha256: "placeholder",
663
+ };
664
+
665
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
666
+ { name: "data/db/assistant.db", data: dbData },
667
+ ]);
668
+ const result = validateVBundle(vbundle);
669
+
670
+ expect(result.is_valid).toBe(false);
671
+ expect(result.errors.some((e) => e.code === "MANIFEST_SCHEMA_ERROR")).toBe(
672
+ true,
673
+ );
674
+ });
675
+
676
+ test("file entry with negative size produces MANIFEST_SCHEMA_ERROR", () => {
677
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
678
+ const manifestObj = {
679
+ schema_version: "1.0",
680
+ created_at: new Date().toISOString(),
681
+ files: [
682
+ {
683
+ path: "data/db/assistant.db",
684
+ sha256: sha256Hex(dbData),
685
+ size: -1,
686
+ },
687
+ ],
688
+ manifest_sha256: "placeholder",
689
+ };
690
+
691
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
692
+ { name: "data/db/assistant.db", data: dbData },
693
+ ]);
694
+ const result = validateVBundle(vbundle);
695
+
696
+ expect(result.is_valid).toBe(false);
697
+ expect(result.errors.some((e) => e.code === "MANIFEST_SCHEMA_ERROR")).toBe(
698
+ true,
699
+ );
700
+ });
701
+
702
+ test("version field error includes descriptive path in diagnostic", () => {
703
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
704
+ const manifestObj = {
705
+ schema_version: 42, // wrong type
706
+ created_at: new Date().toISOString(),
707
+ files: [
708
+ {
709
+ path: "data/db/assistant.db",
710
+ sha256: sha256Hex(dbData),
711
+ size: dbData.length,
712
+ },
713
+ ],
714
+ manifest_sha256: "placeholder",
715
+ };
716
+
717
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
718
+ { name: "data/db/assistant.db", data: dbData },
719
+ ]);
720
+ const result = validateVBundle(vbundle);
721
+
722
+ expect(result.is_valid).toBe(false);
723
+ const error = result.errors.find((e) => e.code === "MANIFEST_SCHEMA_ERROR");
724
+ expect(error).toBeDefined();
725
+ // The error path should indicate where in the manifest the issue is
726
+ expect(error!.path).toBeDefined();
727
+ expect(error!.path).toContain("manifest.json");
728
+ });
729
+ });
730
+
731
+ // ═══════════════════════════════════════════════════════════════════════════
732
+ // 3. Round-trip: export -> validate -> import-preflight -> import
733
+ // ═══════════════════════════════════════════════════════════════════════════
734
+
735
+ describe("round-trip: export -> validate -> preflight -> import", () => {
736
+ test("full round-trip produces consistent results via HTTP handlers", async () => {
737
+ // Step 1: Export
738
+ const exportReq = new Request("http://localhost/v1/migrations/export", {
739
+ method: "POST",
740
+ });
741
+ const exportRes = await handleMigrationExport(exportReq);
742
+ expect(exportRes.status).toBe(200);
743
+
744
+ const archiveBuffer = await exportRes.arrayBuffer();
745
+ const archiveData = new Uint8Array(archiveBuffer);
746
+ expect(archiveData.length).toBeGreaterThan(0);
747
+
748
+ // Step 2: Validate the exported archive
749
+ const validateReq = new Request("http://localhost/v1/migrations/validate", {
750
+ method: "POST",
751
+ headers: { "Content-Type": "application/octet-stream" },
752
+ body: toArrayBuffer(archiveData),
753
+ });
754
+ const validateRes = await handleMigrationValidate(validateReq);
755
+ const validateBody = (await validateRes.json()) as ValidationResponse;
756
+
757
+ expect(validateRes.status).toBe(200);
758
+ expect(validateBody.is_valid).toBe(true);
759
+ expect(validateBody.errors).toHaveLength(0);
760
+ expect(validateBody.manifest).toBeDefined();
761
+
762
+ // Step 3: Import preflight (dry-run)
763
+ const preflightReq = new Request(
764
+ "http://localhost/v1/migrations/import-preflight",
765
+ {
766
+ method: "POST",
767
+ headers: { "Content-Type": "application/octet-stream" },
768
+ body: toArrayBuffer(archiveData),
769
+ },
770
+ );
771
+ const preflightRes = await handleMigrationImportPreflight(preflightReq);
772
+ const preflightBody = (await preflightRes.json()) as ImportDryRunResponse;
773
+
774
+ expect(preflightRes.status).toBe(200);
775
+ expect(preflightBody.can_import).toBe(true);
776
+ expect(preflightBody.summary).toBeDefined();
777
+ expect(preflightBody.files).toBeDefined();
778
+ expect(preflightBody.manifest).toBeDefined();
779
+
780
+ // The files should show "unchanged" since we exported from the same disk
781
+ const dbFile = preflightBody.files!.find(
782
+ (f) => f.path === "data/db/assistant.db",
783
+ );
784
+ expect(dbFile).toBeDefined();
785
+ expect(dbFile!.action).toBe("unchanged");
786
+
787
+ // Step 4: Import (commit)
788
+ const importReq = new Request("http://localhost/v1/migrations/import", {
789
+ method: "POST",
790
+ headers: { "Content-Type": "application/octet-stream" },
791
+ body: toArrayBuffer(archiveData),
792
+ });
793
+ const importRes = await handleMigrationImport(importReq);
794
+ const importBody = (await importRes.json()) as ImportCommitResponse;
795
+
796
+ expect(importRes.status).toBe(200);
797
+ expect(importBody.success).toBe(true);
798
+ expect(importBody.manifest).toBeDefined();
799
+
800
+ // Schema version should be consistent across all steps
801
+ expect(validateBody.manifest!.schema_version).toBe(
802
+ preflightBody.manifest!.schema_version,
803
+ );
804
+ expect(validateBody.manifest!.schema_version).toBe(
805
+ importBody.manifest!.schema_version,
806
+ );
807
+ });
808
+
809
+ test("round-trip via builder: build -> validate -> analyze -> commit", () => {
810
+ const dbData = new Uint8Array([0xca, 0xfe, 0xba, 0xbe]);
811
+ const configData = new TextEncoder().encode('{"model":"test-round-trip"}');
812
+
813
+ // Step 1: Build
814
+ const { archive, manifest } = buildVBundle({
815
+ files: [
816
+ { path: "data/db/assistant.db", data: dbData },
817
+ { path: "config/settings.json", data: configData },
818
+ ],
819
+ schemaVersion: "1.0",
820
+ source: "round-trip-test",
821
+ description: "Testing round-trip",
822
+ });
823
+
824
+ expect(manifest.schema_version).toBe("1.0");
825
+ expect(manifest.files).toHaveLength(2);
826
+
827
+ // Step 2: Validate
828
+ const validationResult = validateVBundle(archive);
829
+ expect(validationResult.is_valid).toBe(true);
830
+ expect(validationResult.manifest?.manifest_sha256).toBe(
831
+ manifest.manifest_sha256,
832
+ );
833
+
834
+ // Step 3: Analyze (preflight)
835
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
836
+ const report = analyzeImport({
837
+ manifest: validationResult.manifest!,
838
+ pathResolver: resolver,
839
+ });
840
+
841
+ expect(report.can_import).toBe(true);
842
+ expect(report.summary.total_files).toBe(2);
843
+
844
+ // Step 4: Commit
845
+ const commitResult = commitImport({
846
+ archiveData: archive,
847
+ pathResolver: resolver,
848
+ });
849
+
850
+ expect(commitResult.ok).toBe(true);
851
+ if (commitResult.ok) {
852
+ expect(commitResult.report.success).toBe(true);
853
+ expect(commitResult.report.manifest.schema_version).toBe("1.0");
854
+ expect(commitResult.report.summary.total_files).toBe(2);
855
+
856
+ // Verify data on disk matches what we built
857
+ const writtenDb = new Uint8Array(readFileSync(testDbPath));
858
+ expect(sha256Hex(writtenDb)).toBe(sha256Hex(dbData));
859
+
860
+ const writtenConfig = readFileSync(testConfigPath, "utf8");
861
+ expect(writtenConfig).toBe('{"model":"test-round-trip"}');
862
+ }
863
+ });
864
+
865
+ test("export manifest checksum remains valid through validate step", async () => {
866
+ const exportReq = new Request("http://localhost/v1/migrations/export", {
867
+ method: "POST",
868
+ });
869
+ const exportRes = await handleMigrationExport(exportReq);
870
+ const archiveData = new Uint8Array(await exportRes.arrayBuffer());
871
+
872
+ // The X-Vbundle-Manifest-Sha256 response header should match
873
+ // the manifest_sha256 inside the archive
874
+ const headerSha = exportRes.headers.get("X-Vbundle-Manifest-Sha256");
875
+ expect(headerSha).toBeDefined();
876
+
877
+ const validationResult = validateVBundle(archiveData);
878
+ expect(validationResult.is_valid).toBe(true);
879
+ expect(validationResult.manifest?.manifest_sha256).toBe(headerSha!);
880
+ });
881
+ });
882
+
883
+ // ═══════════════════════════════════════════════════════════════════════════
884
+ // 4. Partial failure scenarios
885
+ // ═══════════════════════════════════════════════════════════════════════════
886
+
887
+ describe("partial failure scenarios", () => {
888
+ test("commitImport to read-only path returns write_failed with partial report", () => {
889
+ const newDbData = new Uint8Array([0xde, 0xad]);
890
+ const vbundle = createValidVBundle([
891
+ { path: "data/db/assistant.db", data: newDbData },
892
+ ]);
893
+
894
+ // Point to a path that cannot be written (a non-existent deeply nested
895
+ // directory whose parent is a regular file, not a directory)
896
+ const blockerPath = join(testDir, "blocker-file");
897
+ writeFileSync(blockerPath, "I am a file, not a directory");
898
+ const impossibleDbPath = join(blockerPath, "nested", "deep", "db.db");
899
+
900
+ const resolver = new DefaultPathResolver(impossibleDbPath, testConfigPath);
901
+ const result = commitImport({
902
+ archiveData: vbundle,
903
+ pathResolver: resolver,
904
+ });
905
+
906
+ expect(result.ok).toBe(false);
907
+ if (!result.ok) {
908
+ expect(result.reason).toBe("write_failed");
909
+ expect((result as { message: string }).message).toBeDefined();
910
+ expect((result as { message: string }).message.length).toBeGreaterThan(0);
911
+ }
912
+
913
+ // Clean up
914
+ rmSync(blockerPath, { force: true });
915
+ });
916
+
917
+ test("commitImport with invalid archive returns validation_failed", () => {
918
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
919
+ const result = commitImport({
920
+ archiveData: new Uint8Array([0xba, 0xad, 0xf0, 0x0d]),
921
+ pathResolver: resolver,
922
+ });
923
+
924
+ expect(result.ok).toBe(false);
925
+ if (!result.ok) {
926
+ expect(result.reason).toBe("validation_failed");
927
+ const errors = (
928
+ result as {
929
+ errors: Array<{ code: string; message: string }>;
930
+ }
931
+ ).errors;
932
+ expect(errors).toBeDefined();
933
+ expect(errors.length).toBeGreaterThan(0);
934
+ expect(errors[0].code).toBe("INVALID_GZIP");
935
+ }
936
+ });
937
+
938
+ test("import HTTP endpoint returns structured error for invalid archives", async () => {
939
+ const req = new Request("http://localhost/v1/migrations/import", {
940
+ method: "POST",
941
+ headers: { "Content-Type": "application/octet-stream" },
942
+ body: toArrayBuffer(new Uint8Array([0xff, 0xfe, 0xfd])),
943
+ });
944
+
945
+ const res = await handleMigrationImport(req);
946
+ const body = (await res.json()) as ImportCommitResponse;
947
+
948
+ expect(body.success).toBe(false);
949
+ expect(body.reason).toBe("validation_failed");
950
+ expect(body.errors).toBeDefined();
951
+ expect(body.errors!.length).toBeGreaterThan(0);
952
+
953
+ // Each error should have both a code and a message
954
+ for (const err of body.errors!) {
955
+ expect(err.code).toBeDefined();
956
+ expect(err.code.length).toBeGreaterThan(0);
957
+ expect(err.message).toBeDefined();
958
+ expect(err.message.length).toBeGreaterThan(0);
959
+ }
960
+ });
961
+
962
+ test("preflight returns validation errors for corrupted bundle", async () => {
963
+ const req = new Request("http://localhost/v1/migrations/import-preflight", {
964
+ method: "POST",
965
+ headers: { "Content-Type": "application/octet-stream" },
966
+ body: toArrayBuffer(new Uint8Array([0xba, 0xdc, 0x0d, 0xe5])),
967
+ });
968
+
969
+ const res = await handleMigrationImportPreflight(req);
970
+ const body = (await res.json()) as ImportDryRunResponse;
971
+
972
+ expect(res.status).toBe(200);
973
+ expect(body.can_import).toBe(false);
974
+ expect(body.validation).toBeDefined();
975
+ expect(body.validation!.is_valid).toBe(false);
976
+ expect(body.validation!.errors.length).toBeGreaterThan(0);
977
+ });
978
+
979
+ test("import does not modify disk when bundle has checksum mismatch", () => {
980
+ // Save original disk state
981
+ const originalDb = new Uint8Array(readFileSync(testDbPath));
982
+ const originalConfig = readFileSync(testConfigPath, "utf8");
983
+
984
+ // Create bundle with corrupted checksum
985
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
986
+ const manifest = {
987
+ schema_version: "1.0",
988
+ created_at: new Date().toISOString(),
989
+ files: [
990
+ {
991
+ path: "data/db/assistant.db",
992
+ sha256: sha256Hex(dbData),
993
+ size: dbData.length,
994
+ },
995
+ ],
996
+ manifest_sha256:
997
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
998
+ };
999
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1000
+ const tar = createTarArchive([
1001
+ { name: "manifest.json", data: manifestData },
1002
+ { name: "data/db/assistant.db", data: dbData },
1003
+ ]);
1004
+ const corruptVBundle = gzipSync(tar);
1005
+
1006
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1007
+ const result = commitImport({
1008
+ archiveData: corruptVBundle,
1009
+ pathResolver: resolver,
1010
+ });
1011
+
1012
+ expect(result.ok).toBe(false);
1013
+
1014
+ // Disk should be unchanged
1015
+ const currentDb = new Uint8Array(readFileSync(testDbPath));
1016
+ const currentConfig = readFileSync(testConfigPath, "utf8");
1017
+ expect(currentDb).toEqual(originalDb);
1018
+ expect(currentConfig).toBe(originalConfig);
1019
+ });
1020
+ });
1021
+
1022
+ // ═══════════════════════════════════════════════════════════════════════════
1023
+ // 5. Edge cases
1024
+ // ═══════════════════════════════════════════════════════════════════════════
1025
+
1026
+ describe("edge cases", () => {
1027
+ test("bundle with empty database file validates and imports", () => {
1028
+ const emptyDb = new Uint8Array(0);
1029
+ const vbundle = createValidVBundle([
1030
+ { path: "data/db/assistant.db", data: emptyDb },
1031
+ ]);
1032
+
1033
+ const result = validateVBundle(vbundle);
1034
+ expect(result.is_valid).toBe(true);
1035
+ expect(result.manifest?.files[0].size).toBe(0);
1036
+
1037
+ // Import should also succeed
1038
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1039
+ const importResult = commitImport({
1040
+ archiveData: vbundle,
1041
+ pathResolver: resolver,
1042
+ });
1043
+ expect(importResult.ok).toBe(true);
1044
+ if (importResult.ok) {
1045
+ expect(importResult.report.success).toBe(true);
1046
+ }
1047
+ });
1048
+
1049
+ test("bundle with large file entry validates correctly", () => {
1050
+ // Create a 100KB file to test larger payloads
1051
+ const largeData = new Uint8Array(100 * 1024);
1052
+ for (let i = 0; i < largeData.length; i++) {
1053
+ largeData[i] = i % 256;
1054
+ }
1055
+
1056
+ const vbundle = createValidVBundle([
1057
+ { path: "data/db/assistant.db", data: largeData },
1058
+ ]);
1059
+
1060
+ const result = validateVBundle(vbundle);
1061
+ expect(result.is_valid).toBe(true);
1062
+ expect(result.manifest?.files[0].size).toBe(100 * 1024);
1063
+ });
1064
+
1065
+ test("bundle with duplicate archive paths uses last occurrence in tar", () => {
1066
+ // A tar archive with duplicate paths: the tar parser will keep the
1067
+ // last one it sees. Create manually to have duplicates.
1068
+ const dbData1 = new Uint8Array([0x01, 0x02, 0x03]);
1069
+ const dbData2 = new Uint8Array([0x04, 0x05, 0x06]);
1070
+
1071
+ // Build a valid manifest that references the second data
1072
+ const fileEntries = [
1073
+ {
1074
+ path: "data/db/assistant.db",
1075
+ sha256: sha256Hex(dbData2),
1076
+ size: dbData2.length,
1077
+ },
1078
+ ];
1079
+ const manifestWithoutChecksum = {
1080
+ schema_version: "1.0",
1081
+ created_at: new Date().toISOString(),
1082
+ source: "test",
1083
+ description: "Duplicate path test",
1084
+ files: fileEntries,
1085
+ };
1086
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1087
+ const manifest = {
1088
+ ...manifestWithoutChecksum,
1089
+ manifest_sha256: manifestSha256,
1090
+ };
1091
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1092
+
1093
+ // Build tar with duplicate data/db/assistant.db entries
1094
+ const tar = createTarArchive([
1095
+ { name: "manifest.json", data: manifestData },
1096
+ { name: "data/db/assistant.db", data: dbData1 },
1097
+ { name: "data/db/assistant.db", data: dbData2 },
1098
+ ]);
1099
+ const vbundle = gzipSync(tar);
1100
+
1101
+ // The tar parser's Map will keep the last entry for a duplicate key.
1102
+ // Since the manifest references dbData2's checksum and the Map
1103
+ // stores the last occurrence, validation should pass.
1104
+ const result = validateVBundle(vbundle);
1105
+ expect(result.is_valid).toBe(true);
1106
+ });
1107
+
1108
+ test("corrupted file checksum is detected with clear diagnostic", () => {
1109
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1110
+ const wrongChecksum =
1111
+ "0000000000000000000000000000000000000000000000000000000000000000";
1112
+
1113
+ const manifestWithoutChecksum = {
1114
+ schema_version: "1.0",
1115
+ created_at: new Date().toISOString(),
1116
+ files: [
1117
+ {
1118
+ path: "data/db/assistant.db",
1119
+ sha256: wrongChecksum,
1120
+ size: dbData.length,
1121
+ },
1122
+ ],
1123
+ };
1124
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1125
+ const manifest = {
1126
+ ...manifestWithoutChecksum,
1127
+ manifest_sha256: manifestSha256,
1128
+ };
1129
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1130
+
1131
+ const tar = createTarArchive([
1132
+ { name: "manifest.json", data: manifestData },
1133
+ { name: "data/db/assistant.db", data: dbData },
1134
+ ]);
1135
+ const vbundle = gzipSync(tar);
1136
+ const result = validateVBundle(vbundle);
1137
+
1138
+ expect(result.is_valid).toBe(false);
1139
+ const checksumError = result.errors.find(
1140
+ (e) => e.code === "FILE_CHECKSUM_MISMATCH",
1141
+ );
1142
+ expect(checksumError).toBeDefined();
1143
+ expect(checksumError!.message).toContain("data/db/assistant.db");
1144
+ expect(checksumError!.message).toContain(wrongChecksum);
1145
+ expect(checksumError!.path).toBe("data/db/assistant.db");
1146
+ });
1147
+
1148
+ test("manifest declaring non-existent file produces MISSING_DECLARED_FILE", () => {
1149
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1150
+ const ghostSha = sha256Hex(new TextEncoder().encode("ghost-content"));
1151
+
1152
+ const manifestWithoutChecksum = {
1153
+ schema_version: "1.0",
1154
+ created_at: new Date().toISOString(),
1155
+ files: [
1156
+ {
1157
+ path: "data/db/assistant.db",
1158
+ sha256: sha256Hex(dbData),
1159
+ size: dbData.length,
1160
+ },
1161
+ {
1162
+ path: "data/extra/ghost.bin",
1163
+ sha256: ghostSha,
1164
+ size: 13,
1165
+ },
1166
+ ],
1167
+ };
1168
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1169
+ const manifest = {
1170
+ ...manifestWithoutChecksum,
1171
+ manifest_sha256: manifestSha256,
1172
+ };
1173
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1174
+
1175
+ const tar = createTarArchive([
1176
+ { name: "manifest.json", data: manifestData },
1177
+ { name: "data/db/assistant.db", data: dbData },
1178
+ // data/extra/ghost.bin intentionally NOT included
1179
+ ]);
1180
+ const vbundle = gzipSync(tar);
1181
+ const result = validateVBundle(vbundle);
1182
+
1183
+ expect(result.is_valid).toBe(false);
1184
+ const missingError = result.errors.find(
1185
+ (e) => e.code === "MISSING_DECLARED_FILE",
1186
+ );
1187
+ expect(missingError).toBeDefined();
1188
+ expect(missingError!.path).toBe("data/extra/ghost.bin");
1189
+ expect(missingError!.message).toContain("data/extra/ghost.bin");
1190
+ });
1191
+
1192
+ test("bundle with only manifest (no required db) fails validation", () => {
1193
+ const manifestWithoutChecksum = {
1194
+ schema_version: "1.0",
1195
+ created_at: new Date().toISOString(),
1196
+ files: [],
1197
+ };
1198
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1199
+ const manifest = {
1200
+ ...manifestWithoutChecksum,
1201
+ manifest_sha256: manifestSha256,
1202
+ };
1203
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1204
+
1205
+ const tar = createTarArchive([
1206
+ { name: "manifest.json", data: manifestData },
1207
+ ]);
1208
+ const vbundle = gzipSync(tar);
1209
+ const result = validateVBundle(vbundle);
1210
+
1211
+ expect(result.is_valid).toBe(false);
1212
+ expect(
1213
+ result.errors.some(
1214
+ (e) => e.code === "MISSING_ENTRY" && e.path === "data/db/assistant.db",
1215
+ ),
1216
+ ).toBe(true);
1217
+ });
1218
+
1219
+ test("completely empty gzip content (no tar entries) fails gracefully", () => {
1220
+ // A gzip of an empty buffer
1221
+ const emptyGzip = gzipSync(new Uint8Array(0));
1222
+ const result = validateVBundle(emptyGzip);
1223
+
1224
+ expect(result.is_valid).toBe(false);
1225
+ // Should fail with missing entries since the tar is empty
1226
+ expect(result.errors.length).toBeGreaterThan(0);
1227
+ });
1228
+
1229
+ test("non-gzip data (random bytes) produces INVALID_GZIP", () => {
1230
+ const randomBytes = new Uint8Array(256);
1231
+ for (let i = 0; i < randomBytes.length; i++) {
1232
+ randomBytes[i] = Math.floor(Math.random() * 256);
1233
+ }
1234
+
1235
+ const result = validateVBundle(randomBytes);
1236
+
1237
+ expect(result.is_valid).toBe(false);
1238
+ expect(result.errors).toHaveLength(1);
1239
+ expect(result.errors[0].code).toBe("INVALID_GZIP");
1240
+ expect(result.errors[0].message).toContain("gzip");
1241
+ });
1242
+
1243
+ test("truncated gzip data produces INVALID_GZIP", () => {
1244
+ // Create a valid gzip and truncate it
1245
+ const validGzip = gzipSync(new Uint8Array([1, 2, 3, 4, 5]));
1246
+ const truncated = validGzip.subarray(0, Math.floor(validGzip.length / 2));
1247
+
1248
+ const result = validateVBundle(truncated);
1249
+
1250
+ expect(result.is_valid).toBe(false);
1251
+ expect(result.errors[0].code).toBe("INVALID_GZIP");
1252
+ });
1253
+
1254
+ test("single byte input produces INVALID_GZIP", () => {
1255
+ const result = validateVBundle(new Uint8Array([0x00]));
1256
+
1257
+ expect(result.is_valid).toBe(false);
1258
+ expect(result.errors[0].code).toBe("INVALID_GZIP");
1259
+ });
1260
+
1261
+ test("bundle with extra undeclared files in archive still validates (extra files are ignored)", () => {
1262
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1263
+ const extraData = new TextEncoder().encode("bonus content");
1264
+
1265
+ // Manifest only declares the db file
1266
+ const fileEntries = [
1267
+ {
1268
+ path: "data/db/assistant.db",
1269
+ sha256: sha256Hex(dbData),
1270
+ size: dbData.length,
1271
+ },
1272
+ ];
1273
+ const manifestWithoutChecksum = {
1274
+ schema_version: "1.0",
1275
+ created_at: new Date().toISOString(),
1276
+ source: "test",
1277
+ files: fileEntries,
1278
+ };
1279
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1280
+ const manifest = {
1281
+ ...manifestWithoutChecksum,
1282
+ manifest_sha256: manifestSha256,
1283
+ };
1284
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1285
+
1286
+ // Archive has an extra file not in the manifest
1287
+ const tar = createTarArchive([
1288
+ { name: "manifest.json", data: manifestData },
1289
+ { name: "data/db/assistant.db", data: dbData },
1290
+ { name: "bonus/extra.txt", data: extraData },
1291
+ ]);
1292
+ const vbundle = gzipSync(tar);
1293
+
1294
+ const result = validateVBundle(vbundle);
1295
+ // Extra files in the archive (not declared in manifest) are not
1296
+ // an error — the validator only checks declared files.
1297
+ expect(result.is_valid).toBe(true);
1298
+ });
1299
+ });
1300
+
1301
+ // ═══════════════════════════════════════════════════════════════════════════
1302
+ // 6. Diagnostic quality — every error has a code and descriptive message
1303
+ // ═══════════════════════════════════════════════════════════════════════════
1304
+
1305
+ describe("diagnostic quality", () => {
1306
+ test("INVALID_GZIP error message mentions gzip", () => {
1307
+ const result = validateVBundle(new Uint8Array([0xff, 0xfe]));
1308
+ const err = result.errors.find((e) => e.code === "INVALID_GZIP");
1309
+ expect(err).toBeDefined();
1310
+ expect(err!.message.toLowerCase()).toContain("gzip");
1311
+ });
1312
+
1313
+ test("MISSING_ENTRY error identifies which entry is missing", () => {
1314
+ const tar = createTarArchive([
1315
+ { name: "something-else.txt", data: new TextEncoder().encode("hi") },
1316
+ ]);
1317
+ const vbundle = gzipSync(tar);
1318
+ const result = validateVBundle(vbundle);
1319
+
1320
+ const manifestMissing = result.errors.find(
1321
+ (e) => e.code === "MISSING_ENTRY" && e.path === "manifest.json",
1322
+ );
1323
+ expect(manifestMissing).toBeDefined();
1324
+ expect(manifestMissing!.message).toContain("manifest.json");
1325
+ });
1326
+
1327
+ test("MANIFEST_CHECKSUM_MISMATCH includes expected and computed hashes", () => {
1328
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1329
+ const badHash =
1330
+ "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
1331
+ const manifest = {
1332
+ schema_version: "1.0",
1333
+ created_at: new Date().toISOString(),
1334
+ files: [
1335
+ {
1336
+ path: "data/db/assistant.db",
1337
+ sha256: sha256Hex(dbData),
1338
+ size: dbData.length,
1339
+ },
1340
+ ],
1341
+ manifest_sha256: badHash,
1342
+ };
1343
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1344
+ const tar = createTarArchive([
1345
+ { name: "manifest.json", data: manifestData },
1346
+ { name: "data/db/assistant.db", data: dbData },
1347
+ ]);
1348
+ const vbundle = gzipSync(tar);
1349
+ const result = validateVBundle(vbundle);
1350
+
1351
+ const err = result.errors.find(
1352
+ (e) => e.code === "MANIFEST_CHECKSUM_MISMATCH",
1353
+ );
1354
+ expect(err).toBeDefined();
1355
+ expect(err!.message).toContain(badHash); // expected hash
1356
+ expect(err!.message).toContain("computed"); // shows computed hash
1357
+ });
1358
+
1359
+ test("FILE_CHECKSUM_MISMATCH includes file path and both hashes", () => {
1360
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1361
+ const wrongSha =
1362
+ "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
1363
+
1364
+ const manifestWithoutChecksum = {
1365
+ schema_version: "1.0",
1366
+ created_at: new Date().toISOString(),
1367
+ files: [
1368
+ {
1369
+ path: "data/db/assistant.db",
1370
+ sha256: wrongSha,
1371
+ size: dbData.length,
1372
+ },
1373
+ ],
1374
+ };
1375
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1376
+ const manifest = {
1377
+ ...manifestWithoutChecksum,
1378
+ manifest_sha256: manifestSha256,
1379
+ };
1380
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1381
+ const tar = createTarArchive([
1382
+ { name: "manifest.json", data: manifestData },
1383
+ { name: "data/db/assistant.db", data: dbData },
1384
+ ]);
1385
+ const vbundle = gzipSync(tar);
1386
+ const result = validateVBundle(vbundle);
1387
+
1388
+ const err = result.errors.find((e) => e.code === "FILE_CHECKSUM_MISMATCH");
1389
+ expect(err).toBeDefined();
1390
+ expect(err!.path).toBe("data/db/assistant.db");
1391
+ expect(err!.message).toContain(wrongSha);
1392
+ expect(err!.message).toContain("data/db/assistant.db");
1393
+ });
1394
+
1395
+ test("FILE_SIZE_MISMATCH includes file path and both sizes", () => {
1396
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1397
+
1398
+ const manifestWithoutChecksum = {
1399
+ schema_version: "1.0",
1400
+ created_at: new Date().toISOString(),
1401
+ files: [
1402
+ {
1403
+ path: "data/db/assistant.db",
1404
+ sha256: sha256Hex(dbData),
1405
+ size: 99999,
1406
+ },
1407
+ ],
1408
+ };
1409
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1410
+ const manifest = {
1411
+ ...manifestWithoutChecksum,
1412
+ manifest_sha256: manifestSha256,
1413
+ };
1414
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1415
+ const tar = createTarArchive([
1416
+ { name: "manifest.json", data: manifestData },
1417
+ { name: "data/db/assistant.db", data: dbData },
1418
+ ]);
1419
+ const vbundle = gzipSync(tar);
1420
+ const result = validateVBundle(vbundle);
1421
+
1422
+ const err = result.errors.find((e) => e.code === "FILE_SIZE_MISMATCH");
1423
+ expect(err).toBeDefined();
1424
+ expect(err!.path).toBe("data/db/assistant.db");
1425
+ expect(err!.message).toContain("99999");
1426
+ expect(err!.message).toContain(String(dbData.length));
1427
+ });
1428
+
1429
+ test("import commit validation_failed response includes error codes and messages", () => {
1430
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1431
+ const result = commitImport({
1432
+ archiveData: new Uint8Array([0x00]),
1433
+ pathResolver: resolver,
1434
+ });
1435
+
1436
+ expect(result.ok).toBe(false);
1437
+ if (!result.ok && result.reason === "validation_failed") {
1438
+ for (const err of result.errors) {
1439
+ expect(typeof err.code).toBe("string");
1440
+ expect(err.code.length).toBeGreaterThan(0);
1441
+ expect(typeof err.message).toBe("string");
1442
+ expect(err.message.length).toBeGreaterThan(0);
1443
+ }
1444
+ }
1445
+ });
1446
+
1447
+ test("preflight validation_failed response includes structured errors", async () => {
1448
+ const req = new Request("http://localhost/v1/migrations/import-preflight", {
1449
+ method: "POST",
1450
+ headers: { "Content-Type": "application/octet-stream" },
1451
+ body: toArrayBuffer(new Uint8Array([0x00])),
1452
+ });
1453
+
1454
+ const res = await handleMigrationImportPreflight(req);
1455
+ const body = (await res.json()) as ImportDryRunResponse;
1456
+
1457
+ expect(body.can_import).toBe(false);
1458
+ expect(body.validation).toBeDefined();
1459
+ expect(body.validation!.errors.length).toBeGreaterThan(0);
1460
+
1461
+ for (const err of body.validation!.errors) {
1462
+ expect(typeof err.code).toBe("string");
1463
+ expect(err.code.length).toBeGreaterThan(0);
1464
+ expect(typeof err.message).toBe("string");
1465
+ expect(err.message.length).toBeGreaterThan(0);
1466
+ }
1467
+ });
1468
+ });
1469
+
1470
+ // ═══════════════════════════════════════════════════════════════════════════
1471
+ // 7. Multiple errors accumulation
1472
+ // ═══════════════════════════════════════════════════════════════════════════
1473
+
1474
+ describe("multiple error accumulation", () => {
1475
+ test("bundle with multiple file-level errors reports all of them", () => {
1476
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1477
+ const configData = new TextEncoder().encode('{"key":"value"}');
1478
+ const wrongSha =
1479
+ "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
1480
+
1481
+ // Manifest declares correct db checksum but wrong config checksum and
1482
+ // also wrong config size
1483
+ const manifestWithoutChecksum = {
1484
+ schema_version: "1.0",
1485
+ created_at: new Date().toISOString(),
1486
+ files: [
1487
+ {
1488
+ path: "data/db/assistant.db",
1489
+ sha256: sha256Hex(dbData),
1490
+ size: dbData.length,
1491
+ },
1492
+ {
1493
+ path: "config/settings.json",
1494
+ sha256: wrongSha,
1495
+ size: 999,
1496
+ },
1497
+ ],
1498
+ };
1499
+ const manifestSha256 = sha256Hex(canonicalizeJson(manifestWithoutChecksum));
1500
+ const manifest = {
1501
+ ...manifestWithoutChecksum,
1502
+ manifest_sha256: manifestSha256,
1503
+ };
1504
+ const manifestData = new TextEncoder().encode(JSON.stringify(manifest));
1505
+
1506
+ const tar = createTarArchive([
1507
+ { name: "manifest.json", data: manifestData },
1508
+ { name: "data/db/assistant.db", data: dbData },
1509
+ { name: "config/settings.json", data: configData },
1510
+ ]);
1511
+ const vbundle = gzipSync(tar);
1512
+ const result = validateVBundle(vbundle);
1513
+
1514
+ expect(result.is_valid).toBe(false);
1515
+
1516
+ // Should have both a checksum mismatch and a size mismatch for config
1517
+ const checksumError = result.errors.find(
1518
+ (e) =>
1519
+ e.code === "FILE_CHECKSUM_MISMATCH" &&
1520
+ e.path === "config/settings.json",
1521
+ );
1522
+ const sizeError = result.errors.find(
1523
+ (e) =>
1524
+ e.code === "FILE_SIZE_MISMATCH" && e.path === "config/settings.json",
1525
+ );
1526
+
1527
+ expect(checksumError).toBeDefined();
1528
+ expect(sizeError).toBeDefined();
1529
+ // At least 2 errors for the config file
1530
+ expect(result.errors.length).toBeGreaterThanOrEqual(2);
1531
+ });
1532
+
1533
+ test("manifest with multiple missing required fields reports all issues", () => {
1534
+ const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
1535
+ // Completely empty manifest (has none of the required fields)
1536
+ const manifestObj = {};
1537
+
1538
+ const vbundle = createVBundleWithRawManifest(manifestObj, [
1539
+ { name: "data/db/assistant.db", data: dbData },
1540
+ ]);
1541
+ const result = validateVBundle(vbundle);
1542
+
1543
+ expect(result.is_valid).toBe(false);
1544
+ // Should report multiple schema errors for missing fields
1545
+ const schemaErrors = result.errors.filter(
1546
+ (e) => e.code === "MANIFEST_SCHEMA_ERROR",
1547
+ );
1548
+ expect(schemaErrors.length).toBeGreaterThanOrEqual(3);
1549
+ });
1550
+ });
1551
+
1552
+ // ═══════════════════════════════════════════════════════════════════════════
1553
+ // 8. Builder -> validator consistency
1554
+ // ═══════════════════════════════════════════════════════════════════════════
1555
+
1556
+ describe("builder -> validator consistency", () => {
1557
+ test("buildVBundle always produces archives that pass validation", () => {
1558
+ const testCases = [
1559
+ {
1560
+ files: [
1561
+ {
1562
+ path: "data/db/assistant.db",
1563
+ data: new Uint8Array([0x01]),
1564
+ },
1565
+ ],
1566
+ schemaVersion: "1.0",
1567
+ },
1568
+ {
1569
+ files: [
1570
+ {
1571
+ path: "data/db/assistant.db",
1572
+ data: new Uint8Array(10000), // 10KB of zeroes
1573
+ },
1574
+ {
1575
+ path: "config/settings.json",
1576
+ data: new TextEncoder().encode('{"big":"config","keys":[1,2,3]}'),
1577
+ },
1578
+ ],
1579
+ schemaVersion: "2.0",
1580
+ },
1581
+ {
1582
+ files: [
1583
+ {
1584
+ path: "data/db/assistant.db",
1585
+ data: new Uint8Array(0), // empty
1586
+ },
1587
+ ],
1588
+ schemaVersion: "0.1-alpha",
1589
+ },
1590
+ ];
1591
+
1592
+ for (const tc of testCases) {
1593
+ const { archive } = buildVBundle({
1594
+ files: tc.files,
1595
+ schemaVersion: tc.schemaVersion,
1596
+ });
1597
+
1598
+ const result = validateVBundle(archive);
1599
+ expect(result.is_valid).toBe(true);
1600
+ expect(result.manifest?.schema_version).toBe(tc.schemaVersion);
1601
+ }
1602
+ });
1603
+
1604
+ test("builder output manifest_sha256 matches validator computation", () => {
1605
+ const { archive, manifest } = buildVBundle({
1606
+ files: [
1607
+ {
1608
+ path: "data/db/assistant.db",
1609
+ data: new Uint8Array([0xca, 0xfe]),
1610
+ },
1611
+ ],
1612
+ schemaVersion: "1.0",
1613
+ });
1614
+
1615
+ const result = validateVBundle(archive);
1616
+ expect(result.is_valid).toBe(true);
1617
+ expect(result.manifest?.manifest_sha256).toBe(manifest.manifest_sha256);
1618
+ });
1619
+
1620
+ test("builder output file checksums match validator computation", () => {
1621
+ const fileData = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
1622
+ const { archive, manifest } = buildVBundle({
1623
+ files: [{ path: "data/db/assistant.db", data: fileData }],
1624
+ });
1625
+
1626
+ const result = validateVBundle(archive);
1627
+ expect(result.is_valid).toBe(true);
1628
+ expect(result.manifest?.files[0].sha256).toBe(manifest.files[0].sha256);
1629
+ expect(result.manifest?.files[0].sha256).toBe(sha256Hex(fileData));
1630
+ });
1631
+ });
1632
+
1633
+ // ═══════════════════════════════════════════════════════════════════════════
1634
+ // 9. Import analyzer edge cases
1635
+ // ═══════════════════════════════════════════════════════════════════════════
1636
+
1637
+ describe("import analyzer edge cases", () => {
1638
+ test("all-unchanged files produce correct summary", () => {
1639
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1640
+ const existingConfig = new Uint8Array(readFileSync(testConfigPath));
1641
+
1642
+ const report = analyzeImport({
1643
+ manifest: {
1644
+ schema_version: "1.0",
1645
+ created_at: new Date().toISOString(),
1646
+ files: [
1647
+ {
1648
+ path: "data/db/assistant.db",
1649
+ sha256: sha256Hex(EXISTING_DB_DATA),
1650
+ size: EXISTING_DB_DATA.length,
1651
+ },
1652
+ {
1653
+ path: "config/settings.json",
1654
+ sha256: sha256Hex(existingConfig),
1655
+ size: existingConfig.length,
1656
+ },
1657
+ ],
1658
+ manifest_sha256: "test",
1659
+ },
1660
+ pathResolver: resolver,
1661
+ });
1662
+
1663
+ expect(report.can_import).toBe(true);
1664
+ expect(report.summary.files_unchanged).toBe(2);
1665
+ expect(report.summary.files_to_create).toBe(0);
1666
+ expect(report.summary.files_to_overwrite).toBe(0);
1667
+ expect(report.conflicts).toHaveLength(0);
1668
+ });
1669
+
1670
+ test("all-create scenario (fresh install) produces correct summary", () => {
1671
+ const resolver = new DefaultPathResolver(
1672
+ join(testDir, "nonexistent-dir", "db.db"),
1673
+ join(testDir, "nonexistent-dir", "config.json"),
1674
+ );
1675
+
1676
+ const report = analyzeImport({
1677
+ manifest: {
1678
+ schema_version: "1.0",
1679
+ created_at: new Date().toISOString(),
1680
+ files: [
1681
+ {
1682
+ path: "data/db/assistant.db",
1683
+ sha256: sha256Hex(new Uint8Array([1])),
1684
+ size: 1,
1685
+ },
1686
+ {
1687
+ path: "config/settings.json",
1688
+ sha256: sha256Hex(new Uint8Array([2])),
1689
+ size: 1,
1690
+ },
1691
+ ],
1692
+ manifest_sha256: "test",
1693
+ },
1694
+ pathResolver: resolver,
1695
+ });
1696
+
1697
+ expect(report.can_import).toBe(true);
1698
+ expect(report.summary.files_to_create).toBe(2);
1699
+ expect(report.summary.files_unchanged).toBe(0);
1700
+ expect(report.summary.files_to_overwrite).toBe(0);
1701
+ });
1702
+
1703
+ test("mixed create/overwrite/unchanged produces accurate counts", () => {
1704
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1705
+
1706
+ // db: different from disk -> overwrite
1707
+ // config: same as disk -> unchanged
1708
+ const existingConfig = new Uint8Array(readFileSync(testConfigPath));
1709
+
1710
+ const report = analyzeImport({
1711
+ manifest: {
1712
+ schema_version: "1.0",
1713
+ created_at: new Date().toISOString(),
1714
+ files: [
1715
+ {
1716
+ path: "data/db/assistant.db",
1717
+ sha256: sha256Hex(new Uint8Array([0xff])),
1718
+ size: 1,
1719
+ },
1720
+ {
1721
+ path: "config/settings.json",
1722
+ sha256: sha256Hex(existingConfig),
1723
+ size: existingConfig.length,
1724
+ },
1725
+ ],
1726
+ manifest_sha256: "test",
1727
+ },
1728
+ pathResolver: resolver,
1729
+ });
1730
+
1731
+ expect(report.summary.files_to_overwrite).toBe(1);
1732
+ expect(report.summary.files_unchanged).toBe(1);
1733
+ expect(report.summary.files_to_create).toBe(0);
1734
+ });
1735
+
1736
+ test("unknown archive path produces UNKNOWN_ARCHIVE_PATH conflict", () => {
1737
+ const resolver = new DefaultPathResolver(testDbPath, testConfigPath);
1738
+
1739
+ const report = analyzeImport({
1740
+ manifest: {
1741
+ schema_version: "1.0",
1742
+ created_at: new Date().toISOString(),
1743
+ files: [
1744
+ {
1745
+ path: "data/db/assistant.db",
1746
+ sha256: sha256Hex(EXISTING_DB_DATA),
1747
+ size: EXISTING_DB_DATA.length,
1748
+ },
1749
+ {
1750
+ path: "future/unknown-file.dat",
1751
+ sha256: sha256Hex(new Uint8Array([0])),
1752
+ size: 1,
1753
+ },
1754
+ ],
1755
+ manifest_sha256: "test",
1756
+ },
1757
+ pathResolver: resolver,
1758
+ });
1759
+
1760
+ expect(report.conflicts.length).toBe(1);
1761
+ expect(report.conflicts[0].code).toBe("UNKNOWN_ARCHIVE_PATH");
1762
+ expect(report.conflicts[0].message).toContain("future/unknown-file.dat");
1763
+ });
1764
+ });
1765
+
1766
+ // ═══════════════════════════════════════════════════════════════════════════
1767
+ // 10. HTTP endpoint error consistency
1768
+ // ═══════════════════════════════════════════════════════════════════════════
1769
+
1770
+ describe("HTTP endpoint error consistency", () => {
1771
+ test("validate and import agree on invalid bundle diagnosis", async () => {
1772
+ const invalidData = new Uint8Array([0xba, 0xad, 0xca, 0xfe]);
1773
+
1774
+ const validateReq = new Request("http://localhost/v1/migrations/validate", {
1775
+ method: "POST",
1776
+ headers: { "Content-Type": "application/octet-stream" },
1777
+ body: toArrayBuffer(invalidData),
1778
+ });
1779
+ const validateRes = await handleMigrationValidate(validateReq);
1780
+ const validateBody = (await validateRes.json()) as ValidationResponse;
1781
+
1782
+ const importReq = new Request("http://localhost/v1/migrations/import", {
1783
+ method: "POST",
1784
+ headers: { "Content-Type": "application/octet-stream" },
1785
+ body: toArrayBuffer(invalidData),
1786
+ });
1787
+ const importRes = await handleMigrationImport(importReq);
1788
+ const importBody = (await importRes.json()) as ImportCommitResponse;
1789
+
1790
+ // Both should identify the same error type
1791
+ expect(validateBody.is_valid).toBe(false);
1792
+ expect(importBody.success).toBe(false);
1793
+ expect(validateBody.errors[0].code).toBe(importBody.errors![0].code);
1794
+ });
1795
+
1796
+ test("validate and preflight agree on invalid bundle diagnosis", async () => {
1797
+ const invalidData = new Uint8Array([0xba, 0xad, 0xca, 0xfe]);
1798
+
1799
+ const validateReq = new Request("http://localhost/v1/migrations/validate", {
1800
+ method: "POST",
1801
+ headers: { "Content-Type": "application/octet-stream" },
1802
+ body: toArrayBuffer(invalidData),
1803
+ });
1804
+ const validateRes = await handleMigrationValidate(validateReq);
1805
+ const validateBody = (await validateRes.json()) as ValidationResponse;
1806
+
1807
+ const preflightReq = new Request(
1808
+ "http://localhost/v1/migrations/import-preflight",
1809
+ {
1810
+ method: "POST",
1811
+ headers: { "Content-Type": "application/octet-stream" },
1812
+ body: toArrayBuffer(invalidData),
1813
+ },
1814
+ );
1815
+ const preflightRes = await handleMigrationImportPreflight(preflightReq);
1816
+ const preflightBody = (await preflightRes.json()) as ImportDryRunResponse;
1817
+
1818
+ expect(validateBody.is_valid).toBe(false);
1819
+ expect(preflightBody.can_import).toBe(false);
1820
+ expect(validateBody.errors[0].code).toBe(
1821
+ preflightBody.validation!.errors[0].code,
1822
+ );
1823
+ });
1824
+
1825
+ test("all migration endpoints return 400 for empty body", async () => {
1826
+ const endpoints = [
1827
+ "http://localhost/v1/migrations/validate",
1828
+ "http://localhost/v1/migrations/import-preflight",
1829
+ "http://localhost/v1/migrations/import",
1830
+ ];
1831
+ const handlers = [
1832
+ handleMigrationValidate,
1833
+ handleMigrationImportPreflight,
1834
+ handleMigrationImport,
1835
+ ];
1836
+
1837
+ for (let i = 0; i < endpoints.length; i++) {
1838
+ const req = new Request(endpoints[i], {
1839
+ method: "POST",
1840
+ headers: { "Content-Type": "application/octet-stream" },
1841
+ body: toArrayBuffer(new Uint8Array(0)),
1842
+ });
1843
+
1844
+ const res = await handlers[i](req);
1845
+ expect(res.status).toBe(400);
1846
+
1847
+ const body = (await res.json()) as {
1848
+ error: { code: string; message: string };
1849
+ };
1850
+ expect(body.error.code).toBe("BAD_REQUEST");
1851
+ expect(body.error.message).toBeDefined();
1852
+ expect(body.error.message.length).toBeGreaterThan(0);
1853
+ }
1854
+ });
1855
+ });