@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,349 +0,0 @@
1
- /**
2
- * Unit tests for the pure policy module shared by both vbundle importers.
3
- *
4
- * No `node:fs`, no temp dirs — every test exercises a constant or a
5
- * predicate over strings.
6
- */
7
-
8
- import { describe, expect, test } from "bun:test";
9
-
10
- import {
11
- compareSemver,
12
- CONFIG_ARCHIVE_PATHS,
13
- CREDENTIAL_METADATA_ARCHIVE_PATH,
14
- evaluateRuntimeCompatibility,
15
- isConfigArchivePath,
16
- isCredentialMetadataArchivePath,
17
- isLegacyPersonaArchivePath,
18
- isLocalDevRuntimeVersion,
19
- isWorkspaceNamespacedArchivePath,
20
- LEGACY_USER_MD_ARCHIVE_PATH,
21
- partitionWorkspacePreserveSkipDirs,
22
- WORKSPACE_PRESERVE_PATHS,
23
- } from "../vbundle-import-policy.js";
24
-
25
- describe("LEGACY_USER_MD_ARCHIVE_PATH", () => {
26
- test("equals the legacy guardian persona archive path", () => {
27
- expect(LEGACY_USER_MD_ARCHIVE_PATH).toBe("prompts/USER.md");
28
- });
29
- });
30
-
31
- describe("CONFIG_ARCHIVE_PATHS", () => {
32
- test("contains exactly the two known config archive paths", () => {
33
- expect(CONFIG_ARCHIVE_PATHS.size).toBe(2);
34
- expect(CONFIG_ARCHIVE_PATHS.has("workspace/config.json")).toBe(true);
35
- expect(CONFIG_ARCHIVE_PATHS.has("config/settings.json")).toBe(true);
36
- });
37
- });
38
-
39
- describe("CREDENTIAL_METADATA_ARCHIVE_PATH", () => {
40
- test("equals the workspace-namespaced credential metadata path", () => {
41
- expect(CREDENTIAL_METADATA_ARCHIVE_PATH).toBe(
42
- "workspace/data/credentials/metadata.json",
43
- );
44
- });
45
- });
46
-
47
- describe("WORKSPACE_PRESERVE_PATHS", () => {
48
- test("matches the literal 4-element ordered list", () => {
49
- expect(WORKSPACE_PRESERVE_PATHS).toEqual([
50
- "embedding-models",
51
- "deprecated",
52
- "data/db",
53
- "data/qdrant",
54
- ]);
55
- });
56
- });
57
-
58
- describe("isWorkspaceNamespacedArchivePath", () => {
59
- test("true for paths under workspace/", () => {
60
- expect(isWorkspaceNamespacedArchivePath("workspace/foo")).toBe(true);
61
- expect(isWorkspaceNamespacedArchivePath("workspace/data/db/x")).toBe(true);
62
- });
63
-
64
- test("false for non-workspace paths", () => {
65
- expect(isWorkspaceNamespacedArchivePath("prompts/USER.md")).toBe(false);
66
- expect(isWorkspaceNamespacedArchivePath("data/db/assistant.db")).toBe(
67
- false,
68
- );
69
- expect(isWorkspaceNamespacedArchivePath("")).toBe(false);
70
- expect(isWorkspaceNamespacedArchivePath("workspace")).toBe(false);
71
- });
72
- });
73
-
74
- describe("isLegacyPersonaArchivePath", () => {
75
- test("true only for the exact legacy path", () => {
76
- expect(isLegacyPersonaArchivePath("prompts/USER.md")).toBe(true);
77
- });
78
-
79
- test("false for near-misses and unrelated paths", () => {
80
- expect(isLegacyPersonaArchivePath("prompts/USER")).toBe(false);
81
- expect(isLegacyPersonaArchivePath("workspace/prompts/USER.md")).toBe(false);
82
- expect(isLegacyPersonaArchivePath("")).toBe(false);
83
- });
84
- });
85
-
86
- describe("isConfigArchivePath", () => {
87
- test("true for both members of CONFIG_ARCHIVE_PATHS", () => {
88
- expect(isConfigArchivePath("workspace/config.json")).toBe(true);
89
- expect(isConfigArchivePath("config/settings.json")).toBe(true);
90
- });
91
-
92
- test("false for non-members", () => {
93
- expect(isConfigArchivePath("workspace/foo.json")).toBe(false);
94
- expect(isConfigArchivePath("config/settings")).toBe(false);
95
- expect(isConfigArchivePath("")).toBe(false);
96
- });
97
- });
98
-
99
- describe("isCredentialMetadataArchivePath", () => {
100
- test("true for the exact constant", () => {
101
- expect(
102
- isCredentialMetadataArchivePath(
103
- "workspace/data/credentials/metadata.json",
104
- ),
105
- ).toBe(true);
106
- });
107
-
108
- test("false for the legacy non-prefixed form and empty string", () => {
109
- expect(
110
- isCredentialMetadataArchivePath("data/credentials/metadata.json"),
111
- ).toBe(false);
112
- expect(isCredentialMetadataArchivePath("")).toBe(false);
113
- });
114
- });
115
-
116
- describe("partitionWorkspacePreserveSkipDirs", () => {
117
- test("splits preserve-paths into top-level vs data-subdir skip sets", () => {
118
- const { topLevelSkipDirs, dataSubdirSkipDirs } =
119
- partitionWorkspacePreserveSkipDirs();
120
-
121
- expect(topLevelSkipDirs.size).toBe(2);
122
- expect(topLevelSkipDirs.has("embedding-models")).toBe(true);
123
- expect(topLevelSkipDirs.has("deprecated")).toBe(true);
124
-
125
- expect(dataSubdirSkipDirs.size).toBe(2);
126
- expect(dataSubdirSkipDirs.has("db")).toBe(true);
127
- expect(dataSubdirSkipDirs.has("qdrant")).toBe(true);
128
- });
129
- });
130
-
131
- describe("compareSemver", () => {
132
- test("0.10.0 > 0.9.0 (numeric, not lexical)", () => {
133
- expect(compareSemver("0.10.0", "0.9.0")).toBe(1);
134
- });
135
-
136
- test("equal triples return 0", () => {
137
- expect(compareSemver("0.7.1", "0.7.1")).toBe(0);
138
- });
139
-
140
- test("smaller patch returns -1", () => {
141
- expect(compareSemver("0.7.0", "0.7.1")).toBe(-1);
142
- });
143
-
144
- test("prerelease tag is stripped (treated as base release)", () => {
145
- expect(compareSemver("0.7.1-staging.1", "0.7.1")).toBe(0);
146
- });
147
-
148
- test("non-version string returns null", () => {
149
- expect(compareSemver("not-a-version", "0.7.1")).toBe(null);
150
- });
151
-
152
- test("two-part version returns null", () => {
153
- expect(compareSemver("1.2", "0.7.1")).toBe(null);
154
- });
155
-
156
- // Regression: Number.parseInt accepts numeric prefixes and ignores
157
- // trailing junk ("0foo" → 0), which previously coerced malformed
158
- // triples through the gate. parseSemverTriple now requires each
159
- // component to match /^\d+$/ exactly.
160
- test("trailing junk on a component returns null (left arg)", () => {
161
- expect(compareSemver("0.8.0foo", "0.8.0")).toBe(null);
162
- });
163
-
164
- test("trailing junk on a component returns null (right arg)", () => {
165
- expect(compareSemver("0.8.0", "0.7.1xyz")).toBe(null);
166
- });
167
-
168
- // Leading zeros are accepted: "01.02.03" parses to the same numeric
169
- // triple as "1.2.3" since Number("01") === 1. We pin this behavior
170
- // so a future tightening doesn't accidentally regress callers that
171
- // pass zero-padded versions from upstream tooling.
172
- test("leading zeros parse equal to un-padded triple", () => {
173
- expect(compareSemver("01.02.03", "1.2.3")).toBe(0);
174
- });
175
-
176
- test("leading whitespace returns null", () => {
177
- expect(compareSemver(" 0.8.0", "0.8.0")).toBe(null);
178
- });
179
-
180
- test("negative component returns null", () => {
181
- expect(compareSemver("0.8.-1", "0.8.0")).toBe(null);
182
- });
183
- });
184
-
185
- describe("evaluateRuntimeCompatibility", () => {
186
- test("legacy sentinel passes regardless of runtime version", () => {
187
- expect(
188
- evaluateRuntimeCompatibility(
189
- { min_runtime_version: "0.0.0-legacy", max_runtime_version: null },
190
- "0.7.1",
191
- ),
192
- ).toEqual({ ok: true });
193
- });
194
-
195
- test("runtime above min with no max passes", () => {
196
- expect(
197
- evaluateRuntimeCompatibility(
198
- { min_runtime_version: "0.7.0", max_runtime_version: null },
199
- "0.7.1",
200
- ),
201
- ).toEqual({ ok: true });
202
- });
203
-
204
- test("runtime below min fails with full echo", () => {
205
- const compat = {
206
- min_runtime_version: "0.8.0",
207
- max_runtime_version: null,
208
- };
209
- expect(evaluateRuntimeCompatibility(compat, "0.7.1")).toEqual({
210
- ok: false,
211
- reason: "version_incompatible",
212
- bundle_compat: compat,
213
- runtime_version: "0.7.1",
214
- });
215
- });
216
-
217
- test("runtime above max fails", () => {
218
- const compat = {
219
- min_runtime_version: "0.7.0",
220
- max_runtime_version: "0.7.5",
221
- };
222
- expect(evaluateRuntimeCompatibility(compat, "0.8.0")).toEqual({
223
- ok: false,
224
- reason: "version_incompatible",
225
- bundle_compat: compat,
226
- runtime_version: "0.8.0",
227
- });
228
- });
229
-
230
- test("max is inclusive", () => {
231
- expect(
232
- evaluateRuntimeCompatibility(
233
- { min_runtime_version: "0.7.0", max_runtime_version: "0.7.5" },
234
- "0.7.5",
235
- ),
236
- ).toEqual({ ok: true });
237
- });
238
-
239
- test("unparsable min skips the gate (fail-open)", () => {
240
- expect(
241
- evaluateRuntimeCompatibility(
242
- { min_runtime_version: "garbage", max_runtime_version: null },
243
- "0.7.1",
244
- ),
245
- ).toEqual({ ok: true });
246
- });
247
-
248
- // Regression for Codex feedback: a malformed min like "0.8.0foo"
249
- // previously coerced to [0, 8, 0] via Number.parseInt and incorrectly
250
- // produced a version_incompatible decision against runtime "0.7.1".
251
- // With strict per-component digit matching, the parse fails and the
252
- // gate fails open.
253
- test("malformed min with trailing junk fails open, does not block", () => {
254
- expect(
255
- evaluateRuntimeCompatibility(
256
- { min_runtime_version: "0.8.0foo", max_runtime_version: null },
257
- "0.7.1",
258
- ),
259
- ).toEqual({ ok: true });
260
- });
261
-
262
- // A `vel up` minikube assistant image is stamped
263
- // `<pkg.version>-local.<ts>.<sha>` from the checkout's package.json,
264
- // which only moves on release cuts, so the minimum bound cannot be
265
- // decided from it; the explicit maximum ceiling still applies.
266
- test("local dev runtime build skips the minimum bound", () => {
267
- expect(
268
- evaluateRuntimeCompatibility(
269
- { min_runtime_version: "0.12.2-staging.4", max_runtime_version: null },
270
- "0.12.1-local.20260916144827.2f409056e6",
271
- ),
272
- ).toEqual({ ok: true });
273
- });
274
-
275
- test("local dev runtime build within an explicit range passes", () => {
276
- expect(
277
- evaluateRuntimeCompatibility(
278
- { min_runtime_version: "0.12.2", max_runtime_version: "0.12.5" },
279
- "0.12.1-local.20260916144827.2f409056e6",
280
- ),
281
- ).toEqual({ ok: true });
282
- });
283
-
284
- test("local dev runtime build above max still fails", () => {
285
- const compat = {
286
- min_runtime_version: "0.7.0",
287
- max_runtime_version: "0.7.5",
288
- };
289
- const runtime = "0.12.1-local.20260916144827.2f409056e6";
290
- expect(evaluateRuntimeCompatibility(compat, runtime)).toEqual({
291
- ok: false,
292
- reason: "version_incompatible",
293
- bundle_compat: compat,
294
- runtime_version: runtime,
295
- });
296
- });
297
-
298
- test("local dev runtime build with unparsable max fails open", () => {
299
- expect(
300
- evaluateRuntimeCompatibility(
301
- { min_runtime_version: "0.7.0", max_runtime_version: "garbage" },
302
- "0.12.1-local.20260916144827.2f409056e6",
303
- ),
304
- ).toEqual({ ok: true });
305
- });
306
-
307
- test("other prerelease builds are still gated", () => {
308
- const compat = {
309
- min_runtime_version: "0.12.2-staging.4",
310
- max_runtime_version: null,
311
- };
312
- for (const runtime of [
313
- "0.12.1-dev.202609151913.a151da3",
314
- "0.12.1-staging.9",
315
- "0.12.1",
316
- ]) {
317
- expect(evaluateRuntimeCompatibility(compat, runtime)).toEqual({
318
- ok: false,
319
- reason: "version_incompatible",
320
- bundle_compat: compat,
321
- runtime_version: runtime,
322
- });
323
- }
324
- });
325
- });
326
-
327
- describe("isLocalDevRuntimeVersion", () => {
328
- test("matches the local hatch / minikube version stamp", () => {
329
- expect(
330
- isLocalDevRuntimeVersion("0.12.1-local.20260916144827.2f409056e6"),
331
- ).toBe(true);
332
- expect(isLocalDevRuntimeVersion("0.0.0-local.1789584535000")).toBe(true);
333
- });
334
-
335
- test("rejects release, dev and staging builds and lookalikes", () => {
336
- for (const version of [
337
- "0.12.1",
338
- "0.12.1-dev.202609151913.a151da3",
339
- "0.12.2-staging.4",
340
- "0.0.0-dev",
341
- "0.0.0-legacy",
342
- "0.12.1-localhost.1",
343
- "local.0.12.1",
344
- "",
345
- ]) {
346
- expect(isLocalDevRuntimeVersion(version)).toBe(false);
347
- }
348
- });
349
- });
@@ -1,196 +0,0 @@
1
- /**
2
- * Buffer importer runtime-version compat gate.
3
- *
4
- * Wires `commitImport` to `policy.evaluateRuntimeCompatibility` BEFORE any
5
- * state mutation. The cloud-side gate is the primary check; this catches
6
- * legacy bundles whose ExportJob row predates PR #5470 (compat columns NULL
7
- * → platform gate skipped) and any caller that bypasses the cloud-issued
8
- * signed URL flow.
9
- *
10
- * Invariants pinned here:
11
- * - A compatible bundle imports normally.
12
- * - An incompatible bundle (min_runtime_version above APP_VERSION) returns
13
- * `{ ok: false, reason: "version_incompatible", bundle_compat,
14
- * runtime_version }` with NO disk mutation.
15
- * - The legacy sentinel "0.0.0-legacy" passes the gate unconditionally.
16
- * - Pre-existing workspace files are byte-identical and no `*.backup-*`
17
- * files are created when the gate rejects.
18
- */
19
-
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 { join, relative } from "node:path";
32
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
33
-
34
- import { APP_VERSION } from "../../../version.js";
35
- import { buildVBundle } from "../vbundle-builder.js";
36
- import { DefaultPathResolver } from "../vbundle-import-analyzer.js";
37
- import { commitImport } from "../vbundle-importer.js";
38
- import { defaultV1Options } from "./v1-test-helpers.js";
39
-
40
- // ---------------------------------------------------------------------------
41
- // Helpers
42
- // ---------------------------------------------------------------------------
43
-
44
- function freshWorkspace(): string {
45
- const parent = realpathSync(
46
- mkdtempSync(join(tmpdir(), "vbundle-import-version-compat-")),
47
- );
48
- return join(parent, "workspace");
49
- }
50
-
51
- function cleanupWorkspaceParent(workspaceDir: string): void {
52
- try {
53
- rmSync(join(workspaceDir, ".."), { recursive: true, force: true });
54
- } catch {
55
- // best-effort
56
- }
57
- }
58
-
59
- /** Recursively yields every regular file under `root` as a relative path. */
60
- function* walkFiles(root: string): Generator<string> {
61
- if (!existsSync(root)) {
62
- return;
63
- }
64
- const stack: string[] = [root];
65
- while (stack.length > 0) {
66
- const dir = stack.pop()!;
67
- let entries;
68
- try {
69
- entries = readdirSync(dir, { withFileTypes: true });
70
- } catch {
71
- continue;
72
- }
73
- for (const entry of entries) {
74
- const abs = join(dir, entry.name);
75
- if (entry.isDirectory()) {
76
- stack.push(abs);
77
- } else if (entry.isFile()) {
78
- yield relative(root, abs);
79
- }
80
- }
81
- }
82
- }
83
-
84
- function buildBundleWithMinRuntimeVersion(
85
- minRuntimeVersion: string,
86
- ): Uint8Array {
87
- const dbBytes = new TextEncoder().encode("db-payload");
88
- const configJson = JSON.stringify({ version: 1 });
89
- const { archive } = buildVBundle({
90
- files: [
91
- { path: "workspace/data/db/assistant.db", data: dbBytes },
92
- {
93
- path: "workspace/config.json",
94
- data: new TextEncoder().encode(configJson),
95
- },
96
- ],
97
- ...defaultV1Options(),
98
- compatibility: {
99
- min_runtime_version: minRuntimeVersion,
100
- max_runtime_version: null,
101
- },
102
- });
103
- return archive;
104
- }
105
-
106
- // ---------------------------------------------------------------------------
107
- // Tests
108
- // ---------------------------------------------------------------------------
109
-
110
- describe("commitImport runtime-version compat gate", () => {
111
- let workspaceDir: string;
112
-
113
- beforeEach(() => {
114
- workspaceDir = freshWorkspace();
115
- mkdirSync(workspaceDir, { recursive: true });
116
- });
117
-
118
- afterEach(() => {
119
- cleanupWorkspaceParent(workspaceDir);
120
- });
121
-
122
- test("compatible bundle imports normally", () => {
123
- const archive = buildBundleWithMinRuntimeVersion("0.0.1");
124
-
125
- const result = commitImport({
126
- archiveData: archive,
127
- pathResolver: new DefaultPathResolver(workspaceDir),
128
- workspaceDir,
129
- });
130
-
131
- expect(result.ok).toBe(true);
132
- expect(existsSync(join(workspaceDir, "data/db/assistant.db"))).toBe(true);
133
- expect(existsSync(join(workspaceDir, "config.json"))).toBe(true);
134
- });
135
-
136
- test("incompatible bundle (above APP_VERSION) returns version_incompatible without writing", () => {
137
- const archive = buildBundleWithMinRuntimeVersion("99.0.0");
138
-
139
- const result = commitImport({
140
- archiveData: archive,
141
- pathResolver: new DefaultPathResolver(workspaceDir),
142
- workspaceDir,
143
- });
144
-
145
- expect(result.ok).toBe(false);
146
- if (!result.ok && result.reason === "version_incompatible") {
147
- expect(result.bundle_compat.min_runtime_version).toBe("99.0.0");
148
- expect(result.bundle_compat.max_runtime_version).toBeNull();
149
- expect(result.runtime_version).toBe(APP_VERSION);
150
- } else {
151
- throw new Error(
152
- `expected version_incompatible, got ${JSON.stringify(result)}`,
153
- );
154
- }
155
-
156
- // Fresh workspace stays empty — no bundle files, no backups.
157
- expect([...walkFiles(workspaceDir)]).toEqual([]);
158
- });
159
-
160
- test("legacy sentinel passes through", () => {
161
- const archive = buildBundleWithMinRuntimeVersion("0.0.0-legacy");
162
-
163
- const result = commitImport({
164
- archiveData: archive,
165
- pathResolver: new DefaultPathResolver(workspaceDir),
166
- workspaceDir,
167
- });
168
-
169
- expect(result.ok).toBe(true);
170
- expect(existsSync(join(workspaceDir, "data/db/assistant.db"))).toBe(true);
171
- expect(existsSync(join(workspaceDir, "config.json"))).toBe(true);
172
- });
173
-
174
- test("pre-existing workspace files stay untouched on incompatibility", () => {
175
- const sentinelPath = join(workspaceDir, "sentinel.txt");
176
- const sentinelContent = "keep me";
177
- writeFileSync(sentinelPath, sentinelContent);
178
-
179
- const archive = buildBundleWithMinRuntimeVersion("99.0.0");
180
-
181
- const result = commitImport({
182
- archiveData: archive,
183
- pathResolver: new DefaultPathResolver(workspaceDir),
184
- workspaceDir,
185
- });
186
-
187
- expect(result.ok).toBe(false);
188
- if (!result.ok) {
189
- expect(result.reason).toBe("version_incompatible");
190
- }
191
-
192
- expect(readFileSync(sentinelPath, "utf-8")).toBe(sentinelContent);
193
- // Only the seeded sentinel remains — no bundle files, no `*.backup-*`.
194
- expect([...walkFiles(workspaceDir)]).toEqual(["sentinel.txt"]);
195
- });
196
- });