@mycadet/assistant 0.12.11 → 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 (176) hide show
  1. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  3. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  4. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  5. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
  6. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
  7. package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
  8. package/openapi.yaml +2 -708
  9. package/package.json +2 -3
  10. package/scripts/sync-web-search-catalog.ts +1 -2
  11. package/src/__tests__/cloud-profile-guard.test.ts +2 -2
  12. package/src/__tests__/config-schema.test.ts +6 -38
  13. package/src/__tests__/conversation-queue.test.ts +45 -6
  14. package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
  15. package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
  16. package/src/__tests__/persona-resolver.test.ts +0 -51
  17. package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
  18. package/src/__tests__/run-due-schedules.test.ts +0 -4
  19. package/src/__tests__/schedule-store.test.ts +0 -12
  20. package/src/__tests__/stt-stream-session.test.ts +4 -4
  21. package/src/__tests__/telephony-tts-guard.test.ts +2 -50
  22. package/src/__tests__/voice-config-update.test.ts +12 -167
  23. package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
  24. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
  25. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  26. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
  27. package/src/calls/resolve-call-tts-provider.ts +6 -25
  28. package/src/calls/telephony-credential-preflight.ts +0 -10
  29. package/src/calls/telephony-tts-capability.ts +1 -17
  30. package/src/cli/commands/tts.ts +1 -2
  31. package/src/cli/index.help.ts +0 -2
  32. package/src/cli/program.ts +0 -2
  33. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
  34. package/src/config/schemas/__tests__/stt.test.ts +10 -14
  35. package/src/config/schemas/services.ts +5 -6
  36. package/src/config/schemas/stt.ts +1 -3
  37. package/src/config/schemas/tts.ts +1 -30
  38. package/src/daemon/daemon-readiness.ts +4 -29
  39. package/src/daemon/lifecycle.ts +0 -31
  40. package/src/daemon/providers-setup.ts +0 -8
  41. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  42. package/src/heartbeat/heartbeat-service.ts +1 -66
  43. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
  44. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
  45. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
  46. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  47. package/src/live-voice/live-voice-credential-preflight.ts +9 -33
  48. package/src/live-voice/live-voice-session.ts +10 -6
  49. package/src/live-voice/live-voice-tts.ts +1 -8
  50. package/src/plugins/defaults/memory/worker.ts +3 -3
  51. package/src/prompts/persona-resolver.ts +0 -35
  52. package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
  53. package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
  54. package/src/providers/search-provider-catalog.ts +7 -39
  55. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
  56. package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
  57. package/src/providers/speech-to-text/provider-catalog.ts +2 -73
  58. package/src/providers/speech-to-text/resolve.ts +3 -120
  59. package/src/runtime/http-router-types.ts +1 -3
  60. package/src/runtime/http-server.ts +1 -1
  61. package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
  62. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
  63. package/src/runtime/routes/index.ts +0 -4
  64. package/src/runtime/routes/live-voice-routes.test.ts +4 -11
  65. package/src/runtime/routes/live-voice-routes.ts +8 -11
  66. package/src/runtime/routes/secret-routes.ts +0 -6
  67. package/src/runtime/routes/tts-routes.ts +0 -38
  68. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
  69. package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
  70. package/src/schedule/schedule-store.ts +0 -7
  71. package/src/schedule/scheduler.ts +0 -5
  72. package/src/schedule/worker.ts +1 -1
  73. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
  74. package/src/stt/daemon-batch-transcriber.ts +4 -38
  75. package/src/stt/types.ts +1 -9
  76. package/src/tools/network/__tests__/web-search.test.ts +3 -250
  77. package/src/tools/network/web-search.ts +4 -165
  78. package/src/tts/__tests__/provider-resolution.test.ts +0 -5
  79. package/src/tts/provider-catalog.ts +0 -4
  80. package/src/tts/types.ts +0 -1
  81. package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
  82. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
  83. package/src/__tests__/migration-export-http.test.ts +0 -557
  84. package/src/__tests__/migration-export-streaming.test.ts +0 -350
  85. package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
  86. package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
  87. package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
  88. package/src/__tests__/migration-import-from-path.test.ts +0 -324
  89. package/src/__tests__/migration-import-from-url.test.ts +0 -588
  90. package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
  91. package/src/__tests__/migration-jobs-status.test.ts +0 -139
  92. package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
  93. package/src/__tests__/migration-transport.test.ts +0 -1089
  94. package/src/__tests__/migration-validate-http.test.ts +0 -634
  95. package/src/__tests__/migration-wizard.test.ts +0 -1395
  96. package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
  97. package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
  98. package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
  99. package/src/__tests__/validation-results-screen.test.ts +0 -1130
  100. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
  101. package/src/background-wake/background-wake-routes.test.ts +0 -906
  102. package/src/background-wake/cloud-client.test.ts +0 -328
  103. package/src/background-wake/cloud-client.ts +0 -196
  104. package/src/background-wake/next-wake.test.ts +0 -335
  105. package/src/background-wake/next-wake.ts +0 -188
  106. package/src/background-wake/publisher.ts +0 -93
  107. package/src/background-wake/wake-intent-hooks.test.ts +0 -234
  108. package/src/cli/commands/__tests__/backup.test.ts +0 -363
  109. package/src/cli/commands/backup.help.ts +0 -180
  110. package/src/cli/commands/backup.ts +0 -398
  111. package/src/cloud/cloud-speech.test.ts +0 -335
  112. package/src/cloud/cloud-speech.ts +0 -300
  113. package/src/config/cloud-speech-defaults.ts +0 -300
  114. package/src/config/sanitize-for-transfer.ts +0 -111
  115. package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
  116. package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
  117. package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
  118. package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
  119. package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
  120. package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
  121. package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
  122. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
  123. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
  124. package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
  125. package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
  126. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
  127. package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
  128. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
  129. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
  130. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
  131. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
  132. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
  133. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
  134. package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
  135. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
  136. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
  137. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
  138. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
  139. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
  140. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
  141. package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
  142. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
  143. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
  144. package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
  145. package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
  146. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
  147. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
  148. package/src/runtime/migrations/gcs-signed-url.ts +0 -190
  149. package/src/runtime/migrations/job-registry.ts +0 -297
  150. package/src/runtime/migrations/migration-transport.ts +0 -612
  151. package/src/runtime/migrations/migration-wizard.ts +0 -747
  152. package/src/runtime/migrations/origin-mode.ts +0 -42
  153. package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
  154. package/src/runtime/migrations/staged-import-path.ts +0 -116
  155. package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
  156. package/src/runtime/migrations/validation-results-screen.ts +0 -469
  157. package/src/runtime/migrations/vbundle-builder.ts +0 -1382
  158. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
  159. package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
  160. package/src/runtime/migrations/vbundle-importer.ts +0 -826
  161. package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
  162. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
  163. package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
  164. package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
  165. package/src/runtime/migrations/vbundle-validator.ts +0 -724
  166. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
  167. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
  168. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
  169. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
  170. package/src/runtime/routes/background-wake-routes.ts +0 -387
  171. package/src/runtime/routes/migration-routes.ts +0 -2584
  172. package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
  173. package/src/tools/network/cloud-search-proxy.ts +0 -211
  174. package/src/tts/__tests__/cadet-provider.test.ts +0 -253
  175. package/src/tts/providers/cadet-provider.ts +0 -244
  176. 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
- });