@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.
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +0 -20
- package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +2 -2
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +2 -2
- package/openapi.yaml +2 -708
- package/package.json +2 -3
- package/scripts/sync-web-search-catalog.ts +1 -2
- package/src/__tests__/cloud-profile-guard.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +6 -38
- package/src/__tests__/conversation-queue.test.ts +45 -6
- package/src/__tests__/db-readiness-http-gate.test.ts +0 -6
- package/src/__tests__/persist-onboarding-artifacts.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +0 -51
- package/src/__tests__/provider-cloud-proxy-integration.test.ts +2 -110
- package/src/__tests__/run-due-schedules.test.ts +0 -4
- package/src/__tests__/schedule-store.test.ts +0 -12
- package/src/__tests__/stt-stream-session.test.ts +4 -4
- package/src/__tests__/telephony-tts-guard.test.ts +2 -50
- package/src/__tests__/voice-config-update.test.ts +12 -167
- package/src/__tests__/web-search-catalog-parity.test.ts +0 -2
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +9 -7
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -4
- package/src/calls/resolve-call-tts-provider.ts +6 -25
- package/src/calls/telephony-credential-preflight.ts +0 -10
- package/src/calls/telephony-tts-capability.ts +1 -17
- package/src/cli/commands/tts.ts +1 -2
- package/src/cli/index.help.ts +0 -2
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +4 -42
- package/src/config/schemas/__tests__/stt.test.ts +10 -14
- package/src/config/schemas/services.ts +5 -6
- package/src/config/schemas/stt.ts +1 -3
- package/src/config/schemas/tts.ts +1 -30
- package/src/daemon/daemon-readiness.ts +4 -29
- package/src/daemon/lifecycle.ts +0 -31
- package/src/daemon/providers-setup.ts +0 -8
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -66
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -9
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +0 -44
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +4 -6
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-voice-credential-preflight.ts +9 -33
- package/src/live-voice/live-voice-session.ts +10 -6
- package/src/live-voice/live-voice-tts.ts +1 -8
- package/src/plugins/defaults/memory/worker.ts +3 -3
- package/src/prompts/persona-resolver.ts +0 -35
- package/src/providers/__tests__/search-provider-catalog.test.ts +5 -8
- package/src/providers/__tests__/voice-error-copy.test.ts +9 -10
- package/src/providers/search-provider-catalog.ts +7 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +3 -47
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +6 -372
- package/src/providers/speech-to-text/provider-catalog.ts +2 -73
- package/src/providers/speech-to-text/resolve.ts +3 -120
- package/src/runtime/http-router-types.ts +1 -3
- package/src/runtime/http-server.ts +1 -1
- package/src/runtime/routes/__tests__/tts-routes.test.ts +2 -6
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +7 -7
- package/src/runtime/routes/index.ts +0 -4
- package/src/runtime/routes/live-voice-routes.test.ts +4 -11
- package/src/runtime/routes/live-voice-routes.ts +8 -11
- package/src/runtime/routes/secret-routes.ts +0 -6
- package/src/runtime/routes/tts-routes.ts +0 -38
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -7
- package/src/schedule/__tests__/worker-mcp-readiness.test.ts +0 -4
- package/src/schedule/schedule-store.ts +0 -7
- package/src/schedule/scheduler.ts +0 -5
- package/src/schedule/worker.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +2 -10
- package/src/stt/daemon-batch-transcriber.ts +4 -38
- package/src/stt/types.ts +1 -9
- package/src/tools/network/__tests__/web-search.test.ts +3 -250
- package/src/tools/network/web-search.ts +4 -165
- package/src/tts/__tests__/provider-resolution.test.ts +0 -5
- package/src/tts/provider-catalog.ts +0 -4
- package/src/tts/types.ts +0 -1
- package/src/__tests__/cloud-speech-defaults.test.ts +0 -330
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1708
- package/src/__tests__/migration-export-http.test.ts +0 -557
- package/src/__tests__/migration-export-streaming.test.ts +0 -350
- package/src/__tests__/migration-export-to-gcs.test.ts +0 -706
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1176
- package/src/__tests__/migration-import-from-gcs.test.ts +0 -534
- package/src/__tests__/migration-import-from-path.test.ts +0 -324
- package/src/__tests__/migration-import-from-url.test.ts +0 -588
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -739
- package/src/__tests__/migration-jobs-status.test.ts +0 -139
- package/src/__tests__/migration-parity-persistence.test.ts +0 -1891
- package/src/__tests__/migration-transport.test.ts +0 -1089
- package/src/__tests__/migration-validate-http.test.ts +0 -634
- package/src/__tests__/migration-wizard.test.ts +0 -1395
- package/src/__tests__/rebind-secrets-screen.test.ts +0 -876
- package/src/__tests__/sanitize-config-for-transfer.test.ts +0 -259
- package/src/__tests__/transfer-progress-screen.test.ts +0 -1217
- package/src/__tests__/validation-results-screen.test.ts +0 -1130
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -147
- package/src/background-wake/background-wake-routes.test.ts +0 -906
- package/src/background-wake/cloud-client.test.ts +0 -328
- package/src/background-wake/cloud-client.ts +0 -196
- package/src/background-wake/next-wake.test.ts +0 -335
- package/src/background-wake/next-wake.ts +0 -188
- package/src/background-wake/publisher.ts +0 -93
- package/src/background-wake/wake-intent-hooks.test.ts +0 -234
- package/src/cli/commands/__tests__/backup.test.ts +0 -363
- package/src/cli/commands/backup.help.ts +0 -180
- package/src/cli/commands/backup.ts +0 -398
- package/src/cloud/cloud-speech.test.ts +0 -335
- package/src/cloud/cloud-speech.ts +0 -300
- package/src/config/cloud-speech-defaults.ts +0 -300
- package/src/config/sanitize-for-transfer.ts +0 -111
- package/src/live-voice/__tests__/live-voice-cloud-speech-fallback.test.ts +0 -250
- package/src/providers/speech-to-text/__tests__/cadet-cloud-realtime.test.ts +0 -457
- package/src/providers/speech-to-text/__tests__/cadet-cloud.test.ts +0 -271
- package/src/providers/speech-to-text/__tests__/cadet-speech-relay-connection.test.ts +0 -126
- package/src/providers/speech-to-text/cadet-cloud-flux-realtime.ts +0 -309
- package/src/providers/speech-to-text/cadet-cloud-realtime.ts +0 -315
- package/src/providers/speech-to-text/cadet-cloud.ts +0 -108
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +0 -176
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +0 -254
- package/src/runtime/migrations/__tests__/job-registry.test.ts +0 -346
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +0 -62
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +0 -184
- package/src/runtime/migrations/__tests__/staged-import-path.test.ts +0 -110
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +0 -112
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +0 -285
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +0 -126
- package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +0 -253
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +0 -208
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +0 -485
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +0 -349
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +0 -196
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +0 -551
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +0 -428
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +0 -220
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +0 -1823
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +0 -598
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +0 -463
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -538
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +0 -515
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +0 -453
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +0 -319
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +0 -240
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +0 -473
- package/src/runtime/migrations/gcs-signed-url.ts +0 -190
- package/src/runtime/migrations/job-registry.ts +0 -297
- package/src/runtime/migrations/migration-transport.ts +0 -612
- package/src/runtime/migrations/migration-wizard.ts +0 -747
- package/src/runtime/migrations/origin-mode.ts +0 -42
- package/src/runtime/migrations/rebind-secrets-screen.ts +0 -412
- package/src/runtime/migrations/staged-import-path.ts +0 -116
- package/src/runtime/migrations/transfer-progress-screen.ts +0 -323
- package/src/runtime/migrations/validation-results-screen.ts +0 -469
- package/src/runtime/migrations/vbundle-builder.ts +0 -1382
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -405
- package/src/runtime/migrations/vbundle-import-policy.ts +0 -241
- package/src/runtime/migrations/vbundle-importer.ts +0 -826
- package/src/runtime/migrations/vbundle-metadata-merge.ts +0 -113
- package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -2844
- package/src/runtime/migrations/vbundle-streaming-validator.ts +0 -426
- package/src/runtime/migrations/vbundle-tar-stream.ts +0 -244
- package/src/runtime/migrations/vbundle-validator.ts +0 -724
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +0 -227
- package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +0 -54
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +0 -70
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +0 -257
- package/src/runtime/routes/background-wake-routes.ts +0 -387
- package/src/runtime/routes/migration-routes.ts +0 -2584
- package/src/tools/network/__tests__/cloud-search-proxy.test.ts +0 -282
- package/src/tools/network/cloud-search-proxy.ts +0 -211
- package/src/tts/__tests__/cadet-provider.test.ts +0 -253
- package/src/tts/providers/cadet-provider.ts +0 -244
- 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
|
-
});
|