@vellumai/assistant 0.5.6 → 0.5.7
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +1 -1
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +8 -14
- package/src/config/feature-flag-registry.json +48 -8
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +227 -51
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +12 -0
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/jobs-store.ts +30 -5
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +27 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.ts +24 -6
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +3 -50
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -193,13 +193,12 @@ describe("managed proxy integration — credential precedence", () => {
|
|
|
193
193
|
|
|
194
194
|
const provider = getProvider("anthropic");
|
|
195
195
|
|
|
196
|
-
// Unwrap RetryProvider →
|
|
197
|
-
//
|
|
198
|
-
// and AnthropicProvider stores the SDK client as
|
|
196
|
+
// Unwrap RetryProvider → AnthropicProvider to inspect the Anthropic
|
|
197
|
+
// SDK client's baseURL. RetryProvider stores the inner provider as
|
|
198
|
+
// private `inner` and AnthropicProvider stores the SDK client as
|
|
199
|
+
// private `client`.
|
|
199
200
|
const retryInner = (provider as any).inner;
|
|
200
|
-
|
|
201
|
-
const logfireInner = (retryInner as any).inner ?? retryInner;
|
|
202
|
-
const anthropicClient = (logfireInner as any).client;
|
|
201
|
+
const anthropicClient = (retryInner as any).client;
|
|
203
202
|
|
|
204
203
|
expect(anthropicClient).toBeDefined();
|
|
205
204
|
const baseURL: string = anthropicClient.baseURL;
|
|
@@ -312,9 +312,10 @@ describe("QdrantManager", () => {
|
|
|
312
312
|
expect(existsSync(pidPath)).toBe(false);
|
|
313
313
|
}, 10_000);
|
|
314
314
|
|
|
315
|
-
test("
|
|
315
|
+
test("fails fast with exit code when process exits immediately", async () => {
|
|
316
316
|
const pidPath = join(testDataDir, "qdrant", "qdrant.pid");
|
|
317
317
|
|
|
318
|
+
// GIVEN a Qdrant binary that exits immediately with code 1
|
|
318
319
|
placeFakeBinary("#!/bin/sh\nexit 1");
|
|
319
320
|
|
|
320
321
|
const port = getTestPort();
|
|
@@ -323,9 +324,34 @@ describe("QdrantManager", () => {
|
|
|
323
324
|
...FAST_TIMEOUTS,
|
|
324
325
|
});
|
|
325
326
|
|
|
326
|
-
|
|
327
|
+
// WHEN we start the manager
|
|
328
|
+
const startTime = Date.now();
|
|
329
|
+
await expect(mgr.start()).rejects.toThrow(
|
|
330
|
+
/exited with code \d+ before becoming ready/,
|
|
331
|
+
);
|
|
332
|
+
const elapsed = Date.now() - startTime;
|
|
333
|
+
|
|
334
|
+
// THEN it fails fast (well under the 100ms readyz timeout)
|
|
335
|
+
expect(elapsed).toBeLessThan(FAST_TIMEOUTS.readyzTimeoutMs);
|
|
336
|
+
|
|
337
|
+
// AND the PID file is cleaned up
|
|
327
338
|
expect(existsSync(pidPath)).toBe(false);
|
|
328
339
|
}, 10_000);
|
|
340
|
+
|
|
341
|
+
test("includes stderr in error when process crashes", async () => {
|
|
342
|
+
// GIVEN a Qdrant binary that writes to stderr before crashing
|
|
343
|
+
placeFakeBinary('#!/bin/sh\necho "fatal: storage corrupted" >&2\nexit 1');
|
|
344
|
+
|
|
345
|
+
const port = getTestPort();
|
|
346
|
+
const mgr = new QdrantManager({
|
|
347
|
+
url: `http://127.0.0.1:${port}`,
|
|
348
|
+
...FAST_TIMEOUTS,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// WHEN we start the manager
|
|
352
|
+
// THEN the error includes the stderr output
|
|
353
|
+
await expect(mgr.start()).rejects.toThrow("storage corrupted");
|
|
354
|
+
}, 10_000);
|
|
329
355
|
});
|
|
330
356
|
|
|
331
357
|
// ── Binary Detection ─────────────────────────────────────────
|
|
@@ -173,12 +173,6 @@ describe("baseline characterization: hardcoded tool loading", () => {
|
|
|
173
173
|
expect(eagerModuleToolNames).not.toContain(name);
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
|
-
|
|
177
|
-
test("claude_code is NOT in global registry after initializeTools()", async () => {
|
|
178
|
-
await initializeTools();
|
|
179
|
-
const tool = getTool("claude_code");
|
|
180
|
-
expect(tool).toBeUndefined();
|
|
181
|
-
});
|
|
182
176
|
});
|
|
183
177
|
|
|
184
178
|
describe("baseline characterization: core app tool surface", () => {
|
|
@@ -47,10 +47,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
47
47
|
}),
|
|
48
48
|
}));
|
|
49
49
|
|
|
50
|
-
mock.module("../security/secret-ingress.js", () => ({
|
|
51
|
-
checkIngressForSecrets: () => ({ blocked: false }),
|
|
52
|
-
}));
|
|
53
|
-
|
|
54
50
|
import { upsertContact } from "../contacts/contact-store.js";
|
|
55
51
|
import {
|
|
56
52
|
linkAttachmentToMessage,
|
|
@@ -75,10 +75,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
75
75
|
invalidateConfigCache: () => {},
|
|
76
76
|
}));
|
|
77
77
|
|
|
78
|
-
mock.module("../logfire.js", () => ({
|
|
79
|
-
wrapWithLogfire: (provider: unknown) => provider,
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
78
|
mock.module("../security/secure-keys.js", () => ({
|
|
83
79
|
getSecureKeyAsync: async (key: string) => secureKeyStore[key],
|
|
84
80
|
setSecureKeyAsync: async (key: string, value: string) => {
|
|
@@ -23,58 +23,13 @@ mock.module("../util/logger.js", () => ({
|
|
|
23
23
|
}),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Broker client mock — set up before importing secure-keys so the
|
|
28
|
-
// module-level `createBrokerClient()` call picks up our mock.
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
let mockBrokerAvailable = false;
|
|
32
|
-
let mockBrokerStore: Map<string, string> = new Map();
|
|
33
|
-
let mockBrokerGetError = false;
|
|
34
|
-
let mockBrokerSetError = false;
|
|
35
|
-
let mockBrokerDelError = false;
|
|
36
|
-
let mockBrokerGetCalled = false;
|
|
37
|
-
let mockBrokerSetCalled = false;
|
|
38
|
-
|
|
39
|
-
mock.module("../security/keychain-broker-client.js", () => ({
|
|
40
|
-
createBrokerClient: () => ({
|
|
41
|
-
isAvailable: () => mockBrokerAvailable,
|
|
42
|
-
ping: async () => (mockBrokerAvailable ? { pong: true } : null),
|
|
43
|
-
get: async (account: string) => {
|
|
44
|
-
mockBrokerGetCalled = true;
|
|
45
|
-
// null = broker error (fall back to encrypted store)
|
|
46
|
-
if (mockBrokerGetError) return null;
|
|
47
|
-
const value = mockBrokerStore.get(account);
|
|
48
|
-
if (value !== undefined) return { found: true, value };
|
|
49
|
-
return { found: false };
|
|
50
|
-
},
|
|
51
|
-
set: async (account: string, value: string) => {
|
|
52
|
-
mockBrokerSetCalled = true;
|
|
53
|
-
if (mockBrokerSetError)
|
|
54
|
-
return {
|
|
55
|
-
status: "rejected" as const,
|
|
56
|
-
code: "KEYCHAIN_ERROR",
|
|
57
|
-
message: "mock error",
|
|
58
|
-
};
|
|
59
|
-
mockBrokerStore.set(account, value);
|
|
60
|
-
return { status: "ok" as const };
|
|
61
|
-
},
|
|
62
|
-
del: async (account: string) => {
|
|
63
|
-
if (mockBrokerDelError) return false;
|
|
64
|
-
const existed = mockBrokerStore.has(account);
|
|
65
|
-
mockBrokerStore.delete(account);
|
|
66
|
-
return existed;
|
|
67
|
-
},
|
|
68
|
-
list: async () => Array.from(mockBrokerStore.keys()),
|
|
69
|
-
}),
|
|
70
|
-
}));
|
|
71
|
-
|
|
72
26
|
import * as encryptedStore from "../security/encrypted-store.js";
|
|
73
27
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
74
28
|
import {
|
|
75
29
|
_resetBackend,
|
|
76
30
|
deleteSecureKeyAsync,
|
|
77
31
|
getSecureKeyAsync,
|
|
32
|
+
getSecureKeyResultAsync,
|
|
78
33
|
listSecureKeysAsync,
|
|
79
34
|
setSecureKeyAsync,
|
|
80
35
|
} from "../security/secure-keys.js";
|
|
@@ -93,17 +48,9 @@ describe("secure-keys", () => {
|
|
|
93
48
|
beforeEach(() => {
|
|
94
49
|
_resetBackend();
|
|
95
50
|
|
|
96
|
-
//
|
|
97
|
-
mockBrokerAvailable = false;
|
|
98
|
-
mockBrokerStore = new Map();
|
|
99
|
-
mockBrokerGetError = false;
|
|
100
|
-
mockBrokerSetError = false;
|
|
101
|
-
mockBrokerDelError = false;
|
|
102
|
-
mockBrokerGetCalled = false;
|
|
103
|
-
mockBrokerSetCalled = false;
|
|
104
|
-
|
|
105
|
-
// Ensure VELLUM_DEV is NOT set so broker tests work by default
|
|
51
|
+
// Ensure VELLUM_DEV and VELLUM_DESKTOP_APP are NOT set
|
|
106
52
|
delete process.env.VELLUM_DEV;
|
|
53
|
+
delete process.env.VELLUM_DESKTOP_APP;
|
|
107
54
|
|
|
108
55
|
if (existsSync(TEST_DIR)) {
|
|
109
56
|
rmSync(TEST_DIR, { recursive: true });
|
|
@@ -116,6 +63,7 @@ describe("secure-keys", () => {
|
|
|
116
63
|
_setStorePath(null);
|
|
117
64
|
_resetBackend();
|
|
118
65
|
delete process.env.VELLUM_DEV;
|
|
66
|
+
delete process.env.VELLUM_DESKTOP_APP;
|
|
119
67
|
});
|
|
120
68
|
|
|
121
69
|
afterAll(() => {
|
|
@@ -125,9 +73,9 @@ describe("secure-keys", () => {
|
|
|
125
73
|
});
|
|
126
74
|
|
|
127
75
|
// -----------------------------------------------------------------------
|
|
128
|
-
// CRUD operations (
|
|
76
|
+
// CRUD operations (encrypted store backend)
|
|
129
77
|
// -----------------------------------------------------------------------
|
|
130
|
-
describe("CRUD with encrypted backend
|
|
78
|
+
describe("CRUD with encrypted backend", () => {
|
|
131
79
|
test("set and get a key", async () => {
|
|
132
80
|
await setSecureKeyAsync("openai", "sk-openai-789");
|
|
133
81
|
expect(await getSecureKeyAsync("openai")).toBe("sk-openai-789");
|
|
@@ -149,143 +97,90 @@ describe("secure-keys", () => {
|
|
|
149
97
|
});
|
|
150
98
|
|
|
151
99
|
// -----------------------------------------------------------------------
|
|
152
|
-
//
|
|
100
|
+
// Desktop app uses encrypted store (same as dev/CLI)
|
|
153
101
|
// -----------------------------------------------------------------------
|
|
154
|
-
describe("
|
|
155
|
-
test("
|
|
156
|
-
|
|
102
|
+
describe("desktop app uses encrypted store", () => {
|
|
103
|
+
test("VELLUM_DESKTOP_APP=1 writes to encrypted store", async () => {
|
|
104
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
157
105
|
_resetBackend();
|
|
158
106
|
|
|
159
107
|
const result = await setSecureKeyAsync("api-key", "new-value");
|
|
160
108
|
expect(result).toBe(true);
|
|
161
|
-
|
|
162
|
-
expect(mockBrokerStore.get("api-key")).toBe("new-value");
|
|
163
|
-
// Value should NOT be in the encrypted store (single-writer)
|
|
164
|
-
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
109
|
+
expect(encryptedStore.getKey("api-key")).toBe("new-value");
|
|
165
110
|
});
|
|
166
111
|
|
|
167
|
-
test("
|
|
168
|
-
|
|
169
|
-
mockBrokerSetError = true;
|
|
112
|
+
test("VELLUM_DESKTOP_APP=1 reads from encrypted store", async () => {
|
|
113
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
170
114
|
_resetBackend();
|
|
171
115
|
|
|
172
|
-
|
|
173
|
-
expect(result).toBe(false);
|
|
174
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// -----------------------------------------------------------------------
|
|
179
|
-
// Reads: primary backend first, legacy fallback to encrypted store
|
|
180
|
-
// -----------------------------------------------------------------------
|
|
181
|
-
describe("reads with broker available", () => {
|
|
182
|
-
test("getSecureKeyAsync reads from broker (primary backend)", async () => {
|
|
183
|
-
mockBrokerAvailable = true;
|
|
184
|
-
_resetBackend();
|
|
116
|
+
encryptedStore.setKey("api-key", "encrypted-value");
|
|
185
117
|
|
|
186
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
187
118
|
const result = await getSecureKeyAsync("api-key");
|
|
188
|
-
expect(result).toBe("
|
|
189
|
-
expect(mockBrokerGetCalled).toBe(true);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("getSecureKeyAsync falls back to encrypted store for legacy keys", async () => {
|
|
193
|
-
mockBrokerAvailable = true;
|
|
194
|
-
_resetBackend();
|
|
195
|
-
|
|
196
|
-
// Pre-populate encrypted store directly (legacy key not in broker)
|
|
197
|
-
encryptedStore.setKey("legacy-key", "legacy-value");
|
|
198
|
-
|
|
199
|
-
const result = await getSecureKeyAsync("legacy-key");
|
|
200
|
-
expect(result).toBe("legacy-value");
|
|
201
|
-
// Broker was checked first (returned nothing), then encrypted store
|
|
202
|
-
expect(mockBrokerGetCalled).toBe(true);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("getSecureKeyAsync returns undefined when neither store has the key", async () => {
|
|
206
|
-
mockBrokerAvailable = true;
|
|
207
|
-
_resetBackend();
|
|
208
|
-
|
|
209
|
-
expect(await getSecureKeyAsync("missing-key")).toBeUndefined();
|
|
119
|
+
expect(result).toBe("encrypted-value");
|
|
210
120
|
});
|
|
211
121
|
|
|
212
|
-
test("
|
|
213
|
-
|
|
122
|
+
test("VELLUM_DESKTOP_APP=1 deletes from encrypted store", async () => {
|
|
123
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
214
124
|
_resetBackend();
|
|
215
125
|
|
|
216
|
-
// Both stores have a value — broker (primary) should win
|
|
217
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
218
126
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
219
127
|
|
|
220
|
-
const result = await
|
|
221
|
-
expect(result).toBe("
|
|
128
|
+
const result = await deleteSecureKeyAsync("api-key");
|
|
129
|
+
expect(result).toBe("deleted");
|
|
130
|
+
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
222
131
|
});
|
|
223
132
|
});
|
|
224
133
|
|
|
225
134
|
// -----------------------------------------------------------------------
|
|
226
|
-
// Dev mode
|
|
135
|
+
// Dev mode — VELLUM_DEV=1 uses encrypted store
|
|
227
136
|
// -----------------------------------------------------------------------
|
|
228
|
-
describe("dev mode
|
|
229
|
-
test("setSecureKeyAsync writes to encrypted store
|
|
137
|
+
describe("dev mode (VELLUM_DEV=1)", () => {
|
|
138
|
+
test("setSecureKeyAsync writes to encrypted store", async () => {
|
|
230
139
|
process.env.VELLUM_DEV = "1";
|
|
231
|
-
mockBrokerAvailable = true;
|
|
232
140
|
_resetBackend();
|
|
233
141
|
|
|
234
142
|
const result = await setSecureKeyAsync("api-key", "dev-value");
|
|
235
143
|
expect(result).toBe(true);
|
|
236
|
-
// Written to encrypted store
|
|
237
144
|
expect(encryptedStore.getKey("api-key")).toBe("dev-value");
|
|
238
|
-
// NOT written to broker
|
|
239
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
240
|
-
expect(mockBrokerSetCalled).toBe(false);
|
|
241
145
|
});
|
|
242
146
|
|
|
243
|
-
test("getSecureKeyAsync reads from encrypted store
|
|
147
|
+
test("getSecureKeyAsync reads from encrypted store", async () => {
|
|
244
148
|
process.env.VELLUM_DEV = "1";
|
|
245
|
-
mockBrokerAvailable = true;
|
|
246
149
|
_resetBackend();
|
|
247
150
|
|
|
248
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
249
151
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
250
152
|
|
|
251
153
|
const result = await getSecureKeyAsync("api-key");
|
|
252
154
|
expect(result).toBe("encrypted-value");
|
|
253
|
-
// Broker should not have been contacted
|
|
254
|
-
expect(mockBrokerGetCalled).toBe(false);
|
|
255
155
|
});
|
|
256
156
|
|
|
257
|
-
test("getSecureKeyAsync returns undefined when encrypted store is empty
|
|
157
|
+
test("getSecureKeyAsync returns undefined when encrypted store is empty", async () => {
|
|
258
158
|
process.env.VELLUM_DEV = "1";
|
|
259
|
-
mockBrokerAvailable = true;
|
|
260
159
|
_resetBackend();
|
|
261
160
|
|
|
262
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
263
|
-
|
|
264
161
|
const result = await getSecureKeyAsync("api-key");
|
|
265
162
|
expect(result).toBeUndefined();
|
|
266
|
-
expect(mockBrokerGetCalled).toBe(false);
|
|
267
163
|
});
|
|
268
164
|
});
|
|
269
165
|
|
|
270
166
|
// -----------------------------------------------------------------------
|
|
271
|
-
//
|
|
167
|
+
// Non-desktop topology uses encrypted store
|
|
272
168
|
// -----------------------------------------------------------------------
|
|
273
|
-
describe("
|
|
274
|
-
test("
|
|
275
|
-
mockBrokerAvailable = true;
|
|
169
|
+
describe("non-desktop topology", () => {
|
|
170
|
+
test("uses encrypted store", async () => {
|
|
276
171
|
_resetBackend();
|
|
277
172
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const result = await deleteSecureKeyAsync("api-key");
|
|
282
|
-
expect(result).toBe("deleted");
|
|
283
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
284
|
-
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
173
|
+
const result = await setSecureKeyAsync("api-key", "new-value");
|
|
174
|
+
expect(result).toBe(true);
|
|
175
|
+
expect(encryptedStore.getKey("api-key")).toBe("new-value");
|
|
285
176
|
});
|
|
177
|
+
});
|
|
286
178
|
|
|
287
|
-
|
|
288
|
-
|
|
179
|
+
// -----------------------------------------------------------------------
|
|
180
|
+
// Delete — single backend
|
|
181
|
+
// -----------------------------------------------------------------------
|
|
182
|
+
describe("delete from encrypted store", () => {
|
|
183
|
+
test("deleteSecureKeyAsync removes key from encrypted store", async () => {
|
|
289
184
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
290
185
|
|
|
291
186
|
const result = await deleteSecureKeyAsync("api-key");
|
|
@@ -293,116 +188,29 @@ describe("secure-keys", () => {
|
|
|
293
188
|
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
294
189
|
});
|
|
295
190
|
|
|
296
|
-
test("deleteSecureKeyAsync
|
|
297
|
-
mockBrokerAvailable = true;
|
|
298
|
-
mockBrokerDelError = true;
|
|
299
|
-
_resetBackend();
|
|
300
|
-
|
|
301
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
302
|
-
encryptedStore.setKey("api-key", "encrypted-value");
|
|
303
|
-
|
|
304
|
-
const result = await deleteSecureKeyAsync("api-key");
|
|
305
|
-
expect(result).toBe("error");
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("deleteSecureKeyAsync in dev mode still attempts both stores", async () => {
|
|
191
|
+
test("deleteSecureKeyAsync in dev mode deletes from encrypted store", async () => {
|
|
309
192
|
process.env.VELLUM_DEV = "1";
|
|
310
|
-
|
|
193
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
311
194
|
_resetBackend();
|
|
312
195
|
|
|
313
|
-
mockBrokerStore.set("api-key", "broker-value");
|
|
314
196
|
encryptedStore.setKey("api-key", "encrypted-value");
|
|
315
197
|
|
|
316
198
|
const result = await deleteSecureKeyAsync("api-key");
|
|
317
199
|
expect(result).toBe("deleted");
|
|
318
|
-
expect(mockBrokerStore.has("api-key")).toBe(false);
|
|
319
200
|
expect(encryptedStore.getKey("api-key")).toBeUndefined();
|
|
320
201
|
});
|
|
321
202
|
|
|
322
|
-
test("deleteSecureKeyAsync returns not-found when key missing
|
|
323
|
-
// Broker unavailable, encrypted store empty
|
|
203
|
+
test("deleteSecureKeyAsync returns not-found when key missing", async () => {
|
|
324
204
|
const result = await deleteSecureKeyAsync("missing-key");
|
|
325
205
|
expect(result).toBe("not-found");
|
|
326
206
|
});
|
|
327
207
|
});
|
|
328
208
|
|
|
329
209
|
// -----------------------------------------------------------------------
|
|
330
|
-
//
|
|
331
|
-
// -----------------------------------------------------------------------
|
|
332
|
-
describe("legacy read fallback", () => {
|
|
333
|
-
test("returns encrypted store value when broker has no key (legacy migration)", async () => {
|
|
334
|
-
mockBrokerAvailable = true;
|
|
335
|
-
_resetBackend();
|
|
336
|
-
|
|
337
|
-
// Simulate a legacy key that was written to encrypted store before
|
|
338
|
-
// the single-writer migration — broker doesn't have it.
|
|
339
|
-
encryptedStore.setKey("legacy-account", "legacy-secret");
|
|
340
|
-
|
|
341
|
-
const result = await getSecureKeyAsync("legacy-account");
|
|
342
|
-
expect(result).toBe("legacy-secret");
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test("does not fall back to encrypted store when already using encrypted store backend", async () => {
|
|
346
|
-
// Broker unavailable — primary backend IS the encrypted store.
|
|
347
|
-
// No fallback needed.
|
|
348
|
-
encryptedStore.setKey("account", "value");
|
|
349
|
-
encryptedStore.setKey("other-account", "other-value");
|
|
350
|
-
|
|
351
|
-
// Should read directly from encrypted store (primary)
|
|
352
|
-
const result = await getSecureKeyAsync("account");
|
|
353
|
-
expect(result).toBe("value");
|
|
354
|
-
// Broker should not have been contacted
|
|
355
|
-
expect(mockBrokerGetCalled).toBe(false);
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// -----------------------------------------------------------------------
|
|
360
|
-
// Stale-value prevention
|
|
361
|
-
// -----------------------------------------------------------------------
|
|
362
|
-
describe("stale-value prevention", () => {
|
|
363
|
-
test("setSecureKeyAsync failure does not corrupt broker store", async () => {
|
|
364
|
-
mockBrokerAvailable = true;
|
|
365
|
-
_resetBackend();
|
|
366
|
-
|
|
367
|
-
// Pre-seed broker with original value
|
|
368
|
-
mockBrokerStore.set("api-key", "original-value");
|
|
369
|
-
|
|
370
|
-
// Now fail the next set
|
|
371
|
-
mockBrokerSetError = true;
|
|
372
|
-
const ok = await setSecureKeyAsync("api-key", "new-value");
|
|
373
|
-
expect(ok).toBe(false);
|
|
374
|
-
|
|
375
|
-
// Broker should still have original value
|
|
376
|
-
expect(mockBrokerStore.get("api-key")).toBe("original-value");
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// -----------------------------------------------------------------------
|
|
381
|
-
// listSecureKeysAsync — merged/deduplicated key listing
|
|
210
|
+
// listSecureKeysAsync — single-backend key listing
|
|
382
211
|
// -----------------------------------------------------------------------
|
|
383
212
|
describe("listSecureKeysAsync", () => {
|
|
384
|
-
test("returns
|
|
385
|
-
mockBrokerAvailable = true;
|
|
386
|
-
_resetBackend();
|
|
387
|
-
|
|
388
|
-
// Broker has some keys
|
|
389
|
-
mockBrokerStore.set("broker-key-1", "val1");
|
|
390
|
-
mockBrokerStore.set("shared-key", "broker-val");
|
|
391
|
-
|
|
392
|
-
// Encrypted store has legacy keys (some overlapping)
|
|
393
|
-
encryptedStore.setKey("legacy-key-1", "val2");
|
|
394
|
-
encryptedStore.setKey("shared-key", "enc-val");
|
|
395
|
-
|
|
396
|
-
const keys = await listSecureKeysAsync();
|
|
397
|
-
expect(keys).toContain("broker-key-1");
|
|
398
|
-
expect(keys).toContain("legacy-key-1");
|
|
399
|
-
expect(keys).toContain("shared-key");
|
|
400
|
-
// Should be exactly 3 unique keys (no duplicates)
|
|
401
|
-
expect(keys.length).toBe(3);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test("returns only encrypted store keys when broker is unavailable", async () => {
|
|
405
|
-
// Broker unavailable (default state) — primary backend is encrypted store
|
|
213
|
+
test("returns encrypted store keys", async () => {
|
|
406
214
|
encryptedStore.setKey("enc-key-1", "val1");
|
|
407
215
|
encryptedStore.setKey("enc-key-2", "val2");
|
|
408
216
|
|
|
@@ -412,60 +220,72 @@ describe("secure-keys", () => {
|
|
|
412
220
|
expect(keys.length).toBe(2);
|
|
413
221
|
});
|
|
414
222
|
|
|
415
|
-
test("returns
|
|
223
|
+
test("returns encrypted store keys with VELLUM_DEV=1", async () => {
|
|
416
224
|
process.env.VELLUM_DEV = "1";
|
|
417
|
-
mockBrokerAvailable = true;
|
|
418
225
|
_resetBackend();
|
|
419
226
|
|
|
420
|
-
// Broker has keys that should be ignored
|
|
421
|
-
mockBrokerStore.set("broker-only", "val1");
|
|
422
|
-
|
|
423
|
-
// Encrypted store has keys
|
|
424
227
|
encryptedStore.setKey("dev-key-1", "val2");
|
|
425
228
|
encryptedStore.setKey("dev-key-2", "val3");
|
|
426
229
|
|
|
427
230
|
const keys = await listSecureKeysAsync();
|
|
428
231
|
expect(keys).toContain("dev-key-1");
|
|
429
232
|
expect(keys).toContain("dev-key-2");
|
|
430
|
-
// broker-only key should NOT appear since primary backend is encrypted store
|
|
431
|
-
expect(keys).not.toContain("broker-only");
|
|
432
233
|
expect(keys.length).toBe(2);
|
|
433
234
|
});
|
|
434
235
|
|
|
435
|
-
test("returns
|
|
436
|
-
|
|
236
|
+
test("returns encrypted store keys with VELLUM_DESKTOP_APP=1", async () => {
|
|
237
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
437
238
|
_resetBackend();
|
|
438
239
|
|
|
439
|
-
|
|
440
|
-
|
|
240
|
+
encryptedStore.setKey("desktop-key-1", "val1");
|
|
241
|
+
encryptedStore.setKey("desktop-key-2", "val2");
|
|
441
242
|
|
|
442
243
|
const keys = await listSecureKeysAsync();
|
|
443
|
-
expect(keys).toContain("
|
|
444
|
-
expect(keys).toContain("
|
|
244
|
+
expect(keys).toContain("desktop-key-1");
|
|
245
|
+
expect(keys).toContain("desktop-key-2");
|
|
445
246
|
expect(keys.length).toBe(2);
|
|
446
247
|
});
|
|
447
248
|
|
|
448
|
-
test("
|
|
449
|
-
|
|
450
|
-
|
|
249
|
+
test("returns empty array when store is empty", async () => {
|
|
250
|
+
const keys = await listSecureKeysAsync();
|
|
251
|
+
expect(keys).toEqual([]);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// -----------------------------------------------------------------------
|
|
256
|
+
// getSecureKeyResultAsync — richer result with unreachable flag
|
|
257
|
+
// -----------------------------------------------------------------------
|
|
258
|
+
describe("getSecureKeyResultAsync", () => {
|
|
259
|
+
test("returns value and unreachable false on success", async () => {
|
|
260
|
+
encryptedStore.setKey("api-key", "stored-value");
|
|
451
261
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
262
|
+
const result = await getSecureKeyResultAsync("api-key");
|
|
263
|
+
expect(result.value).toBe("stored-value");
|
|
264
|
+
expect(result.unreachable).toBe(false);
|
|
265
|
+
});
|
|
455
266
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
expect(
|
|
460
|
-
expect(keys.filter((k) => k === "api-key").length).toBe(1);
|
|
267
|
+
test("returns unreachable false when key missing (encrypted store always reachable)", async () => {
|
|
268
|
+
const result = await getSecureKeyResultAsync("missing-key");
|
|
269
|
+
expect(result.value).toBeUndefined();
|
|
270
|
+
expect(result.unreachable).toBe(false);
|
|
461
271
|
});
|
|
462
272
|
|
|
463
|
-
test("returns
|
|
464
|
-
|
|
273
|
+
test("returns unreachable false in dev mode", async () => {
|
|
274
|
+
process.env.VELLUM_DEV = "1";
|
|
465
275
|
_resetBackend();
|
|
466
276
|
|
|
467
|
-
const
|
|
468
|
-
expect(
|
|
277
|
+
const result = await getSecureKeyResultAsync("missing-key");
|
|
278
|
+
expect(result.value).toBeUndefined();
|
|
279
|
+
expect(result.unreachable).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("returns unreachable false with VELLUM_DESKTOP_APP=1", async () => {
|
|
283
|
+
process.env.VELLUM_DESKTOP_APP = "1";
|
|
284
|
+
_resetBackend();
|
|
285
|
+
|
|
286
|
+
const result = await getSecureKeyResultAsync("missing-key");
|
|
287
|
+
expect(result.value).toBeUndefined();
|
|
288
|
+
expect(result.unreachable).toBe(false);
|
|
469
289
|
});
|
|
470
290
|
});
|
|
471
291
|
});
|