@vellumai/cli 0.6.5 → 0.7.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 (47) hide show
  1. package/AGENTS.md +8 -2
  2. package/package.json +1 -1
  3. package/src/__tests__/assistant-config.test.ts +1 -7
  4. package/src/__tests__/config-utils.test.ts +159 -0
  5. package/src/__tests__/env-drift.test.ts +10 -32
  6. package/src/__tests__/llm-provider-env-var-parity.test.ts +1 -21
  7. package/src/__tests__/multi-local.test.ts +0 -5
  8. package/src/__tests__/sleep.test.ts +1 -2
  9. package/src/__tests__/teleport.test.ts +919 -1255
  10. package/src/commands/env.ts +93 -0
  11. package/src/commands/events.ts +2 -0
  12. package/src/commands/exec.ts +40 -8
  13. package/src/commands/hatch.ts +6 -2
  14. package/src/commands/login.ts +89 -6
  15. package/src/commands/ps.ts +104 -20
  16. package/src/commands/retire.ts +23 -0
  17. package/src/commands/sleep.ts +5 -2
  18. package/src/commands/ssh.ts +15 -2
  19. package/src/commands/teleport.ts +447 -583
  20. package/src/commands/terminal.ts +225 -0
  21. package/src/commands/wake.ts +2 -1
  22. package/src/components/DefaultMainScreen.tsx +304 -152
  23. package/src/index.ts +6 -0
  24. package/src/lib/__tests__/docker.test.ts +50 -74
  25. package/src/lib/__tests__/job-polling.test.ts +278 -0
  26. package/src/lib/__tests__/local-runtime-client.test.ts +383 -0
  27. package/src/lib/__tests__/platform-client-signed-url.test.ts +405 -0
  28. package/src/lib/assistant-config.ts +12 -8
  29. package/src/lib/client-identity.ts +67 -0
  30. package/src/lib/config-utils.ts +97 -1
  31. package/src/lib/docker.ts +73 -75
  32. package/src/lib/environments/__tests__/paths.test.ts +2 -0
  33. package/src/lib/environments/resolve.ts +89 -7
  34. package/src/lib/environments/seeds.ts +8 -5
  35. package/src/lib/environments/types.ts +10 -0
  36. package/src/lib/hatch-local.ts +15 -120
  37. package/src/lib/health-check.ts +98 -0
  38. package/src/lib/job-polling.ts +195 -0
  39. package/src/lib/local-runtime-client.ts +178 -0
  40. package/src/lib/local.ts +139 -15
  41. package/src/lib/orphan-detection.ts +2 -35
  42. package/src/lib/platform-client.ts +215 -0
  43. package/src/lib/retire-local.ts +6 -2
  44. package/src/lib/terminal-client.ts +177 -0
  45. package/src/lib/terminal-session.ts +457 -0
  46. package/src/shared/provider-env-vars.ts +2 -3
  47. package/src/__tests__/orphan-detection.test.ts +0 -214
@@ -13,7 +13,7 @@ import { tmpdir } from "node:os";
13
13
  import { join } from "node:path";
14
14
 
15
15
  // ---------------------------------------------------------------------------
16
- // Temp directory for lockfile isolation (same pattern as assistant-config.test.ts)
16
+ // Temp directory for lockfile isolation
17
17
  // ---------------------------------------------------------------------------
18
18
 
19
19
  const testDir = mkdtempSync(join(tmpdir(), "cli-teleport-test-"));
@@ -23,16 +23,10 @@ process.env.VELLUM_LOCKFILE_DIR = testDir;
23
23
  // Mocks — must be set up before importing the module under test
24
24
  // ---------------------------------------------------------------------------
25
25
 
26
- // Import the real modules — do NOT mock them with mock.module() because
27
- // Bun's mock.module() replaces modules globally and the replacement leaks
28
- // into other test files (e.g. platform-client.test.ts, guardian-token.test.ts,
29
- // multi-local.test.ts) running in the same process with no way to unmock.
30
- // Instead, we use spyOn() on the imported module namespace objects — these
31
- // mutate only the current process's live binding and are restored via
32
- // mockRestore() in afterAll.
33
26
  import * as assistantConfig from "../lib/assistant-config.js";
34
27
  import * as guardianToken from "../lib/guardian-token.js";
35
28
  import * as platformClient from "../lib/platform-client.js";
29
+ import * as localRuntimeClient from "../lib/local-runtime-client.js";
36
30
 
37
31
  const findAssistantByNameMock = spyOn(
38
32
  assistantConfig,
@@ -101,45 +95,35 @@ const platformInitiateExportMock = spyOn(
101
95
  platformClient,
102
96
  "platformInitiateExport",
103
97
  ).mockResolvedValue({
104
- jobId: "job-1",
98
+ jobId: "platform-export-job-1",
105
99
  status: "pending",
106
100
  });
107
101
 
108
- const platformPollExportStatusMock = spyOn(
102
+ const platformPollJobStatusMock = spyOn(
109
103
  platformClient,
110
- "platformPollExportStatus",
104
+ "platformPollJobStatus",
111
105
  ).mockResolvedValue({
112
- status: "complete" as string,
113
- downloadUrl: "https://cdn.example.com/bundle.tar.gz",
106
+ jobId: "platform-job-1",
107
+ type: "export",
108
+ status: "complete",
109
+ bundleKey: "platform-bundle-key-abc",
114
110
  });
115
111
 
116
- const platformDownloadExportMock = spyOn(
112
+ const platformRequestSignedUrlMock = spyOn(
117
113
  platformClient,
118
- "platformDownloadExport",
119
- ).mockImplementation(async () => {
120
- const data = new Uint8Array([10, 20, 30]);
121
- return new Response(data, { status: 200 });
122
- });
123
-
124
- const platformImportPreflightMock = spyOn(
125
- platformClient,
126
- "platformImportPreflight",
127
- ).mockResolvedValue({
128
- statusCode: 200,
129
- body: {
130
- can_import: true,
131
- summary: {
132
- files_to_create: 2,
133
- files_to_overwrite: 1,
134
- files_unchanged: 0,
135
- total_files: 3,
136
- },
137
- } as Record<string, unknown>,
138
- });
114
+ "platformRequestSignedUrl",
115
+ ).mockImplementation(async (params) => ({
116
+ url:
117
+ params.operation === "upload"
118
+ ? "https://storage.googleapis.com/bucket/signed-upload"
119
+ : "https://storage.googleapis.com/bucket/signed-download",
120
+ bundleKey: params.bundleKey ?? "bundle-key-123",
121
+ expiresAt: new Date(Date.now() + 3600_000).toISOString(),
122
+ }));
139
123
 
140
- const platformImportBundleMock = spyOn(
124
+ const platformImportBundleFromGcsMock = spyOn(
141
125
  platformClient,
142
- "platformImportBundle",
126
+ "platformImportBundleFromGcs",
143
127
  ).mockResolvedValue({
144
128
  statusCode: 200,
145
129
  body: {
@@ -154,20 +138,6 @@ const platformImportBundleMock = spyOn(
154
138
  } as Record<string, unknown>,
155
139
  });
156
140
 
157
- const platformRequestUploadUrlMock = spyOn(
158
- platformClient,
159
- "platformRequestUploadUrl",
160
- ).mockResolvedValue({
161
- uploadUrl: "https://storage.googleapis.com/bucket/signed-upload-url",
162
- bundleKey: "bundle-key-123",
163
- expiresAt: new Date(Date.now() + 3600_000).toISOString(),
164
- });
165
-
166
- const platformUploadToSignedUrlMock = spyOn(
167
- platformClient,
168
- "platformUploadToSignedUrl",
169
- ).mockResolvedValue(undefined);
170
-
171
141
  const platformImportPreflightFromGcsMock = spyOn(
172
142
  platformClient,
173
143
  "platformImportPreflightFromGcs",
@@ -184,23 +154,6 @@ const platformImportPreflightFromGcsMock = spyOn(
184
154
  } as Record<string, unknown>,
185
155
  });
186
156
 
187
- const platformImportBundleFromGcsMock = spyOn(
188
- platformClient,
189
- "platformImportBundleFromGcs",
190
- ).mockResolvedValue({
191
- statusCode: 200,
192
- body: {
193
- success: true,
194
- summary: {
195
- total_files: 3,
196
- files_created: 2,
197
- files_overwritten: 1,
198
- files_skipped: 0,
199
- backups_created: 1,
200
- },
201
- } as Record<string, unknown>,
202
- });
203
-
204
157
  const checkExistingPlatformAssistantMock = spyOn(
205
158
  platformClient,
206
159
  "checkExistingPlatformAssistant",
@@ -241,6 +194,35 @@ const fetchOrganizationIdMock = spyOn(
241
194
  "fetchOrganizationId",
242
195
  ).mockResolvedValue("org-1");
243
196
 
197
+ const localRuntimeExportToGcsMock = spyOn(
198
+ localRuntimeClient,
199
+ "localRuntimeExportToGcs",
200
+ ).mockResolvedValue({ jobId: "local-export-job-1" });
201
+
202
+ const localRuntimeImportFromGcsMock = spyOn(
203
+ localRuntimeClient,
204
+ "localRuntimeImportFromGcs",
205
+ ).mockResolvedValue({ jobId: "local-import-job-1" });
206
+
207
+ const localRuntimePollJobStatusMock = spyOn(
208
+ localRuntimeClient,
209
+ "localRuntimePollJobStatus",
210
+ ).mockImplementation(async (_runtimeUrl, _token, jobId) => ({
211
+ jobId,
212
+ type: jobId.includes("import") ? "import" : "export",
213
+ status: "complete",
214
+ result: {
215
+ success: true,
216
+ summary: {
217
+ total_files: 3,
218
+ files_created: 2,
219
+ files_overwritten: 1,
220
+ files_skipped: 0,
221
+ backups_created: 1,
222
+ },
223
+ },
224
+ }));
225
+
244
226
  const hatchLocalMock = mock(async () => {});
245
227
 
246
228
  mock.module("../lib/hatch-local.js", () => ({
@@ -313,19 +295,18 @@ afterAll(() => {
313
295
  hatchAssistantMock.mockRestore();
314
296
  checkExistingPlatformAssistantMock.mockRestore();
315
297
  platformInitiateExportMock.mockRestore();
316
- platformPollExportStatusMock.mockRestore();
317
- platformDownloadExportMock.mockRestore();
318
- platformImportPreflightMock.mockRestore();
319
- platformImportBundleMock.mockRestore();
320
- platformRequestUploadUrlMock.mockRestore();
321
- platformUploadToSignedUrlMock.mockRestore();
322
- platformImportPreflightFromGcsMock.mockRestore();
298
+ platformPollJobStatusMock.mockRestore();
299
+ platformRequestSignedUrlMock.mockRestore();
323
300
  platformImportBundleFromGcsMock.mockRestore();
301
+ platformImportPreflightFromGcsMock.mockRestore();
324
302
  ensureSelfHostedLocalRegistrationMock.mockRestore();
325
303
  injectCredentialsIntoAssistantMock.mockRestore();
326
304
  fetchCurrentUserMock.mockRestore();
327
305
  fetchOrganizationIdMock.mockRestore();
328
306
  computeDeviceIdMock.mockRestore();
307
+ localRuntimeExportToGcsMock.mockRestore();
308
+ localRuntimeImportFromGcsMock.mockRestore();
309
+ localRuntimePollJobStatusMock.mockRestore();
329
310
  rmSync(testDir, { recursive: true, force: true });
330
311
  delete process.env.VELLUM_LOCKFILE_DIR;
331
312
  });
@@ -335,11 +316,39 @@ let exitMock: ReturnType<typeof mock>;
335
316
  let originalExit: typeof process.exit;
336
317
  let consoleLogSpy: ReturnType<typeof spyOn>;
337
318
  let consoleErrorSpy: ReturnType<typeof spyOn>;
319
+ let fetchCalls: Array<{ url: string; body: unknown }>;
320
+
321
+ function defaultLocalRuntimePollImpl(
322
+ _runtimeUrl: string,
323
+ _token: string,
324
+ jobId: string,
325
+ ): Promise<{
326
+ jobId: string;
327
+ type: "export" | "import";
328
+ status: "complete";
329
+ result: Record<string, unknown>;
330
+ }> {
331
+ return Promise.resolve({
332
+ jobId,
333
+ type: jobId.includes("import") ? "import" : "export",
334
+ status: "complete",
335
+ result: {
336
+ success: true,
337
+ summary: {
338
+ total_files: 3,
339
+ files_created: 2,
340
+ files_overwritten: 1,
341
+ files_skipped: 0,
342
+ backups_created: 1,
343
+ },
344
+ },
345
+ });
346
+ }
338
347
 
339
348
  beforeEach(() => {
340
349
  originalArgv = [...process.argv];
350
+ fetchCalls = [];
341
351
 
342
- // Reset all mocks
343
352
  findAssistantByNameMock.mockReset();
344
353
  findAssistantByNameMock.mockReturnValue(null);
345
354
  saveAssistantEntryMock.mockReset();
@@ -371,33 +380,27 @@ beforeEach(() => {
371
380
  });
372
381
  platformInitiateExportMock.mockReset();
373
382
  platformInitiateExportMock.mockResolvedValue({
374
- jobId: "job-1",
383
+ jobId: "platform-export-job-1",
375
384
  status: "pending",
376
385
  });
377
- platformPollExportStatusMock.mockReset();
378
- platformPollExportStatusMock.mockResolvedValue({
386
+ platformPollJobStatusMock.mockReset();
387
+ platformPollJobStatusMock.mockResolvedValue({
388
+ jobId: "platform-job-1",
389
+ type: "export",
379
390
  status: "complete",
380
- downloadUrl: "https://cdn.example.com/bundle.tar.gz",
381
- });
382
- platformDownloadExportMock.mockReset();
383
- platformDownloadExportMock.mockResolvedValue(
384
- new Response(new Uint8Array([10, 20, 30]), { status: 200 }),
385
- );
386
- platformImportPreflightMock.mockReset();
387
- platformImportPreflightMock.mockResolvedValue({
388
- statusCode: 200,
389
- body: {
390
- can_import: true,
391
- summary: {
392
- files_to_create: 2,
393
- files_to_overwrite: 1,
394
- files_unchanged: 0,
395
- total_files: 3,
396
- },
397
- },
398
- });
399
- platformImportBundleMock.mockReset();
400
- platformImportBundleMock.mockResolvedValue({
391
+ bundleKey: "platform-bundle-key-abc",
392
+ });
393
+ platformRequestSignedUrlMock.mockReset();
394
+ platformRequestSignedUrlMock.mockImplementation(async (params) => ({
395
+ url:
396
+ params.operation === "upload"
397
+ ? "https://storage.googleapis.com/bucket/signed-upload"
398
+ : "https://storage.googleapis.com/bucket/signed-download",
399
+ bundleKey: params.bundleKey ?? "bundle-key-123",
400
+ expiresAt: new Date(Date.now() + 3600_000).toISOString(),
401
+ }));
402
+ platformImportBundleFromGcsMock.mockReset();
403
+ platformImportBundleFromGcsMock.mockResolvedValue({
401
404
  statusCode: 200,
402
405
  body: {
403
406
  success: true,
@@ -410,14 +413,6 @@ beforeEach(() => {
410
413
  },
411
414
  },
412
415
  });
413
- platformRequestUploadUrlMock.mockReset();
414
- platformRequestUploadUrlMock.mockResolvedValue({
415
- uploadUrl: "https://storage.googleapis.com/bucket/signed-upload-url",
416
- bundleKey: "bundle-key-123",
417
- expiresAt: new Date(Date.now() + 3600_000).toISOString(),
418
- });
419
- platformUploadToSignedUrlMock.mockReset();
420
- platformUploadToSignedUrlMock.mockResolvedValue(undefined);
421
416
  platformImportPreflightFromGcsMock.mockReset();
422
417
  platformImportPreflightFromGcsMock.mockResolvedValue({
423
418
  statusCode: 200,
@@ -431,20 +426,6 @@ beforeEach(() => {
431
426
  },
432
427
  },
433
428
  });
434
- platformImportBundleFromGcsMock.mockReset();
435
- platformImportBundleFromGcsMock.mockResolvedValue({
436
- statusCode: 200,
437
- body: {
438
- success: true,
439
- summary: {
440
- total_files: 3,
441
- files_created: 2,
442
- files_overwritten: 1,
443
- files_skipped: 0,
444
- backups_created: 1,
445
- },
446
- },
447
- });
448
429
  checkExistingPlatformAssistantMock.mockReset();
449
430
  checkExistingPlatformAssistantMock.mockResolvedValue(null);
450
431
  ensureSelfHostedLocalRegistrationMock.mockReset();
@@ -471,6 +452,17 @@ beforeEach(() => {
471
452
  computeDeviceIdMock.mockReset();
472
453
  computeDeviceIdMock.mockReturnValue("device-id-123");
473
454
 
455
+ localRuntimeExportToGcsMock.mockReset();
456
+ localRuntimeExportToGcsMock.mockResolvedValue({
457
+ jobId: "local-export-job-1",
458
+ });
459
+ localRuntimeImportFromGcsMock.mockReset();
460
+ localRuntimeImportFromGcsMock.mockResolvedValue({
461
+ jobId: "local-import-job-1",
462
+ });
463
+ localRuntimePollJobStatusMock.mockReset();
464
+ localRuntimePollJobStatusMock.mockImplementation(defaultLocalRuntimePollImpl);
465
+
474
466
  hatchLocalMock.mockReset();
475
467
  hatchLocalMock.mockResolvedValue(undefined);
476
468
  hatchDockerMock.mockReset();
@@ -505,7 +497,6 @@ afterEach(() => {
505
497
  });
506
498
 
507
499
  function setArgv(...args: string[]): void {
508
- // teleport reads process.argv.slice(3)
509
500
  process.argv = ["bun", "vellum", "teleport", ...args];
510
501
  }
511
502
 
@@ -521,44 +512,24 @@ function makeEntry(
521
512
  };
522
513
  }
523
514
 
524
- /** Create a mock fetch that handles export and import endpoints. */
525
- function createFetchMock() {
526
- return mock(async (url: string | URL | Request) => {
527
- const urlStr = typeof url === "string" ? url : url.toString();
528
- if (urlStr.includes("/export")) {
529
- return new Response(new Uint8Array([1, 2, 3]), { status: 200 });
530
- }
531
- if (urlStr.includes("/import-preflight")) {
532
- return new Response(
533
- JSON.stringify({
534
- can_import: true,
535
- summary: {
536
- files_to_create: 1,
537
- files_to_overwrite: 0,
538
- files_unchanged: 0,
539
- total_files: 1,
540
- },
541
- }),
542
- { status: 200, headers: { "Content-Type": "application/json" } },
543
- );
544
- }
545
- if (urlStr.includes("/import")) {
546
- return new Response(
547
- JSON.stringify({
548
- success: true,
549
- summary: {
550
- total_files: 1,
551
- files_created: 1,
552
- files_overwritten: 0,
553
- files_skipped: 0,
554
- backups_created: 0,
555
- },
556
- }),
557
- { status: 200, headers: { "Content-Type": "application/json" } },
558
- );
559
- }
560
- return new Response("not found", { status: 404 });
561
- });
515
+ /**
516
+ * Tracking fetch mock — records every call so tests can verify that the CLI
517
+ * never sends a bundle-sized request body. With the new GCS-unified flow
518
+ * all bundle bytes travel between the runtime and GCS directly, so the CLI
519
+ * should make zero fetch calls carrying binary payloads.
520
+ */
521
+ function installTrackingFetch(): () => void {
522
+ const originalFetch = globalThis.fetch;
523
+ globalThis.fetch = mock(
524
+ async (url: string | URL | Request, init?: RequestInit) => {
525
+ const urlStr = typeof url === "string" ? url : url.toString();
526
+ fetchCalls.push({ url: urlStr, body: init?.body });
527
+ return new Response("not found", { status: 404 });
528
+ },
529
+ ) as unknown as typeof globalThis.fetch;
530
+ return () => {
531
+ globalThis.fetch = originalFetch;
532
+ };
562
533
  }
563
534
 
564
535
  // ---------------------------------------------------------------------------
@@ -663,20 +634,13 @@ describe("same-environment rejection", () => {
663
634
  return null;
664
635
  });
665
636
 
666
- const originalFetch = globalThis.fetch;
667
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
668
-
669
- try {
670
- await expect(teleport()).rejects.toThrow("process.exit:1");
671
- expect(consoleErrorSpy).toHaveBeenCalledWith(
672
- expect.stringContaining("Cannot teleport between two local assistants"),
673
- );
674
- } finally {
675
- globalThis.fetch = originalFetch;
676
- }
637
+ await expect(teleport()).rejects.toThrow("process.exit:1");
638
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
639
+ expect.stringContaining("Cannot teleport between two local assistants"),
640
+ );
677
641
  });
678
642
 
679
- test("source docker, target docker -> error (after resolving target)", async () => {
643
+ test("source docker, target docker -> error", async () => {
680
644
  setArgv("--from", "src", "--docker", "dst");
681
645
 
682
646
  const srcEntry = makeEntry("src", { cloud: "docker" });
@@ -688,22 +652,13 @@ describe("same-environment rejection", () => {
688
652
  return null;
689
653
  });
690
654
 
691
- const originalFetch = globalThis.fetch;
692
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
693
-
694
- try {
695
- await expect(teleport()).rejects.toThrow("process.exit:1");
696
- expect(consoleErrorSpy).toHaveBeenCalledWith(
697
- expect.stringContaining(
698
- "Cannot teleport between two docker assistants",
699
- ),
700
- );
701
- } finally {
702
- globalThis.fetch = originalFetch;
703
- }
655
+ await expect(teleport()).rejects.toThrow("process.exit:1");
656
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
657
+ expect.stringContaining("Cannot teleport between two docker assistants"),
658
+ );
704
659
  });
705
660
 
706
- test("source vellum, target platform -> error (after resolving target)", async () => {
661
+ test("source vellum, target platform -> error", async () => {
707
662
  setArgv("--from", "src", "--platform", "dst");
708
663
 
709
664
  const srcEntry = makeEntry("src", {
@@ -721,19 +676,12 @@ describe("same-environment rejection", () => {
721
676
  return null;
722
677
  });
723
678
 
724
- const originalFetch = globalThis.fetch;
725
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
726
-
727
- try {
728
- await expect(teleport()).rejects.toThrow("process.exit:1");
729
- expect(consoleErrorSpy).toHaveBeenCalledWith(
730
- expect.stringContaining(
731
- "Cannot teleport between two platform assistants",
732
- ),
733
- );
734
- } finally {
735
- globalThis.fetch = originalFetch;
736
- }
679
+ await expect(teleport()).rejects.toThrow("process.exit:1");
680
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
681
+ expect.stringContaining(
682
+ "Cannot teleport between two platform assistants",
683
+ ),
684
+ );
737
685
  });
738
686
 
739
687
  test("same-env rejection happens before hatching (no orphaned assistants)", async () => {
@@ -749,59 +697,15 @@ describe("same-environment rejection", () => {
749
697
  expect(consoleErrorSpy).toHaveBeenCalledWith(
750
698
  expect.stringContaining("Cannot teleport between two local assistants"),
751
699
  );
752
- // Crucially: no hatch should have been called — the early guard fires first
753
- expect(hatchLocalMock).not.toHaveBeenCalled();
754
- expect(hatchDockerMock).not.toHaveBeenCalled();
755
- expect(hatchAssistantMock).not.toHaveBeenCalled();
756
- });
757
-
758
- test("same-env rejection before hatching for docker", async () => {
759
- setArgv("--from", "my-docker", "--docker");
760
-
761
- const dockerEntry = makeEntry("my-docker", { cloud: "docker" });
762
- findAssistantByNameMock.mockImplementation((name: string) => {
763
- if (name === "my-docker") return dockerEntry;
764
- return null;
765
- });
766
-
767
- await expect(teleport()).rejects.toThrow("process.exit:1");
768
- expect(consoleErrorSpy).toHaveBeenCalledWith(
769
- expect.stringContaining("Cannot teleport between two docker assistants"),
770
- );
771
- expect(hatchLocalMock).not.toHaveBeenCalled();
772
- expect(hatchDockerMock).not.toHaveBeenCalled();
773
- expect(hatchAssistantMock).not.toHaveBeenCalled();
774
- });
775
-
776
- test("same-env rejection before hatching for platform (vellum cloud)", async () => {
777
- setArgv("--from", "my-cloud", "--platform");
778
-
779
- const platformEntry = makeEntry("my-cloud", {
780
- cloud: "vellum",
781
- runtimeUrl: "https://platform.vellum.ai",
782
- });
783
- findAssistantByNameMock.mockImplementation((name: string) => {
784
- if (name === "my-cloud") return platformEntry;
785
- return null;
786
- });
787
-
788
- await expect(teleport()).rejects.toThrow("process.exit:1");
789
- expect(consoleErrorSpy).toHaveBeenCalledWith(
790
- expect.stringContaining(
791
- "Cannot teleport between two platform assistants",
792
- ),
793
- );
794
700
  expect(hatchLocalMock).not.toHaveBeenCalled();
795
701
  expect(hatchDockerMock).not.toHaveBeenCalled();
796
702
  expect(hatchAssistantMock).not.toHaveBeenCalled();
797
703
  });
798
704
 
799
705
  test("flag says docker but resolved target is local -> rejects cloud mismatch", async () => {
800
- // User passes --docker but the named target is actually a local assistant
801
706
  setArgv("--from", "src", "--docker", "misidentified");
802
707
 
803
708
  const srcEntry = makeEntry("src", { cloud: "vellum" });
804
- // Target is actually local despite the --docker flag
805
709
  const dstEntry = makeEntry("misidentified", { cloud: "local" });
806
710
 
807
711
  findAssistantByNameMock.mockImplementation((name: string) => {
@@ -832,16 +736,11 @@ describe("resolveOrHatchTarget", () => {
832
736
  const result = await resolveOrHatchTarget("docker", "my-docker");
833
737
  expect(result).toBe(dockerEntry);
834
738
  expect(hatchDockerMock).not.toHaveBeenCalled();
835
- expect(consoleLogSpy).toHaveBeenCalledWith(
836
- expect.stringContaining("Target: my-docker (docker)"),
837
- );
838
739
  });
839
740
 
840
741
  test("name not found -> hatch docker", async () => {
841
742
  const newEntry = makeEntry("new-one", { cloud: "docker" });
842
743
  findAssistantByNameMock.mockImplementation((name: string) => {
843
- // First call: lookup by name -> not found
844
- // Second call: after hatch -> found
845
744
  if (name === "new-one" && hatchDockerMock.mock.calls.length > 0) {
846
745
  return newEntry;
847
746
  }
@@ -859,12 +758,10 @@ describe("resolveOrHatchTarget", () => {
859
758
  expect(result).toBe(newEntry);
860
759
  });
861
760
 
862
- test("no name -> hatch local with null name, discovers via diff", async () => {
761
+ test("no name -> hatch local, discovers via diff", async () => {
863
762
  const existingEntry = makeEntry("existing-local", { cloud: "local" });
864
763
  const newEntry = makeEntry("auto-generated", { cloud: "local" });
865
764
 
866
- // Before hatch: only the existing entry
867
- // After hatch: existing + new entry
868
765
  loadAllAssistantsMock.mockImplementation(() => {
869
766
  if (hatchLocalMock.mock.calls.length > 0) {
870
767
  return [existingEntry, newEntry];
@@ -873,13 +770,7 @@ describe("resolveOrHatchTarget", () => {
873
770
  });
874
771
 
875
772
  const result = await resolveOrHatchTarget("local");
876
- expect(hatchLocalMock).toHaveBeenCalledWith(
877
- "vellum",
878
- null,
879
- false,
880
- false,
881
- {},
882
- );
773
+ expect(hatchLocalMock).toHaveBeenCalled();
883
774
  expect(result).toBe(newEntry);
884
775
  });
885
776
 
@@ -903,16 +794,10 @@ describe("resolveOrHatchTarget", () => {
903
794
 
904
795
  const result = await resolveOrHatchTarget("platform", "nonexistent");
905
796
  expect(hatchAssistantMock).toHaveBeenCalledWith("platform-token");
906
- expect(saveAssistantEntryMock).toHaveBeenCalledWith(
907
- expect.objectContaining({
908
- assistantId: "platform-new-id",
909
- cloud: "vellum",
910
- }),
911
- );
912
797
  expect(result.assistantId).toBe("platform-new-id");
913
798
  });
914
799
 
915
- test("platform with no name -> blocks when hatch returns reusedExisting (defensive safety net)", async () => {
800
+ test("platform with no name -> blocks when hatch returns reusedExisting", async () => {
916
801
  findAssistantByNameMock.mockReturnValue(null);
917
802
  hatchAssistantMock.mockResolvedValue({
918
803
  assistant: {
@@ -923,25 +808,12 @@ describe("resolveOrHatchTarget", () => {
923
808
  reusedExisting: true,
924
809
  });
925
810
 
926
- // Defensive safety net: even though the pre-check in teleport() should
927
- // catch this first, resolveOrHatchTarget still blocks on reusedExisting
928
- // to guard against a TOCTOU race (matching the Swift client behavior).
929
811
  await expect(resolveOrHatchTarget("platform", undefined)).rejects.toThrow(
930
812
  "process.exit:1",
931
813
  );
932
814
  expect(consoleErrorSpy).toHaveBeenCalledWith(
933
815
  expect.stringContaining("already have a platform assistant"),
934
816
  );
935
- expect(consoleErrorSpy).toHaveBeenCalledWith(
936
- expect.stringContaining("Retire it first"),
937
- );
938
- // The existing assistant is saved to the lockfile so `vellum retire` can find it
939
- expect(saveAssistantEntryMock).toHaveBeenCalledWith(
940
- expect.objectContaining({
941
- assistantId: "existing-platform-id",
942
- cloud: "vellum",
943
- }),
944
- );
945
817
  });
946
818
 
947
819
  test("existing assistant with wrong cloud -> rejects", async () => {
@@ -973,212 +845,139 @@ describe("resolveOrHatchTarget", () => {
973
845
  });
974
846
 
975
847
  // ---------------------------------------------------------------------------
976
- // Auto-retire tests
848
+ // Unified GCS teleport flow — the four directions
977
849
  // ---------------------------------------------------------------------------
978
850
 
979
- describe("auto-retire", () => {
980
- test("local -> docker: stops source before hatch, retires after import", async () => {
981
- setArgv("--from", "my-local", "--docker");
851
+ describe("unified GCS flow — four directions", () => {
852
+ test("local platform: requests upload URL, drives local runtime export, imports from GCS", async () => {
853
+ setArgv("--from", "my-local", "--platform");
982
854
 
983
- const localEntry = makeEntry("my-local", {
984
- cloud: "local",
985
- resources: {
986
- instanceDir: "/home/test",
987
- pidFile: "/home/test/.vellum/assistant.pid",
988
- signingKey: "key",
989
- daemonPort: 7821,
990
- gatewayPort: 7830,
991
- qdrantPort: 6333,
992
- cesPort: 8090,
993
- },
994
- });
995
- const dockerEntry = makeEntry("new-docker", { cloud: "docker" });
855
+ const localEntry = makeEntry("my-local", { cloud: "local" });
856
+ findAssistantByNameMock.mockImplementation((name: string) =>
857
+ name === "my-local" ? localEntry : null,
858
+ );
996
859
 
997
- findAssistantByNameMock.mockImplementation((name: string) => {
998
- if (name === "my-local") return localEntry;
999
- return null;
1000
- });
860
+ const restoreFetch = installTrackingFetch();
861
+ try {
862
+ await teleport();
1001
863
 
1002
- // Simulate hatch creating a new docker entry
1003
- loadAllAssistantsMock.mockImplementation(() => {
1004
- if (hatchDockerMock.mock.calls.length > 0) {
1005
- return [localEntry, dockerEntry];
1006
- }
1007
- return [localEntry];
1008
- });
864
+ // Signed-URL request for upload pinned to the platform target's URL
865
+ // so upload and download land on the same platform.
866
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
867
+ expect.objectContaining({ operation: "upload" }),
868
+ "platform-token",
869
+ "https://platform.vellum.ai",
870
+ );
1009
871
 
1010
- const originalFetch = globalThis.fetch;
1011
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
872
+ // Runtime export-to-gcs kicked off with the signed upload URL
873
+ expect(localRuntimeExportToGcsMock).toHaveBeenCalledWith(
874
+ "http://localhost:7821",
875
+ "local-token",
876
+ expect.objectContaining({
877
+ uploadUrl: "https://storage.googleapis.com/bucket/signed-upload",
878
+ }),
879
+ );
1012
880
 
1013
- try {
1014
- await teleport();
1015
- // Source should be stopped (slept) before hatch
1016
- expect(stopProcessByPidFileMock).toHaveBeenCalled();
1017
- // Retire happens after successful import
1018
- expect(retireLocalMock).toHaveBeenCalledWith("my-local", localEntry);
1019
- expect(removeAssistantEntryMock).toHaveBeenCalledWith("my-local");
881
+ // Poll continued until complete
882
+ expect(localRuntimePollJobStatusMock).toHaveBeenCalled();
883
+
884
+ // Import via GCS with the bundleKey returned from signed-URL request
885
+ expect(platformImportBundleFromGcsMock).toHaveBeenCalledWith(
886
+ "bundle-key-123",
887
+ "platform-token",
888
+ expect.any(String),
889
+ );
890
+
891
+ // No download-URL request on the import side (platform target pulls
892
+ // directly from GCS).
893
+ const downloadOps = platformRequestSignedUrlMock.mock.calls.filter(
894
+ (call: unknown[]) =>
895
+ (call[0] as { operation: string }).operation === "download",
896
+ );
897
+ expect(downloadOps.length).toBe(0);
1020
898
  } finally {
1021
- globalThis.fetch = originalFetch;
899
+ restoreFetch();
1022
900
  }
1023
901
  });
1024
902
 
1025
- test("docker -> local: sleeps containers before hatch, retires after import", async () => {
1026
- setArgv("--from", "my-docker", "--local");
903
+ test("platform local: drives platform export, reads bundle_key, requests download URL for runtime import", async () => {
904
+ setArgv("--from", "my-platform", "--local", "my-local");
1027
905
 
1028
- const dockerEntry = makeEntry("my-docker", { cloud: "docker" });
1029
- const localEntry = makeEntry("new-local", { cloud: "local" });
906
+ const platformEntry = makeEntry("my-platform", {
907
+ cloud: "vellum",
908
+ runtimeUrl: "https://platform.vellum.ai",
909
+ });
910
+ const localEntry = makeEntry("my-local", {
911
+ cloud: "local",
912
+ bearerToken: "local-bearer",
913
+ });
1030
914
 
1031
915
  findAssistantByNameMock.mockImplementation((name: string) => {
1032
- if (name === "my-docker") return dockerEntry;
916
+ if (name === "my-platform") return platformEntry;
917
+ if (name === "my-local") return localEntry;
1033
918
  return null;
1034
919
  });
1035
920
 
1036
- loadAllAssistantsMock.mockImplementation(() => {
1037
- if (hatchLocalMock.mock.calls.length > 0) {
1038
- return [dockerEntry, localEntry];
1039
- }
1040
- return [dockerEntry];
921
+ // Platform poll returns export-complete with a bundle_key.
922
+ platformPollJobStatusMock.mockResolvedValue({
923
+ jobId: "platform-export-job-1",
924
+ type: "export",
925
+ status: "complete",
926
+ bundleKey: "platform-exports/org-1/bundle-abc.vbundle",
1041
927
  });
1042
928
 
1043
- const originalFetch = globalThis.fetch;
1044
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1045
-
929
+ const restoreFetch = installTrackingFetch();
1046
930
  try {
1047
931
  await teleport();
1048
- // Docker source should be slept (containers stopped) before hatch
1049
- expect(sleepContainersMock).toHaveBeenCalled();
1050
- // Retire happens after successful import
1051
- expect(retireDockerMock).toHaveBeenCalledWith("my-docker");
1052
- expect(removeAssistantEntryMock).toHaveBeenCalledWith("my-docker");
1053
- } finally {
1054
- globalThis.fetch = originalFetch;
1055
- }
1056
- });
1057
-
1058
- test("--keep-source skips retire and removeAssistantEntry", async () => {
1059
- setArgv("--from", "my-local", "--docker", "--keep-source");
1060
-
1061
- const localEntry = makeEntry("my-local", { cloud: "local" });
1062
- const dockerEntry = makeEntry("new-docker", { cloud: "docker" });
1063
-
1064
- findAssistantByNameMock.mockImplementation((name: string) => {
1065
- if (name === "my-local") return localEntry;
1066
- return null;
1067
- });
1068
-
1069
- loadAllAssistantsMock.mockImplementation(() => {
1070
- if (hatchDockerMock.mock.calls.length > 0) {
1071
- return [localEntry, dockerEntry];
1072
- }
1073
- return [localEntry];
1074
- });
1075
-
1076
- const originalFetch = globalThis.fetch;
1077
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1078
932
 
1079
- try {
1080
- await teleport();
1081
- expect(retireLocalMock).not.toHaveBeenCalled();
1082
- expect(retireDockerMock).not.toHaveBeenCalled();
1083
- expect(removeAssistantEntryMock).not.toHaveBeenCalled();
1084
- expect(consoleLogSpy).toHaveBeenCalledWith(
1085
- expect.stringContaining("kept (--keep-source)"),
933
+ // Platform side: initiated server export and polled the unified status.
934
+ expect(platformInitiateExportMock).toHaveBeenCalled();
935
+ expect(platformPollJobStatusMock).toHaveBeenCalled();
936
+
937
+ // For the local target we request a download URL keyed by the
938
+ // platform's bundle_key. The URL must target the SOURCE platform
939
+ // (where the bundle was written) — pinned so a lockfile change
940
+ // can't split upload and download across instances.
941
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
942
+ {
943
+ operation: "download",
944
+ bundleKey: "platform-exports/org-1/bundle-abc.vbundle",
945
+ },
946
+ "platform-token",
947
+ "https://platform.vellum.ai",
1086
948
  );
1087
- } finally {
1088
- globalThis.fetch = originalFetch;
1089
- }
1090
- });
1091
-
1092
- test("platform transfers skip retire", async () => {
1093
- setArgv("--from", "my-local", "--platform");
1094
-
1095
- const localEntry = makeEntry("my-local", { cloud: "local" });
1096
-
1097
- findAssistantByNameMock.mockImplementation((name: string) => {
1098
- if (name === "my-local") return localEntry;
1099
- return null;
1100
- });
1101
-
1102
- const originalFetch = globalThis.fetch;
1103
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1104
949
 
1105
- try {
1106
- await teleport();
1107
- expect(retireLocalMock).not.toHaveBeenCalled();
1108
- expect(retireDockerMock).not.toHaveBeenCalled();
1109
- expect(removeAssistantEntryMock).not.toHaveBeenCalled();
1110
- } finally {
1111
- globalThis.fetch = originalFetch;
1112
- }
1113
- });
1114
-
1115
- test("dry-run without existing target does not hatch or export", async () => {
1116
- setArgv("--from", "my-local", "--docker", "--dry-run");
1117
-
1118
- const localEntry = makeEntry("my-local", { cloud: "local" });
1119
-
1120
- findAssistantByNameMock.mockImplementation((name: string) => {
1121
- if (name === "my-local") return localEntry;
1122
- return null;
1123
- });
1124
-
1125
- await teleport();
1126
-
1127
- // Should NOT hatch, export, import, or retire
1128
- expect(hatchDockerMock).not.toHaveBeenCalled();
1129
- expect(hatchLocalMock).not.toHaveBeenCalled();
1130
- expect(hatchAssistantMock).not.toHaveBeenCalled();
1131
- expect(retireLocalMock).not.toHaveBeenCalled();
1132
- expect(retireDockerMock).not.toHaveBeenCalled();
1133
- expect(removeAssistantEntryMock).not.toHaveBeenCalled();
1134
- });
1135
-
1136
- test("dry-run with existing target runs preflight without hatching", async () => {
1137
- setArgv("--from", "my-local", "--docker", "my-docker", "--dry-run");
1138
-
1139
- const localEntry = makeEntry("my-local", { cloud: "local" });
1140
- const dockerEntry = makeEntry("my-docker", { cloud: "docker" });
1141
-
1142
- findAssistantByNameMock.mockImplementation((name: string) => {
1143
- if (name === "my-local") return localEntry;
1144
- if (name === "my-docker") return dockerEntry;
1145
- return null;
1146
- });
1147
-
1148
- const originalFetch = globalThis.fetch;
1149
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1150
-
1151
- try {
1152
- await teleport();
950
+ // Runtime import-from-gcs was kicked off with that URL.
951
+ expect(localRuntimeImportFromGcsMock).toHaveBeenCalledWith(
952
+ "http://localhost:7821",
953
+ "local-token",
954
+ expect.objectContaining({
955
+ bundleUrl: "https://storage.googleapis.com/bucket/signed-download",
956
+ }),
957
+ );
958
+ expect(localRuntimePollJobStatusMock).toHaveBeenCalled();
1153
959
 
1154
- // Should NOT hatch or retire
1155
- expect(hatchDockerMock).not.toHaveBeenCalled();
1156
- expect(hatchLocalMock).not.toHaveBeenCalled();
1157
- expect(retireLocalMock).not.toHaveBeenCalled();
1158
- expect(retireDockerMock).not.toHaveBeenCalled();
1159
- expect(removeAssistantEntryMock).not.toHaveBeenCalled();
960
+ // No legacy inline-import helpers were touched.
961
+ // (Verified by the absence of fetch calls carrying bundle bodies —
962
+ // see "never buffers bundle bytes" assertion below.)
1160
963
  } finally {
1161
- globalThis.fetch = originalFetch;
964
+ restoreFetch();
1162
965
  }
1163
966
  });
1164
- });
1165
-
1166
- // ---------------------------------------------------------------------------
1167
- // Full flow tests
1168
- // ---------------------------------------------------------------------------
1169
967
 
1170
- describe("teleport full flow", () => {
1171
- test("hatch and import: --from my-local --docker", async () => {
968
+ test("local docker: export via upload URL, import via download URL", async () => {
1172
969
  setArgv("--from", "my-local", "--docker");
1173
970
 
1174
971
  const localEntry = makeEntry("my-local", { cloud: "local" });
1175
- const dockerEntry = makeEntry("new-docker", { cloud: "docker" });
1176
-
1177
- findAssistantByNameMock.mockImplementation((name: string) => {
1178
- if (name === "my-local") return localEntry;
1179
- return null;
972
+ const dockerEntry = makeEntry("new-docker", {
973
+ cloud: "docker",
974
+ runtimeUrl: "http://localhost:7822",
1180
975
  });
1181
976
 
977
+ findAssistantByNameMock.mockImplementation((name: string) =>
978
+ name === "my-local" ? localEntry : null,
979
+ );
980
+
1182
981
  loadAllAssistantsMock.mockImplementation(() => {
1183
982
  if (hatchDockerMock.mock.calls.length > 0) {
1184
983
  return [localEntry, dockerEntry];
@@ -1186,605 +985,338 @@ describe("teleport full flow", () => {
1186
985
  return [localEntry];
1187
986
  });
1188
987
 
1189
- const originalFetch = globalThis.fetch;
1190
- const fetchMock = createFetchMock();
1191
- globalThis.fetch = fetchMock as unknown as typeof globalThis.fetch;
1192
-
1193
- try {
1194
- await teleport();
1195
-
1196
- // Verify sequence: export, hatch, import, retire
1197
- expect(hatchDockerMock).toHaveBeenCalled();
1198
- expect(retireLocalMock).toHaveBeenCalledWith("my-local", localEntry);
1199
- expect(removeAssistantEntryMock).toHaveBeenCalledWith("my-local");
1200
- expect(consoleLogSpy).toHaveBeenCalledWith(
1201
- expect.stringContaining("Teleport complete"),
1202
- );
1203
- } finally {
1204
- globalThis.fetch = originalFetch;
1205
- }
1206
- });
1207
-
1208
- test("existing target overwrite: --from my-local --docker my-existing", async () => {
1209
- setArgv("--from", "my-local", "--docker", "my-existing");
1210
-
1211
- const localEntry = makeEntry("my-local", { cloud: "local" });
1212
- const dockerEntry = makeEntry("my-existing", { cloud: "docker" });
1213
-
1214
- findAssistantByNameMock.mockImplementation((name: string) => {
1215
- if (name === "my-local") return localEntry;
1216
- if (name === "my-existing") return dockerEntry;
1217
- return null;
1218
- });
1219
-
1220
- const originalFetch = globalThis.fetch;
1221
- const fetchMock = createFetchMock();
1222
- globalThis.fetch = fetchMock as unknown as typeof globalThis.fetch;
1223
-
1224
- try {
1225
- await teleport();
1226
-
1227
- // No hatch should happen — existing target is used
1228
- expect(hatchDockerMock).not.toHaveBeenCalled();
1229
- // Source should still be retired
1230
- expect(retireLocalMock).toHaveBeenCalledWith("my-local", localEntry);
1231
- expect(consoleLogSpy).toHaveBeenCalledWith(
1232
- expect.stringContaining("Teleport complete"),
1233
- );
1234
- } finally {
1235
- globalThis.fetch = originalFetch;
1236
- }
1237
- });
1238
-
1239
- test("legacy --to flag shows deprecation message", async () => {
1240
- setArgv("--from", "source", "--to", "target");
1241
-
1242
- await expect(teleport()).rejects.toThrow("process.exit:1");
1243
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1244
- expect.stringContaining("--to is deprecated"),
1245
- );
1246
- });
1247
- });
1248
-
1249
- // ---------------------------------------------------------------------------
1250
- // Signed-URL upload tests
1251
- // ---------------------------------------------------------------------------
1252
-
1253
- describe("signed-URL upload flow", () => {
1254
- test("happy path: signed URL upload succeeds → GCS-based import used", async () => {
1255
- setArgv("--from", "my-local", "--platform");
1256
-
1257
- const localEntry = makeEntry("my-local", { cloud: "local" });
1258
-
1259
- findAssistantByNameMock.mockImplementation((name: string) => {
1260
- if (name === "my-local") return localEntry;
1261
- return null;
1262
- });
1263
-
1264
- const originalFetch = globalThis.fetch;
1265
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1266
-
988
+ const restoreFetch = installTrackingFetch();
1267
989
  try {
1268
990
  await teleport();
1269
991
 
1270
- // Signed-URL flow should be used
1271
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1272
- expect(platformUploadToSignedUrlMock).toHaveBeenCalled();
1273
- expect(platformImportBundleFromGcsMock).toHaveBeenCalledWith(
1274
- "bundle-key-123",
992
+ // Export and import must pin the same platform URL so the bundle
993
+ // lives in one place end-to-end. For local→docker neither side is
994
+ // platform, so we default to getPlatformUrl() (resolved once).
995
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
996
+ expect.objectContaining({ operation: "upload" }),
1275
997
  "platform-token",
1276
998
  "https://platform.vellum.ai",
1277
999
  );
1278
- // Inline import should NOT be called
1279
- expect(platformImportBundleMock).not.toHaveBeenCalled();
1280
- expect(consoleLogSpy).toHaveBeenCalledWith(
1281
- expect.stringContaining("Teleport complete"),
1282
- );
1283
- } finally {
1284
- globalThis.fetch = originalFetch;
1285
- }
1286
- });
1287
-
1288
- test("happy path dry-run: signed URL upload succeeds → GCS-based preflight used", async () => {
1289
- setArgv(
1290
- "--from",
1291
- "my-local",
1292
- "--platform",
1293
- "existing-platform",
1294
- "--dry-run",
1295
- );
1296
-
1297
- const localEntry = makeEntry("my-local", { cloud: "local" });
1298
- const platformEntry = makeEntry("existing-platform", {
1299
- cloud: "vellum",
1300
- runtimeUrl: "https://platform.vellum.ai",
1301
- });
1302
-
1303
- findAssistantByNameMock.mockImplementation((name: string) => {
1304
- if (name === "my-local") return localEntry;
1305
- if (name === "existing-platform") return platformEntry;
1306
- return null;
1307
- });
1308
-
1309
- const originalFetch = globalThis.fetch;
1310
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1000
+ expect(localRuntimeExportToGcsMock).toHaveBeenCalled();
1311
1001
 
1312
- try {
1313
- await teleport();
1314
-
1315
- // Signed-URL flow should be used for preflight
1316
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1317
- expect(platformUploadToSignedUrlMock).toHaveBeenCalled();
1318
- expect(platformImportPreflightFromGcsMock).toHaveBeenCalledWith(
1319
- "bundle-key-123",
1002
+ // Import: download-URL for the docker target, then runtime import.
1003
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
1004
+ {
1005
+ operation: "download",
1006
+ bundleKey: "bundle-key-123",
1007
+ },
1320
1008
  "platform-token",
1321
1009
  "https://platform.vellum.ai",
1322
1010
  );
1323
- // Inline preflight should NOT be called
1324
- expect(platformImportPreflightMock).not.toHaveBeenCalled();
1325
- } finally {
1326
- globalThis.fetch = originalFetch;
1327
- }
1328
- });
1329
-
1330
- test("fallback: platformRequestUploadUrl throws 503 → falls back to inline import", async () => {
1331
- setArgv("--from", "my-local", "--platform");
1332
-
1333
- const localEntry = makeEntry("my-local", { cloud: "local" });
1334
-
1335
- findAssistantByNameMock.mockImplementation((name: string) => {
1336
- if (name === "my-local") return localEntry;
1337
- return null;
1338
- });
1339
-
1340
- // Simulate 503 — "not available" in the error message
1341
- platformRequestUploadUrlMock.mockRejectedValue(
1342
- new Error("Signed uploads are not available on this platform instance"),
1343
- );
1344
-
1345
- const originalFetch = globalThis.fetch;
1346
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1347
-
1348
- try {
1349
- await teleport();
1350
-
1351
- // Should fall back to inline import
1352
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1353
- expect(platformUploadToSignedUrlMock).not.toHaveBeenCalled();
1354
- expect(platformImportBundleFromGcsMock).not.toHaveBeenCalled();
1355
- expect(platformImportBundleMock).toHaveBeenCalled();
1356
- expect(consoleLogSpy).toHaveBeenCalledWith(
1357
- expect.stringContaining("Teleport complete"),
1011
+ expect(localRuntimeImportFromGcsMock).toHaveBeenCalledWith(
1012
+ "http://localhost:7822",
1013
+ "local-token",
1014
+ expect.objectContaining({
1015
+ bundleUrl: "https://storage.googleapis.com/bucket/signed-download",
1016
+ }),
1358
1017
  );
1018
+
1019
+ // Source retirement still happens on success for local↔docker.
1020
+ expect(retireLocalMock).toHaveBeenCalledWith("my-local", localEntry);
1021
+ expect(removeAssistantEntryMock).toHaveBeenCalledWith("my-local");
1359
1022
  } finally {
1360
- globalThis.fetch = originalFetch;
1023
+ restoreFetch();
1361
1024
  }
1362
1025
  });
1363
1026
 
1364
- test("fallback: platformRequestUploadUrl throws 404 falls back to inline import", async () => {
1365
- setArgv("--from", "my-local", "--platform");
1366
-
1367
- const localEntry = makeEntry("my-local", { cloud: "local" });
1027
+ test("docker → local: export via upload URL, import via download URL", async () => {
1028
+ setArgv("--from", "my-docker", "--local");
1368
1029
 
1369
- findAssistantByNameMock.mockImplementation((name: string) => {
1370
- if (name === "my-local") return localEntry;
1371
- return null;
1030
+ const dockerEntry = makeEntry("my-docker", {
1031
+ cloud: "docker",
1032
+ runtimeUrl: "http://localhost:7822",
1033
+ });
1034
+ const localEntry = makeEntry("new-local", {
1035
+ cloud: "local",
1036
+ runtimeUrl: "http://localhost:7823",
1372
1037
  });
1373
1038
 
1374
- // Simulate 404 — endpoint doesn't exist on older platform versions
1375
- platformRequestUploadUrlMock.mockRejectedValue(
1376
- new Error("Signed uploads are not available on this platform instance"),
1039
+ findAssistantByNameMock.mockImplementation((name: string) =>
1040
+ name === "my-docker" ? dockerEntry : null,
1377
1041
  );
1378
1042
 
1379
- const originalFetch = globalThis.fetch;
1380
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1043
+ loadAllAssistantsMock.mockImplementation(() => {
1044
+ if (hatchLocalMock.mock.calls.length > 0) {
1045
+ return [dockerEntry, localEntry];
1046
+ }
1047
+ return [dockerEntry];
1048
+ });
1381
1049
 
1050
+ const restoreFetch = installTrackingFetch();
1382
1051
  try {
1383
1052
  await teleport();
1384
1053
 
1385
- // Should fall back to inline import
1386
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1387
- expect(platformUploadToSignedUrlMock).not.toHaveBeenCalled();
1388
- expect(platformImportBundleFromGcsMock).not.toHaveBeenCalled();
1389
- expect(platformImportBundleMock).toHaveBeenCalled();
1390
- expect(consoleLogSpy).toHaveBeenCalledWith(
1391
- expect.stringContaining("Teleport complete"),
1392
- );
1393
- } finally {
1394
- globalThis.fetch = originalFetch;
1395
- }
1396
- });
1397
-
1398
- test("upload error: platformUploadToSignedUrl throws → error propagates", async () => {
1399
- setArgv("--from", "my-local", "--platform");
1400
-
1401
- const localEntry = makeEntry("my-local", { cloud: "local" });
1402
-
1403
- findAssistantByNameMock.mockImplementation((name: string) => {
1404
- if (name === "my-local") return localEntry;
1405
- return null;
1406
- });
1407
-
1408
- // Upload succeeds at getting URL but fails during PUT
1409
- platformUploadToSignedUrlMock.mockRejectedValue(
1410
- new Error("Upload to signed URL failed: 500 Internal Server Error"),
1411
- );
1412
-
1413
- const originalFetch = globalThis.fetch;
1414
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1054
+ // Docker source should be put to sleep first.
1055
+ expect(sleepContainersMock).toHaveBeenCalled();
1415
1056
 
1416
- try {
1417
- await expect(teleport()).rejects.toThrow(
1418
- "Upload to signed URL failed: 500 Internal Server Error",
1057
+ // Export leg: upload-URL (pinned to the same platform as import),
1058
+ // then runtime export.
1059
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
1060
+ expect.objectContaining({ operation: "upload" }),
1061
+ "platform-token",
1062
+ "https://platform.vellum.ai",
1063
+ );
1064
+ expect(localRuntimeExportToGcsMock).toHaveBeenCalledWith(
1065
+ "http://localhost:7822",
1066
+ "local-token",
1067
+ expect.objectContaining({
1068
+ uploadUrl: "https://storage.googleapis.com/bucket/signed-upload",
1069
+ }),
1419
1070
  );
1420
- // Should NOT fall back to inline import
1421
- expect(platformImportBundleMock).not.toHaveBeenCalled();
1422
- expect(platformImportBundleFromGcsMock).not.toHaveBeenCalled();
1423
- } finally {
1424
- globalThis.fetch = originalFetch;
1425
- }
1426
- });
1427
-
1428
- test("413 from GCS import: error message includes 'too large'", async () => {
1429
- setArgv("--from", "my-local", "--platform");
1430
-
1431
- const localEntry = makeEntry("my-local", { cloud: "local" });
1432
-
1433
- findAssistantByNameMock.mockImplementation((name: string) => {
1434
- if (name === "my-local") return localEntry;
1435
- return null;
1436
- });
1437
-
1438
- // GCS import returns 413
1439
- platformImportBundleFromGcsMock.mockRejectedValue(
1440
- new Error("Bundle too large to import"),
1441
- );
1442
1071
 
1443
- const originalFetch = globalThis.fetch;
1444
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1072
+ // Import leg: download-URL targets the new local runtime
1073
+ expect(localRuntimeImportFromGcsMock).toHaveBeenCalledWith(
1074
+ "http://localhost:7823",
1075
+ "local-token",
1076
+ expect.anything(),
1077
+ );
1445
1078
 
1446
- try {
1447
- await expect(teleport()).rejects.toThrow("too large");
1079
+ // Source retirement
1080
+ expect(retireDockerMock).toHaveBeenCalledWith("my-docker");
1081
+ expect(removeAssistantEntryMock).toHaveBeenCalledWith("my-docker");
1448
1082
  } finally {
1449
- globalThis.fetch = originalFetch;
1083
+ restoreFetch();
1450
1084
  }
1451
1085
  });
1452
1086
  });
1453
1087
 
1454
1088
  // ---------------------------------------------------------------------------
1455
- // Platform teleport org ID and reordered flow tests
1089
+ // Target-platform URL threading: signed URL must be requested from the same
1090
+ // platform instance the import will run against. Codex P2 regression guard.
1456
1091
  // ---------------------------------------------------------------------------
1457
1092
 
1458
- describe("platform teleport org ID and reordered flow", () => {
1459
- test("hatchAssistant is called without orgId (authHeaders fetches it internally)", async () => {
1460
- setArgv("--from", "my-local", "--platform");
1461
-
1462
- const localEntry = makeEntry("my-local", { cloud: "local" });
1463
-
1464
- findAssistantByNameMock.mockImplementation((name: string) => {
1465
- if (name === "my-local") return localEntry;
1466
- return null;
1467
- });
1468
-
1469
- const originalFetch = globalThis.fetch;
1470
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1471
-
1472
- try {
1473
- await teleport();
1474
-
1475
- // hatchAssistant should be called with just the token (orgId is resolved internally by authHeaders)
1476
- expect(hatchAssistantMock).toHaveBeenCalledWith("platform-token");
1477
- } finally {
1478
- globalThis.fetch = originalFetch;
1479
- }
1480
- });
1481
-
1482
- test("upload to GCS happens before hatchAssistant for platform targets", async () => {
1483
- setArgv("--from", "my-local", "--platform");
1484
-
1485
- const localEntry = makeEntry("my-local", { cloud: "local" });
1486
-
1487
- findAssistantByNameMock.mockImplementation((name: string) => {
1488
- if (name === "my-local") return localEntry;
1489
- return null;
1490
- });
1491
-
1492
- const callOrder: string[] = [];
1493
-
1494
- platformRequestUploadUrlMock.mockImplementation(async () => {
1495
- callOrder.push("platformRequestUploadUrl");
1496
- return {
1497
- uploadUrl: "https://storage.googleapis.com/bucket/signed-upload-url",
1498
- bundleKey: "bundle-key-123",
1499
- expiresAt: new Date(Date.now() + 3600_000).toISOString(),
1500
- };
1501
- });
1502
-
1503
- platformUploadToSignedUrlMock.mockImplementation(async () => {
1504
- callOrder.push("platformUploadToSignedUrl");
1505
- });
1506
-
1507
- hatchAssistantMock.mockImplementation(async () => {
1508
- callOrder.push("hatchAssistant");
1509
- return {
1510
- assistant: {
1511
- id: "platform-new-id",
1512
- name: "platform-new",
1513
- status: "active",
1514
- },
1515
- reusedExisting: false,
1516
- };
1517
- });
1518
-
1519
- const originalFetch = globalThis.fetch;
1520
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1521
-
1522
- try {
1523
- await teleport();
1524
-
1525
- // Verify ordering: upload steps come before hatch
1526
- const uploadUrlIdx = callOrder.indexOf("platformRequestUploadUrl");
1527
- const uploadIdx = callOrder.indexOf("platformUploadToSignedUrl");
1528
- const hatchIdx = callOrder.indexOf("hatchAssistant");
1529
-
1530
- expect(uploadUrlIdx).toBeGreaterThanOrEqual(0);
1531
- expect(uploadIdx).toBeGreaterThanOrEqual(0);
1532
- expect(hatchIdx).toBeGreaterThanOrEqual(0);
1533
- expect(uploadUrlIdx).toBeLessThan(hatchIdx);
1534
- expect(uploadIdx).toBeLessThan(hatchIdx);
1535
- } finally {
1536
- globalThis.fetch = originalFetch;
1537
- }
1538
- });
1539
-
1540
- test("signed-URL fallback: when platformRequestUploadUrl throws 'not available', falls back to inline upload via importToAssistant", async () => {
1541
- setArgv("--from", "my-local", "--platform");
1093
+ describe("signed-URL request targets the bundle-owning platform", () => {
1094
+ test("local existing platform target with non-default runtimeUrl: upload URL pinned to target's runtimeUrl", async () => {
1095
+ setArgv("--from", "my-local", "--platform", "existing-platform");
1542
1096
 
1543
1097
  const localEntry = makeEntry("my-local", { cloud: "local" });
1098
+ // Crucially, the target's runtimeUrl is NOT the default getPlatformUrl()
1099
+ // return value — this is the regression case Codex flagged.
1100
+ const platformEntry = makeEntry("existing-platform", {
1101
+ cloud: "vellum",
1102
+ runtimeUrl: "https://staging-platform.vellum.ai",
1103
+ });
1544
1104
 
1545
1105
  findAssistantByNameMock.mockImplementation((name: string) => {
1546
1106
  if (name === "my-local") return localEntry;
1107
+ if (name === "existing-platform") return platformEntry;
1547
1108
  return null;
1548
1109
  });
1549
1110
 
1550
- // Simulate 503 — signed uploads not available
1551
- platformRequestUploadUrlMock.mockRejectedValue(
1552
- new Error("Signed uploads are not available on this platform instance"),
1553
- );
1554
-
1555
- const originalFetch = globalThis.fetch;
1556
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1557
-
1111
+ const restoreFetch = installTrackingFetch();
1558
1112
  try {
1559
1113
  await teleport();
1560
1114
 
1561
- // Upload URL was attempted but failed
1562
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1563
- // No signed URL upload should have happened
1564
- expect(platformUploadToSignedUrlMock).not.toHaveBeenCalled();
1565
- // Should NOT use GCS-based import
1566
- expect(platformImportBundleFromGcsMock).not.toHaveBeenCalled();
1567
- // Should fall back to inline import
1568
- expect(platformImportBundleMock).toHaveBeenCalled();
1569
- // Hatch should still succeed
1570
- expect(hatchAssistantMock).toHaveBeenCalled();
1571
- expect(consoleLogSpy).toHaveBeenCalledWith(
1572
- expect.stringContaining("Teleport complete"),
1115
+ // The signed-URL request for upload MUST target the existing
1116
+ // platform assistant's runtimeUrl, not the default platform URL.
1117
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
1118
+ expect.objectContaining({ operation: "upload" }),
1119
+ "platform-token",
1120
+ "https://staging-platform.vellum.ai",
1121
+ );
1122
+
1123
+ // And the import must run against the same platform.
1124
+ expect(platformImportBundleFromGcsMock).toHaveBeenCalledWith(
1125
+ "bundle-key-123",
1126
+ "platform-token",
1127
+ "https://staging-platform.vellum.ai",
1573
1128
  );
1129
+
1130
+ // Assert none of the signed-URL calls used the default URL — if any
1131
+ // did, upload and download would hit different platforms.
1132
+ for (const call of platformRequestSignedUrlMock.mock.calls) {
1133
+ expect(call[2]).toBe("https://staging-platform.vellum.ai");
1134
+ }
1574
1135
  } finally {
1575
- globalThis.fetch = originalFetch;
1136
+ restoreFetch();
1576
1137
  }
1577
1138
  });
1578
1139
 
1579
- test("bundleKey from pre-upload is forwarded to platformImportBundleFromGcs", async () => {
1580
- setArgv("--from", "my-local", "--platform");
1140
+ test("platform local with non-default source runtimeUrl: download URL pinned to source's runtimeUrl", async () => {
1141
+ setArgv("--from", "my-platform", "--local", "my-local");
1581
1142
 
1143
+ const platformEntry = makeEntry("my-platform", {
1144
+ cloud: "vellum",
1145
+ runtimeUrl: "https://dev-platform.vellum.ai",
1146
+ });
1582
1147
  const localEntry = makeEntry("my-local", { cloud: "local" });
1583
1148
 
1584
1149
  findAssistantByNameMock.mockImplementation((name: string) => {
1150
+ if (name === "my-platform") return platformEntry;
1585
1151
  if (name === "my-local") return localEntry;
1586
1152
  return null;
1587
1153
  });
1588
1154
 
1589
- // Return a specific bundle key from the pre-upload step
1590
- platformRequestUploadUrlMock.mockResolvedValue({
1591
- uploadUrl: "https://storage.googleapis.com/bucket/signed-upload-url",
1592
- bundleKey: "pre-uploaded-key-789",
1593
- expiresAt: new Date(Date.now() + 3600_000).toISOString(),
1155
+ platformPollJobStatusMock.mockResolvedValue({
1156
+ jobId: "platform-export-job-1",
1157
+ type: "export",
1158
+ status: "complete",
1159
+ bundleKey: "dev-bundle-key",
1594
1160
  });
1595
1161
 
1596
- const originalFetch = globalThis.fetch;
1597
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1598
-
1162
+ const restoreFetch = installTrackingFetch();
1599
1163
  try {
1600
1164
  await teleport();
1601
1165
 
1602
- // The bundle key from the pre-upload step should be forwarded to GCS import
1603
- expect(platformImportBundleFromGcsMock).toHaveBeenCalledWith(
1604
- "pre-uploaded-key-789",
1166
+ // The download URL must be requested from the SOURCE platform (where
1167
+ // the bundle was written by the server-side export), not the default.
1168
+ expect(platformRequestSignedUrlMock).toHaveBeenCalledWith(
1169
+ { operation: "download", bundleKey: "dev-bundle-key" },
1605
1170
  "platform-token",
1606
- expect.any(String),
1171
+ "https://dev-platform.vellum.ai",
1607
1172
  );
1608
- // Inline import should NOT be used since signed upload succeeded
1609
- expect(platformImportBundleMock).not.toHaveBeenCalled();
1610
1173
  } finally {
1611
- globalThis.fetch = originalFetch;
1174
+ restoreFetch();
1612
1175
  }
1613
1176
  });
1614
1177
  });
1615
1178
 
1616
1179
  // ---------------------------------------------------------------------------
1617
- // Pre-check: block teleport to platform when existing assistant detected
1180
+ // Invariants: CLI never buffers bundle bytes
1618
1181
  // ---------------------------------------------------------------------------
1619
1182
 
1620
- describe("pre-check: block teleport to platform when existing assistant detected", () => {
1621
- test("blocks BEFORE GCS upload when pre-check finds existing assistant", async () => {
1622
- setArgv("--from", "my-local", "--platform");
1623
-
1624
- const localEntry = makeEntry("my-local", { cloud: "local" });
1183
+ describe("CLI never buffers bundle bytes", () => {
1184
+ // A teleport bundle is always > 1 KiB in practice; anything near that size
1185
+ // would mean the CLI is shuttling bytes when it shouldn't.
1186
+ const BUNDLE_BODY_THRESHOLD_BYTES = 1024;
1625
1187
 
1626
- findAssistantByNameMock.mockImplementation((name: string) => {
1627
- if (name === "my-local") return localEntry;
1628
- return null;
1629
- });
1188
+ function bodySize(body: unknown): number {
1189
+ if (typeof body === "string") return body.length;
1190
+ if (body instanceof Uint8Array) return body.byteLength;
1191
+ if (body instanceof ArrayBuffer) return body.byteLength;
1192
+ if (body instanceof Blob) return body.size;
1193
+ return 0;
1194
+ }
1630
1195
 
1631
- // Pre-check returns an existing assistant
1632
- checkExistingPlatformAssistantMock.mockResolvedValue({
1633
- id: "existing-platform-id",
1634
- name: "existing-platform",
1635
- status: "active",
1636
- });
1196
+ test("local → platform: no fetch call carries a bundle-sized body", async () => {
1197
+ setArgv("--from", "my-local", "--platform");
1637
1198
 
1638
- const originalFetch = globalThis.fetch;
1639
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1199
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1200
+ findAssistantByNameMock.mockImplementation((name: string) =>
1201
+ name === "my-local" ? localEntry : null,
1202
+ );
1640
1203
 
1204
+ const restoreFetch = installTrackingFetch();
1641
1205
  try {
1642
- await expect(teleport()).rejects.toThrow("process.exit:1");
1643
-
1644
- // Pre-check should have been called
1645
- expect(checkExistingPlatformAssistantMock).toHaveBeenCalledWith(
1646
- "platform-token",
1647
- undefined,
1648
- );
1649
-
1650
- // GCS upload should NOT have been attempted
1651
- expect(platformRequestUploadUrlMock).not.toHaveBeenCalled();
1652
- expect(platformUploadToSignedUrlMock).not.toHaveBeenCalled();
1653
-
1654
- // Hatch should NOT have been called
1655
- expect(hatchAssistantMock).not.toHaveBeenCalled();
1656
-
1657
- // Error message should be actionable
1658
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1659
- expect.stringContaining("already have a platform assistant"),
1660
- );
1661
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1662
- expect.stringContaining("Retire it first"),
1663
- );
1206
+ await teleport();
1664
1207
 
1665
- // The existing assistant is saved to the lockfile
1666
- expect(saveAssistantEntryMock).toHaveBeenCalledWith(
1667
- expect.objectContaining({
1668
- assistantId: "existing-platform-id",
1669
- cloud: "vellum",
1670
- }),
1671
- );
1208
+ for (const call of fetchCalls) {
1209
+ expect(bodySize(call.body)).toBeLessThan(BUNDLE_BODY_THRESHOLD_BYTES);
1210
+ }
1672
1211
  } finally {
1673
- globalThis.fetch = originalFetch;
1212
+ restoreFetch();
1674
1213
  }
1675
1214
  });
1676
1215
 
1677
- test("skips pre-check when targeting an existing named assistant", async () => {
1678
- setArgv("--from", "my-local", "--platform", "existing-platform");
1216
+ test("platform local: no fetch call carries a bundle-sized body", async () => {
1217
+ setArgv("--from", "my-platform", "--local", "my-local");
1679
1218
 
1680
- const localEntry = makeEntry("my-local", { cloud: "local" });
1681
- const platformEntry = makeEntry("existing-platform", {
1219
+ const platformEntry = makeEntry("my-platform", {
1682
1220
  cloud: "vellum",
1683
1221
  runtimeUrl: "https://platform.vellum.ai",
1684
1222
  });
1223
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1685
1224
 
1686
1225
  findAssistantByNameMock.mockImplementation((name: string) => {
1226
+ if (name === "my-platform") return platformEntry;
1687
1227
  if (name === "my-local") return localEntry;
1688
- if (name === "existing-platform") return platformEntry;
1689
1228
  return null;
1690
1229
  });
1691
1230
 
1692
- const originalFetch = globalThis.fetch;
1693
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1231
+ platformPollJobStatusMock.mockResolvedValue({
1232
+ jobId: "platform-export-job-1",
1233
+ type: "export",
1234
+ status: "complete",
1235
+ bundleKey: "platform-exports/org-1/bundle.vbundle",
1236
+ });
1694
1237
 
1238
+ const restoreFetch = installTrackingFetch();
1695
1239
  try {
1696
1240
  await teleport();
1697
1241
 
1698
- // Pre-check should NOT be called when targeting a known named assistant
1699
- expect(checkExistingPlatformAssistantMock).not.toHaveBeenCalled();
1700
-
1701
- // Upload should proceed normally
1702
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1703
- expect(consoleLogSpy).toHaveBeenCalledWith(
1704
- expect.stringContaining("Teleport complete"),
1705
- );
1242
+ for (const call of fetchCalls) {
1243
+ expect(bodySize(call.body)).toBeLessThan(BUNDLE_BODY_THRESHOLD_BYTES);
1244
+ }
1706
1245
  } finally {
1707
- globalThis.fetch = originalFetch;
1246
+ restoreFetch();
1708
1247
  }
1709
1248
  });
1249
+ });
1250
+
1251
+ // ---------------------------------------------------------------------------
1252
+ // Polling behavior
1253
+ // ---------------------------------------------------------------------------
1710
1254
 
1711
- test("pre-check failure is non-fatal — teleport proceeds", async () => {
1255
+ describe("polling", () => {
1256
+ test("local-runtime export poll continues until complete", async () => {
1712
1257
  setArgv("--from", "my-local", "--platform");
1713
1258
 
1714
1259
  const localEntry = makeEntry("my-local", { cloud: "local" });
1260
+ findAssistantByNameMock.mockImplementation((name: string) =>
1261
+ name === "my-local" ? localEntry : null,
1262
+ );
1715
1263
 
1716
- findAssistantByNameMock.mockImplementation((name: string) => {
1717
- if (name === "my-local") return localEntry;
1718
- return null;
1719
- });
1720
-
1721
- // Pre-check returns null (no existing assistant or API failed gracefully)
1722
- checkExistingPlatformAssistantMock.mockResolvedValue(null);
1723
-
1724
- const originalFetch = globalThis.fetch;
1725
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1264
+ let callIdx = 0;
1265
+ localRuntimePollJobStatusMock.mockImplementation(
1266
+ async (_runtimeUrl, _token, jobId) => {
1267
+ callIdx++;
1268
+ if (callIdx < 3) {
1269
+ return {
1270
+ jobId,
1271
+ type: "export" as const,
1272
+ status: "processing" as const,
1273
+ };
1274
+ }
1275
+ return {
1276
+ jobId,
1277
+ type: "export" as const,
1278
+ status: "complete" as const,
1279
+ result: undefined,
1280
+ };
1281
+ },
1282
+ );
1726
1283
 
1284
+ const restoreFetch = installTrackingFetch();
1727
1285
  try {
1728
1286
  await teleport();
1729
-
1730
- // Pre-check was called
1731
- expect(checkExistingPlatformAssistantMock).toHaveBeenCalled();
1732
-
1733
- // Upload and hatch should proceed normally
1734
- expect(platformRequestUploadUrlMock).toHaveBeenCalled();
1735
- expect(hatchAssistantMock).toHaveBeenCalled();
1736
- expect(consoleLogSpy).toHaveBeenCalledWith(
1737
- expect.stringContaining("Teleport complete"),
1738
- );
1287
+ expect(callIdx).toBeGreaterThanOrEqual(3);
1739
1288
  } finally {
1740
- globalThis.fetch = originalFetch;
1289
+ restoreFetch();
1741
1290
  }
1742
1291
  });
1743
- });
1744
-
1745
- // ---------------------------------------------------------------------------
1746
- // Version guard: block platform→non-platform when target is behind
1747
- // ---------------------------------------------------------------------------
1748
1292
 
1749
- describe("version guard: block platformnon-platform when target is behind", () => {
1750
- test("blocks platform→local when local version is behind platform", async () => {
1751
- setArgv("--from", "my-platform", "--local", "my-local");
1293
+ test("local-runtime export failureteleport exits 1", async () => {
1294
+ setArgv("--from", "my-local", "--platform");
1752
1295
 
1753
- const platformEntry = makeEntry("my-platform", {
1754
- cloud: "vellum",
1755
- runtimeUrl: "https://platform.vellum.ai",
1756
- });
1757
1296
  const localEntry = makeEntry("my-local", { cloud: "local" });
1297
+ findAssistantByNameMock.mockImplementation((name: string) =>
1298
+ name === "my-local" ? localEntry : null,
1299
+ );
1758
1300
 
1759
- findAssistantByNameMock.mockImplementation((name: string) => {
1760
- if (name === "my-platform") return platformEntry;
1761
- if (name === "my-local") return localEntry;
1762
- return null;
1763
- });
1764
-
1765
- // Source (platform) is on 0.7.0, target (local) is on 0.6.0
1766
- fetchCurrentVersionMock.mockImplementation((url: string) => {
1767
- if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
1768
- return Promise.resolve("0.6.0");
1301
+ localRuntimePollJobStatusMock.mockResolvedValue({
1302
+ jobId: "local-export-job-1",
1303
+ type: "export",
1304
+ status: "failed",
1305
+ error: "simulated failure",
1769
1306
  });
1770
1307
 
1771
- const originalFetch = globalThis.fetch;
1772
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1773
-
1308
+ const restoreFetch = installTrackingFetch();
1774
1309
  try {
1775
1310
  await expect(teleport()).rejects.toThrow("process.exit:1");
1776
1311
  expect(consoleErrorSpy).toHaveBeenCalledWith(
1777
- expect.stringContaining("is running 0.6.0"),
1778
- );
1779
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1780
- expect.stringContaining("Upgrade your local assistant first"),
1312
+ expect.stringContaining("simulated failure"),
1781
1313
  );
1782
1314
  } finally {
1783
- globalThis.fetch = originalFetch;
1315
+ restoreFetch();
1784
1316
  }
1785
1317
  });
1786
1318
 
1787
- test("allows platformlocal when versions are equal", async () => {
1319
+ test("local-runtime import failure teleport exits 1", async () => {
1788
1320
  setArgv("--from", "my-platform", "--local", "my-local");
1789
1321
 
1790
1322
  const platformEntry = makeEntry("my-platform", {
@@ -1799,57 +1331,89 @@ describe("version guard: block platform→non-platform when target is behind", (
1799
1331
  return null;
1800
1332
  });
1801
1333
 
1802
- // Both on 0.7.0
1803
- fetchCurrentVersionMock.mockResolvedValue("0.7.0");
1804
-
1805
- const originalFetch = globalThis.fetch;
1806
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1334
+ platformPollJobStatusMock.mockResolvedValue({
1335
+ jobId: "platform-export-job-1",
1336
+ type: "export",
1337
+ status: "complete",
1338
+ bundleKey: "key-1",
1339
+ });
1340
+
1341
+ localRuntimePollJobStatusMock.mockImplementation(
1342
+ async (_runtimeUrl, _token, jobId) => {
1343
+ if (jobId.includes("import")) {
1344
+ return {
1345
+ jobId,
1346
+ type: "import" as const,
1347
+ status: "failed" as const,
1348
+ error: "import blew up",
1349
+ };
1350
+ }
1351
+ return {
1352
+ jobId,
1353
+ type: "export" as const,
1354
+ status: "complete" as const,
1355
+ result: undefined,
1356
+ };
1357
+ },
1358
+ );
1807
1359
 
1360
+ const restoreFetch = installTrackingFetch();
1808
1361
  try {
1809
- await teleport();
1810
- expect(consoleLogSpy).toHaveBeenCalledWith(
1811
- expect.stringContaining("Teleport complete"),
1362
+ await expect(teleport()).rejects.toThrow("process.exit:1");
1363
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
1364
+ expect.stringContaining("import blew up"),
1812
1365
  );
1813
1366
  } finally {
1814
- globalThis.fetch = originalFetch;
1367
+ restoreFetch();
1815
1368
  }
1816
1369
  });
1370
+ });
1817
1371
 
1818
- test("allows platform→local when local is ahead of platform", async () => {
1819
- setArgv("--from", "my-platform", "--local", "my-local");
1372
+ // ---------------------------------------------------------------------------
1373
+ // MigrationInProgressError handling
1374
+ // ---------------------------------------------------------------------------
1375
+
1376
+ describe("MigrationInProgressError handling", () => {
1377
+ test("local-runtime export already in flight → fail fast with existing job id", async () => {
1378
+ setArgv("--from", "my-local", "--platform");
1820
1379
 
1821
- const platformEntry = makeEntry("my-platform", {
1822
- cloud: "vellum",
1823
- runtimeUrl: "https://platform.vellum.ai",
1824
- });
1825
1380
  const localEntry = makeEntry("my-local", { cloud: "local" });
1381
+ findAssistantByNameMock.mockImplementation((name: string) =>
1382
+ name === "my-local" ? localEntry : null,
1383
+ );
1826
1384
 
1827
- findAssistantByNameMock.mockImplementation((name: string) => {
1828
- if (name === "my-platform") return platformEntry;
1829
- if (name === "my-local") return localEntry;
1830
- return null;
1831
- });
1385
+ localRuntimeExportToGcsMock.mockRejectedValue(
1386
+ new localRuntimeClient.MigrationInProgressError(
1387
+ "export_in_progress",
1388
+ "existing-job-42",
1389
+ ),
1390
+ );
1832
1391
 
1833
- // Source (platform) is on 0.7.0, target (local) is on 0.8.0
1834
- fetchCurrentVersionMock.mockImplementation((url: string) => {
1835
- if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
1836
- return Promise.resolve("0.8.0");
1837
- });
1392
+ const restoreFetch = installTrackingFetch();
1393
+ try {
1394
+ await expect(teleport()).rejects.toThrow("process.exit:1");
1838
1395
 
1839
- const originalFetch = globalThis.fetch;
1840
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1396
+ // Must not have polled the existing job — the existing job's bundle
1397
+ // lives at a different GCS key (its caller's signed URL), so polling
1398
+ // it would leave the teleport pointing at an empty/unrelated bundle.
1399
+ const polledIds = localRuntimePollJobStatusMock.mock.calls.map(
1400
+ (call: unknown[]) => call[2],
1401
+ );
1402
+ expect(polledIds).not.toContain("existing-job-42");
1841
1403
 
1842
- try {
1843
- await teleport();
1844
- expect(consoleLogSpy).toHaveBeenCalledWith(
1845
- expect.stringContaining("Teleport complete"),
1404
+ // Error must mention the existing job id so the user can act on it.
1405
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
1406
+ expect.stringContaining("existing-job-42"),
1407
+ );
1408
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
1409
+ expect.stringContaining("already in progress"),
1846
1410
  );
1847
1411
  } finally {
1848
- globalThis.fetch = originalFetch;
1412
+ restoreFetch();
1849
1413
  }
1850
1414
  });
1851
1415
 
1852
- test("allows teleport when source version cannot be fetched (best-effort)", async () => {
1416
+ test("local-runtime import already in flight fail fast with existing job id", async () => {
1853
1417
  setArgv("--from", "my-platform", "--local", "my-local");
1854
1418
 
1855
1419
  const platformEntry = makeEntry("my-platform", {
@@ -1864,62 +1428,104 @@ describe("version guard: block platform→non-platform when target is behind", (
1864
1428
  return null;
1865
1429
  });
1866
1430
 
1867
- // Source (platform) is unreachable, target (local) is on 0.6.0
1868
- fetchCurrentVersionMock.mockImplementation((url: string) => {
1869
- if (url === "https://platform.vellum.ai")
1870
- return Promise.resolve(undefined);
1871
- return Promise.resolve("0.6.0");
1431
+ platformPollJobStatusMock.mockResolvedValue({
1432
+ jobId: "platform-export-job-1",
1433
+ type: "export",
1434
+ status: "complete",
1435
+ bundleKey: "bundle-key-from-platform",
1872
1436
  });
1873
1437
 
1874
- const originalFetch = globalThis.fetch;
1875
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1438
+ localRuntimeImportFromGcsMock.mockRejectedValue(
1439
+ new localRuntimeClient.MigrationInProgressError(
1440
+ "import_in_progress",
1441
+ "existing-import-99",
1442
+ ),
1443
+ );
1876
1444
 
1445
+ const restoreFetch = installTrackingFetch();
1877
1446
  try {
1878
- await teleport();
1879
- expect(consoleLogSpy).toHaveBeenCalledWith(
1880
- expect.stringContaining("Teleport complete"),
1447
+ await expect(teleport()).rejects.toThrow("process.exit:1");
1448
+
1449
+ // Must not poll the existing import — it's importing somebody else's
1450
+ // bundle, not ours, so reporting on it would be misleading.
1451
+ const polledIds = localRuntimePollJobStatusMock.mock.calls.map(
1452
+ (call: unknown[]) => call[2],
1453
+ );
1454
+ expect(polledIds).not.toContain("existing-import-99");
1455
+
1456
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
1457
+ expect.stringContaining("existing-import-99"),
1458
+ );
1459
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
1460
+ expect.stringContaining("already in progress"),
1881
1461
  );
1882
1462
  } finally {
1883
- globalThis.fetch = originalFetch;
1463
+ restoreFetch();
1884
1464
  }
1885
1465
  });
1466
+ });
1886
1467
 
1887
- test("allows teleport when target version cannot be fetched (best-effort)", async () => {
1888
- setArgv("--from", "my-platform", "--local", "my-local");
1468
+ // ---------------------------------------------------------------------------
1469
+ // Dry-run behavior
1470
+ // ---------------------------------------------------------------------------
1889
1471
 
1890
- const platformEntry = makeEntry("my-platform", {
1472
+ describe("dry-run", () => {
1473
+ test("dry-run without existing target does not hatch or export", async () => {
1474
+ setArgv("--from", "my-local", "--docker", "--dry-run");
1475
+
1476
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1477
+ findAssistantByNameMock.mockImplementation((name: string) =>
1478
+ name === "my-local" ? localEntry : null,
1479
+ );
1480
+
1481
+ await teleport();
1482
+
1483
+ expect(hatchDockerMock).not.toHaveBeenCalled();
1484
+ expect(hatchLocalMock).not.toHaveBeenCalled();
1485
+ expect(hatchAssistantMock).not.toHaveBeenCalled();
1486
+ expect(retireLocalMock).not.toHaveBeenCalled();
1487
+ expect(retireDockerMock).not.toHaveBeenCalled();
1488
+ expect(localRuntimeExportToGcsMock).not.toHaveBeenCalled();
1489
+ expect(localRuntimeImportFromGcsMock).not.toHaveBeenCalled();
1490
+ });
1491
+
1492
+ test("dry-run with existing platform target runs preflight-from-gcs", async () => {
1493
+ setArgv(
1494
+ "--from",
1495
+ "my-local",
1496
+ "--platform",
1497
+ "existing-platform",
1498
+ "--dry-run",
1499
+ );
1500
+
1501
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1502
+ const platformEntry = makeEntry("existing-platform", {
1891
1503
  cloud: "vellum",
1892
1504
  runtimeUrl: "https://platform.vellum.ai",
1893
1505
  });
1894
- const localEntry = makeEntry("my-local", { cloud: "local" });
1895
1506
 
1896
1507
  findAssistantByNameMock.mockImplementation((name: string) => {
1897
- if (name === "my-platform") return platformEntry;
1898
1508
  if (name === "my-local") return localEntry;
1509
+ if (name === "existing-platform") return platformEntry;
1899
1510
  return null;
1900
1511
  });
1901
1512
 
1902
- // Source (platform) is on 0.7.0, target (local) is unreachable
1903
- fetchCurrentVersionMock.mockImplementation((url: string) => {
1904
- if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
1905
- return Promise.resolve(undefined);
1906
- });
1907
-
1908
- const originalFetch = globalThis.fetch;
1909
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1910
-
1513
+ const restoreFetch = installTrackingFetch();
1911
1514
  try {
1912
1515
  await teleport();
1913
- expect(consoleLogSpy).toHaveBeenCalledWith(
1914
- expect.stringContaining("Teleport complete"),
1516
+ expect(platformImportPreflightFromGcsMock).toHaveBeenCalledWith(
1517
+ "bundle-key-123",
1518
+ "platform-token",
1519
+ "https://platform.vellum.ai",
1915
1520
  );
1521
+ expect(hatchAssistantMock).not.toHaveBeenCalled();
1916
1522
  } finally {
1917
- globalThis.fetch = originalFetch;
1523
+ restoreFetch();
1918
1524
  }
1919
1525
  });
1920
1526
 
1921
- test("pre-release target is behind release source: 0.7.0-local.xxx < 0.7.0", async () => {
1922
- setArgv("--from", "my-platform", "--local", "my-local");
1527
+ test("dry-run against local target fails fast (no preflight-from-gcs runtime endpoint yet)", async () => {
1528
+ setArgv("--from", "my-platform", "--local", "my-local", "--dry-run");
1923
1529
 
1924
1530
  const platformEntry = makeEntry("my-platform", {
1925
1531
  cloud: "vellum",
@@ -1933,64 +1539,107 @@ describe("version guard: block platform→non-platform when target is behind", (
1933
1539
  return null;
1934
1540
  });
1935
1541
 
1936
- // Per semver, pre-release < release for same core version
1937
- fetchCurrentVersionMock.mockImplementation((url: string) => {
1938
- if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
1939
- return Promise.resolve("0.7.0-local.20260411.abc123");
1542
+ platformPollJobStatusMock.mockResolvedValue({
1543
+ jobId: "platform-export-job-1",
1544
+ type: "export",
1545
+ status: "complete",
1546
+ bundleKey: "bundle-key-from-platform",
1940
1547
  });
1941
1548
 
1942
- const originalFetch = globalThis.fetch;
1943
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1944
-
1549
+ const restoreFetch = installTrackingFetch();
1945
1550
  try {
1946
1551
  await expect(teleport()).rejects.toThrow("process.exit:1");
1947
1552
  expect(consoleErrorSpy).toHaveBeenCalledWith(
1948
- expect.stringContaining("is running 0.7.0-local.20260411.abc123"),
1553
+ expect.stringContaining(
1554
+ "--dry-run is not yet supported for local or docker targets",
1555
+ ),
1949
1556
  );
1557
+
1558
+ // Must fail BEFORE any export work — no signed URL request, no platform
1559
+ // export initiation, nothing that costs time or bandwidth.
1560
+ expect(platformRequestSignedUrlMock).not.toHaveBeenCalled();
1561
+ expect(platformInitiateExportMock).not.toHaveBeenCalled();
1562
+ expect(localRuntimeExportToGcsMock).not.toHaveBeenCalled();
1950
1563
  } finally {
1951
- globalThis.fetch = originalFetch;
1564
+ restoreFetch();
1952
1565
  }
1953
1566
  });
1567
+ });
1568
+
1569
+ // ---------------------------------------------------------------------------
1570
+ // Pre-check: block teleport to platform when existing assistant detected
1571
+ // ---------------------------------------------------------------------------
1572
+
1573
+ describe("pre-check: existing platform assistant", () => {
1574
+ test("blocks before any work when pre-check finds existing assistant", async () => {
1575
+ setArgv("--from", "my-local", "--platform");
1576
+
1577
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1578
+ findAssistantByNameMock.mockImplementation((name: string) =>
1579
+ name === "my-local" ? localEntry : null,
1580
+ );
1581
+
1582
+ checkExistingPlatformAssistantMock.mockResolvedValue({
1583
+ id: "existing-platform-id",
1584
+ name: "existing-platform",
1585
+ status: "active",
1586
+ });
1587
+
1588
+ await expect(teleport()).rejects.toThrow("process.exit:1");
1589
+
1590
+ expect(checkExistingPlatformAssistantMock).toHaveBeenCalledWith(
1591
+ "platform-token",
1592
+ undefined,
1593
+ );
1594
+ // No signed-URL or runtime calls
1595
+ expect(platformRequestSignedUrlMock).not.toHaveBeenCalled();
1596
+ expect(localRuntimeExportToGcsMock).not.toHaveBeenCalled();
1597
+ expect(hatchAssistantMock).not.toHaveBeenCalled();
1598
+
1599
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
1600
+ expect.stringContaining("already have a platform assistant"),
1601
+ );
1602
+ });
1603
+ });
1604
+
1605
+ // ---------------------------------------------------------------------------
1606
+ // Version guard
1607
+ // ---------------------------------------------------------------------------
1954
1608
 
1955
- test("blocks platform→docker when docker version is behind platform", async () => {
1956
- setArgv("--from", "my-platform", "--docker", "my-docker");
1609
+ describe("version guard", () => {
1610
+ test("blocks platform→local when local version is behind", async () => {
1611
+ setArgv("--from", "my-platform", "--local", "my-local");
1957
1612
 
1958
1613
  const platformEntry = makeEntry("my-platform", {
1959
1614
  cloud: "vellum",
1960
1615
  runtimeUrl: "https://platform.vellum.ai",
1961
1616
  });
1962
- const dockerEntry = makeEntry("my-docker", { cloud: "docker" });
1617
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1963
1618
 
1964
1619
  findAssistantByNameMock.mockImplementation((name: string) => {
1965
1620
  if (name === "my-platform") return platformEntry;
1966
- if (name === "my-docker") return dockerEntry;
1621
+ if (name === "my-local") return localEntry;
1967
1622
  return null;
1968
1623
  });
1969
1624
 
1970
- // Source (platform) is on 0.7.0, target (docker) is on 0.5.0
1971
1625
  fetchCurrentVersionMock.mockImplementation((url: string) => {
1972
1626
  if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
1973
- return Promise.resolve("0.5.0");
1627
+ return Promise.resolve("0.6.0");
1974
1628
  });
1975
1629
 
1976
- const originalFetch = globalThis.fetch;
1977
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1978
-
1630
+ const restoreFetch = installTrackingFetch();
1979
1631
  try {
1980
1632
  await expect(teleport()).rejects.toThrow("process.exit:1");
1981
1633
  expect(consoleErrorSpy).toHaveBeenCalledWith(
1982
- expect.stringContaining("is running 0.5.0"),
1983
- );
1984
- expect(consoleErrorSpy).toHaveBeenCalledWith(
1985
- expect.stringContaining("Upgrade your docker assistant first"),
1634
+ expect.stringContaining("is running 0.6.0"),
1986
1635
  );
1987
1636
  } finally {
1988
- globalThis.fetch = originalFetch;
1637
+ restoreFetch();
1989
1638
  }
1990
1639
  });
1991
1640
 
1992
- test("dry-run: blocks platform→local when local version is behind", async () => {
1993
- setArgv("--from", "my-platform", "--local", "my-local", "--dry-run");
1641
+ test("allows equal versions", async () => {
1642
+ setArgv("--from", "my-platform", "--local", "my-local");
1994
1643
 
1995
1644
  const platformEntry = makeEntry("my-platform", {
1996
1645
  cloud: "vellum",
@@ -2004,28 +1653,26 @@ describe("version guard: block platform→non-platform when target is behind", (
2004
1653
  return null;
2005
1654
  });
2006
1655
 
2007
- // Source (platform) is on 0.7.0, target (local) is on 0.6.0
2008
- fetchCurrentVersionMock.mockImplementation((url: string) => {
2009
- if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
2010
- return Promise.resolve("0.6.0");
1656
+ fetchCurrentVersionMock.mockResolvedValue("0.7.0");
1657
+ platformPollJobStatusMock.mockResolvedValue({
1658
+ jobId: "platform-export-job-1",
1659
+ type: "export",
1660
+ status: "complete",
1661
+ bundleKey: "b",
2011
1662
  });
2012
1663
 
2013
- const originalFetch = globalThis.fetch;
2014
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2015
-
1664
+ const restoreFetch = installTrackingFetch();
2016
1665
  try {
2017
- await expect(teleport()).rejects.toThrow("process.exit:1");
2018
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2019
- expect.stringContaining("is running 0.6.0"),
1666
+ await teleport();
1667
+ expect(consoleLogSpy).toHaveBeenCalledWith(
1668
+ expect.stringContaining("Teleport complete"),
2020
1669
  );
2021
1670
  } finally {
2022
- globalThis.fetch = originalFetch;
1671
+ restoreFetch();
2023
1672
  }
2024
1673
  });
2025
1674
 
2026
1675
  test("newly hatched target is cleaned up when version check fails", async () => {
2027
- // No existing local target — teleport will hatch a new one, then
2028
- // the version guard should retire it to avoid orphans.
2029
1676
  setArgv("--from", "my-platform", "--local");
2030
1677
 
2031
1678
  const platformEntry = makeEntry("my-platform", {
@@ -2034,12 +1681,10 @@ describe("version guard: block platform→non-platform when target is behind", (
2034
1681
  });
2035
1682
  const newLocalEntry = makeEntry("new-local", { cloud: "local" });
2036
1683
 
2037
- findAssistantByNameMock.mockImplementation((name: string) => {
2038
- if (name === "my-platform") return platformEntry;
2039
- return null;
2040
- });
1684
+ findAssistantByNameMock.mockImplementation((name: string) =>
1685
+ name === "my-platform" ? platformEntry : null,
1686
+ );
2041
1687
 
2042
- // Simulate hatch creating a new local entry
2043
1688
  loadAllAssistantsMock.mockImplementation(() => {
2044
1689
  if (hatchLocalMock.mock.calls.length > 0) {
2045
1690
  return [platformEntry, newLocalEntry];
@@ -2047,70 +1692,42 @@ describe("version guard: block platform→non-platform when target is behind", (
2047
1692
  return [platformEntry];
2048
1693
  });
2049
1694
 
2050
- // Source (platform) is on 0.7.0, newly hatched local is on 0.6.0
1695
+ platformPollJobStatusMock.mockResolvedValue({
1696
+ jobId: "platform-export-job-1",
1697
+ type: "export",
1698
+ status: "complete",
1699
+ bundleKey: "b",
1700
+ });
1701
+
2051
1702
  fetchCurrentVersionMock.mockImplementation((url: string) => {
2052
1703
  if (url === "https://platform.vellum.ai") return Promise.resolve("0.7.0");
2053
1704
  return Promise.resolve("0.6.0");
2054
1705
  });
2055
1706
 
2056
- const originalFetch = globalThis.fetch;
2057
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2058
-
1707
+ const restoreFetch = installTrackingFetch();
2059
1708
  try {
2060
1709
  await expect(teleport()).rejects.toThrow("process.exit:1");
2061
- // Should have hatched a new local assistant
2062
1710
  expect(hatchLocalMock).toHaveBeenCalled();
2063
- // Should retire the orphaned assistant
2064
1711
  expect(retireLocalMock).toHaveBeenCalledWith("new-local", newLocalEntry);
2065
1712
  expect(removeAssistantEntryMock).toHaveBeenCalledWith("new-local");
2066
- expect(consoleErrorSpy).toHaveBeenCalledWith(
2067
- expect.stringContaining("Cleaning up newly hatched assistant"),
2068
- );
2069
- } finally {
2070
- globalThis.fetch = originalFetch;
2071
- }
2072
- });
2073
-
2074
- test("does not check versions for local→platform direction", async () => {
2075
- setArgv("--from", "my-local", "--platform");
2076
-
2077
- const localEntry = makeEntry("my-local", { cloud: "local" });
2078
-
2079
- findAssistantByNameMock.mockImplementation((name: string) => {
2080
- if (name === "my-local") return localEntry;
2081
- return null;
2082
- });
2083
-
2084
- const originalFetch = globalThis.fetch;
2085
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2086
-
2087
- try {
2088
- await teleport();
2089
- // fetchCurrentVersion should NOT be called for local→platform
2090
- expect(fetchCurrentVersionMock).not.toHaveBeenCalled();
2091
- expect(consoleLogSpy).toHaveBeenCalledWith(
2092
- expect.stringContaining("Teleport complete"),
2093
- );
2094
1713
  } finally {
2095
- globalThis.fetch = originalFetch;
1714
+ restoreFetch();
2096
1715
  }
2097
1716
  });
2098
1717
  });
2099
1718
 
2100
1719
  // ---------------------------------------------------------------------------
2101
- // Credential import display tests
1720
+ // Credential import display
2102
1721
  // ---------------------------------------------------------------------------
2103
1722
 
2104
1723
  describe("credential import display", () => {
2105
- test("prints credential counts when credentialsImported is present", async () => {
1724
+ test("prints credential counts when credentialsImported is present (platform target)", async () => {
2106
1725
  setArgv("--from", "my-local", "--platform");
2107
1726
 
2108
1727
  const localEntry = makeEntry("my-local", { cloud: "local" });
2109
-
2110
- findAssistantByNameMock.mockImplementation((name: string) => {
2111
- if (name === "my-local") return localEntry;
2112
- return null;
2113
- });
1728
+ findAssistantByNameMock.mockImplementation((name: string) =>
1729
+ name === "my-local" ? localEntry : null,
1730
+ );
2114
1731
 
2115
1732
  platformImportBundleFromGcsMock.mockResolvedValue({
2116
1733
  statusCode: 200,
@@ -2132,47 +1749,24 @@ describe("credential import display", () => {
2132
1749
  },
2133
1750
  });
2134
1751
 
2135
- const originalFetch = globalThis.fetch;
2136
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2137
-
1752
+ const restoreFetch = installTrackingFetch();
2138
1753
  try {
2139
1754
  await teleport();
2140
1755
  expect(consoleLogSpy).toHaveBeenCalledWith(" Credentials imported: 5/5");
2141
1756
  } finally {
2142
- globalThis.fetch = originalFetch;
1757
+ restoreFetch();
2143
1758
  }
2144
1759
  });
2145
1760
 
2146
- test("does not print credential line when credentialsImported is absent (old server)", async () => {
1761
+ test("does not print credential line when absent", async () => {
2147
1762
  setArgv("--from", "my-local", "--platform");
2148
1763
 
2149
1764
  const localEntry = makeEntry("my-local", { cloud: "local" });
1765
+ findAssistantByNameMock.mockImplementation((name: string) =>
1766
+ name === "my-local" ? localEntry : null,
1767
+ );
2150
1768
 
2151
- findAssistantByNameMock.mockImplementation((name: string) => {
2152
- if (name === "my-local") return localEntry;
2153
- return null;
2154
- });
2155
-
2156
- // Mock the GCS import path (used by default since platformRequestUploadUrl
2157
- // succeeds) with a response that has no credentialsImported field, simulating
2158
- // an older server.
2159
- platformImportBundleFromGcsMock.mockResolvedValue({
2160
- statusCode: 200,
2161
- body: {
2162
- success: true,
2163
- summary: {
2164
- total_files: 3,
2165
- files_created: 2,
2166
- files_overwritten: 1,
2167
- files_skipped: 0,
2168
- backups_created: 1,
2169
- },
2170
- },
2171
- });
2172
-
2173
- const originalFetch = globalThis.fetch;
2174
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2175
-
1769
+ const restoreFetch = installTrackingFetch();
2176
1770
  try {
2177
1771
  await teleport();
2178
1772
  const allLogCalls = consoleLogSpy.mock.calls.map((c: unknown[]) => c[0]);
@@ -2182,98 +1776,7 @@ describe("credential import display", () => {
2182
1776
  );
2183
1777
  expect(credentialLines).toHaveLength(0);
2184
1778
  } finally {
2185
- globalThis.fetch = originalFetch;
2186
- }
2187
- });
2188
-
2189
- test("lists failed credential accounts individually", async () => {
2190
- setArgv("--from", "my-local", "--platform");
2191
-
2192
- const localEntry = makeEntry("my-local", { cloud: "local" });
2193
-
2194
- findAssistantByNameMock.mockImplementation((name: string) => {
2195
- if (name === "my-local") return localEntry;
2196
- return null;
2197
- });
2198
-
2199
- platformImportBundleFromGcsMock.mockResolvedValue({
2200
- statusCode: 200,
2201
- body: {
2202
- success: true,
2203
- summary: {
2204
- total_files: 3,
2205
- files_created: 2,
2206
- files_overwritten: 1,
2207
- files_skipped: 0,
2208
- backups_created: 1,
2209
- },
2210
- credentialsImported: {
2211
- total: 5,
2212
- succeeded: 3,
2213
- failed: 2,
2214
- failedAccounts: ["google:user@example.com", "github:octocat"],
2215
- },
2216
- },
2217
- });
2218
-
2219
- const originalFetch = globalThis.fetch;
2220
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2221
-
2222
- try {
2223
- await teleport();
2224
- expect(consoleLogSpy).toHaveBeenCalledWith(" Credentials imported: 3/5");
2225
- expect(consoleLogSpy).toHaveBeenCalledWith(" Credentials failed: 2");
2226
- expect(consoleLogSpy).toHaveBeenCalledWith(
2227
- " - google:user@example.com",
2228
- );
2229
- expect(consoleLogSpy).toHaveBeenCalledWith(" - github:octocat");
2230
- } finally {
2231
- globalThis.fetch = originalFetch;
2232
- }
2233
- });
2234
-
2235
- test("shows platform credentials skipped count", async () => {
2236
- setArgv("--from", "my-local", "--platform");
2237
-
2238
- const localEntry = makeEntry("my-local", { cloud: "local" });
2239
-
2240
- findAssistantByNameMock.mockImplementation((name: string) => {
2241
- if (name === "my-local") return localEntry;
2242
- return null;
2243
- });
2244
-
2245
- platformImportBundleFromGcsMock.mockResolvedValue({
2246
- statusCode: 200,
2247
- body: {
2248
- success: true,
2249
- summary: {
2250
- total_files: 3,
2251
- files_created: 2,
2252
- files_overwritten: 1,
2253
- files_skipped: 0,
2254
- backups_created: 1,
2255
- },
2256
- credentialsImported: {
2257
- total: 5,
2258
- succeeded: 5,
2259
- failed: 0,
2260
- failedAccounts: [],
2261
- skippedPlatform: 3,
2262
- },
2263
- },
2264
- });
2265
-
2266
- const originalFetch = globalThis.fetch;
2267
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2268
-
2269
- try {
2270
- await teleport();
2271
- expect(consoleLogSpy).toHaveBeenCalledWith(" Credentials imported: 5/5");
2272
- expect(consoleLogSpy).toHaveBeenCalledWith(
2273
- " Platform credentials skipped: 3",
2274
- );
2275
- } finally {
2276
- globalThis.fetch = originalFetch;
1779
+ restoreFetch();
2277
1780
  }
2278
1781
  });
2279
1782
  });
@@ -2282,7 +1785,7 @@ describe("credential import display", () => {
2282
1785
  // Platform credential injection after teleport
2283
1786
  // ---------------------------------------------------------------------------
2284
1787
 
2285
- describe("teleport platform credential injection", () => {
1788
+ describe("platform credential injection", () => {
2286
1789
  test("platform→local teleport calls ensureSelfHostedLocalRegistration and injectCredentialsIntoAssistant", async () => {
2287
1790
  setArgv("--from", "my-platform", "--local", "my-local");
2288
1791
 
@@ -2301,9 +1804,14 @@ describe("teleport platform credential injection", () => {
2301
1804
  return null;
2302
1805
  });
2303
1806
 
2304
- const originalFetch = globalThis.fetch;
2305
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1807
+ platformPollJobStatusMock.mockResolvedValue({
1808
+ jobId: "platform-export-job-1",
1809
+ type: "export",
1810
+ status: "complete",
1811
+ bundleKey: "bundle-xyz",
1812
+ });
2306
1813
 
1814
+ const restoreFetch = installTrackingFetch();
2307
1815
  try {
2308
1816
  await teleport();
2309
1817
  expect(ensureSelfHostedLocalRegistrationMock).toHaveBeenCalledWith(
@@ -2323,76 +1831,87 @@ describe("teleport platform credential injection", () => {
2323
1831
  userId: "user-1",
2324
1832
  webhookSecret: "webhook-secret-123",
2325
1833
  });
2326
- expect(consoleLogSpy).toHaveBeenCalledWith(
2327
- " Platform credentials injected.",
2328
- );
2329
1834
  } finally {
2330
- globalThis.fetch = originalFetch;
1835
+ restoreFetch();
2331
1836
  }
2332
1837
  });
2333
1838
 
2334
- test("platform→docker teleport also calls credential injection", async () => {
2335
- setArgv("--from", "my-platform", "--docker", "my-docker");
1839
+ test("local→docker teleport does NOT call credential injection", async () => {
1840
+ setArgv("--from", "my-local", "--docker", "my-docker");
2336
1841
 
2337
- const platformEntry = makeEntry("my-platform", {
2338
- cloud: "vellum",
2339
- runtimeUrl: "https://platform.vellum.ai",
2340
- });
1842
+ const localEntry = makeEntry("my-local", { cloud: "local" });
2341
1843
  const dockerEntry = makeEntry("my-docker", {
2342
1844
  cloud: "docker",
2343
1845
  runtimeUrl: "http://localhost:8821",
2344
- bearerToken: "docker-bearer",
2345
1846
  });
2346
1847
 
2347
1848
  findAssistantByNameMock.mockImplementation((name: string) => {
2348
- if (name === "my-platform") return platformEntry;
1849
+ if (name === "my-local") return localEntry;
2349
1850
  if (name === "my-docker") return dockerEntry;
2350
1851
  return null;
2351
1852
  });
2352
1853
 
2353
- const originalFetch = globalThis.fetch;
2354
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
2355
-
1854
+ const restoreFetch = installTrackingFetch();
2356
1855
  try {
2357
1856
  await teleport();
2358
- expect(ensureSelfHostedLocalRegistrationMock).toHaveBeenCalled();
2359
- expect(injectCredentialsIntoAssistantMock).toHaveBeenCalled();
2360
- expect(consoleLogSpy).toHaveBeenCalledWith(
2361
- " Platform credentials injected.",
2362
- );
1857
+ expect(ensureSelfHostedLocalRegistrationMock).not.toHaveBeenCalled();
1858
+ expect(injectCredentialsIntoAssistantMock).not.toHaveBeenCalled();
2363
1859
  } finally {
2364
- globalThis.fetch = originalFetch;
1860
+ restoreFetch();
2365
1861
  }
2366
1862
  });
1863
+ });
2367
1864
 
2368
- test("local→local teleport does NOT call credential injection", async () => {
2369
- setArgv("--from", "my-local", "--docker", "my-docker");
1865
+ // ---------------------------------------------------------------------------
1866
+ // Auth / transient-error resilience (Codex P1/P2 regression guards)
1867
+ // ---------------------------------------------------------------------------
1868
+
1869
+ describe("auth + transient-error resilience", () => {
1870
+ test("runtime 401 on export kickoff triggers token refresh and retry", async () => {
1871
+ setArgv("--from", "my-local", "--platform");
2370
1872
 
2371
1873
  const localEntry = makeEntry("my-local", { cloud: "local" });
2372
- const dockerEntry = makeEntry("my-docker", {
2373
- cloud: "docker",
2374
- runtimeUrl: "http://localhost:8821",
2375
- });
1874
+ findAssistantByNameMock.mockImplementation((name: string) =>
1875
+ name === "my-local" ? localEntry : null,
1876
+ );
2376
1877
 
2377
- findAssistantByNameMock.mockImplementation((name: string) => {
2378
- if (name === "my-local") return localEntry;
2379
- if (name === "my-docker") return dockerEntry;
2380
- return null;
1878
+ // First kickoff call fails with 401, second succeeds.
1879
+ localRuntimeExportToGcsMock.mockImplementationOnce(async () => {
1880
+ throw new Error("Local runtime export-to-gcs failed (401): stale token");
2381
1881
  });
1882
+ localRuntimeExportToGcsMock.mockImplementationOnce(async () => ({
1883
+ jobId: "local-export-job-after-refresh",
1884
+ }));
2382
1885
 
2383
- const originalFetch = globalThis.fetch;
2384
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1886
+ // Ensure the refresh path returns a distinguishable token.
1887
+ leaseGuardianTokenMock.mockResolvedValueOnce({
1888
+ accessToken: "refreshed-token",
1889
+ accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
1890
+ } as unknown as Awaited<
1891
+ ReturnType<typeof guardianToken.leaseGuardianToken>
1892
+ >);
2385
1893
 
1894
+ const restoreFetch = installTrackingFetch();
2386
1895
  try {
2387
1896
  await teleport();
2388
- expect(ensureSelfHostedLocalRegistrationMock).not.toHaveBeenCalled();
2389
- expect(injectCredentialsIntoAssistantMock).not.toHaveBeenCalled();
2390
1897
  } finally {
2391
- globalThis.fetch = originalFetch;
1898
+ restoreFetch();
2392
1899
  }
1900
+
1901
+ // Kickoff was attempted twice: once with the cached token, once after
1902
+ // a forced refresh lease.
1903
+ expect(localRuntimeExportToGcsMock).toHaveBeenCalledTimes(2);
1904
+
1905
+ const firstTokenArg = localRuntimeExportToGcsMock.mock.calls[0][1];
1906
+ const secondTokenArg = localRuntimeExportToGcsMock.mock.calls[1][1];
1907
+ expect(firstTokenArg).toBe("local-token");
1908
+ expect(secondTokenArg).toBe("refreshed-token");
1909
+
1910
+ // A fresh lease was requested exactly once (the forceRefresh path).
1911
+ expect(leaseGuardianTokenMock).toHaveBeenCalledTimes(1);
2393
1912
  });
2394
1913
 
2395
- test("if user is not logged in (readPlatformToken returns null), injection is skipped gracefully", async () => {
1914
+ test("runtime 401 on import kickoff triggers token refresh and retry", async () => {
2396
1915
  setArgv("--from", "my-platform", "--local", "my-local");
2397
1916
 
2398
1917
  const platformEntry = makeEntry("my-platform", {
@@ -2407,36 +1926,181 @@ describe("teleport platform credential injection", () => {
2407
1926
  return null;
2408
1927
  });
2409
1928
 
2410
- // The readPlatformToken mock is called twice during teleport:
2411
- // once during the platform export flow and once during credential injection.
2412
- // We need the first call to return a token (for the export to work)
2413
- // and the second call to return null (to test the skip path).
2414
- let callCount = 0;
2415
- readPlatformTokenMock.mockImplementation(() => {
2416
- callCount++;
2417
- // The platform export path calls readPlatformToken first;
2418
- // the credential injection helper calls it after import.
2419
- // Return null only on the last call (the injection helper).
2420
- if (callCount <= 1) return "platform-token";
2421
- return null;
1929
+ platformPollJobStatusMock.mockResolvedValue({
1930
+ jobId: "platform-export-job-1",
1931
+ type: "export",
1932
+ status: "complete",
1933
+ bundleKey: "b-key",
2422
1934
  });
2423
1935
 
2424
- const originalFetch = globalThis.fetch;
2425
- globalThis.fetch = createFetchMock() as unknown as typeof globalThis.fetch;
1936
+ localRuntimeImportFromGcsMock.mockImplementationOnce(async () => {
1937
+ throw new Error(
1938
+ "Local runtime import-from-gcs failed (401): stale token",
1939
+ );
1940
+ });
1941
+ localRuntimeImportFromGcsMock.mockImplementationOnce(async () => ({
1942
+ jobId: "local-import-after-refresh",
1943
+ }));
2426
1944
 
1945
+ leaseGuardianTokenMock.mockResolvedValueOnce({
1946
+ accessToken: "refreshed-import-token",
1947
+ accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
1948
+ } as unknown as Awaited<
1949
+ ReturnType<typeof guardianToken.leaseGuardianToken>
1950
+ >);
1951
+
1952
+ const restoreFetch = installTrackingFetch();
2427
1953
  try {
2428
1954
  await teleport();
2429
- expect(ensureSelfHostedLocalRegistrationMock).not.toHaveBeenCalled();
2430
- expect(injectCredentialsIntoAssistantMock).not.toHaveBeenCalled();
2431
- expect(consoleLogSpy).toHaveBeenCalledWith(
2432
- " Skipped platform credential injection (not logged in).",
1955
+ } finally {
1956
+ restoreFetch();
1957
+ }
1958
+
1959
+ expect(localRuntimeImportFromGcsMock).toHaveBeenCalledTimes(2);
1960
+ expect(localRuntimeImportFromGcsMock.mock.calls[0][1]).toBe("local-token");
1961
+ expect(localRuntimeImportFromGcsMock.mock.calls[1][1]).toBe(
1962
+ "refreshed-import-token",
1963
+ );
1964
+ });
1965
+
1966
+ test("runtime non-401 errors do NOT trigger token refresh", async () => {
1967
+ setArgv("--from", "my-local", "--platform");
1968
+
1969
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1970
+ findAssistantByNameMock.mockImplementation((name: string) =>
1971
+ name === "my-local" ? localEntry : null,
1972
+ );
1973
+
1974
+ localRuntimeExportToGcsMock.mockRejectedValue(
1975
+ new Error("Local runtime export-to-gcs failed (500): boom"),
1976
+ );
1977
+
1978
+ const restoreFetch = installTrackingFetch();
1979
+ try {
1980
+ await expect(teleport()).rejects.toThrow(/500.*boom/);
1981
+ } finally {
1982
+ restoreFetch();
1983
+ }
1984
+
1985
+ // One attempt, no forced-refresh lease.
1986
+ expect(localRuntimeExportToGcsMock).toHaveBeenCalledTimes(1);
1987
+ expect(leaseGuardianTokenMock).not.toHaveBeenCalled();
1988
+ });
1989
+
1990
+ test("runtime poll 401 mid-migration triggers forceRefresh lease and completes", async () => {
1991
+ setArgv("--from", "my-local", "--platform");
1992
+
1993
+ const localEntry = makeEntry("my-local", { cloud: "local" });
1994
+ findAssistantByNameMock.mockImplementation((name: string) =>
1995
+ name === "my-local" ? localEntry : null,
1996
+ );
1997
+
1998
+ // Export kickoff succeeds with the cached "local-token".
1999
+ // During polling, the first status check fails with 401 (token expired
2000
+ // mid-migration), the poll loop calls refreshOn401 → leaseGuardianToken,
2001
+ // then the next poll succeeds with the new token.
2002
+ const tokensSeenByPoll: string[] = [];
2003
+ localRuntimePollJobStatusMock.mockImplementation(
2004
+ async (_runtimeUrl, token, jobId) => {
2005
+ tokensSeenByPoll.push(token);
2006
+ if (tokensSeenByPoll.length === 1) {
2007
+ throw new Error("Local job status check failed: 401 Unauthorized");
2008
+ }
2009
+ return {
2010
+ jobId,
2011
+ type: "export" as const,
2012
+ status: "complete" as const,
2013
+ result: undefined,
2014
+ };
2015
+ },
2016
+ );
2017
+
2018
+ leaseGuardianTokenMock.mockResolvedValueOnce({
2019
+ accessToken: "poll-refreshed-token",
2020
+ accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
2021
+ } as unknown as Awaited<
2022
+ ReturnType<typeof guardianToken.leaseGuardianToken>
2023
+ >);
2024
+
2025
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
2026
+ const restoreFetch = installTrackingFetch();
2027
+ try {
2028
+ await teleport();
2029
+
2030
+ // The first poll used the cached token; the second (post-refresh) poll
2031
+ // used the freshly leased one.
2032
+ expect(tokensSeenByPoll.length).toBeGreaterThanOrEqual(2);
2033
+ expect(tokensSeenByPoll[0]).toBe("local-token");
2034
+ expect(tokensSeenByPoll[1]).toBe("poll-refreshed-token");
2035
+
2036
+ // leaseGuardianToken was invoked for the forceRefresh path.
2037
+ expect(leaseGuardianTokenMock).toHaveBeenCalledTimes(1);
2038
+
2039
+ // The 401 branch emits its own warning — distinct from the generic
2040
+ // transient-error warning — so this asserts the refresh path fired.
2041
+ expect(warnSpy).toHaveBeenCalledWith(
2042
+ expect.stringContaining("refreshing auth"),
2433
2043
  );
2434
- // Teleport still succeeds
2435
- expect(consoleLogSpy).toHaveBeenCalledWith(
2436
- expect.stringContaining("Teleport complete"),
2044
+ } finally {
2045
+ restoreFetch();
2046
+ warnSpy.mockRestore();
2047
+ }
2048
+ });
2049
+
2050
+ test("transient poll error does not abort teleport (job completes after retry)", async () => {
2051
+ setArgv("--from", "my-local", "--platform");
2052
+
2053
+ const localEntry = makeEntry("my-local", { cloud: "local" });
2054
+ findAssistantByNameMock.mockImplementation((name: string) =>
2055
+ name === "my-local" ? localEntry : null,
2056
+ );
2057
+
2058
+ // Throw once with a 503 (transient), then succeed with terminal complete.
2059
+ let pollCalls = 0;
2060
+ localRuntimePollJobStatusMock.mockImplementation(
2061
+ async (_runtimeUrl, _token, jobId) => {
2062
+ pollCalls += 1;
2063
+ if (pollCalls === 1) {
2064
+ throw new Error(
2065
+ "Local job status check failed: 503 Service Unavailable",
2066
+ );
2067
+ }
2068
+ return {
2069
+ jobId,
2070
+ type: "export" as const,
2071
+ status: "complete" as const,
2072
+ result: undefined,
2073
+ };
2074
+ },
2075
+ );
2076
+
2077
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
2078
+ const restoreFetch = installTrackingFetch();
2079
+ try {
2080
+ // Should NOT reject — a single transient 503 is retried, not fatal.
2081
+ await teleport();
2082
+ expect(pollCalls).toBeGreaterThanOrEqual(2);
2083
+ expect(warnSpy).toHaveBeenCalledWith(
2084
+ expect.stringContaining("polling failed, retrying"),
2437
2085
  );
2438
2086
  } finally {
2439
- globalThis.fetch = originalFetch;
2087
+ restoreFetch();
2088
+ warnSpy.mockRestore();
2440
2089
  }
2441
2090
  });
2442
2091
  });
2092
+
2093
+ // ---------------------------------------------------------------------------
2094
+ // Misc: legacy --to deprecation
2095
+ // ---------------------------------------------------------------------------
2096
+
2097
+ describe("misc", () => {
2098
+ test("legacy --to flag shows deprecation message", async () => {
2099
+ setArgv("--from", "source", "--to", "target");
2100
+
2101
+ await expect(teleport()).rejects.toThrow("process.exit:1");
2102
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
2103
+ expect.stringContaining("--to is deprecated"),
2104
+ );
2105
+ });
2106
+ });