@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,405 +0,0 @@
1
- /**
2
- * Analyzes a validated .vbundle archive to produce a dry-run import report.
3
- *
4
- * Given a valid .vbundle archive (already validated), this module inspects
5
- * its manifest and contents to determine what would happen if the bundle
6
- * were imported. It compares the bundle's files against the current
7
- * assistant state on disk and reports:
8
- * - Which files would be written or overwritten
9
- * - Size changes for each file
10
- * - Whether existing data would be replaced
11
- * - Any potential conflicts
12
- *
13
- * This is a read-only analysis — no files are written or modified.
14
- */
15
-
16
- import { createHash } from "node:crypto";
17
- import { existsSync, readFileSync, statSync } from "node:fs";
18
- import { join, resolve } from "node:path";
19
-
20
- import { resolveGuardianPersonaPath } from "../../prompts/persona-resolver.js";
21
- import { getLogger } from "../../util/logger.js";
22
- import {
23
- isGatewayArchivePath,
24
- isRetiredArchivePath,
25
- } from "./vbundle-import-policy.js";
26
- import type { ManifestType } from "./vbundle-validator.js";
27
-
28
- const log = getLogger("vbundle-import-analyzer");
29
-
30
- /**
31
- * Only these prompt filenames are accepted during import.
32
- *
33
- * `USER.md` is retained for backward compatibility with legacy bundles —
34
- * on import, its content is translated to `users/<slug>.md` at the
35
- * current guardian's location (see `DefaultPathResolver.resolve`).
36
- */
37
- const ALLOWED_PROMPT_FILENAMES = new Set(["IDENTITY.md", "SOUL.md", "USER.md"]);
38
-
39
- /** Archive path for the legacy guardian user persona file. */
40
- const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
41
-
42
- // ---------------------------------------------------------------------------
43
- // Public types
44
- // ---------------------------------------------------------------------------
45
-
46
- type ImportAction = "create" | "overwrite" | "unchanged" | "skip";
47
-
48
- interface ImportFileReport {
49
- /** Archive path (e.g. "data/db/assistant.db") */
50
- path: string;
51
- /** What would happen to this file on import */
52
- action: ImportAction;
53
- /** Size of the file in the bundle (bytes) */
54
- bundle_size: number;
55
- /** Size of the existing file on disk, or null if it does not exist */
56
- current_size: number | null;
57
- /** SHA-256 of the file in the bundle */
58
- bundle_sha256: string;
59
- /** SHA-256 of the existing file on disk, or null if it does not exist */
60
- current_sha256: string | null;
61
- }
62
-
63
- interface ImportConflict {
64
- code: string;
65
- message: string;
66
- path?: string;
67
- }
68
-
69
- export interface ImportDryRunReport {
70
- /** Whether the import can proceed (bundle is valid and no blocking conflicts) */
71
- can_import: boolean;
72
- /** Summary of what would happen */
73
- summary: {
74
- total_files: number;
75
- files_to_create: number;
76
- files_to_overwrite: number;
77
- files_unchanged: number;
78
- files_to_skip: number;
79
- };
80
- /** Per-file analysis of what would change */
81
- files: ImportFileReport[];
82
- /** Any conflicts or warnings that might block or complicate import */
83
- conflicts: ImportConflict[];
84
- /** The manifest from the bundle */
85
- manifest: ManifestType;
86
- }
87
-
88
- // ---------------------------------------------------------------------------
89
- // Path mapping
90
- // ---------------------------------------------------------------------------
91
-
92
- /**
93
- * Maps archive paths to their corresponding locations on disk.
94
- * This is the canonical mapping used during actual import (PR-5) —
95
- * dry-run uses the same mapping for consistency.
96
- */
97
- export interface PathResolver {
98
- resolve(archivePath: string): string | null;
99
- }
100
-
101
- export class DefaultPathResolver implements PathResolver {
102
- /**
103
- * @param workspaceDir absolute path to the workspace directory.
104
- * @param hooksDir absolute path to the hooks directory.
105
- * @param guardianPersonaPathResolver function that returns the
106
- * current guardian's persona path (`users/<slug>.md`) or `null`
107
- * when no guardian exists. Defaults to the production
108
- * `resolveGuardianPersonaPath` helper; tests inject a stub so they
109
- * don't need to mock the contact store.
110
- */
111
- constructor(
112
- private workspaceDir?: string,
113
- private hooksDir?: string,
114
- private guardianPersonaPathResolver: () =>
115
- | string
116
- | null = resolveGuardianPersonaPath,
117
- ) {}
118
-
119
- resolve(archivePath: string): string | null {
120
- // Skip credential entries — handled separately by the credential import step
121
- if (archivePath.startsWith("credentials/")) {
122
- return null;
123
- }
124
-
125
- // Retired-feature files (both legacy and workspace-format spellings)
126
- // must never resolve to a disk target — resolving would write them
127
- // back into the workspace on import. The analyzer and both importers
128
- // turn the resulting null into a silent non-blocking skip.
129
- if (
130
- isRetiredArchivePath(archivePath) ||
131
- isGatewayArchivePath(archivePath)
132
- ) {
133
- return null;
134
- }
135
-
136
- // New format: workspace/ prefix — maps directly into the workspace dir
137
- if (archivePath.startsWith("workspace/") && this.workspaceDir) {
138
- const relPath = archivePath.slice("workspace/".length);
139
- if (!relPath) {
140
- return null;
141
- }
142
- const resolved = resolve(this.workspaceDir, relPath);
143
- const wsRoot = resolve(this.workspaceDir);
144
- // Path traversal containment
145
- if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
146
- return null;
147
- }
148
- return resolved;
149
- }
150
-
151
- // Backward compat: old bundle formats with specific archive paths
152
- if (archivePath === "data/db/assistant.db" && this.workspaceDir) {
153
- return join(this.workspaceDir, "data", "db", "assistant.db");
154
- }
155
- if (archivePath === "config/settings.json" && this.workspaceDir) {
156
- return join(this.workspaceDir, "config.json");
157
- }
158
- if (archivePath.startsWith("skills/") && this.workspaceDir) {
159
- const resolved = resolve(
160
- this.workspaceDir,
161
- "skills",
162
- archivePath.slice("skills/".length),
163
- );
164
- const skillsRoot = resolve(this.workspaceDir, "skills");
165
- if (resolved !== skillsRoot && !resolved.startsWith(skillsRoot + "/")) {
166
- return null;
167
- }
168
- return resolved;
169
- }
170
- if (archivePath.startsWith("prompts/") && this.workspaceDir) {
171
- // Old bundles stored prompts as prompts/IDENTITY.md etc — these map
172
- // to the workspace root (e.g. workspace/IDENTITY.md).
173
- // Only accepted prompt filenames resolve — unknown entries are
174
- // skipped so they cannot trigger workspace clearing.
175
- const filename = archivePath.slice("prompts/".length);
176
- if (!ALLOWED_PROMPT_FILENAMES.has(filename)) {
177
- return null;
178
- }
179
-
180
- // Legacy USER.md translation: rewrite the destination to the
181
- // current guardian's per-user persona file `users/<slug>.md`.
182
- // Guardian-less workspaces return null → importer skips with a
183
- // warning (see vbundle-importer.ts). This lookup runs against
184
- // whatever DB state is live at the time of resolve — typically
185
- // the pre-import workspace, which is the common upgrade case.
186
- if (archivePath === LEGACY_USER_MD_ARCHIVE_PATH) {
187
- const guardianPath = this.guardianPersonaPathResolver();
188
- if (!guardianPath) {
189
- log.warn(
190
- { path: archivePath },
191
- "Legacy prompts/USER.md has no guardian target — will be skipped on import",
192
- );
193
- return null;
194
- }
195
- // Containment check: guardian path must live under the workspace.
196
- const wsRoot = resolve(this.workspaceDir);
197
- const guardianResolved = resolve(guardianPath);
198
- if (
199
- guardianResolved !== wsRoot &&
200
- !guardianResolved.startsWith(wsRoot + "/")
201
- ) {
202
- log.warn(
203
- { path: archivePath, guardianPath },
204
- "Guardian persona path falls outside workspace — refusing to write",
205
- );
206
- return null;
207
- }
208
- return guardianResolved;
209
- }
210
-
211
- const resolved = resolve(this.workspaceDir, filename);
212
- const wsRoot = resolve(this.workspaceDir);
213
- if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
214
- return null;
215
- }
216
- return resolved;
217
- }
218
- if (archivePath.startsWith("hooks/") && this.hooksDir) {
219
- const resolved = resolve(
220
- this.hooksDir,
221
- archivePath.slice("hooks/".length),
222
- );
223
- const hooksRoot = resolve(this.hooksDir);
224
- if (resolved !== hooksRoot && !resolved.startsWith(hooksRoot + "/")) {
225
- return null;
226
- }
227
- return resolved;
228
- }
229
-
230
- return null;
231
- }
232
- }
233
-
234
- // ---------------------------------------------------------------------------
235
- // Hash helper
236
- // ---------------------------------------------------------------------------
237
-
238
- function sha256Hex(data: Uint8Array): string {
239
- return createHash("sha256").update(data).digest("hex");
240
- }
241
-
242
- // ---------------------------------------------------------------------------
243
- // Core analyzer
244
- // ---------------------------------------------------------------------------
245
-
246
- interface AnalyzeImportOptions {
247
- /** The parsed and validated manifest from the bundle */
248
- manifest: ManifestType;
249
- /** Resolves archive paths to disk paths for comparison */
250
- pathResolver: PathResolver;
251
- }
252
-
253
- /**
254
- * Analyze what importing a .vbundle archive would do without modifying
255
- * any state. Compares bundle contents against current files on disk.
256
- */
257
- export function analyzeImport(
258
- options: AnalyzeImportOptions,
259
- ): ImportDryRunReport {
260
- const { manifest, pathResolver } = options;
261
- const files: ImportFileReport[] = [];
262
- const conflicts: ImportConflict[] = [];
263
-
264
- for (const fileEntry of manifest.contents) {
265
- const diskPath = pathResolver.resolve(fileEntry.path);
266
-
267
- // Credential entries are handled separately by the credential import
268
- // step — skip them without flagging as unknown/conflict.
269
- if (fileEntry.path.startsWith("credentials/")) {
270
- files.push({
271
- path: fileEntry.path,
272
- action: "skip",
273
- bundle_size: fileEntry.size_bytes,
274
- bundle_sha256: fileEntry.sha256,
275
- current_size: null,
276
- current_sha256: null,
277
- });
278
- continue;
279
- }
280
-
281
- if (!diskPath) {
282
- // Legacy `prompts/USER.md` in a guardian-less workspace has no
283
- // destination to translate to. The commit-time path skips this
284
- // entry with a warning rather than failing, so preflight mirrors
285
- // that: emit a non-blocking skip so `can_import` stays true and
286
- // upgrade paths proceed. No conflict is registered.
287
- if (fileEntry.path === LEGACY_USER_MD_ARCHIVE_PATH) {
288
- log.warn(
289
- { path: fileEntry.path },
290
- "Legacy prompts/USER.md has no guardian target — will be skipped on import",
291
- );
292
- files.push({
293
- path: fileEntry.path,
294
- action: "skip",
295
- bundle_size: fileEntry.size_bytes,
296
- bundle_sha256: fileEntry.sha256,
297
- current_size: null,
298
- current_sha256: null,
299
- });
300
- continue;
301
- }
302
-
303
- // Retired-feature file from a legacy bundle: nothing consumes it
304
- // anymore, so drop it silently instead of blocking the restore with
305
- // an UNKNOWN_ARCHIVE_PATH conflict.
306
- if (
307
- isRetiredArchivePath(fileEntry.path) ||
308
- isGatewayArchivePath(fileEntry.path)
309
- ) {
310
- log.info(
311
- { path: fileEntry.path },
312
- "Archive path is not workspace data — will be skipped on import",
313
- );
314
- files.push({
315
- path: fileEntry.path,
316
- action: "skip",
317
- bundle_size: fileEntry.size_bytes,
318
- bundle_sha256: fileEntry.sha256,
319
- current_size: null,
320
- current_sha256: null,
321
- });
322
- continue;
323
- }
324
-
325
- // Unknown archive path — would have nowhere to write
326
- conflicts.push({
327
- code: "UNKNOWN_ARCHIVE_PATH",
328
- message: `Archive path "${fileEntry.path}" has no known disk target — it would be skipped during import`,
329
- path: fileEntry.path,
330
- });
331
- files.push({
332
- path: fileEntry.path,
333
- action: "skip",
334
- bundle_size: fileEntry.size_bytes,
335
- bundle_sha256: fileEntry.sha256,
336
- current_size: null,
337
- current_sha256: null,
338
- });
339
- continue;
340
- }
341
-
342
- let currentSize: number | null = null;
343
- let currentSha256: string | null = null;
344
- let action: ImportAction;
345
-
346
- if (existsSync(diskPath)) {
347
- try {
348
- const stat = statSync(diskPath);
349
- currentSize = stat.size;
350
- const diskData = new Uint8Array(readFileSync(diskPath));
351
- currentSha256 = sha256Hex(diskData);
352
- } catch {
353
- // If we cannot read the file, treat it as a conflict
354
- conflicts.push({
355
- code: "UNREADABLE_EXISTING_FILE",
356
- message: `Cannot read existing file at disk path for "${fileEntry.path}" — import would overwrite it`,
357
- path: fileEntry.path,
358
- });
359
- action = "overwrite";
360
- files.push({
361
- path: fileEntry.path,
362
- action,
363
- bundle_size: fileEntry.size_bytes,
364
- bundle_sha256: fileEntry.sha256,
365
- current_size: currentSize,
366
- current_sha256: currentSha256,
367
- });
368
- continue;
369
- }
370
-
371
- if (currentSha256 === fileEntry.sha256) {
372
- action = "unchanged";
373
- } else {
374
- action = "overwrite";
375
- }
376
- } else {
377
- action = "create";
378
- }
379
-
380
- files.push({
381
- path: fileEntry.path,
382
- action,
383
- bundle_size: fileEntry.size_bytes,
384
- bundle_sha256: fileEntry.sha256,
385
- current_size: currentSize,
386
- current_sha256: currentSha256,
387
- });
388
- }
389
-
390
- const summary = {
391
- total_files: files.length,
392
- files_to_create: files.filter((f) => f.action === "create").length,
393
- files_to_overwrite: files.filter((f) => f.action === "overwrite").length,
394
- files_unchanged: files.filter((f) => f.action === "unchanged").length,
395
- files_to_skip: files.filter((f) => f.action === "skip").length,
396
- };
397
-
398
- return {
399
- can_import: conflicts.length === 0,
400
- summary,
401
- files,
402
- conflicts,
403
- manifest,
404
- };
405
- }
@@ -1,241 +0,0 @@
1
- /**
2
- * Shared invariants and predicate functions consumed by both the buffer-
3
- * based `commitImport` and the streaming `streamCommitImport`. These
4
- * decisions must stay in lockstep across both importers — moving them
5
- * here removes the parallel-implementation skew risk that would otherwise
6
- * grow as either importer evolves.
7
- *
8
- * Pure: no `node:fs`, no I/O, no async. Functions over strings + manifest
9
- * data shapes only.
10
- */
11
-
12
- export const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
13
-
14
- export const CONFIG_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
15
- "workspace/config.json",
16
- "config/settings.json",
17
- ]);
18
-
19
- export const CREDENTIAL_METADATA_ARCHIVE_PATH =
20
- "workspace/data/credentials/metadata.json";
21
-
22
- export const WORKSPACE_PRESERVE_PATHS: readonly string[] = [
23
- "embedding-models",
24
- "deprecated",
25
- "data/db",
26
- "data/qdrant",
27
- ];
28
-
29
- export function isWorkspaceNamespacedArchivePath(archivePath: string): boolean {
30
- return archivePath.startsWith("workspace/");
31
- }
32
-
33
- export function isLegacyPersonaArchivePath(archivePath: string): boolean {
34
- return archivePath === LEGACY_USER_MD_ARCHIVE_PATH;
35
- }
36
-
37
- /**
38
- * Archive paths for retired features. Bundles exported while those features
39
- * existed still contain these entries; preflight and both importers must
40
- * treat them as silent non-blocking skips (no conflict, no warning) so old
41
- * backups stay restorable without noise, and the files are never written
42
- * back to the workspace.
43
- *
44
- * `UPDATES.md` carried the retired update-bulletin release notes. Legacy
45
- * bundles archived it as `prompts/UPDATES.md`; current-format bundles walk
46
- * the workspace dir, so it appears as `workspace/UPDATES.md`. Both spellings
47
- * must be retired — `DefaultPathResolver.resolve()` short-circuits them to
48
- * `null` before any prefix handling so the workspace-format entry cannot
49
- * resolve normally and recreate the file on import.
50
- */
51
- export const RETIRED_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
52
- "prompts/UPDATES.md",
53
- "workspace/UPDATES.md",
54
- ]);
55
-
56
- export function isRetiredArchivePath(archivePath: string): boolean {
57
- return RETIRED_ARCHIVE_PATHS.has(archivePath);
58
- }
59
-
60
- /**
61
- * `gateway/` holds the gateway's own database and logs, added by the debug
62
- * export profile for Cadet staff to open on a debug clone. It is never
63
- * part of a workspace, so preflight and both importers skip it silently,
64
- * the same way they skip retired paths.
65
- */
66
- export function isGatewayArchivePath(archivePath: string): boolean {
67
- return archivePath.startsWith("gateway/");
68
- }
69
-
70
- export function isConfigArchivePath(archivePath: string): boolean {
71
- return CONFIG_ARCHIVE_PATHS.has(archivePath);
72
- }
73
-
74
- export function isCredentialMetadataArchivePath(archivePath: string): boolean {
75
- return archivePath === CREDENTIAL_METADATA_ARCHIVE_PATH;
76
- }
77
-
78
- /**
79
- * Partition `WORKSPACE_PRESERVE_PATHS` into the two skip sets the buffer
80
- * importer's selective-clear loop consumes:
81
- *
82
- * - `topLevelSkipDirs`: single-segment preserve-paths (e.g. "embedding-models").
83
- * - `dataSubdirSkipDirs`: second segment of `data/<x>` preserve-paths
84
- * (e.g. "db" for "data/db").
85
- *
86
- * Stays in sync with WORKSPACE_PRESERVE_PATHS automatically — adding a
87
- * new entry of either shape doesn't require touching the buffer importer.
88
- * Multi-segment paths outside the `data/` subtree are intentionally
89
- * unsupported here; the buffer importer's walk doesn't recurse into
90
- * arbitrary subdirs. If a future preserve-path needs deeper coverage,
91
- * widen this helper and the buffer importer's walk together.
92
- */
93
- export function partitionWorkspacePreserveSkipDirs(): {
94
- topLevelSkipDirs: ReadonlySet<string>;
95
- dataSubdirSkipDirs: ReadonlySet<string>;
96
- } {
97
- const topLevelSkipDirs = new Set<string>();
98
- const dataSubdirSkipDirs = new Set<string>();
99
- for (const rel of WORKSPACE_PRESERVE_PATHS) {
100
- const parts = rel.split("/");
101
- if (parts.length === 1) {
102
- topLevelSkipDirs.add(parts[0]!);
103
- } else if (parts.length === 2 && parts[0] === "data") {
104
- dataSubdirSkipDirs.add(parts[1]!);
105
- }
106
- }
107
- return { topLevelSkipDirs, dataSubdirSkipDirs };
108
- }
109
-
110
- export const LEGACY_RUNTIME_VERSION_SENTINEL = "0.0.0-legacy";
111
-
112
- type SemverTriple = readonly [number, number, number];
113
-
114
- function parseSemverTriple(version: string): SemverTriple | null {
115
- // Strip optional prerelease/build suffix ("-foo", "+sha"). We treat
116
- // "0.7.1-staging.1" as equal-to-release "0.7.1" for gating purposes.
117
- // The cloud-side check uses packaging.version.Version, which sorts
118
- // prereleases BEFORE the corresponding release; matching that exactly
119
- // would require a fuller parser — for runtime-side defense-in-depth
120
- // this conservative read is sufficient (matches release of the same
121
- // base triple).
122
- const base = version.split(/[-+]/)[0] ?? version;
123
- const parts = base.split(".");
124
- if (parts.length !== 3) {
125
- return null;
126
- }
127
- // Reject components with anything other than ASCII digits to avoid
128
- // Number.parseInt's lenient prefix-parse — e.g. "0.8.0foo" would
129
- // otherwise coerce to [0, 8, 0] and silently pass the gate, defeating
130
- // the parse-failure-fail-open contract in evaluateRuntimeCompatibility.
131
- // Leading zeros (e.g. "01.02.03") are intentionally accepted since
132
- // they round-trip to the same numeric triple as the un-padded form.
133
- if (!parts.every((p) => /^\d+$/.test(p))) {
134
- return null;
135
- }
136
- const [maj, min, pat] = parts.map((p) => Number(p));
137
- if (![maj, min, pat].every((n) => Number.isFinite(n) && n >= 0)) {
138
- return null;
139
- }
140
- return [maj!, min!, pat!] as const;
141
- }
142
-
143
- // -1 if a < b, 0 if equal, +1 if a > b. Returns null on parse failure.
144
- export function compareSemver(a: string, b: string): -1 | 0 | 1 | null {
145
- const ta = parseSemverTriple(a);
146
- const tb = parseSemverTriple(b);
147
- if (!ta || !tb) {
148
- return null;
149
- }
150
- for (let i = 0; i < 3; i++) {
151
- if (ta[i]! < tb[i]!) {
152
- return -1;
153
- }
154
- if (ta[i]! > tb[i]!) {
155
- return +1;
156
- }
157
- }
158
- return 0;
159
- }
160
-
161
- export interface RuntimeCompatibility {
162
- min_runtime_version: string;
163
- max_runtime_version: string | null;
164
- }
165
-
166
- export type RuntimeCompatibilityResult =
167
- | { ok: true }
168
- | {
169
- ok: false;
170
- reason: "version_incompatible";
171
- bundle_compat: RuntimeCompatibility;
172
- runtime_version: string;
173
- };
174
-
175
- export function formatRuntimeCompatibilityMessage(
176
- compat: RuntimeCompatibility,
177
- runtimeVersion: string,
178
- ): string {
179
- const range = compat.max_runtime_version
180
- ? `${compat.min_runtime_version}–${compat.max_runtime_version}`
181
- : `${compat.min_runtime_version}+`;
182
- return `Cannot import: bundle requires runtime ${range}, but this runtime is ${runtimeVersion}. Update your runtime before importing.`;
183
- }
184
-
185
- /**
186
- * Whether `version` names a runtime built from a source checkout: the
187
- * `<pkg.version>-local.<timestamp>.<sha>` `APP_VERSION` that the cloud
188
- * repo's `vel up` stamps on the assistant images it builds for its local
189
- * minikube cluster. The triple is the checkout's package.json version,
190
- * which only moves on release cuts, so it says nothing about whether the
191
- * checkout is newer or older than a release. A CLI hatch from a checkout
192
- * without an explicit `APP_VERSION` runs as the plain package version and
193
- * is not covered.
194
- */
195
- export function isLocalDevRuntimeVersion(version: string): boolean {
196
- return /^\d+\.\d+\.\d+-local\./.test(version);
197
- }
198
-
199
- export function evaluateRuntimeCompatibility(
200
- compat: RuntimeCompatibility,
201
- runtimeVersion: string,
202
- ): RuntimeCompatibilityResult {
203
- if (compat.min_runtime_version === LEGACY_RUNTIME_VERSION_SENTINEL) {
204
- return { ok: true };
205
- }
206
- // A local dev build skips the minimum bound, like an unparsable version
207
- // does below: its triple is not comparable to release versions (see
208
- // isLocalDevRuntimeVersion), so "runtime older than the bundle" cannot be
209
- // decided from it. The maximum bound still applies: it is an explicit
210
- // ceiling the bundle's producer recorded, and a local triple above it is
211
- // as good a signal as any release's.
212
- if (!isLocalDevRuntimeVersion(runtimeVersion)) {
213
- const minCmp = compareSemver(runtimeVersion, compat.min_runtime_version);
214
- if (minCmp === null) {
215
- return { ok: true };
216
- }
217
- if (minCmp < 0) {
218
- return {
219
- ok: false,
220
- reason: "version_incompatible",
221
- bundle_compat: compat,
222
- runtime_version: runtimeVersion,
223
- };
224
- }
225
- }
226
- if (compat.max_runtime_version !== null) {
227
- const maxCmp = compareSemver(runtimeVersion, compat.max_runtime_version);
228
- if (maxCmp === null) {
229
- return { ok: true };
230
- }
231
- if (maxCmp > 0) {
232
- return {
233
- ok: false,
234
- reason: "version_incompatible",
235
- bundle_compat: compat,
236
- runtime_version: runtimeVersion,
237
- };
238
- }
239
- }
240
- return { ok: true };
241
- }