@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,485 +0,0 @@
1
- /**
2
- * Buffer-vs-streaming `.vbundle` import parity suite.
3
- *
4
- * Pins the existing disk-outcome equivalence between `commitImport`
5
- * (buffer-based) and `streamCommitImport` (streaming) as a regression net
6
- * BEFORE any production code is migrated to a shared policy module.
7
- *
8
- * Each test builds one archive, mkdtemps two sibling workspaces, seeds them
9
- * identically, runs each importer against its own workspace, and asserts the
10
- * post-import disk trees are byte-for-byte identical:
11
- *
12
- * expect(walkDiskTree(streamWs)).toEqual(walkDiskTree(bufferWs))
13
- *
14
- * Per-case invariants (carry-forward markers survive, persona lands at the
15
- * right disk path, traversal entries do not erase the workspace, etc.) are
16
- * asserted on top of disk-tree equality.
17
- */
18
-
19
- import { createHash } from "node:crypto";
20
- import {
21
- existsSync,
22
- mkdirSync,
23
- mkdtempSync,
24
- readdirSync,
25
- readFileSync,
26
- realpathSync,
27
- rmSync,
28
- writeFileSync,
29
- } from "node:fs";
30
- import { tmpdir } from "node:os";
31
- import { dirname, join, relative } from "node:path";
32
- import { Readable } from "node:stream";
33
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
34
-
35
- import { buildVBundle } from "../vbundle-builder.js";
36
- import {
37
- analyzeImport,
38
- DefaultPathResolver,
39
- } from "../vbundle-import-analyzer.js";
40
- import { commitImport } from "../vbundle-importer.js";
41
- import { streamCommitImport } from "../vbundle-streaming-importer.js";
42
- import { defaultV1Options } from "./v1-test-helpers.js";
43
-
44
- // ---------------------------------------------------------------------------
45
- // Shared helpers
46
- // ---------------------------------------------------------------------------
47
-
48
- /**
49
- * Allocate a fresh workspace path under a temp parent dir we own. The parent
50
- * is realpath-resolved so macOS `/var` → `/private/var` symlink mismatches
51
- * don't trip the streaming importer's `rebaseOntoTempWorkspace` containment
52
- * check (which compares `resolve(diskPath)` against `resolve(workspaceDir)`).
53
- *
54
- * Returns `<parent>/workspace`. The directory itself is NOT created — tests
55
- * that need it pre-existing call `mkdirSync` themselves.
56
- */
57
- function freshWorkspace(): string {
58
- const parent = realpathSync(
59
- mkdtempSync(join(tmpdir(), "vbundle-import-parity-")),
60
- );
61
- return join(parent, "workspace");
62
- }
63
-
64
- /** Best-effort cleanup of a workspace's parent dir. */
65
- function cleanupWorkspaceParent(workspaceDir: string): void {
66
- try {
67
- rmSync(join(workspaceDir, ".."), { recursive: true, force: true });
68
- } catch {
69
- // best-effort
70
- }
71
- }
72
-
73
- function sha256Hex(data: Uint8Array): string {
74
- return createHash("sha256").update(data).digest("hex");
75
- }
76
-
77
- /**
78
- * Recursively walk `root` and return a `Map<relPath, sha256Hex>` for every
79
- * regular file. Skips dot-prefixed scratch dirs the streaming importer may
80
- * leave behind on failure paths (`.import-*`, `.pre-import-*`) plus the
81
- * import marker file — the buffer importer never produces these, so they'd
82
- * spuriously break parity if included.
83
- *
84
- * Two importers are parity-equivalent for a given input iff the maps they
85
- * produce on identically-seeded sibling workspaces are equal.
86
- */
87
- function walkDiskTree(root: string): Map<string, string> {
88
- const out = new Map<string, string>();
89
- if (!existsSync(root)) {
90
- return out;
91
- }
92
-
93
- const stack: string[] = [root];
94
- while (stack.length > 0) {
95
- const dir = stack.pop()!;
96
- let entries;
97
- try {
98
- entries = readdirSync(dir, { withFileTypes: true });
99
- } catch {
100
- continue;
101
- }
102
- for (const entry of entries) {
103
- // Skip streaming-importer scratch artifacts so they don't show up as
104
- // false negatives in the parity comparison.
105
- if (
106
- entry.name.startsWith(".import-") ||
107
- entry.name.startsWith(".pre-import-") ||
108
- entry.name === ".import-marker.json"
109
- ) {
110
- continue;
111
- }
112
- const abs = join(dir, entry.name);
113
- if (entry.isDirectory()) {
114
- stack.push(abs);
115
- } else if (entry.isFile()) {
116
- const rel = relative(root, abs);
117
- out.set(rel, sha256Hex(readFileSync(abs)));
118
- }
119
- }
120
- }
121
- return out;
122
- }
123
-
124
- interface SeedFile {
125
- relPath: string;
126
- content: string | Uint8Array;
127
- }
128
-
129
- /** Mkdir parents and write each file to `workspaceDir`. */
130
- function seedLiveWorkspace(workspaceDir: string, files: SeedFile[]): void {
131
- mkdirSync(workspaceDir, { recursive: true });
132
- for (const { relPath, content } of files) {
133
- const abs = join(workspaceDir, relPath);
134
- mkdirSync(dirname(abs), { recursive: true });
135
- writeFileSync(abs, content);
136
- }
137
- }
138
-
139
- function runBufferImport(workspaceDir: string, archive: Uint8Array): void {
140
- const result = commitImport({
141
- archiveData: archive,
142
- pathResolver: new DefaultPathResolver(workspaceDir),
143
- workspaceDir,
144
- });
145
- if (!result.ok) {
146
- throw new Error(
147
- `buffer commitImport unexpectedly failed: ${JSON.stringify(result)}`,
148
- );
149
- }
150
- }
151
-
152
- async function runStreamImport(
153
- workspaceDir: string,
154
- archive: Uint8Array,
155
- importCredentials?: (
156
- credentials: Array<{ account: string; value: string }>,
157
- ) => Promise<void>,
158
- ): Promise<void> {
159
- const result = await streamCommitImport({
160
- source: Readable.from([Buffer.from(archive)]),
161
- pathResolver: new DefaultPathResolver(workspaceDir),
162
- workspaceDir,
163
- importCredentials,
164
- });
165
- if (!result.ok) {
166
- throw new Error(
167
- `streamCommitImport unexpectedly failed: ${JSON.stringify(result)}`,
168
- );
169
- }
170
- }
171
-
172
- // ---------------------------------------------------------------------------
173
- // Parity tests
174
- // ---------------------------------------------------------------------------
175
-
176
- describe("vbundle import parity (buffer vs streaming)", () => {
177
- let bufferWs: string;
178
- let streamWs: string;
179
-
180
- beforeEach(() => {
181
- bufferWs = freshWorkspace();
182
- streamWs = freshWorkspace();
183
- });
184
-
185
- afterEach(() => {
186
- cleanupWorkspaceParent(bufferWs);
187
- cleanupWorkspaceParent(streamWs);
188
- });
189
-
190
- test("A — full workspace + credentials: identical disk outcome", async () => {
191
- const dbBytes = new Uint8Array(16);
192
- for (let i = 0; i < dbBytes.length; i++) {
193
- dbBytes[i] = (i * 17) & 0xff;
194
- }
195
-
196
- const configJson = JSON.stringify({ version: 1 });
197
- const metadataJson = JSON.stringify({
198
- version: 5,
199
- credentials: [
200
- {
201
- credentialId: "id-openai-api_key",
202
- service: "openai",
203
- field: "api_key",
204
- allowedTools: [],
205
- allowedDomains: [],
206
- createdAt: 1700000000000,
207
- updatedAt: 1700000000000,
208
- },
209
- ],
210
- });
211
-
212
- const openaiKey = new Uint8Array(16);
213
- for (let i = 0; i < openaiKey.length; i++) {
214
- openaiKey[i] = (i + 5) & 0xff;
215
- }
216
- const anthropicKey = new TextEncoder().encode("sk-ant-test");
217
-
218
- const { archive } = buildVBundle({
219
- files: [
220
- { path: "workspace/data/db/assistant.db", data: dbBytes },
221
- {
222
- path: "workspace/config.json",
223
- data: new TextEncoder().encode(configJson),
224
- },
225
- {
226
- path: "workspace/data/credentials/metadata.json",
227
- data: new TextEncoder().encode(metadataJson),
228
- },
229
- { path: "credentials/openai-key", data: openaiKey },
230
- { path: "credentials/anthropic-key", data: anthropicKey },
231
- ],
232
- ...defaultV1Options(),
233
- });
234
-
235
- // Pre-create both workspaces (streaming importer expects to operate
236
- // against an existing dir and the atomic-swap path requires it).
237
- mkdirSync(bufferWs, { recursive: true });
238
- mkdirSync(streamWs, { recursive: true });
239
-
240
- runBufferImport(bufferWs, archive);
241
- await runStreamImport(streamWs, archive, async () => {
242
- // parity test: credentials are intercepted but not persisted
243
- });
244
-
245
- const bufferMap = walkDiskTree(bufferWs);
246
- const streamMap = walkDiskTree(streamWs);
247
-
248
- expect(streamMap).toEqual(bufferMap);
249
-
250
- // Sanity: the three workspace-bound files we expect both importers to
251
- // land are present in the parity map.
252
- expect(bufferMap.has("data/db/assistant.db")).toBe(true);
253
- expect(bufferMap.has("config.json")).toBe(true);
254
- expect(bufferMap.has("data/credentials/metadata.json")).toBe(true);
255
- });
256
-
257
- test("B — config-only partial bundle: live preserved paths survive on both sides", async () => {
258
- const seeds: SeedFile[] = [
259
- { relPath: "data/db/marker", content: "db-marker" },
260
- { relPath: "data/qdrant/marker", content: "qdrant-marker" },
261
- { relPath: "embedding-models/marker", content: "embedding-marker" },
262
- { relPath: "deprecated/marker", content: "deprecated-marker" },
263
- ];
264
-
265
- seedLiveWorkspace(bufferWs, seeds);
266
- seedLiveWorkspace(streamWs, seeds);
267
-
268
- const configJson = JSON.stringify({ version: 1 });
269
- const { archive } = buildVBundle({
270
- files: [
271
- // Validator requires the DB entry (legacy or workspace-prefixed).
272
- // It's not the focus of this case — config.json is — but it must
273
- // be present for the bundle to validate.
274
- { path: "data/db/assistant.db", data: new Uint8Array() },
275
- {
276
- path: "workspace/config.json",
277
- data: new TextEncoder().encode(configJson),
278
- },
279
- ],
280
- ...defaultV1Options(),
281
- });
282
-
283
- runBufferImport(bufferWs, archive);
284
- await runStreamImport(streamWs, archive);
285
-
286
- const bufferMap = walkDiskTree(bufferWs);
287
- const streamMap = walkDiskTree(streamWs);
288
-
289
- expect(streamMap).toEqual(bufferMap);
290
-
291
- // Each carry-forward marker must still be on disk.
292
- for (const seed of seeds) {
293
- expect(bufferMap.has(seed.relPath)).toBe(true);
294
- expect(streamMap.has(seed.relPath)).toBe(true);
295
- }
296
- });
297
-
298
- test("C — legacy prompts/USER.md: both importers land it at users/<slug>.md", async () => {
299
- const personaBody = `_ Lines starting with _ are comments - they won't appear in the system prompt
300
-
301
- # User Profile
302
-
303
- - Preferred name/reference: Captain Parity
304
- - Pronouns: they/them
305
- - Locale: en-US
306
- - Work role: Quartermaster
307
- - Goals: Verify importer parity
308
- - Hobbies/fun: Diff'ing trees
309
- - Daily tools: Terminal
310
- `;
311
-
312
- // Pre-create users/ in both workspaces so the resolver's containment
313
- // check has a real on-disk parent to anchor against.
314
- mkdirSync(join(bufferWs, "users"), { recursive: true });
315
- mkdirSync(join(streamWs, "users"), { recursive: true });
316
-
317
- const { archive } = buildVBundle({
318
- files: [
319
- { path: "data/db/assistant.db", data: new Uint8Array() },
320
- {
321
- path: "prompts/USER.md",
322
- data: new TextEncoder().encode(personaBody),
323
- },
324
- ],
325
- ...defaultV1Options(),
326
- });
327
-
328
- const bufferGuardianPath = join(bufferWs, "users", "captain.md");
329
- const streamGuardianPath = join(streamWs, "users", "captain.md");
330
-
331
- const bufferResolver = new DefaultPathResolver(
332
- bufferWs,
333
- undefined,
334
- () => bufferGuardianPath,
335
- );
336
- const streamResolver = new DefaultPathResolver(
337
- streamWs,
338
- undefined,
339
- () => streamGuardianPath,
340
- );
341
-
342
- const bufferResult = commitImport({
343
- archiveData: archive,
344
- pathResolver: bufferResolver,
345
- workspaceDir: bufferWs,
346
- });
347
- expect(bufferResult.ok).toBe(true);
348
-
349
- const streamResult = await streamCommitImport({
350
- source: Readable.from([Buffer.from(archive)]),
351
- pathResolver: streamResolver,
352
- workspaceDir: streamWs,
353
- });
354
- expect(streamResult.ok).toBe(true);
355
-
356
- const bufferMap = walkDiskTree(bufferWs);
357
- const streamMap = walkDiskTree(streamWs);
358
-
359
- expect(streamMap).toEqual(bufferMap);
360
-
361
- expect(existsSync(bufferGuardianPath)).toBe(true);
362
- expect(existsSync(streamGuardianPath)).toBe(true);
363
- expect(readFileSync(bufferGuardianPath, "utf-8")).toBe(personaBody);
364
- expect(readFileSync(streamGuardianPath, "utf-8")).toBe(personaBody);
365
- });
366
-
367
- test("D — no workspace entries at all: legacy bundle leaves seeded files in place", async () => {
368
- const seeds: SeedFile[] = [
369
- { relPath: "unrelated.txt", content: "stay" },
370
- { relPath: "custom-dir/note.md", content: "# note" },
371
- ];
372
-
373
- seedLiveWorkspace(bufferWs, seeds);
374
- seedLiveWorkspace(streamWs, seeds);
375
-
376
- const dbBytes = new TextEncoder().encode("legacy-db-payload");
377
- const { archive } = buildVBundle({
378
- files: [{ path: "data/db/assistant.db", data: dbBytes }],
379
- ...defaultV1Options(),
380
- });
381
-
382
- runBufferImport(bufferWs, archive);
383
- await runStreamImport(streamWs, archive);
384
-
385
- const bufferMap = walkDiskTree(bufferWs);
386
- const streamMap = walkDiskTree(streamWs);
387
-
388
- expect(streamMap).toEqual(bufferMap);
389
-
390
- for (const seed of seeds) {
391
- expect(bufferMap.has(seed.relPath)).toBe(true);
392
- expect(streamMap.has(seed.relPath)).toBe(true);
393
- }
394
- });
395
-
396
- test("E — path-traversal workspace entry: both importers refuse to clear", async () => {
397
- seedLiveWorkspace(bufferWs, [{ relPath: "marker.txt", content: "keep" }]);
398
- seedLiveWorkspace(streamWs, [{ relPath: "marker.txt", content: "keep" }]);
399
-
400
- const { archive } = buildVBundle({
401
- files: [
402
- { path: "data/db/assistant.db", data: new Uint8Array() },
403
- {
404
- path: "workspace/../../etc/passwd",
405
- data: new TextEncoder().encode("nope"),
406
- },
407
- ],
408
- ...defaultV1Options(),
409
- });
410
-
411
- runBufferImport(bufferWs, archive);
412
- await runStreamImport(streamWs, archive);
413
-
414
- const bufferMap = walkDiskTree(bufferWs);
415
- const streamMap = walkDiskTree(streamWs);
416
-
417
- expect(streamMap).toEqual(bufferMap);
418
-
419
- expect(bufferMap.has("marker.txt")).toBe(true);
420
- expect(streamMap.has("marker.txt")).toBe(true);
421
- });
422
-
423
- test("F — debug-profile gateway entry: both importers drain it and write nothing", async () => {
424
- const configJson = JSON.stringify({ version: 1 });
425
- const { archive, manifest } = buildVBundle({
426
- files: [
427
- { path: "workspace/data/db/assistant.db", data: new Uint8Array(8) },
428
- {
429
- path: "workspace/config.json",
430
- data: new TextEncoder().encode(configJson),
431
- },
432
- {
433
- path: "gateway/export.tar.gz",
434
- data: new TextEncoder().encode("not-a-real-tarball"),
435
- },
436
- ],
437
- ...defaultV1Options(),
438
- });
439
-
440
- // Preflight must not block on the gateway entry, or the CLI would
441
- // refuse the restore before either importer runs.
442
- const preflight = analyzeImport({
443
- manifest,
444
- pathResolver: new DefaultPathResolver(bufferWs),
445
- });
446
- expect(preflight.can_import).toBe(true);
447
- expect(preflight.conflicts).toEqual([]);
448
-
449
- mkdirSync(bufferWs, { recursive: true });
450
- mkdirSync(streamWs, { recursive: true });
451
-
452
- const bufferResult = commitImport({
453
- archiveData: archive,
454
- pathResolver: new DefaultPathResolver(bufferWs),
455
- workspaceDir: bufferWs,
456
- });
457
- const streamResult = await streamCommitImport({
458
- source: Readable.from([Buffer.from(archive)]),
459
- pathResolver: new DefaultPathResolver(streamWs),
460
- workspaceDir: streamWs,
461
- });
462
- if (!bufferResult.ok || !streamResult.ok) {
463
- throw new Error("import unexpectedly failed");
464
- }
465
-
466
- const bufferMap = walkDiskTree(bufferWs);
467
- const streamMap = walkDiskTree(streamWs);
468
-
469
- expect(streamMap).toEqual(bufferMap);
470
- expect(bufferMap.has("config.json")).toBe(true);
471
- expect(bufferMap.has("data/db/assistant.db")).toBe(true);
472
- for (const relPath of bufferMap.keys()) {
473
- expect(relPath.startsWith("gateway")).toBe(false);
474
- }
475
- // Skipped silently, like a retired path: counted, but no
476
- // "no known disk target" warning.
477
- for (const { report } of [bufferResult, streamResult]) {
478
- expect(report.warnings).toEqual([]);
479
- const gateway = report.files.find(
480
- (f) => f.path === "gateway/export.tar.gz",
481
- );
482
- expect(gateway?.action).toBe("skipped");
483
- }
484
- });
485
- });