@vellumai/assistant 0.10.12 → 0.11.0-staging.2

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 (809) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +4 -5
  3. package/README.md +1 -1
  4. package/docs/architecture/memory.md +60 -23
  5. package/docs/error-handling.md +3 -3
  6. package/docs/guardian-request-flow.md +112 -0
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/app.d.ts +28 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/app.d.ts.map +1 -1
  14. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.d.ts +2 -2
  15. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.d.ts.map +1 -1
  16. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.js.map +1 -1
  17. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/message.d.ts +2 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/message.d.ts.map +1 -1
  19. package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/package.json +1 -1
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
  25. package/node_modules/@vellumai/gateway-client/package.json +1 -1
  26. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +6 -0
  27. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +34 -0
  28. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  29. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
  30. package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
  31. package/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
  32. package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
  33. package/openapi.yaml +264 -14
  34. package/package.json +2 -2
  35. package/src/__tests__/acp-session.test.ts +3 -3
  36. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +3 -0
  37. package/src/__tests__/anthropic-provider.test.ts +40 -0
  38. package/src/__tests__/app-conversation-ids-backfill.test.ts +33 -2
  39. package/src/__tests__/app-conversation-ids.test.ts +204 -0
  40. package/src/__tests__/approval-cascade.test.ts +5 -5
  41. package/src/__tests__/approval-routes-http.test.ts +3 -3
  42. package/src/__tests__/ask-question-resolver.test.ts +207 -0
  43. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +16 -16
  44. package/src/__tests__/assistant-event-hub-targeted.test.ts +13 -11
  45. package/src/__tests__/assistant-event-hub.test.ts +13 -11
  46. package/src/__tests__/assistant-event.test.ts +12 -9
  47. package/src/__tests__/assistant-feature-flag-guard.test.ts +3 -3
  48. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +1 -1
  49. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -58
  50. package/src/__tests__/assistant-stream-state.test.ts +17 -7
  51. package/src/__tests__/avatar-identity-sync.test.ts +3 -3
  52. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
  53. package/src/__tests__/call-conversation-messages.test.ts +134 -18
  54. package/src/__tests__/call-start-guardian-guard.test.ts +27 -0
  55. package/src/__tests__/channel-approvals.test.ts +0 -27
  56. package/src/__tests__/channel-guardian.test.ts +85 -512
  57. package/src/__tests__/channel-readiness-slack-remote.test.ts +151 -3
  58. package/src/__tests__/channel-retry-sweep.test.ts +88 -1
  59. package/src/__tests__/channel-setup-panel-ack.test.ts +12 -9
  60. package/src/__tests__/chat-reveal-guard-priming.test.ts +31 -34
  61. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +2 -2
  62. package/src/__tests__/compaction-events.test.ts +8 -8
  63. package/src/__tests__/config-loader-platform-defaults.test.ts +54 -28
  64. package/src/__tests__/config-model-image-provider.test.ts +20 -0
  65. package/src/__tests__/config-schema-memory-substrate.test.ts +306 -0
  66. package/src/__tests__/config-schema.test.ts +14 -2
  67. package/src/__tests__/config-set-platform-guard.test.ts +11 -12
  68. package/src/__tests__/config-sounds-sync.test.ts +4 -4
  69. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +5 -0
  70. package/src/__tests__/context-search-memory-source.test.ts +1 -1
  71. package/src/__tests__/context-search-memory-v2-source.test.ts +6 -6
  72. package/src/__tests__/context-token-estimator.test.ts +54 -1
  73. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +12 -3
  74. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +13 -4
  75. package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +4 -4
  76. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -1
  77. package/src/__tests__/conversation-agent-loop-overflow.test.ts +22 -13
  78. package/src/__tests__/conversation-agent-loop.test.ts +39 -30
  79. package/src/__tests__/conversation-confirmation-signals.test.ts +14 -14
  80. package/src/__tests__/conversation-evictor.test.ts +49 -0
  81. package/src/__tests__/conversation-fork-crud.test.ts +1 -1
  82. package/src/__tests__/conversation-group-tools.test.ts +279 -0
  83. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  84. package/src/__tests__/conversation-key-store-disk-view.test.ts +5 -5
  85. package/src/__tests__/conversation-lineage.test.ts +235 -0
  86. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +5 -5
  87. package/src/__tests__/conversation-message-sync-tags.test.ts +4 -4
  88. package/src/__tests__/conversation-queue.test.ts +81 -81
  89. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +8 -1
  90. package/src/__tests__/conversation-routes-hidden-queue.test.ts +8 -1
  91. package/src/__tests__/conversation-routes-slash-commands.test.ts +10 -1
  92. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -2
  93. package/src/__tests__/conversation-slash-queue.test.ts +11 -11
  94. package/src/__tests__/conversation-slash-unknown.test.ts +2 -2
  95. package/src/__tests__/conversation-speed-override.test.ts +2 -2
  96. package/src/__tests__/conversation-summarize-route.test.ts +9 -0
  97. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +18 -16
  98. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +17 -17
  99. package/src/__tests__/conversation-surfaces-app-open.test.ts +7 -4
  100. package/src/__tests__/conversation-surfaces-data-persist.test.ts +7 -7
  101. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +9 -9
  102. package/src/__tests__/conversation-surfaces-standalone.test.ts +12 -12
  103. package/src/__tests__/conversation-surfaces-state-update.test.ts +8 -5
  104. package/src/__tests__/conversation-surfaces-table-action.test.ts +6 -3
  105. package/src/__tests__/conversation-surfaces-task-progress.test.ts +46 -46
  106. package/src/__tests__/conversation-sync-tags.test.ts +5 -5
  107. package/src/__tests__/conversation-wait-for-idle.test.ts +2 -2
  108. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  109. package/src/__tests__/conversation-workspace-injection.test.ts +0 -3
  110. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -3
  111. package/src/__tests__/copy-composer-tc-templates.test.ts +3 -131
  112. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  113. package/src/__tests__/daemon-assistant-events.test.ts +14 -15
  114. package/src/__tests__/db-test-helpers.ts +13 -27
  115. package/src/__tests__/disk-pressure-routes.test.ts +12 -3
  116. package/src/__tests__/document-tool-security.test.ts +95 -0
  117. package/src/__tests__/drain-kick-guard.test.ts +232 -0
  118. package/src/__tests__/dynamic-skill-background-guard.test.ts +19 -6
  119. package/src/__tests__/emit-event-signal.test.ts +5 -6
  120. package/src/__tests__/encrypted-store-test-helpers.ts +10 -20
  121. package/src/__tests__/events-tail-route.test.ts +6 -4
  122. package/src/__tests__/feature-flag-test-helpers.ts +10 -20
  123. package/src/__tests__/file-edit-tool.test.ts +24 -4
  124. package/src/__tests__/file-list-tool.test.ts +2 -1
  125. package/src/__tests__/file-read-tool.test.ts +62 -10
  126. package/src/__tests__/file-write-tool.test.ts +24 -5
  127. package/src/__tests__/followup-tools.test.ts +58 -0
  128. package/src/__tests__/gateway-threshold-reader-mock.ts +99 -0
  129. package/src/__tests__/graph-extraction-event-date.test.ts +1 -1
  130. package/src/__tests__/guardian-question-mode.test.ts +42 -3
  131. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +283 -0
  132. package/src/__tests__/guardian-vellum-destination-pinning.test.ts +221 -0
  133. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  134. package/src/__tests__/heartbeat-service.test.ts +1 -0
  135. package/src/__tests__/helpers/native-web-search-harness.ts +10 -10
  136. package/src/__tests__/host-browser-proxy.test.ts +1 -1
  137. package/src/__tests__/host-file-edit-tool.test.ts +3 -3
  138. package/src/__tests__/host-file-read-tool.test.ts +2 -2
  139. package/src/__tests__/host-file-write-tool.test.ts +7 -3
  140. package/src/__tests__/host-shell-tool.test.ts +17 -5
  141. package/src/__tests__/host-transfer-pending-interactions.test.ts +8 -8
  142. package/src/__tests__/image-credentials.test.ts +65 -8
  143. package/src/__tests__/injection-block.test.ts +4 -4
  144. package/src/__tests__/injector-chain.test.ts +1 -1
  145. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  146. package/src/__tests__/injector-registry-order-guard.test.ts +4 -9
  147. package/src/__tests__/injector-v3-suppression.test.ts +6 -4
  148. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +27 -28
  149. package/src/__tests__/list-messages-page-latest.test.ts +2 -2
  150. package/src/__tests__/llm-usage-store.test.ts +67 -4
  151. package/src/__tests__/log-redact.test.ts +18 -0
  152. package/src/__tests__/managed-profile-guard.test.ts +27 -0
  153. package/src/__tests__/managed-skill-lifecycle.test.ts +2 -2
  154. package/src/__tests__/media-generate-image.test.ts +62 -11
  155. package/src/__tests__/memory-identity-context-parity.test.ts +1 -1
  156. package/src/__tests__/memory-jobs-worker-lanes.test.ts +9 -1
  157. package/src/__tests__/memory-retrieval-hook.test.ts +4 -4
  158. package/src/__tests__/notification-decision-strategy.test.ts +115 -11
  159. package/src/__tests__/notification-deep-link.test.ts +12 -12
  160. package/src/__tests__/notification-vellum-adapter.test.ts +8 -8
  161. package/src/__tests__/outbound-slack-persistence.test.ts +8 -8
  162. package/src/__tests__/path-policy.test.ts +315 -0
  163. package/src/__tests__/pending-interactions-resolved-event.test.ts +6 -6
  164. package/src/__tests__/persistence-layering-guard.test.ts +2 -2
  165. package/src/__tests__/pkb-autoinject.test.ts +1 -1
  166. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -0
  167. package/src/__tests__/platform-callback-registration.test.ts +10 -10
  168. package/src/__tests__/plugin-event-hub-facade.test.ts +22 -15
  169. package/src/__tests__/plugin-import-boundary-guard.test.ts +15 -15
  170. package/src/__tests__/processing-flag-persist-failure.test.ts +0 -4
  171. package/src/__tests__/provider-platform-proxy-integration.test.ts +0 -1
  172. package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
  173. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +5 -5
  174. package/src/__tests__/require-fresh-approval.test.ts +38 -38
  175. package/src/__tests__/resolve-app-id.test.ts +32 -0
  176. package/src/__tests__/run-due-schedules.test.ts +72 -1
  177. package/src/__tests__/runtime-events-sse-parity.test.ts +11 -10
  178. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  179. package/src/__tests__/schedule-routes.test.ts +44 -5
  180. package/src/__tests__/schedule-store.test.ts +3 -3
  181. package/src/__tests__/schedule-tools.test.ts +176 -0
  182. package/src/__tests__/secret-prompt-log-hygiene.test.ts +3 -3
  183. package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -3
  184. package/src/__tests__/secret-response-routing.test.ts +3 -3
  185. package/src/__tests__/secret-routes-platform-proxy.test.ts +4 -7
  186. package/src/__tests__/secret-scanner.test.ts +36 -0
  187. package/src/__tests__/send-endpoint-busy.test.ts +7 -7
  188. package/src/__tests__/slack-required-scopes-mirror.test.ts +81 -0
  189. package/src/__tests__/starter-task-flow.test.ts +26 -3
  190. package/src/__tests__/subagent-call-site-routing.test.ts +2 -2
  191. package/src/__tests__/subagent-disposal.test.ts +40 -11
  192. package/src/__tests__/subagent-fork-notifications.test.ts +4 -4
  193. package/src/__tests__/subagent-fork-prompt-role.test.ts +3 -3
  194. package/src/__tests__/subagent-fork-spawn.test.ts +4 -4
  195. package/src/__tests__/subagent-manager-notify.test.ts +73 -14
  196. package/src/__tests__/subagent-notify-parent.test.ts +48 -4
  197. package/src/__tests__/subagent-spawn-and-await.test.ts +62 -14
  198. package/src/__tests__/subagent-tools.test.ts +64 -0
  199. package/src/__tests__/sync-message-contract.test.ts +3 -3
  200. package/src/__tests__/terminal-tools.test.ts +12 -3
  201. package/src/__tests__/test-preload.ts +9 -0
  202. package/src/__tests__/tool-approval-handler.test.ts +587 -43
  203. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +7 -4
  204. package/src/__tests__/tool-executor.test.ts +116 -0
  205. package/src/__tests__/tool-grant-request-escalation.test.ts +5 -0
  206. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -29
  207. package/src/__tests__/tool-result-metadata-plumbing.test.ts +5 -5
  208. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  209. package/src/__tests__/tool-start-timestamp.test.ts +4 -4
  210. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -22
  211. package/src/__tests__/ui-choice-copy-surfaces.test.ts +13 -13
  212. package/src/__tests__/ui-work-result-surface.test.ts +3 -3
  213. package/src/__tests__/voice-scoped-grant-consumer.test.ts +6 -6
  214. package/src/__tests__/voice-session-bridge.test.ts +44 -44
  215. package/src/__tests__/workspace-migration-134-image-generation-mode-to-provider.test.ts +284 -0
  216. package/src/__tests__/workspace-policy.test.ts +482 -2
  217. package/src/acp/__tests__/client-handler.test.ts +3 -3
  218. package/src/acp/__tests__/session-manager-persistence.test.ts +10 -10
  219. package/src/acp/__tests__/session-manager-resume.test.ts +7 -7
  220. package/src/acp/client-handler.ts +3 -3
  221. package/src/acp/session-manager.ts +8 -8
  222. package/src/api/events/subagent-event.ts +2 -2
  223. package/src/api/responses/memory-v2-activation-log.ts +2 -2
  224. package/src/api/responses/subagent-detail.ts +13 -0
  225. package/src/approvals/AGENTS.md +2 -0
  226. package/src/approvals/guardian-request-resolvers.ts +118 -24
  227. package/src/apps/app-store.ts +95 -4
  228. package/src/calls/__tests__/voice-control-protocol.test.ts +57 -0
  229. package/src/calls/__tests__/voice-session-bridge.test.ts +229 -7
  230. package/src/calls/__tests__/voice-triage-escalate.test.ts +3 -4
  231. package/src/calls/call-controller.ts +27 -27
  232. package/src/calls/call-conversation-messages.ts +58 -18
  233. package/src/calls/voice-control-protocol.ts +56 -0
  234. package/src/calls/voice-session-bridge.ts +178 -65
  235. package/src/calls/voice-triage-escalate.ts +2 -3
  236. package/src/channels/config.ts +8 -0
  237. package/src/channels/types.ts +1 -0
  238. package/src/cli/commands/config.ts +49 -13
  239. package/src/cli/commands/memory/index.help.ts +4 -2
  240. package/src/cli/commands/memory/memory-retrospective.ts +5 -0
  241. package/src/cli/commands/webhooks.help.ts +9 -4
  242. package/src/cli/lib/bundled-marketplace.json +12 -0
  243. package/src/cli.ts +2 -109
  244. package/src/config/__tests__/deployment-context-defaults.test.ts +9 -0
  245. package/src/config/__tests__/substrate-twin-shadowing.test.ts +191 -0
  246. package/src/config/assistant-feature-flags.ts +1 -1
  247. package/src/config/bundled-skills/conversation-groups/SKILL.md +29 -0
  248. package/src/config/bundled-skills/conversation-groups/TOOLS.json +58 -0
  249. package/src/config/bundled-skills/conversation-groups/tools/group-create.ts +12 -0
  250. package/src/config/bundled-skills/conversation-groups/tools/group-list.ts +12 -0
  251. package/src/config/bundled-skills/conversation-groups/tools/move-to-group.ts +12 -0
  252. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +13 -7
  253. package/src/config/bundled-skills/schedule/SKILL.md +2 -0
  254. package/src/config/feature-flag-cache.ts +6 -16
  255. package/src/config/feature-flag-registry.json +14 -38
  256. package/src/config/loader.ts +7 -1
  257. package/src/config/memory-tier.ts +15 -10
  258. package/src/config/memory-v3-gate.test.ts +278 -1
  259. package/src/config/memory-v3-gate.ts +88 -26
  260. package/src/config/raw-config-utils.ts +1 -1
  261. package/src/config/schemas/__tests__/live-voice.test.ts +27 -16
  262. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  263. package/src/config/schemas/live-voice.ts +33 -7
  264. package/src/config/schemas/llm.ts +1 -0
  265. package/src/config/schemas/memory-retrospective.ts +13 -0
  266. package/src/config/schemas/memory-substrate.ts +183 -0
  267. package/src/config/schemas/memory-v2.ts +3 -2
  268. package/src/config/schemas/memory-v3.ts +11 -4
  269. package/src/config/schemas/memory.ts +35 -1
  270. package/src/config/schemas/services.ts +10 -2
  271. package/src/config/substrate-twin-shadowing.ts +195 -0
  272. package/src/config/webhook-routing.ts +67 -0
  273. package/src/context/compactor.ts +10 -3
  274. package/src/context/token-estimator.ts +21 -0
  275. package/src/daemon/__tests__/channel-ui-capability.test.ts +85 -0
  276. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +3 -3
  277. package/src/daemon/__tests__/embedding-reconcile.test.ts +3 -3
  278. package/src/daemon/__tests__/inference-profile-notification.test.ts +3 -6
  279. package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -5
  280. package/src/daemon/__tests__/web-search-status-text.test.ts +2 -2
  281. package/src/daemon/channel-ui-capability.ts +70 -1
  282. package/src/daemon/conversation-agent-loop-handlers.ts +4 -4
  283. package/src/daemon/conversation-agent-loop.ts +12 -5
  284. package/src/daemon/conversation-evictor.ts +26 -4
  285. package/src/daemon/conversation-launch.ts +11 -14
  286. package/src/daemon/conversation-lineage.ts +76 -0
  287. package/src/daemon/conversation-messaging.ts +14 -14
  288. package/src/daemon/conversation-notifiers.ts +2 -2
  289. package/src/daemon/conversation-process.ts +133 -21
  290. package/src/daemon/conversation-queue-manager.ts +7 -7
  291. package/src/daemon/conversation-runtime-assembly.ts +16 -0
  292. package/src/daemon/conversation-store.ts +11 -3
  293. package/src/daemon/conversation-surfaces.ts +8 -14
  294. package/src/daemon/conversation-tool-setup.ts +13 -7
  295. package/src/daemon/conversation-usage.ts +12 -11
  296. package/src/daemon/conversation.ts +38 -15
  297. package/src/daemon/disk-pressure-guard.ts +5 -12
  298. package/src/daemon/embedding-reconcile.ts +14 -9
  299. package/src/daemon/handlers/config-channels.ts +40 -134
  300. package/src/daemon/handlers/config-model.ts +15 -7
  301. package/src/daemon/handlers/conversations.ts +4 -2
  302. package/src/daemon/handlers/shared.ts +29 -0
  303. package/src/daemon/host-proxy-base.ts +4 -4
  304. package/src/daemon/lifecycle.ts +13 -0
  305. package/src/daemon/message-protocol.ts +8 -144
  306. package/src/daemon/message-types/computer-use.ts +0 -12
  307. package/src/daemon/message-types/conversations.ts +9 -345
  308. package/src/daemon/message-types/host-app-control.ts +0 -9
  309. package/src/daemon/message-types/host-browser.ts +0 -7
  310. package/src/daemon/message-types/host-ui-snapshot.ts +0 -7
  311. package/src/daemon/message-types/messages.ts +0 -48
  312. package/src/daemon/message-types/notifications.ts +0 -7
  313. package/src/daemon/message-types/skills.ts +0 -5
  314. package/src/daemon/message-types/surfaces.ts +0 -9
  315. package/src/daemon/message-types/sync.ts +0 -2
  316. package/src/daemon/process-message.ts +5 -5
  317. package/src/daemon/skill-memory-refresh.ts +5 -5
  318. package/src/daemon/tool-side-effects.ts +32 -16
  319. package/src/daemon/trust-context.ts +1 -1
  320. package/src/daemon/wake-conversation-ops.ts +4 -4
  321. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +100 -7
  322. package/src/heartbeat/__tests__/heartbeat-service.test.ts +67 -0
  323. package/src/heartbeat/heartbeat-run-store.ts +81 -4
  324. package/src/heartbeat/heartbeat-service.ts +32 -1
  325. package/src/home/__tests__/feed-writer.test.ts +7 -7
  326. package/src/home/__tests__/home-content-refresh.test.ts +3 -0
  327. package/src/home/__tests__/suggested-prompts.test.ts +1 -0
  328. package/src/home/feed-writer.ts +6 -13
  329. package/src/home/home-content-refresh.ts +6 -9
  330. package/src/home/relationship-state-writer.ts +5 -12
  331. package/src/home/suggested-prompts-cache.ts +6 -19
  332. package/src/ipc/__tests__/events-publish-client.test.ts +67 -0
  333. package/src/ipc/events-publish-client.ts +44 -0
  334. package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +2 -2
  335. package/src/ipc/routes/__tests__/events-ipc-routes.test.ts +38 -9
  336. package/src/ipc/routes/events-ipc-routes.ts +11 -31
  337. package/src/live-voice/__tests__/front-decision.test.ts +4 -176
  338. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +276 -68
  339. package/src/live-voice/__tests__/live-voice-continuation-fork-hydration.test.ts +286 -0
  340. package/src/live-voice/__tests__/live-voice-events.test.ts +6 -3
  341. package/src/live-voice/__tests__/live-voice-progress.test.ts +185 -46
  342. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +24 -59
  343. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +38 -54
  344. package/src/live-voice/__tests__/live-voice-vad.test.ts +1154 -636
  345. package/src/live-voice/front-decision.ts +5 -155
  346. package/src/live-voice/live-voice-metrics.ts +4 -1
  347. package/src/live-voice/live-voice-session.ts +1288 -439
  348. package/src/live-voice/progress-phrases.ts +17 -0
  349. package/src/live-voice/protocol.ts +15 -0
  350. package/src/media/app-icon-generator.ts +15 -8
  351. package/src/media/avatar-router.ts +9 -5
  352. package/src/media/image-credentials.ts +42 -10
  353. package/src/media/image-service.ts +1 -13
  354. package/src/media/types.ts +26 -3
  355. package/src/notifications/AGENTS.md +2 -0
  356. package/src/notifications/__tests__/broadcaster.test.ts +95 -0
  357. package/src/notifications/adapters/macos.ts +3 -3
  358. package/src/notifications/broadcaster.ts +57 -0
  359. package/src/notifications/copy-composer.ts +0 -15
  360. package/src/notifications/decision-engine.ts +21 -22
  361. package/src/notifications/guardian-question-mode.ts +57 -2
  362. package/src/notifications/home-feed-side-effect.ts +4 -0
  363. package/src/notifications/signal.ts +6 -4
  364. package/src/notifications/trusted-contact-payloads.ts +3 -4
  365. package/src/notifications/vellum-card-affinity.ts +39 -0
  366. package/src/oauth/AGENTS.md +1 -1
  367. package/src/permissions/channel-permission-query.test.ts +69 -0
  368. package/src/permissions/channel-permission-query.ts +73 -1
  369. package/src/permissions/checker.test.ts +298 -15
  370. package/src/permissions/checker.ts +102 -36
  371. package/src/permissions/confirmation-guardian-request.test.ts +2 -2
  372. package/src/permissions/confirmation-guardian-request.ts +2 -2
  373. package/src/permissions/gateway-threshold-reader.test.ts +224 -17
  374. package/src/permissions/gateway-threshold-reader.ts +82 -10
  375. package/src/permissions/ipc-risk-types.ts +8 -0
  376. package/src/permissions/prompter.ts +5 -5
  377. package/src/permissions/question-guardian-request.test.ts +222 -0
  378. package/src/permissions/question-guardian-request.ts +179 -0
  379. package/src/permissions/question-prompter.test.ts +8 -8
  380. package/src/permissions/question-prompter.ts +18 -0
  381. package/src/permissions/secret-prompter.ts +2 -4
  382. package/src/permissions/workspace-policy.ts +269 -34
  383. package/src/persistence/__tests__/lifecycle-quiesce.test.ts +82 -0
  384. package/src/persistence/__tests__/message-content-file.test.ts +58 -3
  385. package/src/persistence/conversation-crud.ts +93 -0
  386. package/src/persistence/conversation-key-store.ts +63 -0
  387. package/src/persistence/conversation-parent.ts +27 -0
  388. package/src/persistence/db-async-query.ts +1 -1
  389. package/src/persistence/db-singleton.ts +14 -27
  390. package/src/persistence/delivery-crud.ts +45 -0
  391. package/src/persistence/embeddings/embedding-backend.ts +2 -1
  392. package/src/persistence/embeddings/embedding-identity.ts +7 -3
  393. package/src/persistence/job-utils.ts +32 -27
  394. package/src/persistence/jobs-store.ts +92 -3
  395. package/src/persistence/lifecycle-quiesce.ts +80 -0
  396. package/src/persistence/llm-usage-store.ts +45 -8
  397. package/src/persistence/message-content-file.ts +4 -4
  398. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +1 -1
  399. package/src/persistence/migrations/351-schedule-skill-script-handoff.ts +39 -0
  400. package/src/persistence/migrations/352-drop-schedule-skill-script-handoff.test.ts +83 -0
  401. package/src/persistence/migrations/352-drop-schedule-skill-script-handoff.ts +36 -0
  402. package/src/persistence/migrations/353-add-llm-usage-conversation-type.test.ts +110 -0
  403. package/src/persistence/migrations/353-add-llm-usage-conversation-type.ts +49 -0
  404. package/src/persistence/migrations/354-backfill-app-conversation-lineage.ts +41 -0
  405. package/src/persistence/schema/infrastructure.ts +11 -0
  406. package/src/persistence/steps.ts +9 -1
  407. package/src/plugin-api/__tests__/publish-event-forwarding.test.ts +113 -0
  408. package/src/plugin-api/__tests__/publish-event.test.ts +57 -0
  409. package/src/plugin-api/conversation-turn.ts +4 -4
  410. package/src/plugin-api/event-hub-facade.ts +15 -6
  411. package/src/plugin-api/index.ts +10 -4
  412. package/src/plugin-api/publish-event.ts +36 -0
  413. package/src/plugins/AGENTS.md +2 -0
  414. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  415. package/src/plugins/defaults/index.ts +9 -13
  416. package/src/plugins/defaults/memory/AGENTS.md +775 -0
  417. package/src/plugins/defaults/memory/__tests__/capability-seed-embed-gate.test.ts +186 -0
  418. package/src/plugins/defaults/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +1 -1
  419. package/src/plugins/defaults/memory/__tests__/indexer-tier-dispatch.test.ts +129 -0
  420. package/src/plugins/defaults/memory/__tests__/jobs-worker-quiesce-gate.test.ts +91 -0
  421. package/src/plugins/defaults/memory/__tests__/jobs-worker-v1-graph-substrate-noop.test.ts +342 -0
  422. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +9 -1
  423. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +36 -1
  424. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-sweep-defer.test.ts +9 -1
  425. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +2 -2
  426. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +44 -10
  427. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +151 -10
  428. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1155 -0
  429. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
  430. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -3
  431. package/src/plugins/defaults/memory/__tests__/tools-delete-memory-page.test.ts +1 -1
  432. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +71 -35
  433. package/src/plugins/defaults/memory/context-search/sources/memory.ts +5 -1
  434. package/src/plugins/defaults/memory/fork-conversation-memory.ts +4 -4
  435. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +80 -10
  436. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +1 -1
  437. package/src/plugins/defaults/memory/graph/capability-seed.ts +24 -10
  438. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +123 -49
  439. package/src/plugins/defaults/memory/graph/graph-search.ts +6 -118
  440. package/src/plugins/defaults/memory/graph/in-context-tracker.test.ts +173 -0
  441. package/src/plugins/defaults/memory/graph/in-context-tracker.ts +109 -0
  442. package/src/plugins/defaults/memory/graph/store.ts +1 -0
  443. package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +1 -1
  444. package/src/plugins/defaults/memory/graph/tool-handlers.ts +5 -2
  445. package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +1 -1
  446. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +6 -9
  447. package/src/plugins/defaults/memory/hooks/__tests__/user-prompt-submit.test.ts +30 -16
  448. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +18 -14
  449. package/src/plugins/defaults/memory/indexer.ts +146 -89
  450. package/src/plugins/defaults/memory/injectors.ts +86 -41
  451. package/src/plugins/defaults/memory/job-handlers.ts +130 -39
  452. package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +2 -2
  453. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +22 -10
  454. package/src/plugins/defaults/memory/jobs-worker.ts +279 -208
  455. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +4 -2
  456. package/src/plugins/defaults/memory/memory-retrospective-job.ts +40 -3
  457. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +99 -11
  458. package/src/plugins/defaults/memory/src/__tests__/memory-v2-routes.test.ts +5 -3
  459. package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +3 -4
  460. package/src/plugins/defaults/memory/src/memory-graph-routes.ts +2 -1
  461. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +7 -5
  462. package/src/plugins/defaults/memory/src/memory-item-routes.ts +51 -19
  463. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +31 -20
  464. package/src/plugins/defaults/memory/startup.ts +58 -21
  465. package/src/plugins/defaults/memory/substrate/__tests__/boot-maintenance.test.ts +335 -0
  466. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/cli-command-store.test.ts +14 -8
  467. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/consolidation-job.test.ts +51 -21
  468. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/edge-index.test.ts +1 -1
  469. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/frontmatter-sweep.test.ts +3 -3
  470. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/page-index.test.ts +1 -1
  471. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/page-store.test.ts +1 -1
  472. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/prompts-consolidation.test.ts +2 -2
  473. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/qdrant.test.ts +23 -14
  474. package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +262 -0
  475. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sim.test.ts +9 -12
  476. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/skill-content.test.ts +2 -2
  477. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/skill-store.test.ts +9 -9
  478. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sparse-bm25.test.ts +12 -5
  479. package/src/plugins/defaults/memory/substrate/__tests__/spread.test.ts +276 -0
  480. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/static-context.test.ts +17 -8
  481. package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sweep-job.test.ts +9 -9
  482. package/src/plugins/defaults/memory/{v3/substrate/memory-v2-startup.ts → substrate/boot-maintenance.ts} +43 -24
  483. package/src/plugins/defaults/memory/{v3/substrate → substrate}/cli-command-store.ts +13 -8
  484. package/src/plugins/defaults/memory/{v3/substrate → substrate}/consolidation-job.ts +51 -27
  485. package/src/plugins/defaults/memory/{v3/substrate → substrate}/constants.ts +1 -0
  486. package/src/plugins/defaults/memory/{v3/substrate → substrate}/edge-index.ts +28 -9
  487. package/src/plugins/defaults/memory/{v3/substrate → substrate}/frontmatter-sweep.ts +14 -7
  488. package/src/plugins/defaults/memory/{v3/substrate → substrate}/injected-block-slugs.ts +9 -3
  489. package/src/plugins/defaults/memory/{v3/substrate → substrate}/page-index.ts +29 -10
  490. package/src/plugins/defaults/memory/{v3/substrate → substrate}/page-store.ts +35 -12
  491. package/src/plugins/defaults/memory/{v3/substrate → substrate}/prompts/consolidation.ts +14 -8
  492. package/src/plugins/defaults/memory/{v3/substrate → substrate}/prompts/sweep.ts +4 -1
  493. package/src/plugins/defaults/memory/{v3/substrate → substrate}/qdrant.ts +96 -36
  494. package/src/plugins/defaults/memory/substrate/reembed-job.ts +54 -0
  495. package/src/plugins/defaults/memory/{v3/substrate → substrate}/sim.ts +26 -15
  496. package/src/plugins/defaults/memory/{v3/substrate → substrate}/skill-content.ts +1 -1
  497. package/src/plugins/defaults/memory/{v3/substrate → substrate}/skill-store.ts +13 -8
  498. package/src/plugins/defaults/memory/{v3/substrate → substrate}/sparse-bm25.ts +27 -10
  499. package/src/plugins/defaults/memory/substrate/spread.ts +165 -0
  500. package/src/plugins/defaults/memory/{v3/substrate → substrate}/static-context.ts +12 -7
  501. package/src/plugins/defaults/memory/{v3/substrate → substrate}/sweep-job.ts +20 -15
  502. package/src/plugins/defaults/memory/substrate/tuning.ts +93 -0
  503. package/src/plugins/defaults/memory/{v3/substrate → substrate}/types.ts +7 -4
  504. package/src/plugins/defaults/memory/tools.test.ts +1 -1
  505. package/src/plugins/defaults/memory/tools.ts +1 -1
  506. package/src/plugins/defaults/memory/v1/README.md +5 -0
  507. package/src/plugins/defaults/memory/{__tests__ → v1/__tests__}/filing-jobs.test.ts +4 -4
  508. package/src/plugins/defaults/memory/{filing-jobs.ts → v1/filing-jobs.ts} +3 -3
  509. package/src/plugins/defaults/memory/{graph → v1/graph}/bootstrap.test.ts +68 -6
  510. package/src/plugins/defaults/memory/{graph → v1/graph}/bootstrap.ts +40 -21
  511. package/src/plugins/defaults/memory/{graph → v1/graph}/consolidation.ts +14 -14
  512. package/src/plugins/defaults/memory/{graph → v1/graph}/decay.test.ts +1 -1
  513. package/src/plugins/defaults/memory/{graph → v1/graph}/decay.ts +3 -3
  514. package/src/plugins/defaults/memory/{graph → v1/graph}/extraction-job.ts +5 -5
  515. package/src/plugins/defaults/memory/{graph → v1/graph}/extraction.test.ts +1 -1
  516. package/src/plugins/defaults/memory/{graph → v1/graph}/extraction.ts +20 -14
  517. package/src/plugins/defaults/memory/{graph → v1/graph}/graph-search.test.ts +3 -3
  518. package/src/plugins/defaults/memory/v1/graph/graph-search.ts +123 -0
  519. package/src/plugins/defaults/memory/{graph → v1/graph}/injection.test.ts +1 -118
  520. package/src/plugins/defaults/memory/{graph → v1/graph}/injection.ts +9 -111
  521. package/src/plugins/defaults/memory/{graph → v1/graph}/narrative.ts +6 -6
  522. package/src/plugins/defaults/memory/{graph → v1/graph}/pattern-scan.ts +5 -5
  523. package/src/plugins/defaults/memory/{graph → v1/graph}/retriever.test.ts +135 -42
  524. package/src/plugins/defaults/memory/{graph → v1/graph}/retriever.ts +40 -22
  525. package/src/plugins/defaults/memory/{graph → v1/graph}/scoring.test.ts +1 -1
  526. package/src/plugins/defaults/memory/{graph → v1/graph}/scoring.ts +1 -1
  527. package/src/plugins/defaults/memory/{graph → v1/graph}/serendipity.ts +1 -1
  528. package/src/plugins/defaults/memory/{graph → v1/graph}/triggers.test.ts +1 -1
  529. package/src/plugins/defaults/memory/{graph → v1/graph}/triggers.ts +1 -1
  530. package/src/plugins/defaults/memory/{identity-context.ts → v1/identity-context.ts} +4 -4
  531. package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/backfill.ts +6 -6
  532. package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/embedding.test.ts +6 -6
  533. package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/embedding.ts +6 -6
  534. package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/index-maintenance.ts +8 -8
  535. package/src/plugins/defaults/memory/{jobs → v1/jobs}/embed-pkb-file.test.ts +7 -7
  536. package/src/plugins/defaults/memory/{jobs → v1/jobs}/embed-pkb-file.ts +5 -5
  537. package/src/plugins/defaults/memory/{pkb → v1/pkb}/autoinject.ts +1 -1
  538. package/src/plugins/defaults/memory/{pkb → v1/pkb}/context.ts +2 -2
  539. package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-index.test.ts +3 -3
  540. package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-index.ts +2 -2
  541. package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-reconcile.test.ts +3 -3
  542. package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-reconcile.ts +2 -2
  543. package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-search.test.ts +3 -3
  544. package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-search.ts +6 -6
  545. package/src/plugins/defaults/memory/{pkb → v1/pkb}/types.ts +3 -5
  546. package/src/plugins/defaults/memory/{pkb-schedule.ts → v1/pkb-schedule.ts} +2 -2
  547. package/src/plugins/defaults/memory/{search/semantic.ts → v1/semantic-search.ts} +1 -1
  548. package/src/plugins/defaults/memory/v2/__tests__/activation-store.test.ts +1 -1
  549. package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +11 -274
  550. package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +9 -131
  551. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -1
  552. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -1
  553. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
  554. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +14 -10
  555. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +7 -5
  556. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +12 -6
  557. package/src/plugins/defaults/memory/{memory-v2-activation-log-store.ts → v2/activation-log-store.ts} +2 -2
  558. package/src/plugins/defaults/memory/v2/activation-store.ts +22 -8
  559. package/src/plugins/defaults/memory/v2/activation.ts +30 -163
  560. package/src/plugins/defaults/memory/v2/backfill-jobs.ts +7 -54
  561. package/src/plugins/defaults/memory/{memory-v2-concept-frequency.ts → v2/concept-frequency.ts} +3 -3
  562. package/src/plugins/defaults/memory/v2/harness/oracle.ts +1 -1
  563. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +2 -2
  564. package/src/plugins/defaults/memory/v2/harness/retriever.ts +1 -1
  565. package/src/plugins/defaults/memory/v2/injection.ts +36 -26
  566. package/src/plugins/defaults/memory/v2/migration.ts +2 -2
  567. package/src/plugins/defaults/memory/{rerank-local.ts → v2/rerank-local.ts} +44 -18
  568. package/src/plugins/defaults/memory/v2/reranker.ts +29 -11
  569. package/src/plugins/defaults/memory/v2/router.ts +38 -15
  570. package/src/plugins/defaults/memory/v3/__tests__/candidate-match.test.ts +5 -3
  571. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +21 -8
  572. package/src/plugins/defaults/memory/v3/__tests__/edge.test.ts +1 -1
  573. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +1 -1
  574. package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +4 -1
  575. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +181 -3
  576. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +3 -0
  577. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +14 -5
  578. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +40 -50
  579. package/src/plugins/defaults/memory/v3/__tests__/tuning-profile.test.ts +1 -0
  580. package/src/plugins/defaults/memory/v3/candidate-match.ts +2 -2
  581. package/src/plugins/defaults/memory/v3/capabilities.ts +6 -4
  582. package/src/plugins/defaults/memory/v3/card.ts +25 -9
  583. package/src/plugins/defaults/memory/v3/edge.ts +52 -18
  584. package/src/plugins/defaults/memory/v3/gate.ts +4 -4
  585. package/src/plugins/defaults/memory/v3/injector.ts +10 -3
  586. package/src/plugins/defaults/memory/v3/maintain-job.ts +3 -3
  587. package/src/plugins/defaults/memory/v3/orchestrate.ts +100 -18
  588. package/src/plugins/defaults/memory/v3/page-content.ts +26 -10
  589. package/src/plugins/defaults/memory/v3/prune.ts +40 -16
  590. package/src/plugins/defaults/memory/v3/selection-log-store.ts +34 -12
  591. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +9 -13
  592. package/src/plugins/defaults/memory/v3/span-query.test.ts +86 -0
  593. package/src/plugins/defaults/memory/v3/span-query.ts +71 -0
  594. package/src/plugins/defaults/memory/v3/tuning-profile.ts +3 -0
  595. package/src/plugins/defaults/memory/v3/types.ts +4 -1
  596. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +2 -2
  597. package/src/plugins/defaults/memory/validation.ts +1 -1
  598. package/src/plugins/defaults/memory/worker.ts +2 -1
  599. package/src/plugins/defaults/platform-hosted/__tests__/reengage.test.ts +104 -12
  600. package/src/plugins/defaults/platform-hosted/routes/reengage.ts +104 -12
  601. package/src/plugins/types.ts +2 -2
  602. package/src/prompts/sections.ts +4 -16
  603. package/src/providers/__tests__/registry-native-web-search.test.ts +47 -2
  604. package/src/providers/anthropic/client.ts +15 -6
  605. package/src/providers/content-block-schema.ts +26 -0
  606. package/src/providers/inference/__tests__/connections-credential-normalize.test.ts +87 -0
  607. package/src/providers/inference/adapter-factory.ts +6 -0
  608. package/src/providers/inference/connections.ts +30 -14
  609. package/src/providers/model-catalog.ts +77 -0
  610. package/src/providers/openai/__tests__/chat-completions-input-audio.test.ts +260 -0
  611. package/src/providers/openai/chat-completions-provider.ts +65 -17
  612. package/src/providers/openai/input-audio.ts +76 -0
  613. package/src/providers/openrouter/client.ts +0 -2
  614. package/src/providers/platform-proxy/constants.ts +4 -0
  615. package/src/providers/poolside/client.ts +42 -0
  616. package/src/providers/registry.ts +40 -8
  617. package/src/providers/retry.ts +2 -0
  618. package/src/providers/types.ts +39 -1
  619. package/src/providers/vercel-ai-gateway/client.ts +0 -2
  620. package/src/runtime/AGENTS.md +7 -5
  621. package/src/runtime/__tests__/question-request-guardian-bridge.test.ts +173 -0
  622. package/src/runtime/__tests__/question-resolution.test.ts +111 -0
  623. package/src/runtime/access-request-helper.ts +7 -15
  624. package/src/runtime/assistant-event-hub.ts +43 -31
  625. package/src/runtime/assistant-event-publish-options.ts +31 -0
  626. package/src/runtime/assistant-event.ts +19 -30
  627. package/src/runtime/assistant-stream-state.ts +4 -4
  628. package/src/runtime/channel-approvals.ts +0 -25
  629. package/src/runtime/channel-readiness-service.ts +99 -40
  630. package/src/runtime/channel-retry-sweep.ts +38 -15
  631. package/src/runtime/confirmation-request-guardian-bridge.ts +4 -0
  632. package/src/runtime/guardian-reply-router.ts +108 -0
  633. package/src/runtime/http-server.ts +8 -0
  634. package/src/runtime/http-types.ts +2 -2
  635. package/src/runtime/process-role.ts +42 -0
  636. package/src/runtime/question-request-guardian-bridge.ts +184 -0
  637. package/src/runtime/question-resolution.ts +153 -0
  638. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +6 -2
  639. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -1
  640. package/src/runtime/routes/__tests__/subagents-routes.test.ts +250 -0
  641. package/src/runtime/routes/__tests__/webhook-routes.test.ts +201 -0
  642. package/src/runtime/routes/acp-routes.ts +2 -2
  643. package/src/runtime/routes/bookmark-routes.ts +3 -15
  644. package/src/runtime/routes/canned-message-complete.ts +2 -2
  645. package/src/runtime/routes/consolidation-routes.ts +8 -2
  646. package/src/runtime/routes/conversation-list-routes.ts +3 -19
  647. package/src/runtime/routes/conversation-management-routes.ts +1 -5
  648. package/src/runtime/routes/conversation-query-routes.ts +52 -8
  649. package/src/runtime/routes/conversation-routes.ts +36 -22
  650. package/src/runtime/routes/credential-request-routes.ts +0 -1
  651. package/src/runtime/routes/filing-routes.ts +7 -7
  652. package/src/runtime/routes/global-search-routes.ts +1 -1
  653. package/src/runtime/routes/guardian-approval-prompt.ts +2 -2
  654. package/src/runtime/routes/image-generation-routes.ts +12 -9
  655. package/src/runtime/routes/inbound-message-handler.ts +17 -0
  656. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +50 -0
  657. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +19 -0
  658. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -4
  659. package/src/runtime/routes/inbound-stages/inbound-content-prep.test.ts +129 -0
  660. package/src/runtime/routes/inbound-stages/inbound-content-prep.ts +141 -9
  661. package/src/runtime/routes/index.ts +2 -0
  662. package/src/runtime/routes/lifecycle-routes.ts +199 -0
  663. package/src/runtime/routes/platform-routes.ts +3 -8
  664. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +7 -7
  665. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +5 -5
  666. package/src/runtime/routes/question-routes.ts +34 -87
  667. package/src/runtime/routes/secret-routes.ts +11 -1
  668. package/src/runtime/routes/settings-routes.ts +8 -18
  669. package/src/runtime/routes/subagents-routes.ts +35 -8
  670. package/src/runtime/routes/upgrade-broadcast-routes.ts +4 -5
  671. package/src/runtime/routes/webhook-routes.ts +89 -30
  672. package/src/runtime/routes/workspace-routes.test.ts +7 -4
  673. package/src/runtime/slack-reply-session.test.ts +12 -12
  674. package/src/runtime/slack-reply-session.ts +17 -17
  675. package/src/runtime/sync/sync-publisher.test.ts +7 -5
  676. package/src/runtime/tool-grant-request-helper.ts +4 -0
  677. package/src/schedule/schedule-store.ts +70 -0
  678. package/src/schedule/scheduler.ts +27 -0
  679. package/src/security/AGENTS.md +1 -1
  680. package/src/security/credential-key.ts +17 -0
  681. package/src/security/secret-ingress.ts +14 -105
  682. package/src/security/secret-patterns.ts +8 -139
  683. package/src/security/secret-scanner.ts +8 -3
  684. package/src/security/store-path-override.ts +6 -16
  685. package/src/sequence/store.ts +8 -0
  686. package/src/signals/emit-event.ts +5 -10
  687. package/src/signals/event-stream.ts +20 -10
  688. package/src/subagent/manager.ts +76 -25
  689. package/src/subagent/notify.ts +8 -0
  690. package/src/subagent/types.ts +13 -3
  691. package/src/telegram/__tests__/webhook-health.test.ts +442 -0
  692. package/src/telegram/webhook-health.ts +478 -0
  693. package/src/telemetry/AGENTS.md +6 -0
  694. package/src/telemetry/memory-corpus-size.test.ts +129 -0
  695. package/src/telemetry/memory-corpus-size.ts +184 -0
  696. package/src/telemetry/memory-tier-reporter.test.ts +67 -2
  697. package/src/telemetry/memory-tier-reporter.ts +30 -3
  698. package/src/tools/__tests__/conversation-group-tool-input-schemas.test.ts +61 -0
  699. package/src/tools/__tests__/document-tool-input-schemas.test.ts +103 -0
  700. package/src/tools/__tests__/followup-call-acp-tool-input-schemas.test.ts +125 -0
  701. package/src/tools/__tests__/schedule-tool-input-schemas.test.ts +83 -0
  702. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +116 -0
  703. package/src/tools/__tests__/tool-input-schemas.test.ts +192 -0
  704. package/src/tools/__tests__/tool-schema-drift-harness.ts +142 -0
  705. package/src/tools/acp/abort.ts +21 -1
  706. package/src/tools/acp/context.ts +5 -5
  707. package/src/tools/acp/spawn.test.ts +15 -5
  708. package/src/tools/acp/spawn.ts +26 -3
  709. package/src/tools/acp/status.ts +20 -1
  710. package/src/tools/acp/steer.test.ts +20 -0
  711. package/src/tools/acp/steer.ts +23 -2
  712. package/src/tools/apps/resolve-app-id.ts +15 -3
  713. package/src/tools/ask-question/ask-question-tool.test.ts +65 -0
  714. package/src/tools/ask-question/ask-question-tool.ts +68 -92
  715. package/src/tools/browser/browser-screencast.ts +6 -6
  716. package/src/tools/calls/call-end.ts +24 -3
  717. package/src/tools/calls/call-start.ts +35 -11
  718. package/src/tools/calls/call-status.ts +20 -1
  719. package/src/tools/conversation-groups/group_create.ts +63 -0
  720. package/src/tools/conversation-groups/group_list.ts +20 -0
  721. package/src/tools/conversation-groups/group_shared.ts +52 -0
  722. package/src/tools/conversation-groups/move_to_group.ts +106 -0
  723. package/src/tools/document/document-comment-tool.test.ts +49 -0
  724. package/src/tools/document/document-comment-tool.ts +45 -6
  725. package/src/tools/document/document-tool.ts +142 -24
  726. package/src/tools/executor.ts +27 -13
  727. package/src/tools/filesystem/edit.ts +62 -60
  728. package/src/tools/filesystem/list.ts +36 -29
  729. package/src/tools/filesystem/read.ts +56 -39
  730. package/src/tools/filesystem/write.ts +44 -38
  731. package/src/tools/followups/followup_create.ts +41 -12
  732. package/src/tools/followups/followup_list.ts +34 -6
  733. package/src/tools/followups/followup_resolve.ts +26 -4
  734. package/src/tools/host-filesystem/edit.ts +50 -60
  735. package/src/tools/host-filesystem/read.ts +45 -39
  736. package/src/tools/host-filesystem/write.ts +31 -36
  737. package/src/tools/host-terminal/host-shell.ts +76 -62
  738. package/src/tools/policy-context.ts +27 -10
  739. package/src/tools/schedule/create.ts +76 -33
  740. package/src/tools/schedule/delete.ts +21 -2
  741. package/src/tools/schedule/list.ts +25 -3
  742. package/src/tools/schedule/update.ts +105 -48
  743. package/src/tools/shared/filesystem/path-policy.ts +246 -40
  744. package/src/tools/shared/zod-tool-schema.ts +117 -0
  745. package/src/tools/subagent/abort.ts +12 -4
  746. package/src/tools/subagent/consult-deadline.ts +9 -3
  747. package/src/tools/subagent/message.ts +21 -5
  748. package/src/tools/subagent/notify-parent.ts +44 -23
  749. package/src/tools/subagent/read.ts +15 -4
  750. package/src/tools/subagent/resolve.ts +26 -4
  751. package/src/tools/subagent/spawn.ts +37 -13
  752. package/src/tools/subagent/status.ts +12 -4
  753. package/src/tools/system/request-permission.ts +35 -26
  754. package/src/tools/terminal/shell.ts +68 -55
  755. package/src/tools/tool-approval-handler.ts +327 -53
  756. package/src/tools/tool-input-schemas.ts +85 -0
  757. package/src/tools/tool-types.ts +7 -0
  758. package/src/tools/types.ts +8 -0
  759. package/src/tools/workflows/manage-workflows.ts +30 -2
  760. package/src/tools/workflows/run-workflow.test.ts +54 -2
  761. package/src/tools/workflows/run-workflow.ts +32 -4
  762. package/src/util/fs-symlinks.ts +30 -0
  763. package/src/util/log-redact.ts +7 -3
  764. package/src/util/platform.ts +15 -0
  765. package/src/vellum-assistant-namespace.d.ts +80 -0
  766. package/src/watcher/watcher-store.ts +8 -0
  767. package/src/workflows/leaf-runner.test.ts +104 -2
  768. package/src/workflows/leaf-runner.ts +31 -6
  769. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +2 -2
  770. package/src/workspace/migrations/134-image-generation-mode-to-provider.ts +128 -0
  771. package/src/workspace/migrations/135-copy-substrate-tunables.ts +248 -0
  772. package/src/workspace/migrations/__tests__/135-copy-substrate-tunables.test.ts +446 -0
  773. package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +2 -2
  774. package/src/workspace/migrations/registry.ts +4 -0
  775. package/tsconfig.plugin-api.json +1 -1
  776. package/src/__tests__/memory-retrieval-budget.test.ts +0 -49
  777. package/src/__tests__/secret-patterns.test.ts +0 -27
  778. package/src/config/__tests__/proc-to-skills.test.ts +0 -21
  779. package/src/config/memory-tier.test.ts +0 -44
  780. package/src/daemon/message-types/acp.ts +0 -20
  781. package/src/daemon/message-types/apps.ts +0 -11
  782. package/src/daemon/message-types/background-tools.ts +0 -14
  783. package/src/daemon/message-types/bookmarks.ts +0 -13
  784. package/src/daemon/message-types/contacts.ts +0 -13
  785. package/src/daemon/message-types/document-comments.ts +0 -14
  786. package/src/daemon/message-types/documents.ts +0 -13
  787. package/src/daemon/message-types/home.ts +0 -15
  788. package/src/daemon/message-types/host-bash.ts +0 -12
  789. package/src/daemon/message-types/host-cu.ts +0 -10
  790. package/src/daemon/message-types/host-file.ts +0 -12
  791. package/src/daemon/message-types/host-transfer.ts +0 -12
  792. package/src/daemon/message-types/integrations.ts +0 -90
  793. package/src/daemon/message-types/memory.ts +0 -10
  794. package/src/daemon/message-types/schedules.ts +0 -14
  795. package/src/daemon/message-types/settings.ts +0 -17
  796. package/src/daemon/message-types/subagents.ts +0 -63
  797. package/src/daemon/message-types/upgrades.ts +0 -14
  798. package/src/daemon/message-types/workflows.ts +0 -25
  799. package/src/daemon/message-types/workspace.ts +0 -137
  800. package/src/live-voice/ack-phrases.ts +0 -58
  801. package/src/plugins/defaults/memory/retrieval-budget.ts +0 -29
  802. package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +0 -69
  803. package/src/plugins/defaults/memory/v3/gate-flag.ts +0 -20
  804. package/src/plugins/defaults/memory/v3/substrate/__tests__/memory-v2-startup.test.ts +0 -86
  805. package/src/plugins/defaults/memory/v3/substrate/memory-v2-startup.test.ts +0 -205
  806. /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/concept-page-frontmatter-schema.test.ts +0 -0
  807. /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -0
  808. /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/injected-block-slugs.test.ts +0 -0
  809. /package/src/plugins/defaults/memory/{v3/substrate → substrate}/cli-command-content.ts +0 -0
@@ -0,0 +1,1155 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ import { describe, expect, test } from "bun:test";
4
+
5
+ import { Glob } from "bun";
6
+ import ts from "typescript";
7
+
8
+ /**
9
+ * Guard tests for the memory plugin's tier boundaries. See
10
+ * `assistant/docs/architecture/memory.md` for the full tier map.
11
+ *
12
+ * The memory plugin is layered into tier directories: `substrate/` is the
13
+ * shared concept-page substrate used by both v2 and v3, `v1/` is the legacy
14
+ * PKB/graph engine, `v2/` is the activation-spreading engine, and `v3/` (plus
15
+ * its `v3-eval/` sibling) is the lane/orchestrator engine. Everything else
16
+ * under the plugin root — including `graph/` (the all-tier legacy store and
17
+ * dispatcher) — is spine code that composes tiers.
18
+ *
19
+ * Guard 1 — tier import edges (production files only):
20
+ * - `substrate/` imports no tier directory (it is the bottom layer).
21
+ * - `v1/` imports no other tier directory (shared `graph/` modules and
22
+ * plugin-root infra are fine — they are spine, not tiers).
23
+ * - `v2/` may import `substrate/` but not `v1/`, `v3/`, or `v3-eval/`.
24
+ * - `v3/` and `v3-eval/` may import `substrate/` (and each other) but not
25
+ * `v1/` or `v2/`.
26
+ * - A spine file importing from two or more tier directories is a
27
+ * composition point and must be on the frozen {@link SPINE_ALLOWLIST}.
28
+ * - A file outside the plugin reaching into a tier directory is a host
29
+ * importer and must be on the frozen {@link HOST_TIER_IMPORT_ALLOWLIST}.
30
+ * Host importers are the set the tier deletion runbooks enumerate: a
31
+ * deletion that skips them leaves broken imports and an unbuildable tree.
32
+ *
33
+ * Guard 2 — tier-key config reads (`memory.substrate.*` / `memory.v2.*` /
34
+ * `memory.v3.*`) across all of `assistant/src/`. Tier selection flows through
35
+ * the predicates in `src/config/memory-v3-gate.ts` (from which
36
+ * `src/config/memory-tier.ts` derives) and substrate tunables resolve via
37
+ * `substrate/tuning.ts`, so raw tier-key reads outside the frozen
38
+ * {@link TIER_KEY_READ_ALLOWLIST} are a layering leak. Each finding carries
39
+ * WHICH key it read, and every exemption is a (path, keys) pair — so the
40
+ * `v2/` engine stays exempt for `memory.v2` only and `v3/` for `memory.v3`
41
+ * only, matching the documented boundary that an engine reads its own tuning
42
+ * namespace and no other.
43
+ *
44
+ * Both guards read one TypeScript AST per file (`ts.createSourceFile`, no
45
+ * type-checker): {@link scanProductionSources} parses every production file
46
+ * under `src/` once and hands each guard the projection it needs, so no file
47
+ * is parsed twice.
48
+ *
49
+ * Guard 1 collects module specifiers from the AST — `import`/`export ... from`
50
+ * (including `import type`), `import x = require("…")`, dynamic `import("…")`,
51
+ * `require("…")`, and type-position `import("…").Foo` — so import-shaped text
52
+ * in a comment or string literal can neither invent an edge nor hide a real
53
+ * one.
54
+ *
55
+ * Guard 2 walks the AST for reads off a `memory` object: property access
56
+ * including optional chaining (`config.memory?.v3.live`), element access with
57
+ * a string literal (`config.memory["v3"]`), and destructuring in any position
58
+ * — variable declarations (`const { v2 } = config.memory ?? {}`), assignments
59
+ * (`({ v3 } = config.memory)`), function and catch parameters, and nested
60
+ * patterns (`const { memory: { v3 } } = config`). Identifiers aliased to a
61
+ * memory object (`const memoryConfig = getConfig().memory`) count as memory
62
+ * objects too — see {@link collectMemoryAliases}. The parser classifies
63
+ * comments and string, template, and regex literals for us, so job names
64
+ * like "memory.v2.sweep" and doc comments never false-positive and no
65
+ * literal can desync the scan.
66
+ *
67
+ * All three allowlists carry a reverse "stale exemption" test — per (path,
68
+ * tier) pair for host imports and per (path, key) pair for tier keys — so an
69
+ * entry whose import or tier-key read disappears fails loudly and gets removed
70
+ * instead of lingering.
71
+ *
72
+ * Tests run from `assistant/`, so paths resolve against `process.cwd()`.
73
+ * Both `.ts` and `.tsx` production files are scanned (the assistant tsconfig
74
+ * ships both); test files (`*.test.ts`, `*.test.tsx`, `__tests__/`) are out
75
+ * of scope — the boundary guarded is the shipped runtime.
76
+ */
77
+
78
+ /**
79
+ * `assistant/src/plugins/defaults/memory`, relative to the `assistant/` cwd
80
+ * and posix-separated (every path this guard compares is posix).
81
+ */
82
+ const MEM_REL = "src/plugins/defaults/memory";
83
+ const MEM_ABS = join(process.cwd(), ...MEM_REL.split("/"));
84
+
85
+ const TIER_DIRS = ["substrate", "v1", "v2", "v3", "v3-eval"] as const;
86
+ type Tier = (typeof TIER_DIRS)[number];
87
+
88
+ /** Tier directories each tier must not import from. */
89
+ const FORBIDDEN_TIER_IMPORTS: Record<Tier, ReadonlySet<Tier>> = {
90
+ substrate: new Set(["v1", "v2", "v3", "v3-eval"]),
91
+ v1: new Set(["substrate", "v2", "v3", "v3-eval"]),
92
+ v2: new Set(["v1", "v3", "v3-eval"]),
93
+ v3: new Set(["v1", "v2"]),
94
+ "v3-eval": new Set(["v1", "v2"]),
95
+ };
96
+
97
+ /**
98
+ * Spine files allowed to import from two or more tier directories — the
99
+ * composition points where the plugin wires tiers together. Paths are
100
+ * relative to the plugin root, posix-separated. Frozen — do not add: new
101
+ * multi-tier composition belongs in an existing spine file, and shrinking
102
+ * this list is how the tier deletion runbooks retire an engine.
103
+ */
104
+ const SPINE_ALLOWLIST: ReadonlySet<string> = new Set([
105
+ "fork-conversation-memory.ts",
106
+ "graph-topology/build-memory-graph.ts",
107
+ "graph/conversation-graph-memory.ts",
108
+ "injectors.ts",
109
+ "job-handlers.ts",
110
+ "jobs-worker.ts",
111
+ "src/memory-v2-routes.ts",
112
+ "startup.ts",
113
+ ]);
114
+
115
+ interface HostTierImportExemption {
116
+ /**
117
+ * Path relative to the `assistant/` cwd, posix-separated; a trailing `/`
118
+ * marks a directory prefix.
119
+ */
120
+ readonly path: string;
121
+ /** Tier directories this path may import from — and only these. */
122
+ readonly tiers: readonly Tier[];
123
+ }
124
+
125
+ /**
126
+ * Files outside the memory plugin allowed to import a tier directory
127
+ * directly, paired with the exact tiers each may reach. Frozen — do not add:
128
+ * host code should depend on the plugin spine, and this list is the deletion
129
+ * runbooks' inventory of what a tier removal has to rewrite. Shrinking it is
130
+ * how retiring an engine finishes.
131
+ *
132
+ * The `substrate/` entries are the shared concept-page surface the daemon and
133
+ * routes read (page/edge indexes, Qdrant collections, static context, boot
134
+ * maintenance, tuning); the `v1/` entries are the legacy PKB engine's
135
+ * remaining host reach (the migration step, semantic search, and PKB file
136
+ * indexing); the `v2/` and `v3/` entries are engine-specific inspector,
137
+ * activation/selection log, and CLI-harness surfaces.
138
+ */
139
+ const HOST_TIER_IMPORT_ALLOWLIST: readonly HostTierImportExemption[] = [
140
+ {
141
+ path: "src/cli/commands/memory/memory-v2-compare-render.ts",
142
+ tiers: ["v2"],
143
+ },
144
+ { path: "src/cli/commands/memory/memory-v2.ts", tiers: ["v2"] },
145
+ {
146
+ path: "src/daemon/conversation-agent-loop-handlers.ts",
147
+ tiers: ["v2", "v3"],
148
+ },
149
+ { path: "src/daemon/conversation-runtime-assembly.ts", tiers: ["v3"] },
150
+ { path: "src/daemon/conversation.ts", tiers: ["v3"] },
151
+ { path: "src/daemon/embedding-reconcile.ts", tiers: ["substrate", "v3"] },
152
+ { path: "src/daemon/skill-memory-refresh.ts", tiers: ["substrate"] },
153
+ { path: "src/daemon/tool-side-effects.ts", tiers: ["substrate"] },
154
+ { path: "src/daemon/trust-context.ts", tiers: ["substrate"] },
155
+ { path: "src/persistence/steps.ts", tiers: ["v1"] },
156
+ { path: "src/runtime/routes/consolidation-routes.ts", tiers: ["substrate"] },
157
+ {
158
+ path: "src/runtime/routes/conversation-query-routes.ts",
159
+ tiers: ["substrate", "v2", "v3"],
160
+ },
161
+ { path: "src/runtime/routes/global-search-routes.ts", tiers: ["v1"] },
162
+ { path: "src/runtime/routes/secret-routes.ts", tiers: ["substrate"] },
163
+ { path: "src/tools/filesystem/write.ts", tiers: ["v1"] },
164
+ { path: "src/tools/skills/find-similar-skills.ts", tiers: ["v3"] },
165
+ ];
166
+
167
+ /** The tier config namespaces this guard tracks. */
168
+ const TIER_KEYS = ["substrate", "v2", "v3"] as const;
169
+ type TierKey = (typeof TIER_KEYS)[number];
170
+
171
+ function isTierKey(key: string | undefined): key is TierKey {
172
+ return (TIER_KEYS as readonly (string | undefined)[]).includes(key);
173
+ }
174
+
175
+ interface TierKeyExemption {
176
+ /**
177
+ * Path relative to the `assistant/` cwd, posix-separated; a trailing `/`
178
+ * marks a directory prefix.
179
+ */
180
+ readonly path: string;
181
+ /** Tier namespaces this path may read — and only these. */
182
+ readonly keys: readonly TierKey[];
183
+ }
184
+
185
+ /**
186
+ * Files allowed to read `memory.v2.*` / `memory.v3.*` config keys directly,
187
+ * paired with the exact namespaces each may read. Frozen — do not add and do
188
+ * not widen an entry's keys: route new tier decisions through
189
+ * `src/config/memory-v3-gate.ts` (or `memory-tier.ts`) and new substrate
190
+ * tunables through `substrate/tuning.ts`; see the tier deletion runbooks
191
+ * before touching this list.
192
+ *
193
+ * - `config/memory-v3-gate.ts` (both) — the gate module; the one sanctioned
194
+ * home for raw tier-predicate reads, and it compares v2 against v3.
195
+ * - `config/loader.ts` (v3) — seeds/normalizes the persisted `memory.v3`
196
+ * shape.
197
+ * - `telemetry/config-setting-snapshot.ts` (both) — reports both tier keys
198
+ * as-is.
199
+ * - the six named migrations — append-only history that rewrites or reads the
200
+ * historical tier keys it was written for. Listed per file, not per
201
+ * directory: a future migration reading a tier key is a new decision and
202
+ * gets challenged like any other.
203
+ * - `substrate/tuning.ts` (substrate, v2) — the substrate tunable resolver;
204
+ * the `memory.substrate`→`memory.v2` fallback lives here by design.
205
+ * - `v2/` (v2), `v3/` (v3) — each engine reads its own tuning namespace and
206
+ * never the other engine's.
207
+ * - `graph/conversation-graph-memory.ts` (v2) — reads `memory.v2.router` for
208
+ * the all-tier dispatcher's historical-pairs routing.
209
+ * - `graph-topology/build-memory-graph.ts` (v3) — reads `memory.v3.edge`
210
+ * tuning when building the graph view.
211
+ * - `src/memory-v2-routes.ts` (v2) — the v2 tuning routes read and merge
212
+ * `memory.v2` config directly.
213
+ */
214
+ const TIER_KEY_READ_ALLOWLIST: readonly TierKeyExemption[] = [
215
+ { path: "src/config/loader.ts", keys: ["v3"] },
216
+ { path: "src/config/memory-v3-gate.ts", keys: ["v2", "v3"] },
217
+ {
218
+ path: "src/persistence/migrations/335-collapse-memory-embed-backlog.ts",
219
+ keys: ["v2"],
220
+ },
221
+ {
222
+ path: "src/plugins/defaults/memory/graph-topology/build-memory-graph.ts",
223
+ keys: ["v3"],
224
+ },
225
+ {
226
+ path: "src/plugins/defaults/memory/graph/conversation-graph-memory.ts",
227
+ keys: ["v2"],
228
+ },
229
+ {
230
+ path: "src/plugins/defaults/memory/src/memory-v2-routes.ts",
231
+ keys: ["v2"],
232
+ },
233
+ {
234
+ path: "src/plugins/defaults/memory/substrate/tuning.ts",
235
+ keys: ["substrate", "v2"],
236
+ },
237
+ { path: "src/plugins/defaults/memory/v2/", keys: ["v2"] },
238
+ { path: "src/plugins/defaults/memory/v3/", keys: ["v3"] },
239
+ { path: "src/telemetry/config-setting-snapshot.ts", keys: ["v2", "v3"] },
240
+ {
241
+ path: "src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts",
242
+ keys: ["v2"],
243
+ },
244
+ {
245
+ path: "src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts",
246
+ keys: ["v3"],
247
+ },
248
+ {
249
+ path: "src/workspace/migrations/117-normalize-stale-lean-memory-v3-defaults.ts",
250
+ keys: ["v3"],
251
+ },
252
+ {
253
+ path: "src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts",
254
+ keys: ["v3"],
255
+ },
256
+ {
257
+ path: "src/workspace/migrations/135-copy-substrate-tunables.ts",
258
+ keys: ["substrate", "v2"],
259
+ },
260
+ ];
261
+
262
+ /** Tier directory a plugin-root-relative path lives in, or `"spine"`. */
263
+ function tierOf(relToMem: string): Tier | "spine" {
264
+ const first = relToMem.split("/")[0]!;
265
+ return (TIER_DIRS as readonly string[]).includes(first)
266
+ ? (first as Tier)
267
+ : "spine";
268
+ }
269
+
270
+ /**
271
+ * Production `.ts`/`.tsx` files under `root`, posix-relative, tests excluded.
272
+ * TSX ships too (the assistant tsconfig includes TSX under `src/`), so it is
273
+ * scanned alongside TS.
274
+ */
275
+ function productionFiles(root: string): string[] {
276
+ const files: string[] = [];
277
+ for (const rel of new Glob("**/*.{ts,tsx}").scanSync({ cwd: root })) {
278
+ const posix = rel.split("\\").join("/");
279
+ if (
280
+ posix.endsWith(".test.ts") ||
281
+ posix.endsWith(".test.tsx") ||
282
+ posix.split("/").includes("__tests__")
283
+ ) {
284
+ continue;
285
+ }
286
+ files.push(posix);
287
+ }
288
+ return files.sort();
289
+ }
290
+
291
+ interface TierImport {
292
+ /** Importing file, relative to the `assistant/` cwd, posix-separated. */
293
+ file: string;
294
+ /**
295
+ * Importing file relative to the plugin root, or `undefined` when the
296
+ * importer lives outside the plugin.
297
+ */
298
+ pluginFile: string | undefined;
299
+ /** `"host"` when the importer lives outside the plugin. */
300
+ sourceTier: Tier | "spine" | "host";
301
+ targetTier: Tier;
302
+ specifier: string;
303
+ }
304
+
305
+ /**
306
+ * Every import that lands in a tier directory, from anywhere under `src/`.
307
+ * Importers inside the plugin carry their own tier (or `"spine"`); importers
308
+ * outside it are `"host"`, which is what makes a host file reaching into
309
+ * `v1/`, `v2/`, `v3/`, `v3-eval/`, or `substrate/` visible to the guard.
310
+ */
311
+ function collectTierImports(sources: readonly ScannedSource[]): TierImport[] {
312
+ const imports: TierImport[] = [];
313
+ const memPrefix = `${MEM_REL}/`;
314
+ for (const source of sources) {
315
+ const inPlugin = source.path.startsWith(memPrefix);
316
+ const pluginFile = inPlugin
317
+ ? source.path.slice(memPrefix.length)
318
+ : undefined;
319
+ const absPath = join(process.cwd(), ...source.path.split("/"));
320
+ for (const specifier of source.specifiers) {
321
+ if (!specifier.startsWith(".")) {
322
+ continue;
323
+ }
324
+ const resolved = resolve(dirname(absPath), specifier);
325
+ const relToMem = relative(MEM_ABS, resolved).split("\\").join("/");
326
+ if (relToMem.startsWith("..")) {
327
+ continue;
328
+ }
329
+ const targetTier = tierOf(relToMem);
330
+ if (targetTier === "spine") {
331
+ continue;
332
+ }
333
+ imports.push({
334
+ file: source.path,
335
+ pluginFile,
336
+ sourceTier: pluginFile === undefined ? "host" : tierOf(pluginFile),
337
+ targetTier,
338
+ specifier,
339
+ });
340
+ }
341
+ }
342
+ return imports;
343
+ }
344
+
345
+ /** Distinct tier directories each spine file imports from. */
346
+ function spineTierUsage(imports: TierImport[]): Map<string, Set<Tier>> {
347
+ const usage = new Map<string, Set<Tier>>();
348
+ for (const imp of imports) {
349
+ if (imp.sourceTier !== "spine" || imp.pluginFile === undefined) {
350
+ continue;
351
+ }
352
+ let tiers = usage.get(imp.pluginFile);
353
+ if (!tiers) {
354
+ usage.set(imp.pluginFile, (tiers = new Set()));
355
+ }
356
+ tiers.add(imp.targetTier);
357
+ }
358
+ return usage;
359
+ }
360
+
361
+ /** Distinct tier directories each host file (outside the plugin) imports. */
362
+ function hostTierUsage(imports: TierImport[]): Map<string, Set<Tier>> {
363
+ const usage = new Map<string, Set<Tier>>();
364
+ for (const imp of imports) {
365
+ if (imp.sourceTier !== "host") {
366
+ continue;
367
+ }
368
+ let tiers = usage.get(imp.file);
369
+ if (!tiers) {
370
+ usage.set(imp.file, (tiers = new Set()));
371
+ }
372
+ tiers.add(imp.targetTier);
373
+ }
374
+ return usage;
375
+ }
376
+
377
+ /** An object destructuring pattern, in binding or assignment-target form. */
378
+ type ObjectPattern = ts.ObjectBindingPattern | ts.ObjectLiteralExpression;
379
+
380
+ /** One property destructured by an {@link ObjectPattern}. */
381
+ interface PatternEntry {
382
+ /** Property read off the source object; `undefined` when computed. */
383
+ readonly key: string | undefined;
384
+ /** Sub-pattern this property destructures into, if it has one. */
385
+ readonly nested: ObjectPattern | undefined;
386
+ }
387
+
388
+ /** Parses one file; `.tsx` parses as TSX, everything else as TS. */
389
+ function parseSourceFile(fileName: string, sourceText: string): ts.SourceFile {
390
+ return ts.createSourceFile(
391
+ fileName,
392
+ sourceText,
393
+ ts.ScriptTarget.Latest,
394
+ /* setParentNodes */ true,
395
+ fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
396
+ );
397
+ }
398
+
399
+ /** Expression with parentheses, `!`, and type assertions peeled off. */
400
+ function unwrapExpression(node: ts.Expression): ts.Expression {
401
+ let current = node;
402
+ while (
403
+ ts.isParenthesizedExpression(current) ||
404
+ ts.isNonNullExpression(current) ||
405
+ ts.isAsExpression(current) ||
406
+ ts.isSatisfiesExpression(current) ||
407
+ ts.isTypeAssertionExpression(current)
408
+ ) {
409
+ current = current.expression;
410
+ }
411
+ return current;
412
+ }
413
+
414
+ /** Text of a string-ish literal, or `undefined` for anything computed. */
415
+ function literalText(node: ts.Node | undefined): string | undefined {
416
+ return node !== undefined && ts.isStringLiteralLike(node)
417
+ ? node.text
418
+ : undefined;
419
+ }
420
+
421
+ /**
422
+ * Module specifiers a file depends on, taken from the AST: `import ... from`
423
+ * and re-exporting `export ... from` declarations, `import x = require("…")`,
424
+ * dynamic `import("…")`, `require("…")`, and type-position
425
+ * `import("…").Foo`. Type-only imports count — the guard freezes layering,
426
+ * not emitted code, and a `v3/` file importing a `v1/` type is still a tier
427
+ * edge. Only string-literal specifiers are collected; a computed
428
+ * `import(path)` has no static target to attribute.
429
+ */
430
+ function importSpecifiers(sourceFile: ts.SourceFile): string[] {
431
+ const specifiers: string[] = [];
432
+ const add = (node: ts.Node | undefined): void => {
433
+ const text = literalText(node);
434
+ if (text !== undefined) {
435
+ specifiers.push(text);
436
+ }
437
+ };
438
+ const visit = (node: ts.Node): void => {
439
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
440
+ // An `export { x }` with no module specifier is not an edge.
441
+ add(node.moduleSpecifier);
442
+ } else if (
443
+ ts.isImportEqualsDeclaration(node) &&
444
+ ts.isExternalModuleReference(node.moduleReference)
445
+ ) {
446
+ add(node.moduleReference.expression);
447
+ } else if (ts.isCallExpression(node)) {
448
+ const callee = node.expression;
449
+ const isImportCall = callee.kind === ts.SyntaxKind.ImportKeyword;
450
+ const isRequireCall =
451
+ ts.isIdentifier(callee) && callee.text === "require";
452
+ if (isImportCall || isRequireCall) {
453
+ add(node.arguments[0]);
454
+ }
455
+ } else if (
456
+ ts.isImportTypeNode(node) &&
457
+ ts.isLiteralTypeNode(node.argument)
458
+ ) {
459
+ add(node.argument.literal);
460
+ }
461
+ ts.forEachChild(node, visit);
462
+ };
463
+ ts.forEachChild(sourceFile, visit);
464
+ return specifiers;
465
+ }
466
+
467
+ /** Static name of a property or binding name, or `undefined` if computed. */
468
+ function staticNameText(
469
+ name: ts.PropertyName | ts.BindingName | undefined,
470
+ ): string | undefined {
471
+ if (name === undefined) {
472
+ return undefined;
473
+ }
474
+ if (
475
+ ts.isIdentifier(name) ||
476
+ ts.isStringLiteral(name) ||
477
+ ts.isNumericLiteral(name)
478
+ ) {
479
+ return name.text;
480
+ }
481
+ return undefined;
482
+ }
483
+
484
+ /**
485
+ * Whether an expression resolves to a `memory` object: a bare `memory`
486
+ * identifier or a known `aliases` identifier, any `.memory` / `["memory"]`
487
+ * access, or either arm of a `??`/`||` fallback or a conditional
488
+ * (`config.memory ?? {}`).
489
+ */
490
+ function resolvesToMemory(
491
+ node: ts.Expression,
492
+ aliases: ReadonlySet<string>,
493
+ ): boolean {
494
+ const expr = unwrapExpression(node);
495
+ if (ts.isIdentifier(expr)) {
496
+ return expr.text === "memory" || aliases.has(expr.text);
497
+ }
498
+ if (ts.isPropertyAccessExpression(expr)) {
499
+ return expr.name.text === "memory";
500
+ }
501
+ if (ts.isElementAccessExpression(expr)) {
502
+ return literalText(expr.argumentExpression) === "memory";
503
+ }
504
+ if (
505
+ ts.isBinaryExpression(expr) &&
506
+ (expr.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||
507
+ expr.operatorToken.kind === ts.SyntaxKind.BarBarToken)
508
+ ) {
509
+ return (
510
+ resolvesToMemory(expr.left, aliases) ||
511
+ resolvesToMemory(expr.right, aliases)
512
+ );
513
+ }
514
+ if (ts.isConditionalExpression(expr)) {
515
+ return (
516
+ resolvesToMemory(expr.whenTrue, aliases) ||
517
+ resolvesToMemory(expr.whenFalse, aliases)
518
+ );
519
+ }
520
+ return false;
521
+ }
522
+
523
+ /** A binding that may name a `memory` object. */
524
+ interface AliasCandidate {
525
+ /** Identifier the binding introduces. */
526
+ readonly name: string;
527
+ /** Initializer (or default value) the binding takes, if any. */
528
+ readonly initializer: ts.Expression | undefined;
529
+ /** Whether the binding destructures a `memory` property off its source. */
530
+ readonly fromMemoryProperty: boolean;
531
+ }
532
+
533
+ /** Every identifier binding in a file that could name a `memory` object. */
534
+ function aliasCandidates(sourceFile: ts.SourceFile): AliasCandidate[] {
535
+ const candidates: AliasCandidate[] = [];
536
+ const visit = (node: ts.Node): void => {
537
+ if (
538
+ (ts.isVariableDeclaration(node) ||
539
+ ts.isParameter(node) ||
540
+ ts.isBindingElement(node)) &&
541
+ ts.isIdentifier(node.name)
542
+ ) {
543
+ candidates.push({
544
+ name: node.name.text,
545
+ initializer: node.initializer,
546
+ fromMemoryProperty:
547
+ ts.isBindingElement(node) &&
548
+ staticNameText(node.propertyName ?? node.name) === "memory",
549
+ });
550
+ }
551
+ ts.forEachChild(node, visit);
552
+ };
553
+ ts.forEachChild(sourceFile, visit);
554
+ return candidates;
555
+ }
556
+
557
+ /**
558
+ * Identifiers a file binds to a `memory` config object, so later reads through
559
+ * the alias are seen: `const memoryConfig = getConfig().memory` (the shape
560
+ * `workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts`
561
+ * uses), `const m = config.memory ?? {}`, `const { memory: m } = config`, and
562
+ * a parameter defaulted to any of those. Aliases of aliases resolve too — the
563
+ * pass iterates to a fixed point, so `const a = config.memory; const b = a;`
564
+ * makes both `a` and `b` memory objects.
565
+ *
566
+ * The set is file-level and scope-free on purpose: an inner scope that
567
+ * shadows an alias name with something unrelated is deliberately still
568
+ * treated as the alias. That over-detects rather than under-detects, which is
569
+ * the safe direction for a freeze guard — the cost of a shadowed name is one
570
+ * exemption discussion, while missing an aliased read is a silent layering
571
+ * leak. Reassignment (`let m; m = config.memory;`) is out of scope; use a
572
+ * declaration.
573
+ */
574
+ function collectMemoryAliases(sourceFile: ts.SourceFile): Set<string> {
575
+ const candidates = aliasCandidates(sourceFile);
576
+ const aliases = new Set<string>();
577
+ for (const candidate of candidates) {
578
+ if (candidate.fromMemoryProperty) {
579
+ aliases.add(candidate.name);
580
+ }
581
+ }
582
+ // Alias chains resolve in any declaration order, so iterate until the set
583
+ // stops growing (bounded by the number of candidates).
584
+ let grew = true;
585
+ while (grew) {
586
+ grew = false;
587
+ for (const candidate of candidates) {
588
+ if (aliases.has(candidate.name) || candidate.initializer === undefined) {
589
+ continue;
590
+ }
591
+ if (resolvesToMemory(candidate.initializer, aliases)) {
592
+ aliases.add(candidate.name);
593
+ grew = true;
594
+ }
595
+ }
596
+ }
597
+ return aliases;
598
+ }
599
+
600
+ /** Assignment-pattern target with its parentheses and `= default` removed. */
601
+ function unwrapAssignmentTarget(node: ts.Expression): ts.Expression {
602
+ const expr = unwrapExpression(node);
603
+ if (
604
+ ts.isBinaryExpression(expr) &&
605
+ expr.operatorToken.kind === ts.SyntaxKind.EqualsToken
606
+ ) {
607
+ return unwrapAssignmentTarget(expr.left);
608
+ }
609
+ return expr;
610
+ }
611
+
612
+ /** Properties a pattern destructures, in either binding or assignment form. */
613
+ function patternEntries(pattern: ObjectPattern): PatternEntry[] {
614
+ const entries: PatternEntry[] = [];
615
+ if (ts.isObjectBindingPattern(pattern)) {
616
+ for (const element of pattern.elements) {
617
+ if (element.dotDotDotToken !== undefined) {
618
+ // A rest element carries no single key.
619
+ continue;
620
+ }
621
+ entries.push({
622
+ key: staticNameText(element.propertyName ?? element.name),
623
+ nested: ts.isObjectBindingPattern(element.name)
624
+ ? element.name
625
+ : undefined,
626
+ });
627
+ }
628
+ return entries;
629
+ }
630
+ for (const property of pattern.properties) {
631
+ if (ts.isShorthandPropertyAssignment(property)) {
632
+ entries.push({ key: property.name.text, nested: undefined });
633
+ continue;
634
+ }
635
+ if (!ts.isPropertyAssignment(property)) {
636
+ // Spreads and methods carry no single key.
637
+ continue;
638
+ }
639
+ const target = unwrapAssignmentTarget(property.initializer);
640
+ entries.push({
641
+ key: staticNameText(property.name),
642
+ nested: ts.isObjectLiteralExpression(target) ? target : undefined,
643
+ });
644
+ }
645
+ return entries;
646
+ }
647
+
648
+ /** Adds every tier key bound directly by `pattern`. */
649
+ function addTopLevelTierKeys(pattern: ObjectPattern, keys: Set<TierKey>): void {
650
+ for (const entry of patternEntries(pattern)) {
651
+ if (isTierKey(entry.key)) {
652
+ keys.add(entry.key);
653
+ }
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Adds tier keys taken through a nested `memory:` sub-pattern at any depth,
659
+ * as in `const { memory: { v3 } } = config`.
660
+ */
661
+ function addNestedMemoryPatternKeys(
662
+ pattern: ObjectPattern,
663
+ keys: Set<TierKey>,
664
+ ): void {
665
+ for (const entry of patternEntries(pattern)) {
666
+ if (entry.nested === undefined) {
667
+ continue;
668
+ }
669
+ if (entry.key === "memory") {
670
+ addTopLevelTierKeys(entry.nested, keys);
671
+ }
672
+ addNestedMemoryPatternKeys(entry.nested, keys);
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Tier namespaces a source file reads off a `memory` object, found by walking
678
+ * its TypeScript AST: property access (`config.memory?.v3.live`), element
679
+ * access with a string literal (`config.memory["v3"]`), and destructuring in
680
+ * every position — declarations, assignments, parameters, and nested
681
+ * patterns. Identifiers aliased to a memory object count as memory objects
682
+ * (see {@link collectMemoryAliases}), so `alias.v3.live`, `alias["v2"]`, and
683
+ * `const { v3 } = alias` are all reads. Comments and string, template, and
684
+ * regex literals are classified by the parser, so no literal reads as code or
685
+ * desyncs the walk. Dynamic computed keys (`memory[key]`) stay out of reach
686
+ * without a type-checker.
687
+ */
688
+ function tierKeysOf(sourceFile: ts.SourceFile): Set<TierKey> {
689
+ const keys = new Set<TierKey>();
690
+ const aliases = collectMemoryAliases(sourceFile);
691
+
692
+ const readPattern = (
693
+ pattern: ObjectPattern,
694
+ source: ts.Expression | undefined,
695
+ ): void => {
696
+ if (source !== undefined && resolvesToMemory(source, aliases)) {
697
+ addTopLevelTierKeys(pattern, keys);
698
+ }
699
+ addNestedMemoryPatternKeys(pattern, keys);
700
+ };
701
+
702
+ const visit = (node: ts.Node): void => {
703
+ if (
704
+ ts.isPropertyAccessExpression(node) &&
705
+ resolvesToMemory(node.expression, aliases)
706
+ ) {
707
+ const key = node.name.text;
708
+ if (isTierKey(key)) {
709
+ keys.add(key);
710
+ }
711
+ } else if (
712
+ ts.isElementAccessExpression(node) &&
713
+ resolvesToMemory(node.expression, aliases)
714
+ ) {
715
+ const key = literalText(node.argumentExpression);
716
+ if (isTierKey(key)) {
717
+ keys.add(key);
718
+ }
719
+ } else if (
720
+ (ts.isVariableDeclaration(node) ||
721
+ ts.isParameter(node) ||
722
+ ts.isBindingElement(node)) &&
723
+ ts.isObjectBindingPattern(node.name)
724
+ ) {
725
+ // A binding element's initializer is its default value — reading a
726
+ // tier key from it is still a raw read.
727
+ readPattern(node.name, node.initializer);
728
+ } else if (
729
+ ts.isBinaryExpression(node) &&
730
+ node.operatorToken.kind === ts.SyntaxKind.EqualsToken
731
+ ) {
732
+ const target = unwrapExpression(node.left);
733
+ if (ts.isObjectLiteralExpression(target)) {
734
+ readPattern(target, node.right);
735
+ }
736
+ }
737
+ ts.forEachChild(node, visit);
738
+ };
739
+
740
+ ts.forEachChild(sourceFile, visit);
741
+ return keys;
742
+ }
743
+
744
+ /**
745
+ * Whether `file` is covered by an allowlist entry's path: an exact match, or
746
+ * any file under it when the entry ends in `/` and so names a directory.
747
+ */
748
+ function matchesExemptPath(file: string, exemptPath: string): boolean {
749
+ return exemptPath.endsWith("/")
750
+ ? file.startsWith(exemptPath)
751
+ : file === exemptPath;
752
+ }
753
+
754
+ function isTierKeyExempt(file: string, key: TierKey): boolean {
755
+ return TIER_KEY_READ_ALLOWLIST.some(
756
+ (entry) => matchesExemptPath(file, entry.path) && entry.keys.includes(key),
757
+ );
758
+ }
759
+
760
+ function isHostTierImportExempt(file: string, tier: Tier): boolean {
761
+ return HOST_TIER_IMPORT_ALLOWLIST.some(
762
+ (entry) =>
763
+ matchesExemptPath(file, entry.path) && entry.tiers.includes(tier),
764
+ );
765
+ }
766
+
767
+ /** One production source file, parsed once and projected for both guards. */
768
+ interface ScannedSource {
769
+ /** Path relative to the `assistant/` cwd, posix-separated. */
770
+ readonly path: string;
771
+ /** Module specifiers this file imports, re-exports, or requires. */
772
+ readonly specifiers: readonly string[];
773
+ /** Tier namespaces this file reads off a `memory` object. */
774
+ readonly tierKeys: Set<TierKey>;
775
+ }
776
+
777
+ /**
778
+ * Every production file under `src/`, parsed once. Both guards read this one
779
+ * pass, and guard 1 needs specifiers from every file — a host importer of a
780
+ * tier directory is an edge no matter where it lives — so specifiers and tier
781
+ * keys come off the same AST.
782
+ */
783
+ function scanProductionSources(): ScannedSource[] {
784
+ const srcRoot = join(process.cwd(), "src");
785
+ const sources: ScannedSource[] = [];
786
+ for (const file of productionFiles(srcRoot)) {
787
+ const absolute = join(srcRoot, ...file.split("/"));
788
+ const sourceFile = parseSourceFile(
789
+ absolute,
790
+ readFileSync(absolute, "utf-8"),
791
+ );
792
+ sources.push({
793
+ path: `src/${file}`,
794
+ specifiers: importSpecifiers(sourceFile),
795
+ tierKeys: tierKeysOf(sourceFile),
796
+ });
797
+ }
798
+ return sources;
799
+ }
800
+
801
+ describe("memory tier boundary guard", () => {
802
+ // Parsed once and shared: every guard test reads the same snapshot of the
803
+ // tree, and no file is parsed twice.
804
+ const sources = scanProductionSources();
805
+ const tierImports = collectTierImports(sources);
806
+ const tierKeyReaders = sources.filter((source) => source.tierKeys.size > 0);
807
+
808
+ test("tier directories only import their allowed tiers", () => {
809
+ const violations: string[] = [];
810
+ for (const imp of tierImports) {
811
+ if (
812
+ imp.sourceTier === "spine" ||
813
+ imp.sourceTier === "host" ||
814
+ imp.sourceTier === imp.targetTier
815
+ ) {
816
+ continue;
817
+ }
818
+ if (FORBIDDEN_TIER_IMPORTS[imp.sourceTier].has(imp.targetTier)) {
819
+ violations.push(
820
+ ` - ${imp.file} (${imp.sourceTier}/) imports "${imp.specifier}" (${imp.targetTier}/)`,
821
+ );
822
+ }
823
+ }
824
+ violations.sort();
825
+ const message = [
826
+ "Forbidden tier import edges in the memory plugin:",
827
+ ...violations,
828
+ "",
829
+ "substrate/ imports no tier; v1/ imports no other tier; v2/ may import",
830
+ "substrate/ only; v3/ and v3-eval/ may import substrate/ (and each",
831
+ "other) only. Shared code belongs in substrate/ or the plugin spine.",
832
+ ].join("\n");
833
+ expect(violations, message).toEqual([]);
834
+ });
835
+
836
+ test("multi-tier composition only happens at frozen spine files", () => {
837
+ const violations: string[] = [];
838
+ for (const [file, tiers] of spineTierUsage(tierImports)) {
839
+ if (tiers.size >= 2 && !SPINE_ALLOWLIST.has(file)) {
840
+ violations.push(
841
+ ` - ${MEM_REL}/${file} imports from {${[...tiers].sort().join(", ")}}`,
842
+ );
843
+ }
844
+ }
845
+ violations.sort();
846
+ const message = [
847
+ "Spine files newly composing multiple tiers (not on SPINE_ALLOWLIST):",
848
+ ...violations,
849
+ "",
850
+ "The allowlist is frozen — do not add. Route new multi-tier wiring",
851
+ "through an existing composition point, or push the shared logic into",
852
+ "substrate/.",
853
+ ].join("\n");
854
+ expect(violations, message).toEqual([]);
855
+ });
856
+
857
+ test("every spine allowlist entry still composes multiple tiers", () => {
858
+ const usage = spineTierUsage(tierImports);
859
+ const stale: string[] = [];
860
+ for (const file of [...SPINE_ALLOWLIST].sort()) {
861
+ if ((usage.get(file)?.size ?? 0) < 2) {
862
+ stale.push(` - ${file}`);
863
+ }
864
+ }
865
+ const message = [
866
+ "Stale SPINE_ALLOWLIST entries (no longer import 2+ tiers — remove",
867
+ "them so the freeze stays tight):",
868
+ ...stale,
869
+ ].join("\n");
870
+ expect(stale, message).toEqual([]);
871
+ });
872
+
873
+ test("files outside the plugin only import frozen host tiers", () => {
874
+ const found = new Set<string>();
875
+ for (const [file, tiers] of hostTierUsage(tierImports)) {
876
+ for (const tier of tiers) {
877
+ if (!isHostTierImportExempt(file, tier)) {
878
+ found.add(` - ${file} imports ${MEM_REL}/${tier}/`);
879
+ }
880
+ }
881
+ }
882
+ const violations = [...found].sort();
883
+ const message = [
884
+ "Host files reaching into a memory tier directory without a",
885
+ "HOST_TIER_IMPORT_ALLOWLIST entry:",
886
+ ...violations,
887
+ "",
888
+ "The allowlist is frozen — do not add. Host code depends on the plugin",
889
+ "spine (or a substrate export re-exported by it), never on an engine",
890
+ "directly; every entry here is work a tier deletion has to undo.",
891
+ ].join("\n");
892
+ expect(violations, message).toEqual([]);
893
+ });
894
+
895
+ test("every host tier-import exemption still has a matching import", () => {
896
+ const usage = hostTierUsage(tierImports);
897
+ const stale: string[] = [];
898
+ for (const entry of HOST_TIER_IMPORT_ALLOWLIST) {
899
+ for (const tier of entry.tiers) {
900
+ const matched = [...usage].some(
901
+ ([file, tiers]) =>
902
+ matchesExemptPath(file, entry.path) && tiers.has(tier),
903
+ );
904
+ if (!matched) {
905
+ stale.push(` - ${entry.path} (${MEM_REL}/${tier}/)`);
906
+ }
907
+ }
908
+ }
909
+ stale.sort();
910
+ const message = [
911
+ "Stale HOST_TIER_IMPORT_ALLOWLIST (path, tier) pairs (no import",
912
+ "matches — remove them so the freeze stays tight):",
913
+ ...stale,
914
+ ].join("\n");
915
+ expect(stale, message).toEqual([]);
916
+ });
917
+
918
+ test("tier-key config reads are frozen to the gate module and exemptions", () => {
919
+ const violations: string[] = [];
920
+ for (const { path, tierKeys } of tierKeyReaders) {
921
+ for (const key of [...tierKeys].sort()) {
922
+ if (!isTierKeyExempt(path, key)) {
923
+ violations.push(` - ${path} reads memory.${key}.*`);
924
+ }
925
+ }
926
+ }
927
+ violations.sort();
928
+ const message = [
929
+ "New memory.substrate.* / memory.v2.* / memory.v3.* config reads",
930
+ "outside the frozen TIER_KEY_READ_ALLOWLIST (an engine directory is",
931
+ "exempt for its own tier key only):",
932
+ ...violations,
933
+ "",
934
+ "Tier decisions go through src/config/memory-v3-gate.ts (or",
935
+ "src/config/memory-tier.ts); substrate tunables resolve via",
936
+ "substrate/tuning.ts. Do not extend the allowlist.",
937
+ ].join("\n");
938
+ expect(violations, message).toEqual([]);
939
+ });
940
+
941
+ test("every tier-key exemption still has a matching tier-key read", () => {
942
+ const stale: string[] = [];
943
+ for (const entry of TIER_KEY_READ_ALLOWLIST) {
944
+ for (const key of entry.keys) {
945
+ const matched = tierKeyReaders.some(
946
+ (source) =>
947
+ matchesExemptPath(source.path, entry.path) &&
948
+ source.tierKeys.has(key),
949
+ );
950
+ if (!matched) {
951
+ stale.push(` - ${entry.path} (memory.${key}.*)`);
952
+ }
953
+ }
954
+ }
955
+ stale.sort();
956
+ const message = [
957
+ "Stale TIER_KEY_READ_ALLOWLIST (path, key) pairs (no tier-key read",
958
+ "matches — remove them so the freeze stays tight):",
959
+ ...stale,
960
+ ].join("\n");
961
+ expect(stale, message).toEqual([]);
962
+ });
963
+ });
964
+
965
+ describe("tier-key read detection", () => {
966
+ const keysOf = (source: string): TierKey[] =>
967
+ [...tierKeysOf(parseSourceFile("guard-fixture.ts", source))].sort();
968
+
969
+ test("property chains are detected, with and without optional chaining", () => {
970
+ expect(keysOf(`const on = config.memory.v2.enabled;`)).toEqual(["v2"]);
971
+ expect(keysOf(`return config.memory?.v3?.live === true;`)).toEqual(["v3"]);
972
+ expect(keysOf(`seed.memory.v3 = { live: seed.memory.v3.live };`)).toEqual([
973
+ "v3",
974
+ ]);
975
+ });
976
+
977
+ test("the substrate namespace is tracked alongside the engine tiers", () => {
978
+ expect(keysOf(`const k1 = config.memory.substrate.bm25_k1;`)).toEqual([
979
+ "substrate",
980
+ ]);
981
+ expect(keysOf(`const b = config.memory?.["substrate"]?.bm25_b;`)).toEqual([
982
+ "substrate",
983
+ ]);
984
+ expect(keysOf(`const { substrate } = config.memory ?? {};`)).toEqual([
985
+ "substrate",
986
+ ]);
987
+ expect(keysOf(`const { substrate, v2 } = getConfig().memory;`)).toEqual([
988
+ "substrate",
989
+ "v2",
990
+ ]);
991
+ });
992
+
993
+ test("computed literal access is detected", () => {
994
+ expect(keysOf(`const live = config.memory["v3"].live;`)).toEqual(["v3"]);
995
+ expect(keysOf(`const k = config.memory['v2'].k;`)).toEqual(["v2"]);
996
+ expect(keysOf(`const live = config.memory?.["v3"]?.live;`)).toEqual(["v3"]);
997
+ expect(keysOf(`const both = [memory["v2"], memory["v3"]];`)).toEqual([
998
+ "v2",
999
+ "v3",
1000
+ ]);
1001
+ });
1002
+
1003
+ test("destructuring off a memory object is detected", () => {
1004
+ expect(keysOf(`const { v2 } = config.memory;`)).toEqual(["v2"]);
1005
+ expect(keysOf(`const { v3: tuning } = getConfig().memory;`)).toEqual([
1006
+ "v3",
1007
+ ]);
1008
+ expect(keysOf(`const { v2, v3 } = config?.memory;`)).toEqual(["v2", "v3"]);
1009
+ expect(keysOf(`const { memory: { v3 } } = config;`)).toEqual(["v3"]);
1010
+ });
1011
+
1012
+ test("destructuring is detected through wrappers and in every position", () => {
1013
+ expect(keysOf(`const { v2 } = config.memory ?? {};`)).toEqual(["v2"]);
1014
+ expect(keysOf(`const { v3 } = (config.memory as MemoryConfig)!;`)).toEqual([
1015
+ "v3",
1016
+ ]);
1017
+ expect(keysOf(`({ v3 } = config.memory);`)).toEqual(["v3"]);
1018
+ expect(keysOf(`({ memory: { v3 } } = config);`)).toEqual(["v3"]);
1019
+ expect(keysOf(`function read({ v2 } = config.memory) {}`)).toEqual(["v2"]);
1020
+ expect(keysOf(`try { run(); } catch ({ memory: { v3 } }) {}`)).toEqual([
1021
+ "v3",
1022
+ ]);
1023
+ });
1024
+
1025
+ test("string literals and comments are ignored", () => {
1026
+ expect(keysOf(`enqueue("memory.v2.sweep");`)).toEqual([]);
1027
+ expect(keysOf(`const job = { name: "memory.v2.sweep" };`)).toEqual([]);
1028
+ expect(keysOf(`"memory.v2.sweep";`)).toEqual([]);
1029
+ expect(keysOf(`// falls back to memory.v2.k\nconst k = 1;`)).toEqual([]);
1030
+ expect(keysOf(`// memory.v3.live gates injection\nconst x = 1;`)).toEqual(
1031
+ [],
1032
+ );
1033
+ expect(
1034
+ keysOf(`/** memory.v3.live gates injection */\nconst x = 1;`),
1035
+ ).toEqual([]);
1036
+ expect(keysOf("const label = `memory.v3.edge tuning`;")).toEqual([]);
1037
+ });
1038
+
1039
+ test("reads inside template interpolations are detected", () => {
1040
+ expect(keysOf("const msg = `k=${config.memory.v2.k}`;")).toEqual(["v2"]);
1041
+ expect(
1042
+ keysOf("const msg = `memory.v3.live=${config.memory?.v3?.live}`;"),
1043
+ ).toEqual(["v3"]);
1044
+ expect(keysOf('const msg = `live=${config.memory["v3"].live}`;')).toEqual([
1045
+ "v3",
1046
+ ]);
1047
+ });
1048
+
1049
+ test("unrelated identifiers do not match", () => {
1050
+ expect(keysOf(`const memoryV2 = loadV2();`)).toEqual([]);
1051
+ expect(keysOf(`const x = inMemory.v2Cache;`)).toEqual([]);
1052
+ expect(keysOf(`const { v2 } = engines;`)).toEqual([]);
1053
+ expect(keysOf(`const { lanes: { v3 } } = registry;`)).toEqual([]);
1054
+ expect(keysOf(`const t = pages["v2"];`)).toEqual([]);
1055
+ expect(keysOf(`const dir = plugin.substrate;`)).toEqual([]);
1056
+ expect(keysOf(`const seed = { memory: { v3: { live: true } } };`)).toEqual(
1057
+ [],
1058
+ );
1059
+ });
1060
+
1061
+ test("reads through an aliased memory object are detected", () => {
1062
+ expect(
1063
+ keysOf(`const memoryConfig = getConfig().memory;\nmemoryConfig.v3.live;`),
1064
+ ).toEqual(["v3"]);
1065
+ expect(
1066
+ keysOf(`const m = config.memory ?? {};\nconst k = m["v2"].k;`),
1067
+ ).toEqual(["v2"]);
1068
+ expect(keysOf(`const m = config.memory;\nconst { v3 } = m;`)).toEqual([
1069
+ "v3",
1070
+ ]);
1071
+ expect(keysOf(`const { memory: m } = config;\nm.v2.enabled;`)).toEqual([
1072
+ "v2",
1073
+ ]);
1074
+ expect(
1075
+ keysOf(`function read(m = config.memory) { return m.v3.live; }`),
1076
+ ).toEqual(["v3"]);
1077
+ // The migration-105 shape: cast, then re-alias the cast.
1078
+ expect(
1079
+ keysOf(
1080
+ `const memory = config.memory;\nconst memoryConfig = memory as Record<string, unknown>;\nif (memoryConfig.v3 === undefined) memoryConfig.v3 = {};`,
1081
+ ),
1082
+ ).toEqual(["v3"]);
1083
+ });
1084
+
1085
+ test("alias chains resolve in any declaration order", () => {
1086
+ expect(keysOf(`const a = config.memory;\nconst b = a;\nb.v2.k;`)).toEqual([
1087
+ "v2",
1088
+ ]);
1089
+ expect(
1090
+ keysOf(
1091
+ `function f() { return b.v3.live; }\nconst b = a;\nconst a = m.memory;`,
1092
+ ),
1093
+ ).toEqual(["v3"]);
1094
+ });
1095
+
1096
+ test("aliases of unrelated objects do not match", () => {
1097
+ expect(keysOf(`const cfg = getConfig();\nconst k = cfg.v2;`)).toEqual([]);
1098
+ expect(keysOf(`const store = memories.byId;\nstore.v3.live;`)).toEqual([]);
1099
+ });
1100
+ });
1101
+
1102
+ describe("import specifier detection", () => {
1103
+ const specifiersOf = (source: string): string[] =>
1104
+ importSpecifiers(parseSourceFile("guard-fixture.ts", source)).sort();
1105
+
1106
+ test("every import form is collected", () => {
1107
+ expect(specifiersOf(`import { a } from "./v1/a.js";`)).toEqual([
1108
+ "./v1/a.js",
1109
+ ]);
1110
+ expect(specifiersOf(`import "./v1/side-effect.js";`)).toEqual([
1111
+ "./v1/side-effect.js",
1112
+ ]);
1113
+ expect(specifiersOf(`import type { A } from "./v1/types.js";`)).toEqual([
1114
+ "./v1/types.js",
1115
+ ]);
1116
+ expect(specifiersOf(`export { a } from "./v2/a.js";`)).toEqual([
1117
+ "./v2/a.js",
1118
+ ]);
1119
+ expect(specifiersOf(`export * from "./v2/all.js";`)).toEqual([
1120
+ "./v2/all.js",
1121
+ ]);
1122
+ expect(specifiersOf(`const m = await import("./v3/lazy.js");`)).toEqual([
1123
+ "./v3/lazy.js",
1124
+ ]);
1125
+ expect(specifiersOf(`const m = require("./v3/legacy.js");`)).toEqual([
1126
+ "./v3/legacy.js",
1127
+ ]);
1128
+ expect(specifiersOf(`import a = require("./v1/legacy.js");`)).toEqual([
1129
+ "./v1/legacy.js",
1130
+ ]);
1131
+ expect(specifiersOf(`type A = import("./v1/types.js").A;`)).toEqual([
1132
+ "./v1/types.js",
1133
+ ]);
1134
+ expect(
1135
+ specifiersOf(
1136
+ `import {\n a,\n} from\n "./v1/multiline.js";\nexport { b };`,
1137
+ ),
1138
+ ).toEqual(["./v1/multiline.js"]);
1139
+ });
1140
+
1141
+ test("import-shaped text in comments and strings is not an edge", () => {
1142
+ expect(
1143
+ specifiersOf(`// import { x } from "./v1/x.js";\nconst a = 1;`),
1144
+ ).toEqual([]);
1145
+ expect(
1146
+ specifiersOf(
1147
+ `/** Replaces \`import { x } from "./v1/x.js"\`. */\nconst a = 1;`,
1148
+ ),
1149
+ ).toEqual([]);
1150
+ expect(
1151
+ specifiersOf(`throw new Error('add import { x } from "./v2/x.js"');`),
1152
+ ).toEqual([]);
1153
+ expect(specifiersOf('const hint = `import("./v3/x.js")`;')).toEqual([]);
1154
+ });
1155
+ });