@vellumai/assistant 0.4.53 → 0.4.55
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/bun.lock +62 -349
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/keychain-broker.md +94 -29
- package/docs/architecture/security.md +2 -2
- package/knip.json +7 -29
- package/package.json +2 -9
- package/src/__tests__/agent-loop.test.ts +1 -1
- package/src/__tests__/app-git-history.test.ts +0 -2
- package/src/__tests__/app-git-service.test.ts +1 -6
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -6
- package/src/__tests__/call-domain.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -4
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +0 -1
- package/src/__tests__/checker.test.ts +13 -11
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
- package/src/__tests__/config-loader-backfill.test.ts +0 -3
- package/src/__tests__/config-schema.test.ts +3 -9
- package/src/__tests__/config-watcher.test.ts +11 -3
- package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
- package/src/__tests__/credential-broker-server-use.test.ts +60 -24
- package/src/__tests__/credential-security-e2e.test.ts +1 -6
- package/src/__tests__/credential-security-invariants.test.ts +13 -8
- package/src/__tests__/credential-vault-unit.test.ts +28 -12
- package/src/__tests__/credential-vault.test.ts +40 -28
- package/src/__tests__/credentials-cli.test.ts +1 -21
- package/src/__tests__/email-invite-adapter.test.ts +0 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
- package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
- package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
- package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
- package/src/__tests__/guardian-action-store.test.ts +0 -57
- package/src/__tests__/guardian-outbound-http.test.ts +1 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
- package/src/__tests__/hooks-blocking.test.ts +1 -1
- package/src/__tests__/hooks-config.test.ts +5 -29
- package/src/__tests__/hooks-discovery.test.ts +1 -1
- package/src/__tests__/hooks-integration.test.ts +1 -1
- package/src/__tests__/hooks-manager.test.ts +1 -1
- package/src/__tests__/hooks-runner.test.ts +1 -23
- package/src/__tests__/hooks-settings.test.ts +1 -1
- package/src/__tests__/hooks-templates.test.ts +1 -1
- package/src/__tests__/integration-status.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +0 -3
- package/src/__tests__/list-messages-attachments.test.ts +4 -4
- package/src/__tests__/llm-usage-store.test.ts +50 -0
- package/src/__tests__/managed-proxy-context.test.ts +41 -41
- package/src/__tests__/media-generate-image.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
- package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
- package/src/__tests__/memory-regressions.test.ts +27 -27
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
- package/src/__tests__/notification-decision-fallback.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +1 -4
- package/src/__tests__/oauth-store.test.ts +1 -3
- package/src/__tests__/openai-provider.test.ts +7 -7
- package/src/__tests__/platform.test.ts +14 -4
- package/src/__tests__/pricing.test.ts +0 -223
- package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
- package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
- package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
- package/src/__tests__/provider-registry-ollama.test.ts +3 -3
- package/src/__tests__/public-ingress-urls.test.ts +1 -1
- package/src/__tests__/registry.test.ts +3 -103
- package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
- package/src/__tests__/secret-onetime-send.test.ts +1 -6
- package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
- package/src/__tests__/secure-keys.test.ts +241 -229
- package/src/__tests__/session-abort-tool-results.test.ts +0 -1
- package/src/__tests__/session-confirmation-signals.test.ts +0 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
- package/src/__tests__/session-pre-run-repair.test.ts +0 -1
- package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/session-queue.test.ts +2 -4
- package/src/__tests__/session-slash-known.test.ts +0 -1
- package/src/__tests__/session-slash-queue.test.ts +0 -1
- package/src/__tests__/session-slash-unknown.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +1 -7
- package/src/__tests__/swarm-recursion.test.ts +0 -1
- package/src/__tests__/swarm-session-integration.test.ts +0 -1
- package/src/__tests__/swarm-tool.test.ts +0 -1
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
- package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
- package/src/__tests__/tool-executor.test.ts +1 -1
- package/src/__tests__/trust-store.test.ts +3 -82
- package/src/__tests__/twilio-config.test.ts +0 -1
- package/src/__tests__/twilio-provider.test.ts +0 -5
- package/src/__tests__/twilio-routes.test.ts +0 -1
- package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
- package/src/calls/guardian-question-copy.ts +1 -1
- package/src/cli/commands/bash.ts +3 -0
- package/src/cli/commands/doctor.ts +10 -34
- package/src/cli/commands/memory.ts +3 -5
- package/src/cli/commands/sessions.ts +1 -1
- package/src/cli/commands/usage.ts +359 -0
- package/src/cli/http-client.ts +22 -12
- package/src/cli/program.ts +2 -0
- package/src/cli/reference.ts +1 -0
- package/src/cli.ts +251 -181
- package/src/config/assistant-feature-flags.ts +0 -7
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +0 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -1
- package/src/config/bundled-skills/sequences/SKILL.md +0 -1
- package/src/config/env.ts +13 -0
- package/src/config/feature-flag-registry.json +9 -41
- package/src/config/schemas/security.ts +1 -2
- package/src/config/skills.ts +1 -1
- package/src/contacts/contact-store.ts +0 -50
- package/src/daemon/approved-devices-store.ts +0 -44
- package/src/daemon/classifier.ts +1 -1
- package/src/daemon/config-watcher.ts +14 -8
- package/src/daemon/handlers/config-model.ts +1 -1
- package/src/daemon/handlers/sessions.ts +4 -116
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/lifecycle.ts +13 -15
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +20 -3
- package/src/daemon/session-slash.ts +2 -2
- package/src/daemon/shutdown-handlers.ts +15 -0
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/guardrails.ts +1 -1
- package/src/email/service.ts +0 -5
- package/src/hooks/templates.ts +1 -1
- package/src/media/app-icon-generator.ts +2 -2
- package/src/media/avatar-router.ts +2 -2
- package/src/media/gemini-image-service.ts +5 -5
- package/src/memory/admin.ts +2 -2
- package/src/memory/app-git-service.ts +0 -7
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-title-service.ts +2 -2
- package/src/memory/embedding-backend.ts +30 -26
- package/src/memory/external-conversation-store.ts +0 -30
- package/src/memory/guardian-action-store.ts +0 -31
- package/src/memory/guardian-approvals.ts +1 -56
- package/src/memory/indexer.ts +4 -3
- package/src/memory/items-extractor.ts +1 -1
- package/src/memory/job-handlers/backfill.ts +5 -2
- package/src/memory/job-handlers/index-maintenance.ts +2 -2
- package/src/memory/job-handlers/media-processing.ts +2 -2
- package/src/memory/job-handlers/summarization.ts +1 -1
- package/src/memory/job-utils.ts +1 -2
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/llm-usage-store.ts +57 -11
- package/src/memory/media-store.ts +4 -535
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
- package/src/memory/migrations/110-channel-guardian.ts +0 -1
- package/src/memory/published-pages-store.ts +0 -83
- package/src/memory/qdrant-circuit-breaker.ts +0 -8
- package/src/memory/retriever.ts +1 -1
- package/src/memory/schema/calls.ts +0 -67
- package/src/memory/search/semantic.ts +1 -8
- package/src/memory/shared-app-links-store.ts +0 -15
- package/src/messaging/registry.ts +0 -5
- package/src/messaging/style-analyzer.ts +1 -1
- package/src/notifications/copy-composer.ts +5 -13
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/deliveries-store.ts +0 -39
- package/src/notifications/guardian-question-mode.ts +6 -10
- package/src/notifications/preference-extractor.ts +1 -1
- package/src/oauth/byo-connection.test.ts +29 -20
- package/src/oauth/provider-behaviors.ts +1 -1
- package/src/permissions/checker.ts +1 -1
- package/src/permissions/shell-identity.ts +0 -5
- package/src/permissions/trust-store.ts +0 -37
- package/src/prompts/system-prompt.ts +4 -4
- package/src/prompts/templates/SOUL.md +1 -1
- package/src/providers/managed-proxy/constants.ts +8 -10
- package/src/providers/managed-proxy/context.ts +14 -9
- package/src/providers/provider-send-message.ts +4 -52
- package/src/providers/registry.ts +16 -50
- package/src/runtime/actor-token-store.ts +0 -23
- package/src/runtime/auth/__tests__/guard-tests.test.ts +64 -0
- package/src/runtime/http-router.ts +5 -1
- package/src/runtime/http-server.ts +101 -4
- package/src/runtime/invite-instruction-generator.ts +25 -51
- package/src/runtime/invite-service.ts +0 -20
- package/src/runtime/routes/attachment-routes.ts +1 -1
- package/src/runtime/routes/brain-graph-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +32 -11
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +2 -2
- package/src/runtime/routes/documents-routes.ts +3 -3
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
- package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
- package/src/runtime/routes/secret-routes.ts +4 -4
- package/src/runtime/routes/session-management-routes.ts +27 -0
- package/src/runtime/routes/trust-rules-routes.ts +1 -1
- package/src/security/credential-backend.ts +148 -0
- package/src/security/oauth2.ts +1 -1
- package/src/security/secret-allowlist.ts +1 -1
- package/src/security/secure-keys.ts +98 -160
- package/src/security/token-manager.ts +0 -7
- package/src/sequence/guardrails.ts +0 -4
- package/src/sequence/store.ts +1 -20
- package/src/sequence/types.ts +1 -36
- package/src/signals/bash.ts +33 -0
- package/src/signals/cancel.ts +69 -0
- package/src/signals/conversation-undo.ts +127 -0
- package/src/signals/trust-rule.ts +174 -0
- package/src/skills/clawhub.ts +5 -5
- package/src/skills/managed-store.ts +4 -4
- package/src/subagent/manager.ts +8 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
- package/src/telemetry/usage-telemetry-reporter.ts +181 -0
- package/src/tools/claude-code/claude-code.ts +2 -2
- package/src/tools/credentials/vault.ts +8 -4
- package/src/tools/memory/handlers.test.ts +24 -26
- package/src/tools/memory/handlers.ts +1 -13
- package/src/tools/registry.ts +5 -100
- package/src/tools/terminal/parser.ts +34 -4
- package/src/tools/tool-manifest.ts +0 -10
- package/src/usage/actors.ts +0 -12
- package/src/util/canonicalize-identity.ts +0 -9
- package/src/util/errors.ts +0 -3
- package/src/util/platform.ts +24 -7
- package/src/util/pricing.ts +0 -38
- package/src/watcher/constants.ts +0 -7
- package/src/watcher/providers/linear.ts +1 -1
- package/src/work-items/work-item-store.ts +4 -4
- package/src/workspace/commit-message-provider.ts +1 -1
- package/src/workspace/git-service.ts +44 -1
- package/src/workspace/provider-commit-message-generator.ts +1 -1
- package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
- package/src/browser-extension-relay/client.ts +0 -155
- package/src/contacts/index.ts +0 -18
- package/src/daemon/tls-certs.ts +0 -270
- package/src/errors.ts +0 -41
- package/src/events/index.ts +0 -18
- package/src/followups/index.ts +0 -10
- package/src/playbooks/index.ts +0 -10
- package/src/runtime/auth/index.ts +0 -44
- package/src/tasks/candidate-store.ts +0 -95
- package/src/tools/browser/api-map.ts +0 -313
- package/src/tools/browser/auto-navigate.ts +0 -469
- package/src/tools/browser/headless-browser.ts +0 -590
- package/src/tools/browser/recording-store.ts +0 -75
- package/src/tools/computer-use/registry.ts +0 -21
- package/src/tools/tasks/index.ts +0 -27
|
@@ -152,7 +152,7 @@ sequenceDiagram
|
|
|
152
152
|
Note over UI,API: Tool Execution Flow
|
|
153
153
|
Tool->>TokenMgr: withValidToken("gmail", callback)
|
|
154
154
|
TokenMgr->>Store: getConnectionByProvider("integration:google")
|
|
155
|
-
TokenMgr->>Vault:
|
|
155
|
+
TokenMgr->>Vault: getSecureKeyAsync("oauth_connection/{conn.id}/access_token")
|
|
156
156
|
TokenMgr->>Store: check oauth_connections.expires_at
|
|
157
157
|
alt Token expired
|
|
158
158
|
TokenMgr->>Store: resolveRefreshConfig() → tokenUrl, clientId from provider/app rows
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# macOS Keychain Broker Architecture
|
|
2
2
|
|
|
3
3
|
**Status:** Accepted
|
|
4
|
-
**Last Updated:** 2026-03-
|
|
4
|
+
**Last Updated:** 2026-03-14
|
|
5
5
|
**Owners:** macOS client + assistant runtime
|
|
6
6
|
|
|
7
7
|
## Decision
|
|
8
8
|
|
|
9
9
|
Embed the keychain broker in the macOS app process rather than running a standalone daemon. The app exposes SecItem keychain operations over a Unix domain socket to the assistant runtime and gateway. Debug builds skip the broker entirely (`#if !DEBUG` guard) so developers never see keychain authorization prompts during rebuilds.
|
|
10
10
|
|
|
11
|
+
Credential storage is abstracted behind a `CredentialBackend` interface with two implementations: a keychain backend (backed by the broker) and an encrypted file store backend. Each process resolves a single primary backend at startup and uses single-writer semantics -- all writes go to the resolved backend only, with no dual-writing.
|
|
12
|
+
|
|
11
13
|
## Problem Statement
|
|
12
14
|
|
|
13
15
|
We want macOS to use Keychain as the primary secret store, but direct keychain access from the daemon process causes repeated authorization prompts. The prompts are especially problematic during development with ad-hoc signed builds, where every rebuild changes the signing identity and triggers a new keychain prompt.
|
|
@@ -20,6 +22,21 @@ Prior state:
|
|
|
20
22
|
|
|
21
23
|
## Architecture
|
|
22
24
|
|
|
25
|
+
### CredentialBackend interface
|
|
26
|
+
|
|
27
|
+
All credential storage operations are routed through the `CredentialBackend` interface, which provides a uniform API for get, set, delete, and list operations. This abstraction decouples `secure-keys.ts` from any specific storage mechanism.
|
|
28
|
+
|
|
29
|
+
Two implementations exist:
|
|
30
|
+
|
|
31
|
+
| Backend | Backing store | When used |
|
|
32
|
+
| ---------------- | -------------------------------------------- | ----------------------------------------------------------------------- |
|
|
33
|
+
| Keychain backend | macOS Keychain via the broker UDS connection | Production app context (`VELLUM_DEV` is NOT `"1"`) and broker available |
|
|
34
|
+
| Encrypted store | `~/.vellum/protected/keys.enc` (AES-256-GCM) | `VELLUM_DEV=1`, broker unavailable, CLI-only, headless, CI |
|
|
35
|
+
|
|
36
|
+
The interface is intentionally minimal to make adding future backends trivial -- e.g., Linux secret service (libsecret/D-Bus), 1Password CLI, or cloud KMS. A new backend only needs to implement the `CredentialBackend` interface and be wired into the backend resolution logic.
|
|
37
|
+
|
|
38
|
+
### Broker topology
|
|
39
|
+
|
|
23
40
|
```mermaid
|
|
24
41
|
graph LR
|
|
25
42
|
subgraph "macOS App Process"
|
|
@@ -29,11 +46,19 @@ graph LR
|
|
|
29
46
|
SERVICE --> KC["macOS Keychain"]
|
|
30
47
|
end
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
subgraph "Backend Resolution"
|
|
50
|
+
RESOLVE{"resolveBackend()"}
|
|
51
|
+
KB["Keychain Backend"]
|
|
52
|
+
EB["Encrypted Store Backend"]
|
|
53
|
+
RESOLVE -->|"VELLUM_DEV!=1<br/>+ broker available"| KB
|
|
54
|
+
RESOLVE -->|"VELLUM_DEV=1<br/>OR broker unavailable"| EB
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
KB -->|"UDS JSON"| SERVER
|
|
58
|
+
RUNTIME["Assistant Runtime (Bun)"] --> RESOLVE
|
|
59
|
+
GATEWAY["Gateway (Bun)"] -->|"read-only"| RESOLVE
|
|
34
60
|
|
|
35
|
-
|
|
36
|
-
GATEWAY -.->|"fallback<br/>(broker unavailable)"| ENC
|
|
61
|
+
EB --> ENC["Encrypted Store<br/>(~/.vellum/protected/keys.enc)"]
|
|
37
62
|
```
|
|
38
63
|
|
|
39
64
|
### Key properties
|
|
@@ -42,7 +67,8 @@ graph LR
|
|
|
42
67
|
- **No auth bootstrap problem.** The app writes the broker auth token to disk before launching the daemon, so the daemon always has a valid token at startup.
|
|
43
68
|
- **No keychain prompts on signed builds.** Items are stored with `kSecAttrAccessibleAfterFirstUnlock` under the `vellum-assistant` service name. A stable code-signing identity means macOS grants access without prompting after the first unlock.
|
|
44
69
|
- **No keychain interaction on debug builds.** The entire `KeychainBrokerServer` is compiled out with `#if !DEBUG`, so development builds use the encrypted file store exclusively.
|
|
45
|
-
- **
|
|
70
|
+
- **Single-writer to resolved backend.** Each process resolves exactly one primary backend at startup. Writes go only to that backend. There is no dual-writing.
|
|
71
|
+
- **Encrypted file store as permanent fallback.** CLI-only, headless, and development environments always have the encrypted store (`~/.vellum/protected/keys.enc`) available.
|
|
46
72
|
|
|
47
73
|
## Components
|
|
48
74
|
|
|
@@ -55,11 +81,12 @@ graph LR
|
|
|
55
81
|
|
|
56
82
|
### TypeScript side (runtime + gateway)
|
|
57
83
|
|
|
58
|
-
| File | Role
|
|
59
|
-
| -------------------------------------------------- |
|
|
60
|
-
| `assistant/src/security/
|
|
61
|
-
| `assistant/src/security/
|
|
62
|
-
| `
|
|
84
|
+
| File | Role |
|
|
85
|
+
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `assistant/src/security/credential-backend.ts` | `CredentialBackend` interface definition (`get`, `set`, `delete`, `list`) plus both adapter implementations: `KeychainBackend` (backed by the broker UDS client) and `EncryptedStoreBackend` (backed by `encrypted-store.ts` / `~/.vellum/protected/keys.enc`). Also exports factory functions `createKeychainBackend()` and `createEncryptedStoreBackend()`. |
|
|
87
|
+
| `assistant/src/security/keychain-broker-client.ts` | Async UDS client for the runtime. Persistent socket connection, request/response correlation, auth token caching with auto-refresh on `UNAUTHORIZED`. Falls back gracefully (returns safe defaults, never throws). |
|
|
88
|
+
| `assistant/src/security/secure-keys.ts` | Unified API surface. Resolves a single primary `CredentialBackend` at startup based on environment and broker availability. All writes go to the resolved backend only (single-writer). Reads check the primary backend first, with legacy fallback to the encrypted store for migration. Deletes clean up both stores to prevent stale keys. |
|
|
89
|
+
| `gateway/src/credential-reader.ts` | Read-only credential reader. Tries broker via native async UDS connection (`node:net`), falls back to encrypted store. All public credential read functions are async. |
|
|
63
90
|
|
|
64
91
|
## Message Contract
|
|
65
92
|
|
|
@@ -161,39 +188,77 @@ XPC provides stronger caller identity guarantees via audit tokens and code requi
|
|
|
161
188
|
- **Release builds:** The broker starts automatically with the app. The daemon discovers the broker via the derived socket path (`join(getRootDir(), "keychain-broker.sock")`) and token file. No configuration needed.
|
|
162
189
|
- **CLI-only / headless:** No macOS app means no broker socket. All storage uses the encrypted file store. This is the expected path for CI, servers, and non-macOS platforms.
|
|
163
190
|
|
|
164
|
-
##
|
|
191
|
+
## Store-Routing Policy
|
|
165
192
|
|
|
166
|
-
###
|
|
193
|
+
### Backend resolution
|
|
167
194
|
|
|
168
|
-
|
|
195
|
+
At process startup, `secure-keys.ts` resolves a single primary `CredentialBackend` for the lifetime of the process:
|
|
169
196
|
|
|
170
|
-
|
|
197
|
+
| Condition | Resolved backend |
|
|
198
|
+
| ----------------------------------------------------- | ---------------- |
|
|
199
|
+
| `VELLUM_DEV` is NOT `"1"` **and** broker is available | Keychain backend |
|
|
200
|
+
| `VELLUM_DEV=1` **or** broker is unavailable | Encrypted store |
|
|
171
201
|
|
|
172
|
-
|
|
202
|
+
Once resolved, the backend does not change during the process lifetime. There is no runtime switching between backends.
|
|
173
203
|
|
|
174
|
-
|
|
204
|
+
### Read strategy
|
|
175
205
|
|
|
176
|
-
|
|
206
|
+
Reads go to the **primary backend first**. If the primary backend is the keychain backend and the key is not found, a legacy fallback read is attempted against the encrypted store. This fallback handles the migration period where keys written before the single-writer policy may still exist only in the encrypted store. When the primary backend is the encrypted store, there is no fallback -- the encrypted store is the sole source of truth.
|
|
207
|
+
|
|
208
|
+
### Write strategy
|
|
209
|
+
|
|
210
|
+
Writes go **only to the resolved primary backend**. There is no dual-writing. This eliminates the consistency problems that dual-writing introduced (partial failures leaving stores out of sync, unclear source of truth).
|
|
211
|
+
|
|
212
|
+
- Keychain backend resolved: writes go to the keychain via broker only.
|
|
213
|
+
- Encrypted store resolved: writes go to the encrypted file store only.
|
|
214
|
+
|
|
215
|
+
### Delete strategy
|
|
177
216
|
|
|
178
|
-
|
|
217
|
+
Deletes **always clean up both stores** regardless of the resolved backend. This ensures that stale keys do not linger in the non-primary store, which could cause unexpected reads through the legacy fallback path.
|
|
179
218
|
|
|
180
|
-
###
|
|
219
|
+
### `VELLUM_DEV` environment variable
|
|
181
220
|
|
|
182
|
-
|
|
221
|
+
Setting `VELLUM_DEV=1` forces the encrypted store as the sole backend, bypassing the keychain broker entirely even when the broker socket is available. This serves two purposes:
|
|
183
222
|
|
|
184
|
-
|
|
223
|
+
1. **Development ergonomics.** Developers running the daemon outside the macOS app context (e.g., `bun run` directly) avoid broker connectivity issues and keychain prompt noise.
|
|
224
|
+
2. **Deterministic testing.** Tests and CI environments get predictable encrypted-store-only behavior without depending on macOS Keychain infrastructure.
|
|
185
225
|
|
|
186
|
-
|
|
226
|
+
When `VELLUM_DEV` is not set or is set to any value other than `"1"`, backend resolution proceeds normally (broker availability check).
|
|
187
227
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
228
|
+
## Callsite Policy
|
|
229
|
+
|
|
230
|
+
### Async-only policy
|
|
231
|
+
|
|
232
|
+
**All credential access uses the async functions** (`getSecureKeyAsync`, `setSecureKeyAsync`, `deleteSecureKeyAsync`). These route through the resolved `CredentialBackend`, with legacy fallback reads to the encrypted store when the keychain backend is primary. The migration from sync to async is complete: the sync variants (`getSecureKey`, `setSecureKey`, `deleteSecureKey`) have been deleted and no longer exist in the codebase.
|
|
233
|
+
|
|
234
|
+
### Runtime request handlers (secret-routes, etc.)
|
|
235
|
+
|
|
236
|
+
All runtime HTTP handlers that write or delete secrets use the async APIs (`setSecureKeyAsync`, `deleteSecureKeyAsync`). These are the primary entry points for macOS app flows and route through the resolved backend.
|
|
237
|
+
|
|
238
|
+
### Gateway (credential-reader)
|
|
192
239
|
|
|
193
|
-
|
|
240
|
+
The gateway reads credentials via async `readCredential()` which tries the broker first (native async UDS), falling back to the encrypted store. The gateway never writes credentials -- that responsibility belongs to the assistant runtime.
|
|
241
|
+
|
|
242
|
+
### Sync function removal
|
|
243
|
+
|
|
244
|
+
The sync secure-key functions (`getSecureKey`, `setSecureKey`, `deleteSecureKey`) have been deleted. All code uses the async variants (`getSecureKeyAsync`, `setSecureKeyAsync`, `deleteSecureKeyAsync`).
|
|
194
245
|
|
|
195
246
|
## Migration
|
|
196
247
|
|
|
197
|
-
Existing encrypted store keys remain accessible
|
|
248
|
+
Existing encrypted store keys remain accessible through the legacy read fallback. When the keychain backend is the resolved primary, reads that miss in the keychain fall back to the encrypted store, so keys written before the single-writer policy are still reachable without a one-time migration step.
|
|
249
|
+
|
|
250
|
+
Over time, as keys are re-written through normal credential update flows, they will be written only to the resolved primary backend. The legacy fallback read path will eventually become a no-op as all keys migrate naturally to the keychain.
|
|
251
|
+
|
|
252
|
+
Deletes always clean up both stores, so stale keys in the non-primary store are removed as part of normal credential lifecycle operations.
|
|
198
253
|
|
|
199
254
|
The old `keychain.ts` module (which called `/usr/bin/security` CLI directly) has been deleted. The old keychain-to-encrypted migration code has been removed. All keychain access now flows exclusively through the broker.
|
|
255
|
+
|
|
256
|
+
## Extensibility
|
|
257
|
+
|
|
258
|
+
The `CredentialBackend` interface is the extension point for adding new storage backends. To add a new backend (e.g., Linux secret service via libsecret/D-Bus, 1Password CLI, cloud KMS):
|
|
259
|
+
|
|
260
|
+
1. Implement the `CredentialBackend` interface in a new module.
|
|
261
|
+
2. Wire the new backend into the resolution logic in `secure-keys.ts`.
|
|
262
|
+
3. The rest of the codebase is unaffected -- all callers go through the existing async API surface.
|
|
263
|
+
|
|
264
|
+
No changes to callsites, the gateway, or the broker protocol are needed when adding a backend.
|
|
@@ -233,7 +233,7 @@ sequenceDiagram
|
|
|
233
233
|
UI->>HTTP: secret_response {requestId, value, delivery: "store"}
|
|
234
234
|
HTTP->>Prompter: resolve(value, "store")
|
|
235
235
|
Prompter->>Vault: {value, delivery: "store"}
|
|
236
|
-
Vault->>Keychain:
|
|
236
|
+
Vault->>Keychain: setSecureKeyAsync("credential/svc/field", value)
|
|
237
237
|
Vault->>Model: "Credential stored securely" (no value in output)
|
|
238
238
|
else One-Time Send (if enabled)
|
|
239
239
|
UI->>HTTP: secret_response {requestId, value, delivery: "transient_send"}
|
|
@@ -272,7 +272,7 @@ graph TB
|
|
|
272
272
|
TOOL["Tool (e.g. browser_fill_credential)"] --> BROKER["CredentialBroker.use(service, field, tool, domain)"]
|
|
273
273
|
BROKER --> POLICY{"Check policy:<br/>allowedTools + allowedDomains"}
|
|
274
274
|
POLICY -->|denied| REJECT["PolicyDenied error"]
|
|
275
|
-
POLICY -->|allowed| FETCH["
|
|
275
|
+
POLICY -->|allowed| FETCH["getSecureKeyAsync(credential/svc/field)"]
|
|
276
276
|
FETCH --> INJECT["Inject value into tool execution<br/>(never returned to model)"]
|
|
277
277
|
```
|
|
278
278
|
|
package/knip.json
CHANGED
|
@@ -1,32 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"entry": [
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"src/
|
|
7
|
-
"src/daemon/main.ts",
|
|
8
|
-
"src/daemon/tls-certs.ts",
|
|
9
|
-
"src/errors.ts",
|
|
10
|
-
"src/events/index.ts",
|
|
11
|
-
"src/followups/index.ts",
|
|
12
|
-
"src/playbooks/index.ts",
|
|
13
|
-
"src/runtime/auth/index.ts",
|
|
14
|
-
"src/tasks/candidate-store.ts",
|
|
15
|
-
"src/tools/browser/api-map.ts",
|
|
16
|
-
"src/tools/browser/auto-navigate.ts",
|
|
17
|
-
"src/tools/browser/headless-browser.ts",
|
|
18
|
-
"src/tools/browser/recording-store.ts",
|
|
19
|
-
"src/tools/tasks/index.ts"
|
|
2
|
+
"entry": [
|
|
3
|
+
"src/**/*.test.ts",
|
|
4
|
+
"src/**/__tests__/**/*.ts",
|
|
5
|
+
"scripts/**/*.ts",
|
|
6
|
+
"src/daemon/main.ts"
|
|
20
7
|
],
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"@vellumai/cli",
|
|
24
|
-
"esbuild",
|
|
25
|
-
"hono",
|
|
26
|
-
"ink",
|
|
27
|
-
"preact",
|
|
28
|
-
"quicktype-core",
|
|
29
|
-
"tree-sitter-bash",
|
|
30
|
-
"typescript-json-schema"
|
|
31
|
-
]
|
|
8
|
+
"project": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*.ts"],
|
|
9
|
+
"ignoreDependencies": ["@vellumai/cli"]
|
|
32
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/assistant",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.55",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts"
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"@anthropic-ai/claude-agent-sdk": "^0.2.42",
|
|
30
30
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
31
31
|
"@google/genai": "^1.40.0",
|
|
32
|
-
"@hono/node-server": "^1.19.11",
|
|
33
32
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
34
33
|
"@qdrant/js-client-rest": "^1.16.2",
|
|
35
34
|
"@sentry/node": "^10.38.0",
|
|
@@ -39,9 +38,6 @@
|
|
|
39
38
|
"croner": "^10.0.1",
|
|
40
39
|
"dotenv": "^17.3.1",
|
|
41
40
|
"drizzle-orm": "^0.38.4",
|
|
42
|
-
"esbuild": "^0.24.0",
|
|
43
|
-
"hono": "^4.12.5",
|
|
44
|
-
"ink": "^6.7.0",
|
|
45
41
|
"jszip": "^3.10.1",
|
|
46
42
|
"minimatch": "^10.2.4",
|
|
47
43
|
"openai": "^6.18.0",
|
|
@@ -49,7 +45,6 @@
|
|
|
49
45
|
"pino-pretty": "^13.1.3",
|
|
50
46
|
"playwright": "^1.58.2",
|
|
51
47
|
"postgres": "^3.4.8",
|
|
52
|
-
"preact": "^10.25.0",
|
|
53
48
|
"qrcode": "^1.5.4",
|
|
54
49
|
"react": "^19.2.4",
|
|
55
50
|
"rrule": "^2.8.1",
|
|
@@ -76,9 +71,7 @@
|
|
|
76
71
|
"fast-check": "^4.5.3",
|
|
77
72
|
"knip": "^5.83.1",
|
|
78
73
|
"prettier": "^3.8.1",
|
|
79
|
-
"quicktype-core": "^23.2.6",
|
|
80
74
|
"typescript": "^5.7.3",
|
|
81
|
-
"typescript-eslint": "^8.54.0"
|
|
82
|
-
"typescript-json-schema": "^0.67.1"
|
|
75
|
+
"typescript-eslint": "^8.54.0"
|
|
83
76
|
}
|
|
84
77
|
}
|
|
@@ -1206,7 +1206,7 @@ describe("AgentLoop", () => {
|
|
|
1206
1206
|
// Dynamic tool resolver (resolveTools) tests
|
|
1207
1207
|
// ---------------------------------------------------------------------------
|
|
1208
1208
|
|
|
1209
|
-
// 25. Without resolveTools, static tools are used
|
|
1209
|
+
// 25. Without resolveTools, static tools are used
|
|
1210
1210
|
test("without resolveTools, static tools are passed to provider", async () => {
|
|
1211
1211
|
const { provider, calls } = createMockProvider([textResponse("Hi")]);
|
|
1212
1212
|
const loop = new AgentLoop(provider, "system", {}, dummyTools);
|
|
@@ -3,7 +3,6 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
5
|
|
|
6
|
-
import { _resetAppGitState } from "../memory/app-git-service.js";
|
|
7
6
|
import { _resetGitServiceRegistry } from "../workspace/git-service.js";
|
|
8
7
|
|
|
9
8
|
// Mock getDataDir to use a temp directory
|
|
@@ -35,7 +34,6 @@ describe("App Git History", () => {
|
|
|
35
34
|
);
|
|
36
35
|
mkdirSync(join(testDataDir, "apps"), { recursive: true });
|
|
37
36
|
_resetGitServiceRegistry();
|
|
38
|
-
_resetAppGitState();
|
|
39
37
|
});
|
|
40
38
|
|
|
41
39
|
afterEach(() => {
|
|
@@ -4,10 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
_resetAppGitState,
|
|
9
|
-
commitAppTurnChanges,
|
|
10
|
-
} from "../memory/app-git-service.js";
|
|
7
|
+
import { commitAppTurnChanges } from "../memory/app-git-service.js";
|
|
11
8
|
import { _resetGitServiceRegistry } from "../workspace/git-service.js";
|
|
12
9
|
|
|
13
10
|
// Mock getDataDir to use a temp directory
|
|
@@ -36,7 +33,6 @@ describe("App Git Service", () => {
|
|
|
36
33
|
);
|
|
37
34
|
mkdirSync(join(testDataDir, "apps"), { recursive: true });
|
|
38
35
|
_resetGitServiceRegistry();
|
|
39
|
-
_resetAppGitState();
|
|
40
36
|
});
|
|
41
37
|
|
|
42
38
|
afterEach(() => {
|
|
@@ -128,7 +124,6 @@ describe("App Git Service", () => {
|
|
|
128
124
|
});
|
|
129
125
|
|
|
130
126
|
test("commitAppTurnChanges swallows errors gracefully", async () => {
|
|
131
|
-
_resetAppGitState();
|
|
132
127
|
// This should not throw
|
|
133
128
|
await commitAppTurnChanges("test", 1);
|
|
134
129
|
});
|
|
@@ -34,7 +34,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
34
34
|
mock.module("../security/secure-keys.js", () => ({
|
|
35
35
|
getSecureKeyAsync: async (name: string) =>
|
|
36
36
|
name === "gemini" ? mockGeminiKey : null,
|
|
37
|
-
getSecureKey: () => null,
|
|
38
37
|
}));
|
|
39
38
|
|
|
40
39
|
mock.module("../util/platform.js", () => ({
|
|
@@ -58,16 +58,11 @@ let mockGetSecureKey: ReturnType<typeof mock>;
|
|
|
58
58
|
let mockGetCredentialMetadata: ReturnType<typeof mock>;
|
|
59
59
|
|
|
60
60
|
mock.module("../security/secure-keys.js", () => ({
|
|
61
|
-
getSecureKey: (...args: unknown[]) => mockGetSecureKey(...args),
|
|
62
61
|
getSecureKeyAsync: async (...args: unknown[]) => mockGetSecureKey(...args),
|
|
63
|
-
setSecureKey: () => true,
|
|
64
62
|
setSecureKeyAsync: async () => true,
|
|
65
|
-
deleteSecureKey: () => "deleted",
|
|
66
63
|
deleteSecureKeyAsync: async () => "deleted",
|
|
67
|
-
|
|
68
|
-
getBackendType: () => "encrypted",
|
|
64
|
+
listSecureKeysAsync: async () => [],
|
|
69
65
|
_resetBackend: () => {},
|
|
70
|
-
_setBackend: () => {},
|
|
71
66
|
}));
|
|
72
67
|
|
|
73
68
|
mock.module("../tools/credentials/metadata-store.js", () => ({
|
|
@@ -1441,7 +1441,7 @@ describe("HTTP handler channel-aware guardian status", () => {
|
|
|
1441
1441
|
expect(resp!.guardianDisplayName).toBe("Guardian Name");
|
|
1442
1442
|
});
|
|
1443
1443
|
|
|
1444
|
-
test("status action defaults channel to telegram when omitted
|
|
1444
|
+
test("status action defaults channel to telegram when omitted", async () => {
|
|
1445
1445
|
const { ctx, lastResponse } = createMockCtx();
|
|
1446
1446
|
const msg: ChannelVerificationSessionRequest = {
|
|
1447
1447
|
type: "channel_verification_session",
|
|
@@ -1457,7 +1457,7 @@ describe("HTTP handler channel-aware guardian status", () => {
|
|
|
1457
1457
|
expect(resp!.assistantId).toBe("self");
|
|
1458
1458
|
});
|
|
1459
1459
|
|
|
1460
|
-
test("status action defaults assistantId to self when omitted
|
|
1460
|
+
test("status action defaults assistantId to self when omitted", async () => {
|
|
1461
1461
|
const { ctx, lastResponse } = createMockCtx();
|
|
1462
1462
|
const msg: ChannelVerificationSessionRequest = {
|
|
1463
1463
|
type: "channel_verification_session",
|
|
@@ -2282,9 +2282,9 @@ describe("outbound verification sessions", () => {
|
|
|
2282
2282
|
expect(result2.success).toBe(false);
|
|
2283
2283
|
});
|
|
2284
2284
|
|
|
2285
|
-
// ──
|
|
2285
|
+
// ── Inbound-only verification flow ──
|
|
2286
2286
|
|
|
2287
|
-
test("
|
|
2287
|
+
test("inbound-only challenge without expected identity still works", () => {
|
|
2288
2288
|
const { secret } = createInboundVerificationSession("telegram");
|
|
2289
2289
|
|
|
2290
2290
|
const result = validateAndConsumeVerification(
|
|
@@ -134,8 +134,10 @@ registerTool(mockBundledSkillTool);
|
|
|
134
134
|
|
|
135
135
|
// Register CU tools so classifyRisk returns their declared Low risk level
|
|
136
136
|
// instead of falling through to Medium (unknown tool).
|
|
137
|
-
import {
|
|
138
|
-
|
|
137
|
+
import { allComputerUseTools } from "../tools/computer-use/definitions.js";
|
|
138
|
+
for (const tool of allComputerUseTools) {
|
|
139
|
+
registerTool(tool);
|
|
140
|
+
}
|
|
139
141
|
|
|
140
142
|
function writeSkill(
|
|
141
143
|
skillId: string,
|
|
@@ -1945,7 +1947,7 @@ describe("Permission Checker", () => {
|
|
|
1945
1947
|
).toHaveLength(0);
|
|
1946
1948
|
});
|
|
1947
1949
|
|
|
1948
|
-
test("returns directory options when toolName is omitted
|
|
1950
|
+
test("returns directory options when toolName is omitted", () => {
|
|
1949
1951
|
const options = generateScopeOptions("/home/user/project");
|
|
1950
1952
|
expect(options).toHaveLength(3);
|
|
1951
1953
|
expect(options[0].scope).toBe("/home/user/project");
|
|
@@ -2119,11 +2121,11 @@ describe("Permission Checker", () => {
|
|
|
2119
2121
|
});
|
|
2120
2122
|
});
|
|
2121
2123
|
|
|
2122
|
-
// ──
|
|
2124
|
+
// ── addRule basics ──
|
|
2123
2125
|
// These tests verify that addRule() creates standard rules that
|
|
2124
2126
|
// match by tool name, pattern glob, and scope prefix.
|
|
2125
2127
|
|
|
2126
|
-
describe("
|
|
2128
|
+
describe("addRule basics", () => {
|
|
2127
2129
|
test("rule matches by tool/pattern/scope", async () => {
|
|
2128
2130
|
addRule("skill_test_tool", "skill_test_tool:*", "/tmp", "allow", 2000);
|
|
2129
2131
|
const result = await check("skill_test_tool", {}, "/tmp");
|
|
@@ -2172,7 +2174,7 @@ describe("Permission Checker", () => {
|
|
|
2172
2174
|
expect(v2Result.matchedRule?.id).toBe(v1Result.matchedRule?.id);
|
|
2173
2175
|
});
|
|
2174
2176
|
|
|
2175
|
-
test("findHighestPriorityRule works without policy context
|
|
2177
|
+
test("findHighestPriorityRule works without policy context", () => {
|
|
2176
2178
|
// Calling findHighestPriorityRule without the optional 4th ctx
|
|
2177
2179
|
// parameter still works — wildcard rules match any caller.
|
|
2178
2180
|
addRule("skill_test_tool", "skill_test_tool:*", "/tmp", "allow", 2000);
|
|
@@ -2197,14 +2199,14 @@ describe("Permission Checker", () => {
|
|
|
2197
2199
|
});
|
|
2198
2200
|
});
|
|
2199
2201
|
|
|
2200
|
-
// ──
|
|
2202
|
+
// ── optional policyContext parameter ─────────────
|
|
2201
2203
|
|
|
2202
|
-
describe("
|
|
2203
|
-
test("check() without policyContext still works
|
|
2204
|
-
addRule("bash", "echo
|
|
2204
|
+
describe("optional policyContext parameter", () => {
|
|
2205
|
+
test("check() without policyContext still works", async () => {
|
|
2206
|
+
addRule("bash", "echo test-optional-ctx", "/tmp", "allow", 2000);
|
|
2205
2207
|
const result = await check(
|
|
2206
2208
|
"bash",
|
|
2207
|
-
{ command: "echo
|
|
2209
|
+
{ command: "echo test-optional-ctx" },
|
|
2208
2210
|
"/tmp",
|
|
2209
2211
|
);
|
|
2210
2212
|
expect(result.decision).toBe("allow");
|
|
@@ -35,7 +35,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
35
35
|
mock.module("../security/secure-keys.js", () => ({
|
|
36
36
|
getSecureKeyAsync: async (name: string) =>
|
|
37
37
|
name === "anthropic" ? "fake-anthropic-key" : null,
|
|
38
|
-
getSecureKey: () => null,
|
|
39
38
|
}));
|
|
40
39
|
|
|
41
40
|
// ---------------------------------------------------------------------------
|
|
@@ -40,7 +40,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
40
40
|
mock.module("../security/secure-keys.js", () => ({
|
|
41
41
|
getSecureKeyAsync: async (name: string) =>
|
|
42
42
|
name === "anthropic" ? "fake-anthropic-key" : null,
|
|
43
|
-
getSecureKey: () => null,
|
|
44
43
|
}));
|
|
45
44
|
|
|
46
45
|
import { claudeCodeTool } from "../tools/claude-code/claude-code.js";
|
|
@@ -88,7 +87,7 @@ describe("claude_code tool profile support", () => {
|
|
|
88
87
|
}
|
|
89
88
|
});
|
|
90
89
|
|
|
91
|
-
test("omitted profile defaults to general
|
|
90
|
+
test("omitted profile defaults to general", async () => {
|
|
92
91
|
const result = await claudeCodeTool.execute(
|
|
93
92
|
{ prompt: "test" },
|
|
94
93
|
makeContext(),
|
|
@@ -88,7 +88,6 @@ import {
|
|
|
88
88
|
loadConfig,
|
|
89
89
|
} from "../config/loader.js";
|
|
90
90
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
91
|
-
import { _setBackend } from "../security/secure-keys.js";
|
|
92
91
|
|
|
93
92
|
// ---------------------------------------------------------------------------
|
|
94
93
|
// Helpers
|
|
@@ -183,13 +182,11 @@ describe("config loader backfill", () => {
|
|
|
183
182
|
}
|
|
184
183
|
ensureTestDir();
|
|
185
184
|
_setStorePath(join(TEST_DIR, "keys.enc"));
|
|
186
|
-
_setBackend("encrypted");
|
|
187
185
|
invalidateConfigCache();
|
|
188
186
|
});
|
|
189
187
|
|
|
190
188
|
afterEach(() => {
|
|
191
189
|
_setStorePath(null);
|
|
192
|
-
_setBackend(undefined);
|
|
193
190
|
invalidateConfigCache();
|
|
194
191
|
});
|
|
195
192
|
|