@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.
- package/AGENTS.md +1 -1
- package/ARCHITECTURE.md +4 -5
- package/README.md +1 -1
- package/docs/architecture/memory.md +60 -23
- package/docs/error-handling.md +3 -3
- package/docs/guardian-request-flow.md +112 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/app.d.ts +28 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/app.d.ts.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.d.ts +2 -2
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.d.ts.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/index.js.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/message.d.ts +2 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/dist/events/message.d.ts.map +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@slack/types/package.json +1 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
- package/node_modules/@vellumai/gateway-client/package.json +1 -1
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +6 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +34 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +9 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +449 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +468 -0
- package/openapi.yaml +264 -14
- package/package.json +2 -2
- package/src/__tests__/acp-session.test.ts +3 -3
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +3 -0
- package/src/__tests__/anthropic-provider.test.ts +40 -0
- package/src/__tests__/app-conversation-ids-backfill.test.ts +33 -2
- package/src/__tests__/app-conversation-ids.test.ts +204 -0
- package/src/__tests__/approval-cascade.test.ts +5 -5
- package/src/__tests__/approval-routes-http.test.ts +3 -3
- package/src/__tests__/ask-question-resolver.test.ts +207 -0
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +16 -16
- package/src/__tests__/assistant-event-hub-targeted.test.ts +13 -11
- package/src/__tests__/assistant-event-hub.test.ts +13 -11
- package/src/__tests__/assistant-event.test.ts +12 -9
- package/src/__tests__/assistant-feature-flag-guard.test.ts +3 -3
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +1 -1
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -58
- package/src/__tests__/assistant-stream-state.test.ts +17 -7
- package/src/__tests__/avatar-identity-sync.test.ts +3 -3
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +134 -18
- package/src/__tests__/call-start-guardian-guard.test.ts +27 -0
- package/src/__tests__/channel-approvals.test.ts +0 -27
- package/src/__tests__/channel-guardian.test.ts +85 -512
- package/src/__tests__/channel-readiness-slack-remote.test.ts +151 -3
- package/src/__tests__/channel-retry-sweep.test.ts +88 -1
- package/src/__tests__/channel-setup-panel-ack.test.ts +12 -9
- package/src/__tests__/chat-reveal-guard-priming.test.ts +31 -34
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +2 -2
- package/src/__tests__/compaction-events.test.ts +8 -8
- package/src/__tests__/config-loader-platform-defaults.test.ts +54 -28
- package/src/__tests__/config-model-image-provider.test.ts +20 -0
- package/src/__tests__/config-schema-memory-substrate.test.ts +306 -0
- package/src/__tests__/config-schema.test.ts +14 -2
- package/src/__tests__/config-set-platform-guard.test.ts +11 -12
- package/src/__tests__/config-sounds-sync.test.ts +4 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +5 -0
- package/src/__tests__/context-search-memory-source.test.ts +1 -1
- package/src/__tests__/context-search-memory-v2-source.test.ts +6 -6
- package/src/__tests__/context-token-estimator.test.ts +54 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +12 -3
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +13 -4
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +4 -4
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +22 -13
- package/src/__tests__/conversation-agent-loop.test.ts +39 -30
- package/src/__tests__/conversation-confirmation-signals.test.ts +14 -14
- package/src/__tests__/conversation-evictor.test.ts +49 -0
- package/src/__tests__/conversation-fork-crud.test.ts +1 -1
- package/src/__tests__/conversation-group-tools.test.ts +279 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +5 -5
- package/src/__tests__/conversation-lineage.test.ts +235 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +5 -5
- package/src/__tests__/conversation-message-sync-tags.test.ts +4 -4
- package/src/__tests__/conversation-queue.test.ts +81 -81
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +8 -1
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +8 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +10 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -2
- package/src/__tests__/conversation-slash-queue.test.ts +11 -11
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -2
- package/src/__tests__/conversation-speed-override.test.ts +2 -2
- package/src/__tests__/conversation-summarize-route.test.ts +9 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +18 -16
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +17 -17
- package/src/__tests__/conversation-surfaces-app-open.test.ts +7 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +7 -7
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +9 -9
- package/src/__tests__/conversation-surfaces-standalone.test.ts +12 -12
- package/src/__tests__/conversation-surfaces-state-update.test.ts +8 -5
- package/src/__tests__/conversation-surfaces-table-action.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +46 -46
- package/src/__tests__/conversation-sync-tags.test.ts +5 -5
- package/src/__tests__/conversation-wait-for-idle.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -3
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -3
- package/src/__tests__/copy-composer-tc-templates.test.ts +3 -131
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-assistant-events.test.ts +14 -15
- package/src/__tests__/db-test-helpers.ts +13 -27
- package/src/__tests__/disk-pressure-routes.test.ts +12 -3
- package/src/__tests__/document-tool-security.test.ts +95 -0
- package/src/__tests__/drain-kick-guard.test.ts +232 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +19 -6
- package/src/__tests__/emit-event-signal.test.ts +5 -6
- package/src/__tests__/encrypted-store-test-helpers.ts +10 -20
- package/src/__tests__/events-tail-route.test.ts +6 -4
- package/src/__tests__/feature-flag-test-helpers.ts +10 -20
- package/src/__tests__/file-edit-tool.test.ts +24 -4
- package/src/__tests__/file-list-tool.test.ts +2 -1
- package/src/__tests__/file-read-tool.test.ts +62 -10
- package/src/__tests__/file-write-tool.test.ts +24 -5
- package/src/__tests__/followup-tools.test.ts +58 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +99 -0
- package/src/__tests__/graph-extraction-event-date.test.ts +1 -1
- package/src/__tests__/guardian-question-mode.test.ts +42 -3
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +283 -0
- package/src/__tests__/guardian-vellum-destination-pinning.test.ts +221 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +10 -10
- package/src/__tests__/host-browser-proxy.test.ts +1 -1
- package/src/__tests__/host-file-edit-tool.test.ts +3 -3
- package/src/__tests__/host-file-read-tool.test.ts +2 -2
- package/src/__tests__/host-file-write-tool.test.ts +7 -3
- package/src/__tests__/host-shell-tool.test.ts +17 -5
- package/src/__tests__/host-transfer-pending-interactions.test.ts +8 -8
- package/src/__tests__/image-credentials.test.ts +65 -8
- package/src/__tests__/injection-block.test.ts +4 -4
- package/src/__tests__/injector-chain.test.ts +1 -1
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-registry-order-guard.test.ts +4 -9
- package/src/__tests__/injector-v3-suppression.test.ts +6 -4
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +27 -28
- package/src/__tests__/list-messages-page-latest.test.ts +2 -2
- package/src/__tests__/llm-usage-store.test.ts +67 -4
- package/src/__tests__/log-redact.test.ts +18 -0
- package/src/__tests__/managed-profile-guard.test.ts +27 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +62 -11
- package/src/__tests__/memory-identity-context-parity.test.ts +1 -1
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +9 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +4 -4
- package/src/__tests__/notification-decision-strategy.test.ts +115 -11
- package/src/__tests__/notification-deep-link.test.ts +12 -12
- package/src/__tests__/notification-vellum-adapter.test.ts +8 -8
- package/src/__tests__/outbound-slack-persistence.test.ts +8 -8
- package/src/__tests__/path-policy.test.ts +315 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +6 -6
- package/src/__tests__/persistence-layering-guard.test.ts +2 -2
- package/src/__tests__/pkb-autoinject.test.ts +1 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -0
- package/src/__tests__/platform-callback-registration.test.ts +10 -10
- package/src/__tests__/plugin-event-hub-facade.test.ts +22 -15
- package/src/__tests__/plugin-import-boundary-guard.test.ts +15 -15
- package/src/__tests__/processing-flag-persist-failure.test.ts +0 -4
- package/src/__tests__/provider-platform-proxy-integration.test.ts +0 -1
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +5 -5
- package/src/__tests__/require-fresh-approval.test.ts +38 -38
- package/src/__tests__/resolve-app-id.test.ts +32 -0
- package/src/__tests__/run-due-schedules.test.ts +72 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +11 -10
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/schedule-routes.test.ts +44 -5
- package/src/__tests__/schedule-store.test.ts +3 -3
- package/src/__tests__/schedule-tools.test.ts +176 -0
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +3 -3
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -3
- package/src/__tests__/secret-response-routing.test.ts +3 -3
- package/src/__tests__/secret-routes-platform-proxy.test.ts +4 -7
- package/src/__tests__/secret-scanner.test.ts +36 -0
- package/src/__tests__/send-endpoint-busy.test.ts +7 -7
- package/src/__tests__/slack-required-scopes-mirror.test.ts +81 -0
- package/src/__tests__/starter-task-flow.test.ts +26 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +2 -2
- package/src/__tests__/subagent-disposal.test.ts +40 -11
- package/src/__tests__/subagent-fork-notifications.test.ts +4 -4
- package/src/__tests__/subagent-fork-prompt-role.test.ts +3 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +4 -4
- package/src/__tests__/subagent-manager-notify.test.ts +73 -14
- package/src/__tests__/subagent-notify-parent.test.ts +48 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +62 -14
- package/src/__tests__/subagent-tools.test.ts +64 -0
- package/src/__tests__/sync-message-contract.test.ts +3 -3
- package/src/__tests__/terminal-tools.test.ts +12 -3
- package/src/__tests__/test-preload.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +587 -43
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +7 -4
- package/src/__tests__/tool-executor.test.ts +116 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +5 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -29
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +5 -5
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/tool-start-timestamp.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -22
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +13 -13
- package/src/__tests__/ui-work-result-surface.test.ts +3 -3
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +6 -6
- package/src/__tests__/voice-session-bridge.test.ts +44 -44
- package/src/__tests__/workspace-migration-134-image-generation-mode-to-provider.test.ts +284 -0
- package/src/__tests__/workspace-policy.test.ts +482 -2
- package/src/acp/__tests__/client-handler.test.ts +3 -3
- package/src/acp/__tests__/session-manager-persistence.test.ts +10 -10
- package/src/acp/__tests__/session-manager-resume.test.ts +7 -7
- package/src/acp/client-handler.ts +3 -3
- package/src/acp/session-manager.ts +8 -8
- package/src/api/events/subagent-event.ts +2 -2
- package/src/api/responses/memory-v2-activation-log.ts +2 -2
- package/src/api/responses/subagent-detail.ts +13 -0
- package/src/approvals/AGENTS.md +2 -0
- package/src/approvals/guardian-request-resolvers.ts +118 -24
- package/src/apps/app-store.ts +95 -4
- package/src/calls/__tests__/voice-control-protocol.test.ts +57 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +229 -7
- package/src/calls/__tests__/voice-triage-escalate.test.ts +3 -4
- package/src/calls/call-controller.ts +27 -27
- package/src/calls/call-conversation-messages.ts +58 -18
- package/src/calls/voice-control-protocol.ts +56 -0
- package/src/calls/voice-session-bridge.ts +178 -65
- package/src/calls/voice-triage-escalate.ts +2 -3
- package/src/channels/config.ts +8 -0
- package/src/channels/types.ts +1 -0
- package/src/cli/commands/config.ts +49 -13
- package/src/cli/commands/memory/index.help.ts +4 -2
- package/src/cli/commands/memory/memory-retrospective.ts +5 -0
- package/src/cli/commands/webhooks.help.ts +9 -4
- package/src/cli/lib/bundled-marketplace.json +12 -0
- package/src/cli.ts +2 -109
- package/src/config/__tests__/deployment-context-defaults.test.ts +9 -0
- package/src/config/__tests__/substrate-twin-shadowing.test.ts +191 -0
- package/src/config/assistant-feature-flags.ts +1 -1
- package/src/config/bundled-skills/conversation-groups/SKILL.md +29 -0
- package/src/config/bundled-skills/conversation-groups/TOOLS.json +58 -0
- package/src/config/bundled-skills/conversation-groups/tools/group-create.ts +12 -0
- package/src/config/bundled-skills/conversation-groups/tools/group-list.ts +12 -0
- package/src/config/bundled-skills/conversation-groups/tools/move-to-group.ts +12 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +13 -7
- package/src/config/bundled-skills/schedule/SKILL.md +2 -0
- package/src/config/feature-flag-cache.ts +6 -16
- package/src/config/feature-flag-registry.json +14 -38
- package/src/config/loader.ts +7 -1
- package/src/config/memory-tier.ts +15 -10
- package/src/config/memory-v3-gate.test.ts +278 -1
- package/src/config/memory-v3-gate.ts +88 -26
- package/src/config/raw-config-utils.ts +1 -1
- package/src/config/schemas/__tests__/live-voice.test.ts +27 -16
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/live-voice.ts +33 -7
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-retrospective.ts +13 -0
- package/src/config/schemas/memory-substrate.ts +183 -0
- package/src/config/schemas/memory-v2.ts +3 -2
- package/src/config/schemas/memory-v3.ts +11 -4
- package/src/config/schemas/memory.ts +35 -1
- package/src/config/schemas/services.ts +10 -2
- package/src/config/substrate-twin-shadowing.ts +195 -0
- package/src/config/webhook-routing.ts +67 -0
- package/src/context/compactor.ts +10 -3
- package/src/context/token-estimator.ts +21 -0
- package/src/daemon/__tests__/channel-ui-capability.test.ts +85 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +3 -3
- package/src/daemon/__tests__/embedding-reconcile.test.ts +3 -3
- package/src/daemon/__tests__/inference-profile-notification.test.ts +3 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -5
- package/src/daemon/__tests__/web-search-status-text.test.ts +2 -2
- package/src/daemon/channel-ui-capability.ts +70 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -4
- package/src/daemon/conversation-agent-loop.ts +12 -5
- package/src/daemon/conversation-evictor.ts +26 -4
- package/src/daemon/conversation-launch.ts +11 -14
- package/src/daemon/conversation-lineage.ts +76 -0
- package/src/daemon/conversation-messaging.ts +14 -14
- package/src/daemon/conversation-notifiers.ts +2 -2
- package/src/daemon/conversation-process.ts +133 -21
- package/src/daemon/conversation-queue-manager.ts +7 -7
- package/src/daemon/conversation-runtime-assembly.ts +16 -0
- package/src/daemon/conversation-store.ts +11 -3
- package/src/daemon/conversation-surfaces.ts +8 -14
- package/src/daemon/conversation-tool-setup.ts +13 -7
- package/src/daemon/conversation-usage.ts +12 -11
- package/src/daemon/conversation.ts +38 -15
- package/src/daemon/disk-pressure-guard.ts +5 -12
- package/src/daemon/embedding-reconcile.ts +14 -9
- package/src/daemon/handlers/config-channels.ts +40 -134
- package/src/daemon/handlers/config-model.ts +15 -7
- package/src/daemon/handlers/conversations.ts +4 -2
- package/src/daemon/handlers/shared.ts +29 -0
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/lifecycle.ts +13 -0
- package/src/daemon/message-protocol.ts +8 -144
- package/src/daemon/message-types/computer-use.ts +0 -12
- package/src/daemon/message-types/conversations.ts +9 -345
- package/src/daemon/message-types/host-app-control.ts +0 -9
- package/src/daemon/message-types/host-browser.ts +0 -7
- package/src/daemon/message-types/host-ui-snapshot.ts +0 -7
- package/src/daemon/message-types/messages.ts +0 -48
- package/src/daemon/message-types/notifications.ts +0 -7
- package/src/daemon/message-types/skills.ts +0 -5
- package/src/daemon/message-types/surfaces.ts +0 -9
- package/src/daemon/message-types/sync.ts +0 -2
- package/src/daemon/process-message.ts +5 -5
- package/src/daemon/skill-memory-refresh.ts +5 -5
- package/src/daemon/tool-side-effects.ts +32 -16
- package/src/daemon/trust-context.ts +1 -1
- package/src/daemon/wake-conversation-ops.ts +4 -4
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +100 -7
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +67 -0
- package/src/heartbeat/heartbeat-run-store.ts +81 -4
- package/src/heartbeat/heartbeat-service.ts +32 -1
- package/src/home/__tests__/feed-writer.test.ts +7 -7
- package/src/home/__tests__/home-content-refresh.test.ts +3 -0
- package/src/home/__tests__/suggested-prompts.test.ts +1 -0
- package/src/home/feed-writer.ts +6 -13
- package/src/home/home-content-refresh.ts +6 -9
- package/src/home/relationship-state-writer.ts +5 -12
- package/src/home/suggested-prompts-cache.ts +6 -19
- package/src/ipc/__tests__/events-publish-client.test.ts +67 -0
- package/src/ipc/events-publish-client.ts +44 -0
- package/src/ipc/routes/__tests__/conversation-sync-ipc-routes.test.ts +2 -2
- package/src/ipc/routes/__tests__/events-ipc-routes.test.ts +38 -9
- package/src/ipc/routes/events-ipc-routes.ts +11 -31
- package/src/live-voice/__tests__/front-decision.test.ts +4 -176
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +276 -68
- package/src/live-voice/__tests__/live-voice-continuation-fork-hydration.test.ts +286 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +6 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +185 -46
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +24 -59
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +38 -54
- package/src/live-voice/__tests__/live-voice-vad.test.ts +1154 -636
- package/src/live-voice/front-decision.ts +5 -155
- package/src/live-voice/live-voice-metrics.ts +4 -1
- package/src/live-voice/live-voice-session.ts +1288 -439
- package/src/live-voice/progress-phrases.ts +17 -0
- package/src/live-voice/protocol.ts +15 -0
- package/src/media/app-icon-generator.ts +15 -8
- package/src/media/avatar-router.ts +9 -5
- package/src/media/image-credentials.ts +42 -10
- package/src/media/image-service.ts +1 -13
- package/src/media/types.ts +26 -3
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/__tests__/broadcaster.test.ts +95 -0
- package/src/notifications/adapters/macos.ts +3 -3
- package/src/notifications/broadcaster.ts +57 -0
- package/src/notifications/copy-composer.ts +0 -15
- package/src/notifications/decision-engine.ts +21 -22
- package/src/notifications/guardian-question-mode.ts +57 -2
- package/src/notifications/home-feed-side-effect.ts +4 -0
- package/src/notifications/signal.ts +6 -4
- package/src/notifications/trusted-contact-payloads.ts +3 -4
- package/src/notifications/vellum-card-affinity.ts +39 -0
- package/src/oauth/AGENTS.md +1 -1
- package/src/permissions/channel-permission-query.test.ts +69 -0
- package/src/permissions/channel-permission-query.ts +73 -1
- package/src/permissions/checker.test.ts +298 -15
- package/src/permissions/checker.ts +102 -36
- package/src/permissions/confirmation-guardian-request.test.ts +2 -2
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/gateway-threshold-reader.test.ts +224 -17
- package/src/permissions/gateway-threshold-reader.ts +82 -10
- package/src/permissions/ipc-risk-types.ts +8 -0
- package/src/permissions/prompter.ts +5 -5
- package/src/permissions/question-guardian-request.test.ts +222 -0
- package/src/permissions/question-guardian-request.ts +179 -0
- package/src/permissions/question-prompter.test.ts +8 -8
- package/src/permissions/question-prompter.ts +18 -0
- package/src/permissions/secret-prompter.ts +2 -4
- package/src/permissions/workspace-policy.ts +269 -34
- package/src/persistence/__tests__/lifecycle-quiesce.test.ts +82 -0
- package/src/persistence/__tests__/message-content-file.test.ts +58 -3
- package/src/persistence/conversation-crud.ts +93 -0
- package/src/persistence/conversation-key-store.ts +63 -0
- package/src/persistence/conversation-parent.ts +27 -0
- package/src/persistence/db-async-query.ts +1 -1
- package/src/persistence/db-singleton.ts +14 -27
- package/src/persistence/delivery-crud.ts +45 -0
- package/src/persistence/embeddings/embedding-backend.ts +2 -1
- package/src/persistence/embeddings/embedding-identity.ts +7 -3
- package/src/persistence/job-utils.ts +32 -27
- package/src/persistence/jobs-store.ts +92 -3
- package/src/persistence/lifecycle-quiesce.ts +80 -0
- package/src/persistence/llm-usage-store.ts +45 -8
- package/src/persistence/message-content-file.ts +4 -4
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +1 -1
- package/src/persistence/migrations/351-schedule-skill-script-handoff.ts +39 -0
- package/src/persistence/migrations/352-drop-schedule-skill-script-handoff.test.ts +83 -0
- package/src/persistence/migrations/352-drop-schedule-skill-script-handoff.ts +36 -0
- package/src/persistence/migrations/353-add-llm-usage-conversation-type.test.ts +110 -0
- package/src/persistence/migrations/353-add-llm-usage-conversation-type.ts +49 -0
- package/src/persistence/migrations/354-backfill-app-conversation-lineage.ts +41 -0
- package/src/persistence/schema/infrastructure.ts +11 -0
- package/src/persistence/steps.ts +9 -1
- package/src/plugin-api/__tests__/publish-event-forwarding.test.ts +113 -0
- package/src/plugin-api/__tests__/publish-event.test.ts +57 -0
- package/src/plugin-api/conversation-turn.ts +4 -4
- package/src/plugin-api/event-hub-facade.ts +15 -6
- package/src/plugin-api/index.ts +10 -4
- package/src/plugin-api/publish-event.ts +36 -0
- package/src/plugins/AGENTS.md +2 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/index.ts +9 -13
- package/src/plugins/defaults/memory/AGENTS.md +775 -0
- package/src/plugins/defaults/memory/__tests__/capability-seed-embed-gate.test.ts +186 -0
- package/src/plugins/defaults/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/indexer-tier-dispatch.test.ts +129 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-quiesce-gate.test.ts +91 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v1-graph-substrate-noop.test.ts +342 -0
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +9 -1
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +36 -1
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-sweep-defer.test.ts +9 -1
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +44 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +151 -10
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1155 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -3
- package/src/plugins/defaults/memory/__tests__/tools-delete-memory-page.test.ts +1 -1
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +71 -35
- package/src/plugins/defaults/memory/context-search/sources/memory.ts +5 -1
- package/src/plugins/defaults/memory/fork-conversation-memory.ts +4 -4
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +80 -10
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/capability-seed.ts +24 -10
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +123 -49
- package/src/plugins/defaults/memory/graph/graph-search.ts +6 -118
- package/src/plugins/defaults/memory/graph/in-context-tracker.test.ts +173 -0
- package/src/plugins/defaults/memory/graph/in-context-tracker.ts +109 -0
- package/src/plugins/defaults/memory/graph/store.ts +1 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +5 -2
- package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +1 -1
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +6 -9
- package/src/plugins/defaults/memory/hooks/__tests__/user-prompt-submit.test.ts +30 -16
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +18 -14
- package/src/plugins/defaults/memory/indexer.ts +146 -89
- package/src/plugins/defaults/memory/injectors.ts +86 -41
- package/src/plugins/defaults/memory/job-handlers.ts +130 -39
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +2 -2
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +22 -10
- package/src/plugins/defaults/memory/jobs-worker.ts +279 -208
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +4 -2
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +40 -3
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +99 -11
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-routes.test.ts +5 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +3 -4
- package/src/plugins/defaults/memory/src/memory-graph-routes.ts +2 -1
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +7 -5
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +51 -19
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +31 -20
- package/src/plugins/defaults/memory/startup.ts +58 -21
- package/src/plugins/defaults/memory/substrate/__tests__/boot-maintenance.test.ts +335 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/cli-command-store.test.ts +14 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/consolidation-job.test.ts +51 -21
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/edge-index.test.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/frontmatter-sweep.test.ts +3 -3
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/page-index.test.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/page-store.test.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/prompts-consolidation.test.ts +2 -2
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/qdrant.test.ts +23 -14
- package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +262 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sim.test.ts +9 -12
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/skill-content.test.ts +2 -2
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/skill-store.test.ts +9 -9
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sparse-bm25.test.ts +12 -5
- package/src/plugins/defaults/memory/substrate/__tests__/spread.test.ts +276 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/static-context.test.ts +17 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/sweep-job.test.ts +9 -9
- package/src/plugins/defaults/memory/{v3/substrate/memory-v2-startup.ts → substrate/boot-maintenance.ts} +43 -24
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/cli-command-store.ts +13 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/consolidation-job.ts +51 -27
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/constants.ts +1 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/edge-index.ts +28 -9
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/frontmatter-sweep.ts +14 -7
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/injected-block-slugs.ts +9 -3
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/page-index.ts +29 -10
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/page-store.ts +35 -12
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/prompts/consolidation.ts +14 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/prompts/sweep.ts +4 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/qdrant.ts +96 -36
- package/src/plugins/defaults/memory/substrate/reembed-job.ts +54 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/sim.ts +26 -15
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/skill-content.ts +1 -1
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/skill-store.ts +13 -8
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/sparse-bm25.ts +27 -10
- package/src/plugins/defaults/memory/substrate/spread.ts +165 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/static-context.ts +12 -7
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/sweep-job.ts +20 -15
- package/src/plugins/defaults/memory/substrate/tuning.ts +93 -0
- package/src/plugins/defaults/memory/{v3/substrate → substrate}/types.ts +7 -4
- package/src/plugins/defaults/memory/tools.test.ts +1 -1
- package/src/plugins/defaults/memory/tools.ts +1 -1
- package/src/plugins/defaults/memory/v1/README.md +5 -0
- package/src/plugins/defaults/memory/{__tests__ → v1/__tests__}/filing-jobs.test.ts +4 -4
- package/src/plugins/defaults/memory/{filing-jobs.ts → v1/filing-jobs.ts} +3 -3
- package/src/plugins/defaults/memory/{graph → v1/graph}/bootstrap.test.ts +68 -6
- package/src/plugins/defaults/memory/{graph → v1/graph}/bootstrap.ts +40 -21
- package/src/plugins/defaults/memory/{graph → v1/graph}/consolidation.ts +14 -14
- package/src/plugins/defaults/memory/{graph → v1/graph}/decay.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/decay.ts +3 -3
- package/src/plugins/defaults/memory/{graph → v1/graph}/extraction-job.ts +5 -5
- package/src/plugins/defaults/memory/{graph → v1/graph}/extraction.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/extraction.ts +20 -14
- package/src/plugins/defaults/memory/{graph → v1/graph}/graph-search.test.ts +3 -3
- package/src/plugins/defaults/memory/v1/graph/graph-search.ts +123 -0
- package/src/plugins/defaults/memory/{graph → v1/graph}/injection.test.ts +1 -118
- package/src/plugins/defaults/memory/{graph → v1/graph}/injection.ts +9 -111
- package/src/plugins/defaults/memory/{graph → v1/graph}/narrative.ts +6 -6
- package/src/plugins/defaults/memory/{graph → v1/graph}/pattern-scan.ts +5 -5
- package/src/plugins/defaults/memory/{graph → v1/graph}/retriever.test.ts +135 -42
- package/src/plugins/defaults/memory/{graph → v1/graph}/retriever.ts +40 -22
- package/src/plugins/defaults/memory/{graph → v1/graph}/scoring.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/scoring.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/serendipity.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/triggers.test.ts +1 -1
- package/src/plugins/defaults/memory/{graph → v1/graph}/triggers.ts +1 -1
- package/src/plugins/defaults/memory/{identity-context.ts → v1/identity-context.ts} +4 -4
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/backfill.ts +6 -6
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/embedding.test.ts +6 -6
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/embedding.ts +6 -6
- package/src/plugins/defaults/memory/{job-handlers → v1/job-handlers}/index-maintenance.ts +8 -8
- package/src/plugins/defaults/memory/{jobs → v1/jobs}/embed-pkb-file.test.ts +7 -7
- package/src/plugins/defaults/memory/{jobs → v1/jobs}/embed-pkb-file.ts +5 -5
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/autoinject.ts +1 -1
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/context.ts +2 -2
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-index.test.ts +3 -3
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-index.ts +2 -2
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-reconcile.test.ts +3 -3
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-reconcile.ts +2 -2
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-search.test.ts +3 -3
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/pkb-search.ts +6 -6
- package/src/plugins/defaults/memory/{pkb → v1/pkb}/types.ts +3 -5
- package/src/plugins/defaults/memory/{pkb-schedule.ts → v1/pkb-schedule.ts} +2 -2
- package/src/plugins/defaults/memory/{search/semantic.ts → v1/semantic-search.ts} +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/activation-store.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +11 -274
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +9 -131
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +14 -10
- package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +7 -5
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +12 -6
- package/src/plugins/defaults/memory/{memory-v2-activation-log-store.ts → v2/activation-log-store.ts} +2 -2
- package/src/plugins/defaults/memory/v2/activation-store.ts +22 -8
- package/src/plugins/defaults/memory/v2/activation.ts +30 -163
- package/src/plugins/defaults/memory/v2/backfill-jobs.ts +7 -54
- package/src/plugins/defaults/memory/{memory-v2-concept-frequency.ts → v2/concept-frequency.ts} +3 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +1 -1
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +2 -2
- package/src/plugins/defaults/memory/v2/harness/retriever.ts +1 -1
- package/src/plugins/defaults/memory/v2/injection.ts +36 -26
- package/src/plugins/defaults/memory/v2/migration.ts +2 -2
- package/src/plugins/defaults/memory/{rerank-local.ts → v2/rerank-local.ts} +44 -18
- package/src/plugins/defaults/memory/v2/reranker.ts +29 -11
- package/src/plugins/defaults/memory/v2/router.ts +38 -15
- package/src/plugins/defaults/memory/v3/__tests__/candidate-match.test.ts +5 -3
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +21 -8
- package/src/plugins/defaults/memory/v3/__tests__/edge.test.ts +1 -1
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +1 -1
- package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +4 -1
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +181 -3
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +3 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +40 -50
- package/src/plugins/defaults/memory/v3/__tests__/tuning-profile.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/candidate-match.ts +2 -2
- package/src/plugins/defaults/memory/v3/capabilities.ts +6 -4
- package/src/plugins/defaults/memory/v3/card.ts +25 -9
- package/src/plugins/defaults/memory/v3/edge.ts +52 -18
- package/src/plugins/defaults/memory/v3/gate.ts +4 -4
- package/src/plugins/defaults/memory/v3/injector.ts +10 -3
- package/src/plugins/defaults/memory/v3/maintain-job.ts +3 -3
- package/src/plugins/defaults/memory/v3/orchestrate.ts +100 -18
- package/src/plugins/defaults/memory/v3/page-content.ts +26 -10
- package/src/plugins/defaults/memory/v3/prune.ts +40 -16
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +34 -12
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +9 -13
- package/src/plugins/defaults/memory/v3/span-query.test.ts +86 -0
- package/src/plugins/defaults/memory/v3/span-query.ts +71 -0
- package/src/plugins/defaults/memory/v3/tuning-profile.ts +3 -0
- package/src/plugins/defaults/memory/v3/types.ts +4 -1
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +2 -2
- package/src/plugins/defaults/memory/validation.ts +1 -1
- package/src/plugins/defaults/memory/worker.ts +2 -1
- package/src/plugins/defaults/platform-hosted/__tests__/reengage.test.ts +104 -12
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +104 -12
- package/src/plugins/types.ts +2 -2
- package/src/prompts/sections.ts +4 -16
- package/src/providers/__tests__/registry-native-web-search.test.ts +47 -2
- package/src/providers/anthropic/client.ts +15 -6
- package/src/providers/content-block-schema.ts +26 -0
- package/src/providers/inference/__tests__/connections-credential-normalize.test.ts +87 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/inference/connections.ts +30 -14
- package/src/providers/model-catalog.ts +77 -0
- package/src/providers/openai/__tests__/chat-completions-input-audio.test.ts +260 -0
- package/src/providers/openai/chat-completions-provider.ts +65 -17
- package/src/providers/openai/input-audio.ts +76 -0
- package/src/providers/openrouter/client.ts +0 -2
- package/src/providers/platform-proxy/constants.ts +4 -0
- package/src/providers/poolside/client.ts +42 -0
- package/src/providers/registry.ts +40 -8
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +39 -1
- package/src/providers/vercel-ai-gateway/client.ts +0 -2
- package/src/runtime/AGENTS.md +7 -5
- package/src/runtime/__tests__/question-request-guardian-bridge.test.ts +173 -0
- package/src/runtime/__tests__/question-resolution.test.ts +111 -0
- package/src/runtime/access-request-helper.ts +7 -15
- package/src/runtime/assistant-event-hub.ts +43 -31
- package/src/runtime/assistant-event-publish-options.ts +31 -0
- package/src/runtime/assistant-event.ts +19 -30
- package/src/runtime/assistant-stream-state.ts +4 -4
- package/src/runtime/channel-approvals.ts +0 -25
- package/src/runtime/channel-readiness-service.ts +99 -40
- package/src/runtime/channel-retry-sweep.ts +38 -15
- package/src/runtime/confirmation-request-guardian-bridge.ts +4 -0
- package/src/runtime/guardian-reply-router.ts +108 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/process-role.ts +42 -0
- package/src/runtime/question-request-guardian-bridge.ts +184 -0
- package/src/runtime/question-resolution.ts +153 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/subagents-routes.test.ts +250 -0
- package/src/runtime/routes/__tests__/webhook-routes.test.ts +201 -0
- package/src/runtime/routes/acp-routes.ts +2 -2
- package/src/runtime/routes/bookmark-routes.ts +3 -15
- package/src/runtime/routes/canned-message-complete.ts +2 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -2
- package/src/runtime/routes/conversation-list-routes.ts +3 -19
- package/src/runtime/routes/conversation-management-routes.ts +1 -5
- package/src/runtime/routes/conversation-query-routes.ts +52 -8
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-request-routes.ts +0 -1
- package/src/runtime/routes/filing-routes.ts +7 -7
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-approval-prompt.ts +2 -2
- package/src/runtime/routes/image-generation-routes.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +17 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +50 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +19 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -4
- package/src/runtime/routes/inbound-stages/inbound-content-prep.test.ts +129 -0
- package/src/runtime/routes/inbound-stages/inbound-content-prep.ts +141 -9
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/lifecycle-routes.ts +199 -0
- package/src/runtime/routes/platform-routes.ts +3 -8
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +7 -7
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +5 -5
- package/src/runtime/routes/question-routes.ts +34 -87
- package/src/runtime/routes/secret-routes.ts +11 -1
- package/src/runtime/routes/settings-routes.ts +8 -18
- package/src/runtime/routes/subagents-routes.ts +35 -8
- package/src/runtime/routes/upgrade-broadcast-routes.ts +4 -5
- package/src/runtime/routes/webhook-routes.ts +89 -30
- package/src/runtime/routes/workspace-routes.test.ts +7 -4
- package/src/runtime/slack-reply-session.test.ts +12 -12
- package/src/runtime/slack-reply-session.ts +17 -17
- package/src/runtime/sync/sync-publisher.test.ts +7 -5
- package/src/runtime/tool-grant-request-helper.ts +4 -0
- package/src/schedule/schedule-store.ts +70 -0
- package/src/schedule/scheduler.ts +27 -0
- package/src/security/AGENTS.md +1 -1
- package/src/security/credential-key.ts +17 -0
- package/src/security/secret-ingress.ts +14 -105
- package/src/security/secret-patterns.ts +8 -139
- package/src/security/secret-scanner.ts +8 -3
- package/src/security/store-path-override.ts +6 -16
- package/src/sequence/store.ts +8 -0
- package/src/signals/emit-event.ts +5 -10
- package/src/signals/event-stream.ts +20 -10
- package/src/subagent/manager.ts +76 -25
- package/src/subagent/notify.ts +8 -0
- package/src/subagent/types.ts +13 -3
- package/src/telegram/__tests__/webhook-health.test.ts +442 -0
- package/src/telegram/webhook-health.ts +478 -0
- package/src/telemetry/AGENTS.md +6 -0
- package/src/telemetry/memory-corpus-size.test.ts +129 -0
- package/src/telemetry/memory-corpus-size.ts +184 -0
- package/src/telemetry/memory-tier-reporter.test.ts +67 -2
- package/src/telemetry/memory-tier-reporter.ts +30 -3
- package/src/tools/__tests__/conversation-group-tool-input-schemas.test.ts +61 -0
- package/src/tools/__tests__/document-tool-input-schemas.test.ts +103 -0
- package/src/tools/__tests__/followup-call-acp-tool-input-schemas.test.ts +125 -0
- package/src/tools/__tests__/schedule-tool-input-schemas.test.ts +83 -0
- package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +116 -0
- package/src/tools/__tests__/tool-input-schemas.test.ts +192 -0
- package/src/tools/__tests__/tool-schema-drift-harness.ts +142 -0
- package/src/tools/acp/abort.ts +21 -1
- package/src/tools/acp/context.ts +5 -5
- package/src/tools/acp/spawn.test.ts +15 -5
- package/src/tools/acp/spawn.ts +26 -3
- package/src/tools/acp/status.ts +20 -1
- package/src/tools/acp/steer.test.ts +20 -0
- package/src/tools/acp/steer.ts +23 -2
- package/src/tools/apps/resolve-app-id.ts +15 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +65 -0
- package/src/tools/ask-question/ask-question-tool.ts +68 -92
- package/src/tools/browser/browser-screencast.ts +6 -6
- package/src/tools/calls/call-end.ts +24 -3
- package/src/tools/calls/call-start.ts +35 -11
- package/src/tools/calls/call-status.ts +20 -1
- package/src/tools/conversation-groups/group_create.ts +63 -0
- package/src/tools/conversation-groups/group_list.ts +20 -0
- package/src/tools/conversation-groups/group_shared.ts +52 -0
- package/src/tools/conversation-groups/move_to_group.ts +106 -0
- package/src/tools/document/document-comment-tool.test.ts +49 -0
- package/src/tools/document/document-comment-tool.ts +45 -6
- package/src/tools/document/document-tool.ts +142 -24
- package/src/tools/executor.ts +27 -13
- package/src/tools/filesystem/edit.ts +62 -60
- package/src/tools/filesystem/list.ts +36 -29
- package/src/tools/filesystem/read.ts +56 -39
- package/src/tools/filesystem/write.ts +44 -38
- package/src/tools/followups/followup_create.ts +41 -12
- package/src/tools/followups/followup_list.ts +34 -6
- package/src/tools/followups/followup_resolve.ts +26 -4
- package/src/tools/host-filesystem/edit.ts +50 -60
- package/src/tools/host-filesystem/read.ts +45 -39
- package/src/tools/host-filesystem/write.ts +31 -36
- package/src/tools/host-terminal/host-shell.ts +76 -62
- package/src/tools/policy-context.ts +27 -10
- package/src/tools/schedule/create.ts +76 -33
- package/src/tools/schedule/delete.ts +21 -2
- package/src/tools/schedule/list.ts +25 -3
- package/src/tools/schedule/update.ts +105 -48
- package/src/tools/shared/filesystem/path-policy.ts +246 -40
- package/src/tools/shared/zod-tool-schema.ts +117 -0
- package/src/tools/subagent/abort.ts +12 -4
- package/src/tools/subagent/consult-deadline.ts +9 -3
- package/src/tools/subagent/message.ts +21 -5
- package/src/tools/subagent/notify-parent.ts +44 -23
- package/src/tools/subagent/read.ts +15 -4
- package/src/tools/subagent/resolve.ts +26 -4
- package/src/tools/subagent/spawn.ts +37 -13
- package/src/tools/subagent/status.ts +12 -4
- package/src/tools/system/request-permission.ts +35 -26
- package/src/tools/terminal/shell.ts +68 -55
- package/src/tools/tool-approval-handler.ts +327 -53
- package/src/tools/tool-input-schemas.ts +85 -0
- package/src/tools/tool-types.ts +7 -0
- package/src/tools/types.ts +8 -0
- package/src/tools/workflows/manage-workflows.ts +30 -2
- package/src/tools/workflows/run-workflow.test.ts +54 -2
- package/src/tools/workflows/run-workflow.ts +32 -4
- package/src/util/fs-symlinks.ts +30 -0
- package/src/util/log-redact.ts +7 -3
- package/src/util/platform.ts +15 -0
- package/src/vellum-assistant-namespace.d.ts +80 -0
- package/src/watcher/watcher-store.ts +8 -0
- package/src/workflows/leaf-runner.test.ts +104 -2
- package/src/workflows/leaf-runner.ts +31 -6
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +2 -2
- package/src/workspace/migrations/134-image-generation-mode-to-provider.ts +128 -0
- package/src/workspace/migrations/135-copy-substrate-tunables.ts +248 -0
- package/src/workspace/migrations/__tests__/135-copy-substrate-tunables.test.ts +446 -0
- package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +2 -2
- package/src/workspace/migrations/registry.ts +4 -0
- package/tsconfig.plugin-api.json +1 -1
- package/src/__tests__/memory-retrieval-budget.test.ts +0 -49
- package/src/__tests__/secret-patterns.test.ts +0 -27
- package/src/config/__tests__/proc-to-skills.test.ts +0 -21
- package/src/config/memory-tier.test.ts +0 -44
- package/src/daemon/message-types/acp.ts +0 -20
- package/src/daemon/message-types/apps.ts +0 -11
- package/src/daemon/message-types/background-tools.ts +0 -14
- package/src/daemon/message-types/bookmarks.ts +0 -13
- package/src/daemon/message-types/contacts.ts +0 -13
- package/src/daemon/message-types/document-comments.ts +0 -14
- package/src/daemon/message-types/documents.ts +0 -13
- package/src/daemon/message-types/home.ts +0 -15
- package/src/daemon/message-types/host-bash.ts +0 -12
- package/src/daemon/message-types/host-cu.ts +0 -10
- package/src/daemon/message-types/host-file.ts +0 -12
- package/src/daemon/message-types/host-transfer.ts +0 -12
- package/src/daemon/message-types/integrations.ts +0 -90
- package/src/daemon/message-types/memory.ts +0 -10
- package/src/daemon/message-types/schedules.ts +0 -14
- package/src/daemon/message-types/settings.ts +0 -17
- package/src/daemon/message-types/subagents.ts +0 -63
- package/src/daemon/message-types/upgrades.ts +0 -14
- package/src/daemon/message-types/workflows.ts +0 -25
- package/src/daemon/message-types/workspace.ts +0 -137
- package/src/live-voice/ack-phrases.ts +0 -58
- package/src/plugins/defaults/memory/retrieval-budget.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +0 -69
- package/src/plugins/defaults/memory/v3/gate-flag.ts +0 -20
- package/src/plugins/defaults/memory/v3/substrate/__tests__/memory-v2-startup.test.ts +0 -86
- package/src/plugins/defaults/memory/v3/substrate/memory-v2-startup.test.ts +0 -205
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/concept-page-frontmatter-schema.test.ts +0 -0
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -0
- /package/src/plugins/defaults/memory/{v3/substrate → substrate}/__tests__/injected-block-slugs.test.ts +0 -0
- /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
|
+
});
|