@mycadet/assistant 0.12.10 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  3. package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  5. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  6. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  7. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +10 -10
  8. package/node_modules/@mycadet/service-contracts/src/ingress.ts +3 -3
  9. package/openapi.yaml +2 -708
  10. package/package.json +2 -3
  11. package/scripts/sync-web-search-catalog.ts +1 -2
  12. package/src/__tests__/cloud-profile-guard.test.ts +41 -5
  13. package/src/__tests__/config-schema.test.ts +6 -38
  14. package/src/__tests__/conversation-queue.test.ts +45 -6
  15. package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
  16. package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
  17. package/src/__tests__/persona-resolver.test.ts +0 -51
  18. package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
  19. package/src/__tests__/run-due-schedules.test.ts +0 -4
  20. package/src/__tests__/schedule-store.test.ts +0 -12
  21. package/src/__tests__/stt-stream-session.test.ts +4 -4
  22. package/src/__tests__/telephony-tts-guard.test.ts +2 -50
  23. package/src/__tests__/voice-config-update.test.ts +12 -167
  24. package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
  25. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
  26. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  27. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
  28. package/src/calls/resolve-call-tts-provider.ts +6 -25
  29. package/src/calls/telephony-credential-preflight.ts +0 -10
  30. package/src/calls/telephony-tts-capability.ts +1 -17
  31. package/src/cli/commands/tts.ts +1 -2
  32. package/src/cli/index.help.ts +0 -2
  33. package/src/cli/program.ts +0 -2
  34. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
  35. package/src/config/schemas/__tests__/stt.test.ts +10 -14
  36. package/src/config/schemas/llm.ts +3 -5
  37. package/src/config/schemas/services.ts +5 -6
  38. package/src/config/schemas/stt.ts +1 -3
  39. package/src/config/schemas/tts.ts +1 -30
  40. package/src/daemon/daemon-readiness.ts +4 -29
  41. package/src/daemon/lifecycle.ts +0 -31
  42. package/src/daemon/providers-setup.ts +0 -8
  43. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  44. package/src/heartbeat/heartbeat-service.ts +1 -66
  45. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
  46. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
  47. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
  48. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  49. package/src/live-voice/live-voice-credential-preflight.ts +9 -33
  50. package/src/live-voice/live-voice-session.ts +10 -6
  51. package/src/live-voice/live-voice-tts.ts +1 -8
  52. package/src/plugins/defaults/memory/worker.ts +3 -3
  53. package/src/prompts/persona-resolver.ts +0 -35
  54. package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
  55. package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
  56. package/src/providers/search-provider-catalog.ts +7 -39
  57. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
  58. package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
  59. package/src/providers/speech-to-text/provider-catalog.ts +2 -73
  60. package/src/providers/speech-to-text/resolve.ts +3 -120
  61. package/src/runtime/http-router-types.ts +1 -3
  62. package/src/runtime/http-server.ts +1 -1
  63. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +13 -11
  64. package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
  65. package/src/runtime/routes/conversation-query-routes.ts +0 -96
  66. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
  67. package/src/runtime/routes/index.ts +0 -4
  68. package/src/runtime/routes/live-voice-routes.test.ts +4 -11
  69. package/src/runtime/routes/live-voice-routes.ts +8 -11
  70. package/src/runtime/routes/secret-routes.ts +0 -6
  71. package/src/runtime/routes/tts-routes.ts +0 -38
  72. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
  73. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
  74. package/src/schedule/schedule-store.ts +0 -7
  75. package/src/schedule/scheduler.ts +0 -5
  76. package/src/schedule/worker.ts +1 -1
  77. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
  78. package/src/stt/daemon-batch-transcriber.ts +4 -38
  79. package/src/stt/types.ts +1 -9
  80. package/src/tools/network/__tests__/web-search.test.ts +3 -250
  81. package/src/tools/network/web-search.ts +4 -165
  82. package/src/tts/__tests__/provider-resolution.test.ts +0 -5
  83. package/src/tts/provider-catalog.ts +0 -4
  84. package/src/tts/types.ts +0 -1
  85. package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
  86. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
  87. package/src/__tests__/migration-export-http.test.ts +0 -557
  88. package/src/__tests__/migration-export-streaming.test.ts +0 -350
  89. package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
  90. package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
  91. package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
  92. package/src/__tests__/migration-import-from-path.test.ts +0 -324
  93. package/src/__tests__/migration-import-from-url.test.ts +0 -588
  94. package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
  95. package/src/__tests__/migration-jobs-status.test.ts +0 -139
  96. package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
  97. package/src/__tests__/migration-transport.test.ts +0 -1089
  98. package/src/__tests__/migration-validate-http.test.ts +0 -634
  99. package/src/__tests__/migration-wizard.test.ts +0 -1395
  100. package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
  101. package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
  102. package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
  103. package/src/__tests__/validation-results-screen.test.ts +0 -1130
  104. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
  105. package/src/background-wake/background-wake-routes.test.ts +0 -906
  106. package/src/background-wake/cloud-client.test.ts +0 -328
  107. package/src/background-wake/cloud-client.ts +0 -196
  108. package/src/background-wake/next-wake.test.ts +0 -335
  109. package/src/background-wake/next-wake.ts +0 -188
  110. package/src/background-wake/publisher.ts +0 -93
  111. package/src/background-wake/wake-intent-hooks.test.ts +0 -234
  112. package/src/cli/commands/__tests__/backup.test.ts +0 -363
  113. package/src/cli/commands/backup.help.ts +0 -180
  114. package/src/cli/commands/backup.ts +0 -398
  115. package/src/cloud/cloud-speech.test.ts +0 -335
  116. package/src/cloud/cloud-speech.ts +0 -300
  117. package/src/config/cloud-speech-defaults.ts +0 -300
  118. package/src/config/sanitize-for-transfer.ts +0 -111
  119. package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
  120. package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
  121. package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
  122. package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
  123. package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
  124. package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
  125. package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
  126. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
  127. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
  128. package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
  129. package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
  130. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
  131. package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
  132. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
  133. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
  134. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
  135. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
  136. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
  137. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
  138. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
  139. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
  140. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
  141. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
  142. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
  143. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
  144. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
  145. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
  146. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
  147. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
  148. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
  149. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
  150. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
  151. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
  152. package/src/runtime/migrations/gcs-signed-url.ts +0 -190
  153. package/src/runtime/migrations/job-registry.ts +0 -297
  154. package/src/runtime/migrations/migration-transport.ts +0 -612
  155. package/src/runtime/migrations/migration-wizard.ts +0 -747
  156. package/src/runtime/migrations/origin-mode.ts +0 -42
  157. package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
  158. package/src/runtime/migrations/staged-import-path.ts +0 -116
  159. package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
  160. package/src/runtime/migrations/validation-results-screen.ts +0 -469
  161. package/src/runtime/migrations/vbundle-builder.ts +0 -1382
  162. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
  163. package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
  164. package/src/runtime/migrations/vbundle-importer.ts +0 -826
  165. package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
  166. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
  167. package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
  168. package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
  169. package/src/runtime/migrations/vbundle-validator.ts +0 -724
  170. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
  171. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
  172. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
  173. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
  174. package/src/runtime/routes/background-wake-routes.ts +0 -387
  175. package/src/runtime/routes/migration-routes.ts +0 -2584
  176. package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
  177. package/src/tools/network/cloud-search-proxy.ts +0 -211
  178. package/src/tts/__tests__/cadet-provider.test.ts +0 -253
  179. package/src/tts/providers/cadet-provider.ts +0 -244
  180. package/src/types/tar-stream.d.ts +0 -66
@@ -1,227 +0,0 @@
1
- /**
2
- * Tests for the post-import cadet metadata reconciliation helper.
3
- *
4
- * After every bundle import, `reconcileCadetMetadataFromCes` walks the
5
- * cadet credentials the gateway requires and, for each one that
6
- * CES already holds a value for, ensures `metadata.json` lists a
7
- * matching entry. This closes the race where a provisioning write to
8
- * CES completes successfully but its metadata upsert gets clobbered by
9
- * the import's in-place clear or atomic swap. The reconciled set covers
10
- * the Django-provisioned credentials (assistant_api_key, webhook_secret).
11
- *
12
- * We test the reconcile logic in isolation by mocking the secure-keys
13
- * and metadata-store modules — the real migration handler wires the
14
- * same imports, so the behavior under test matches production.
15
- *
16
- * Covered:
17
- * - CES has all 6 fields + metadata empty → all 6 upserted.
18
- * - CES has all 6 + metadata has 2 → only the missing 4 upserted.
19
- * - CES has no values → nothing upserted.
20
- * - CES has values + metadata already has them → no-op (no duplicate
21
- * upserts).
22
- * - upsert throws for one field → warning recorded, loop continues.
23
- */
24
-
25
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
26
-
27
- import { credentialKey } from "../../../security/credential-key.js";
28
- import * as actualMetadataStore from "../../../tools/credentials/metadata-store.js";
29
-
30
- type MetadataRecord = {
31
- credentialId: string;
32
- service: string;
33
- field: string;
34
- allowedTools: string[];
35
- allowedDomains: string[];
36
- createdAt: number;
37
- updatedAt: number;
38
- };
39
-
40
- const CADET_FIELDS = ["assistant_api_key", "webhook_secret"] as const;
41
-
42
- const upsertCalls: Array<{ service: string; field: string }> = [];
43
- let metadataStore: Map<string, MetadataRecord> = new Map();
44
- let upsertImpl: (service: string, field: string) => void = (service, field) => {
45
- const key = `${service}:${field}`;
46
- metadataStore.set(key, {
47
- credentialId: `id-${key}`,
48
- service,
49
- field,
50
- allowedTools: [],
51
- allowedDomains: [],
52
- createdAt: Date.now(),
53
- updatedAt: Date.now(),
54
- });
55
- };
56
-
57
- // The REAL secure-keys module pinned to a live memory backend; the reconcile
58
- // reads getSecureKeyAsync(cadet credential keys), which the store serves.
59
- import { useMemorySecureKeyMap } from "../../../__tests__/helpers/secure-keys.js";
60
-
61
- const cesValues = useMemorySecureKeyMap();
62
-
63
- // Spread the real module so every export the reconcile handler's transitive
64
- // graph links (e.g. getCredentialMetadataById, listCredentialMetadata pulled in
65
- // via the plugin-api hub) stays present; only the two functions under test are
66
- // overridden. A partial mock replaces the whole namespace and fails to link the
67
- // unlisted exports.
68
- mock.module("../../../tools/credentials/metadata-store.js", () => ({
69
- ...actualMetadataStore,
70
- getCredentialMetadata: (service: string, field: string) =>
71
- metadataStore.get(`${service}:${field}`),
72
- upsertCredentialMetadata: (
73
- service: string,
74
- field: string,
75
- _policy?: unknown,
76
- ) => {
77
- upsertCalls.push({ service, field });
78
- upsertImpl(service, field);
79
- return metadataStore.get(`${service}:${field}`);
80
- },
81
- }));
82
-
83
- // Import under test AFTER the mocks are set up.
84
- const { reconcileCadetMetadataFromCes } =
85
- (await import("../migration-routes.js")) as unknown as {
86
- reconcileCadetMetadataFromCes: (sink: {
87
- warnings: string[];
88
- }) => Promise<void>;
89
- };
90
-
91
- beforeEach(() => {
92
- upsertCalls.length = 0;
93
- metadataStore = new Map();
94
- cesValues.clear();
95
- upsertImpl = (service, field) => {
96
- const key = `${service}:${field}`;
97
- metadataStore.set(key, {
98
- credentialId: `id-${key}`,
99
- service,
100
- field,
101
- allowedTools: [],
102
- allowedDomains: [],
103
- createdAt: Date.now(),
104
- updatedAt: Date.now(),
105
- });
106
- };
107
- });
108
-
109
- afterEach(() => {
110
- upsertCalls.length = 0;
111
- });
112
-
113
- function seedAllInCes(): void {
114
- for (const field of CADET_FIELDS) {
115
- cesValues.set(credentialKey("cadet", field), `value-for-${field}`);
116
- }
117
- }
118
-
119
- describe("reconcileCadetMetadataFromCes", () => {
120
- test("CES holds all fields, metadata empty → all upserted", async () => {
121
- seedAllInCes();
122
- const sink = { warnings: [] as string[] };
123
-
124
- await reconcileCadetMetadataFromCes(sink);
125
-
126
- expect(upsertCalls).toHaveLength(CADET_FIELDS.length);
127
- expect(new Set(upsertCalls.map((c) => c.field))).toEqual(
128
- new Set(CADET_FIELDS),
129
- );
130
- expect(sink.warnings).toHaveLength(0);
131
- });
132
-
133
- test("CES holds all, metadata has 2 → only the missing ones upserted", async () => {
134
- seedAllInCes();
135
- // Pre-populate metadata for 2 of the fields.
136
- const prepopulated = ["assistant_api_key", "webhook_secret"] as const;
137
- for (const field of prepopulated) {
138
- metadataStore.set(`cadet:${field}`, {
139
- credentialId: `id-cadet:${field}`,
140
- service: "cadet",
141
- field,
142
- allowedTools: [],
143
- allowedDomains: [],
144
- createdAt: 1,
145
- updatedAt: 1,
146
- });
147
- }
148
-
149
- const sink = { warnings: [] as string[] };
150
- await reconcileCadetMetadataFromCes(sink);
151
-
152
- const expectedMissing = CADET_FIELDS.filter(
153
- (f) => !(prepopulated as readonly string[]).includes(f),
154
- );
155
- expect(upsertCalls).toHaveLength(expectedMissing.length);
156
- expect(new Set(upsertCalls.map((c) => c.field))).toEqual(
157
- new Set(expectedMissing),
158
- );
159
- });
160
-
161
- test("covers both Django-provisioned and client-injected identity fields", async () => {
162
- seedAllInCes();
163
- const sink = { warnings: [] as string[] };
164
- await reconcileCadetMetadataFromCes(sink);
165
-
166
- const reconciled = new Set(upsertCalls.map((c) => c.field));
167
- // Django-provisioned trio.
168
- expect(reconciled).toContain("assistant_api_key");
169
- expect(reconciled).toContain("webhook_secret");
170
- });
171
-
172
- test("CES empty → nothing upserted", async () => {
173
- const sink = { warnings: [] as string[] };
174
- await reconcileCadetMetadataFromCes(sink);
175
- expect(upsertCalls).toHaveLength(0);
176
- expect(sink.warnings).toHaveLength(0);
177
- });
178
-
179
- test("CES has values, metadata already has entries → no duplicate upserts", async () => {
180
- seedAllInCes();
181
- for (const field of CADET_FIELDS) {
182
- metadataStore.set(`cadet:${field}`, {
183
- credentialId: `id-cadet:${field}`,
184
- service: "cadet",
185
- field,
186
- allowedTools: [],
187
- allowedDomains: [],
188
- createdAt: 1,
189
- updatedAt: 1,
190
- });
191
- }
192
-
193
- const sink = { warnings: [] as string[] };
194
- await reconcileCadetMetadataFromCes(sink);
195
-
196
- expect(upsertCalls).toHaveLength(0);
197
- });
198
-
199
- test("upsert throws for one field → warning recorded, loop continues", async () => {
200
- seedAllInCes();
201
- let calls = 0;
202
- upsertImpl = (service, field) => {
203
- calls += 1;
204
- if (field === "assistant_api_key") {
205
- throw new Error("simulated metadata write failure");
206
- }
207
- const key = `${service}:${field}`;
208
- metadataStore.set(key, {
209
- credentialId: `id-${key}`,
210
- service,
211
- field,
212
- allowedTools: [],
213
- allowedDomains: [],
214
- createdAt: Date.now(),
215
- updatedAt: Date.now(),
216
- });
217
- };
218
-
219
- const sink = { warnings: [] as string[] };
220
- await reconcileCadetMetadataFromCes(sink);
221
-
222
- // Every field was attempted (loop did not abort).
223
- expect(calls).toBe(CADET_FIELDS.length);
224
- expect(sink.warnings).toHaveLength(1);
225
- expect(sink.warnings[0]).toContain("cadet:assistant_api_key");
226
- });
227
- });
@@ -1,54 +0,0 @@
1
- /**
2
- * Unit tests for `computeSecretsRedacted`.
3
- *
4
- * The helper decides whether the v1 manifest may truthfully claim
5
- * `secrets_redacted: true`. Three signals feed in:
6
- * - credentialCount: number of credentials actually included in the bundle.
7
- * - storeUnreachable: top-level `listSecureKeysAsync()` failed — we never
8
- * enumerated the accounts.
9
- * - perAccountUnreachable: enumeration succeeded, but at least one
10
- * `getSecureKeyResultAsync(account)` returned `unreachable: true`. Those
11
- * accounts were silently skipped from the credentials array, so a zero
12
- * count could be hiding real secrets we just couldn't read.
13
- *
14
- * Only the all-clear case (zero credentials, both flags false) may return
15
- * true. Any failure mode forces false — claiming a clean redaction in those
16
- * cases would lie about what's in the bundle.
17
- */
18
-
19
- import { describe, expect, test } from "bun:test";
20
-
21
- import { computeSecretsRedacted } from "../migration-routes.js";
22
-
23
- describe("computeSecretsRedacted", () => {
24
- test("genuinely empty store + reachable + no per-account failures → true", () => {
25
- expect(computeSecretsRedacted(0, false, false)).toBe(true);
26
- });
27
-
28
- test("top-level store unreachable → false (couldn't read at all)", () => {
29
- expect(computeSecretsRedacted(0, true, false)).toBe(false);
30
- });
31
-
32
- test("per-account read failure → false (partial read failure)", () => {
33
- expect(computeSecretsRedacted(0, false, true)).toBe(false);
34
- });
35
-
36
- test("both top-level and per-account failures → false", () => {
37
- expect(computeSecretsRedacted(0, true, true)).toBe(false);
38
- });
39
-
40
- test("credentials included + no failures → false (creds in bundle)", () => {
41
- expect(computeSecretsRedacted(3, false, false)).toBe(false);
42
- });
43
-
44
- test("credentials included + per-account failure → false (creds + partial failure)", () => {
45
- expect(computeSecretsRedacted(3, false, true)).toBe(false);
46
- });
47
-
48
- test("credentials included + store unreachable → false", () => {
49
- // Defensive: in practice `storeUnreachable` implies zero credentials,
50
- // but the helper should still behave correctly if a caller passes a
51
- // non-zero count alongside an unreachable flag.
52
- expect(computeSecretsRedacted(3, true, false)).toBe(false);
53
- });
54
- });
@@ -1,70 +0,0 @@
1
- /**
2
- * Tests for the import-only host-allowlist plumbing derived from the
3
- * platform-injected `CADET_MIGRATION_IMPORT_ALLOWED_HOSTS` env var.
4
- *
5
- * Covers:
6
- * - `parseMigrationImportAllowedHostsEnv`: unset / empty / whitespace-only
7
- * values yield `undefined` (strict default); single + comma-separated
8
- * lists are parsed, trimmed, and empties dropped.
9
- * - `resolveImportValidatorOptions`: an explicit test override wins over the
10
- * env allowlist; otherwise the env value is used. This resolver is applied
11
- * ONLY by the import handlers — the export upload path stays strict — so it
12
- * is the seam that keeps the env allowlist from widening export SSRF.
13
- */
14
-
15
- import { describe, expect, test } from "bun:test";
16
-
17
- import {
18
- parseMigrationImportAllowedHostsEnv,
19
- resolveImportValidatorOptions,
20
- } from "../migration-routes.js";
21
-
22
- describe("parseMigrationImportAllowedHostsEnv", () => {
23
- test("returns undefined when unset", () => {
24
- expect(parseMigrationImportAllowedHostsEnv(undefined)).toBeUndefined();
25
- });
26
-
27
- test("returns undefined for an empty string", () => {
28
- expect(parseMigrationImportAllowedHostsEnv("")).toBeUndefined();
29
- });
30
-
31
- test("returns undefined for whitespace / commas only", () => {
32
- expect(parseMigrationImportAllowedHostsEnv(" ")).toBeUndefined();
33
- expect(parseMigrationImportAllowedHostsEnv(" , , ")).toBeUndefined();
34
- });
35
-
36
- test("parses a single host", () => {
37
- expect(parseMigrationImportAllowedHostsEnv("host.docker.internal")).toEqual(
38
- { allowedHosts: ["host.docker.internal"] },
39
- );
40
- });
41
-
42
- test("parses and trims a comma-separated list, dropping empties", () => {
43
- expect(
44
- parseMigrationImportAllowedHostsEnv(
45
- " host.docker.internal , ,localhost ",
46
- ),
47
- ).toEqual({ allowedHosts: ["host.docker.internal", "localhost"] });
48
- });
49
- });
50
-
51
- describe("resolveImportValidatorOptions", () => {
52
- test("uses the env allowlist when no test override is set", () => {
53
- expect(
54
- resolveImportValidatorOptions(undefined, "host.docker.internal"),
55
- ).toEqual({ allowedHosts: ["host.docker.internal"] });
56
- });
57
-
58
- test("returns undefined (strict) when neither override nor env is set", () => {
59
- expect(resolveImportValidatorOptions(undefined, undefined)).toBeUndefined();
60
- });
61
-
62
- test("test override takes precedence over the env allowlist", () => {
63
- expect(
64
- resolveImportValidatorOptions(
65
- { allowedHosts: ["override.example"] },
66
- "host.docker.internal",
67
- ),
68
- ).toEqual({ allowedHosts: ["override.example"] });
69
- });
70
- });
@@ -1,257 +0,0 @@
1
- /**
2
- * Tests for platform credential filtering during bundle import.
3
- *
4
- * `migration-routes.ts` pushes every bundle credential through a filter that
5
- * must exclude cloud-identity (`cadet:*`) entries so they can't overwrite
6
- * the target's own Django-provisioned identity. The filter runs against the
7
- * raw CES account format — `credential/{service}/{field}` — produced by
8
- * `credentialKey()`, which is what `listSecureKeysAsync()` returns and what
9
- * `extractCredentialsFromBundle` surfaces back as `account`.
10
- *
11
- * The constant is duplicated here (rather than imported) because
12
- * `migration-routes.ts` has heavy transitive imports that are expensive to
13
- * resolve in a test and would require wide mocking. Instead we bind the two
14
- * copies with a regression assertion: if `credentialKey()`'s output format
15
- * changes, the `startsWith(...)` prefix must change in lockstep. That
16
- * regression is explicit at the bottom of this file.
17
- *
18
- * Covered:
19
- * - Platform (cadet:*) credentials stored under the real `credential/cadet/...`
20
- * key format are excluded.
21
- * - User credentials (any other prefix) pass through unchanged.
22
- * - Mixed bundles correctly split platform vs user credentials.
23
- * - skippedCloud count matches the number of excluded entries.
24
- * - Regression: the prefix constant matches `credentialKey("cadet", "")`.
25
- */
26
-
27
- import { describe, expect, test } from "bun:test";
28
-
29
- import { credentialKey } from "../../../security/credential-key.js";
30
- import { extractCredentialsFromBundle } from "../../migrations/vbundle-importer.js";
31
- import type {
32
- ManifestType,
33
- VBundleTarEntry,
34
- } from "../../migrations/vbundle-validator.js";
35
-
36
- // ---------------------------------------------------------------------------
37
- // The same constant used by migration-routes.ts — kept in sync via the
38
- // regression assertion at the bottom of this file.
39
- // ---------------------------------------------------------------------------
40
-
41
- const PLATFORM_CREDENTIAL_PREFIX = credentialKey("cadet", "");
42
-
43
- // ---------------------------------------------------------------------------
44
- // Helpers (same pattern as vbundle-import-credentials.test.ts)
45
- // ---------------------------------------------------------------------------
46
-
47
- function makeTarEntry(data: string): VBundleTarEntry {
48
- const encoded = new TextEncoder().encode(data);
49
- return { name: "", data: encoded, size: encoded.length };
50
- }
51
-
52
- function makeManifest(paths: string[]): ManifestType {
53
- return {
54
- schema_version: 1,
55
- bundle_id: "00000000-0000-4000-8000-000000000000",
56
- created_at: new Date().toISOString(),
57
- assistant: { id: "self", name: "Test", runtime_version: "0.0.0-test" },
58
- origin: { mode: "self-hosted-local" },
59
- compatibility: {
60
- min_runtime_version: "0.0.0-test",
61
- max_runtime_version: null,
62
- },
63
- contents: paths.map((path) => ({
64
- path,
65
- size_bytes: 0,
66
- sha256: "test",
67
- })),
68
- checksum:
69
- "0000000000000000000000000000000000000000000000000000000000000000",
70
- secrets_redacted: false,
71
- export_options: {
72
- include_logs: false,
73
- include_browser_state: false,
74
- include_memory_vectors: false,
75
- },
76
- } as unknown as ManifestType;
77
- }
78
-
79
- /**
80
- * Build a bundle archive entry path for a credential whose CES account is
81
- * `account`. `vbundle-builder.ts` stores credentials under
82
- * `credentials/<account>`; the importer reverses that split.
83
- */
84
- function bundlePathFor(account: string): string {
85
- return `credentials/${account}`;
86
- }
87
-
88
- /**
89
- * Simulate the filtering logic from migration-routes.ts:
90
- *
91
- * const userCredentials = bundleCredentials.filter(
92
- * (c) => !c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
93
- * );
94
- */
95
- function filterCredentials(
96
- bundleCredentials: Array<{ account: string; value: string }>,
97
- ) {
98
- const userCredentials = bundleCredentials.filter(
99
- (c) => !c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
100
- );
101
- const skippedCloud = bundleCredentials.length - userCredentials.length;
102
- return { userCredentials, skippedCloud };
103
- }
104
-
105
- // ---------------------------------------------------------------------------
106
- // Tests
107
- // ---------------------------------------------------------------------------
108
-
109
- describe("migration import credential filtering", () => {
110
- test("platform (cadet:*) credentials are excluded", () => {
111
- const cadetFields = [
112
- "assistant_api_key",
113
- "cloud_assistant_id",
114
- "webhook_secret",
115
- ] as const;
116
- const cadetPaths = cadetFields.map((f) =>
117
- bundlePathFor(credentialKey("cadet", f)),
118
- );
119
-
120
- const entries = new Map<string, VBundleTarEntry>();
121
- for (const path of cadetPaths) {
122
- entries.set(path, makeTarEntry(`value-for-${path}`));
123
- }
124
-
125
- const manifest = makeManifest(cadetPaths);
126
-
127
- const bundleCredentials = extractCredentialsFromBundle(entries, manifest);
128
- const { userCredentials, skippedCloud } =
129
- filterCredentials(bundleCredentials);
130
-
131
- expect(userCredentials).toHaveLength(0);
132
- expect(skippedCloud).toBe(cadetFields.length);
133
- });
134
-
135
- test("user credentials pass through unchanged", () => {
136
- const openaiAccount = credentialKey("openai", "api_key");
137
- const anthropicAccount = credentialKey("anthropic", "api_key");
138
-
139
- const entries = new Map<string, VBundleTarEntry>();
140
- entries.set(bundlePathFor(openaiAccount), makeTarEntry("sk-user-123"));
141
- entries.set(bundlePathFor(anthropicAccount), makeTarEntry("sk-ant-456"));
142
-
143
- const manifest = makeManifest([
144
- bundlePathFor(openaiAccount),
145
- bundlePathFor(anthropicAccount),
146
- ]);
147
-
148
- const bundleCredentials = extractCredentialsFromBundle(entries, manifest);
149
- const { userCredentials, skippedCloud } =
150
- filterCredentials(bundleCredentials);
151
-
152
- expect(userCredentials).toHaveLength(2);
153
- expect(userCredentials).toContainEqual({
154
- account: openaiAccount,
155
- value: "sk-user-123",
156
- });
157
- expect(userCredentials).toContainEqual({
158
- account: anthropicAccount,
159
- value: "sk-ant-456",
160
- });
161
- expect(skippedCloud).toBe(0);
162
- });
163
-
164
- test("mixed bundle with both platform and user credentials correctly splits", () => {
165
- const cadetApiKey = credentialKey("cadet", "assistant_api_key");
166
- const cadetUserId = credentialKey("cadet", "cloud_assistant_id");
167
- const openaiKey = credentialKey("openai", "api_key");
168
- const anthropicKey = credentialKey("anthropic", "api_key");
169
- const githubToken = credentialKey("github", "api_token");
170
-
171
- const entries = new Map<string, VBundleTarEntry>();
172
- entries.set(bundlePathFor(cadetApiKey), makeTarEntry("platform-key"));
173
- entries.set(bundlePathFor(cadetUserId), makeTarEntry("platform-user"));
174
- entries.set(bundlePathFor(openaiKey), makeTarEntry("sk-user-123"));
175
- entries.set(bundlePathFor(anthropicKey), makeTarEntry("sk-ant-456"));
176
- entries.set(bundlePathFor(githubToken), makeTarEntry("ghp-789"));
177
-
178
- const manifest = makeManifest([
179
- bundlePathFor(cadetApiKey),
180
- bundlePathFor(cadetUserId),
181
- bundlePathFor(openaiKey),
182
- bundlePathFor(anthropicKey),
183
- bundlePathFor(githubToken),
184
- ]);
185
-
186
- const bundleCredentials = extractCredentialsFromBundle(entries, manifest);
187
- const { userCredentials, skippedCloud } =
188
- filterCredentials(bundleCredentials);
189
-
190
- // Only user credentials should pass through.
191
- expect(userCredentials).toHaveLength(3);
192
- const accounts = userCredentials.map((c) => c.account).sort();
193
- expect(accounts).toEqual([anthropicKey, githubToken, openaiKey].sort());
194
-
195
- // No platform credentials in the filtered output.
196
- const cadetCreds = userCredentials.filter((c) =>
197
- c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
198
- );
199
- expect(cadetCreds).toHaveLength(0);
200
-
201
- expect(skippedCloud).toBe(2);
202
- });
203
-
204
- test("skippedCloud count is accurate with mixed credentials", () => {
205
- const cadetApiKey = credentialKey("cadet", "assistant_api_key");
206
- const cadetAssistantId = credentialKey("cadet", "cloud_assistant_id");
207
- const cadetWebhook = credentialKey("cadet", "webhook_secret");
208
- const userKey = credentialKey("github", "api_token");
209
-
210
- const entries = new Map<string, VBundleTarEntry>();
211
- entries.set(bundlePathFor(cadetApiKey), makeTarEntry("v1"));
212
- entries.set(bundlePathFor(cadetAssistantId), makeTarEntry("v2"));
213
- entries.set(bundlePathFor(cadetWebhook), makeTarEntry("v3"));
214
- entries.set(bundlePathFor(userKey), makeTarEntry("user-val"));
215
-
216
- const manifest = makeManifest([
217
- bundlePathFor(cadetApiKey),
218
- bundlePathFor(cadetAssistantId),
219
- bundlePathFor(cadetWebhook),
220
- bundlePathFor(userKey),
221
- ]);
222
-
223
- const bundleCredentials = extractCredentialsFromBundle(entries, manifest);
224
- const { userCredentials, skippedCloud } =
225
- filterCredentials(bundleCredentials);
226
-
227
- expect(skippedCloud).toBe(3);
228
- expect(userCredentials).toHaveLength(1);
229
- expect(userCredentials[0]).toEqual({
230
- account: userKey,
231
- value: "user-val",
232
- });
233
-
234
- // Verify total = user + skipped
235
- expect(bundleCredentials.length).toBe(
236
- userCredentials.length + skippedCloud,
237
- );
238
- });
239
-
240
- test("regression: the prefix matches credentialKey('cadet', '') so format changes propagate", () => {
241
- // If credentialKey()'s format ever changes (e.g. slash → something else),
242
- // this assertion will fail and the duplicated constant in
243
- // migration-routes.ts must be updated to stay in sync.
244
- expect(PLATFORM_CREDENTIAL_PREFIX).toBe("credential/cadet/");
245
- expect(
246
- credentialKey("cadet", "assistant_api_key").startsWith(
247
- PLATFORM_CREDENTIAL_PREFIX,
248
- ),
249
- ).toBe(true);
250
-
251
- // The raw "cadet:" string is not a valid CES account prefix — only
252
- // the full "credential/cadet/" format from credentialKey() is correct.
253
- expect(
254
- credentialKey("cadet", "assistant_api_key").startsWith("cadet:"),
255
- ).toBe(false);
256
- });
257
- });