@openparachute/hub 0.5.14-rc.9 → 0.6.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 (83) hide show
  1. package/README.md +23 -0
  2. package/package.json +7 -3
  3. package/src/__tests__/account-home-ui.test.ts +251 -15
  4. package/src/__tests__/account-vault-token.test.ts +355 -0
  5. package/src/__tests__/admin-vaults.test.ts +70 -4
  6. package/src/__tests__/api-mint-token.test.ts +30 -21
  7. package/src/__tests__/api-modules-ops.test.ts +45 -0
  8. package/src/__tests__/api-users.test.ts +7 -2
  9. package/src/__tests__/auth.test.ts +157 -30
  10. package/src/__tests__/cli.test.ts +44 -5
  11. package/src/__tests__/expose-2fa-warning.test.ts +31 -17
  12. package/src/__tests__/expose-auth-preflight.test.ts +71 -72
  13. package/src/__tests__/expose-cloudflare.test.ts +482 -14
  14. package/src/__tests__/expose.test.ts +52 -2
  15. package/src/__tests__/hub-server.test.ts +97 -0
  16. package/src/__tests__/hub.test.ts +85 -6
  17. package/src/__tests__/init.test.ts +102 -1
  18. package/src/__tests__/lifecycle.test.ts +464 -2
  19. package/src/__tests__/oauth-handlers.test.ts +1252 -83
  20. package/src/__tests__/oauth-ui.test.ts +12 -1
  21. package/src/__tests__/operator-token-issuer-self-heal.test.ts +412 -0
  22. package/src/__tests__/resource-binding.test.ts +97 -0
  23. package/src/__tests__/scope-explanations.test.ts +41 -12
  24. package/src/__tests__/services-manifest.test.ts +122 -4
  25. package/src/__tests__/setup-wizard.test.ts +335 -15
  26. package/src/__tests__/status.test.ts +36 -0
  27. package/src/__tests__/two-factor-flow.test.ts +602 -0
  28. package/src/__tests__/two-factor.test.ts +183 -0
  29. package/src/__tests__/upgrade.test.ts +78 -1
  30. package/src/__tests__/users.test.ts +68 -0
  31. package/src/__tests__/vault-auth-status.test.ts +47 -6
  32. package/src/__tests__/vault-hub-origin-env.test.ts +263 -0
  33. package/src/account-home-ui.ts +488 -38
  34. package/src/account-vault-token.ts +282 -0
  35. package/src/admin-handlers.ts +159 -4
  36. package/src/admin-login-ui.ts +49 -5
  37. package/src/admin-vaults.ts +48 -15
  38. package/src/api-account.ts +14 -0
  39. package/src/api-modules-ops.ts +49 -11
  40. package/src/api-users.ts +29 -3
  41. package/src/cli.ts +26 -21
  42. package/src/clients.ts +18 -6
  43. package/src/cloudflare/config.ts +10 -4
  44. package/src/cloudflare/detect.ts +39 -44
  45. package/src/commands/auth.ts +165 -24
  46. package/src/commands/expose-2fa-warning.ts +34 -32
  47. package/src/commands/expose-auth-preflight.ts +89 -78
  48. package/src/commands/expose-cloudflare.ts +370 -12
  49. package/src/commands/expose.ts +8 -0
  50. package/src/commands/init.ts +33 -2
  51. package/src/commands/lifecycle.ts +386 -17
  52. package/src/commands/status.ts +22 -0
  53. package/src/commands/upgrade.ts +55 -11
  54. package/src/commands/wizard.ts +8 -4
  55. package/src/env-file.ts +10 -0
  56. package/src/help.ts +3 -1
  57. package/src/hub-db.ts +39 -1
  58. package/src/hub-server.ts +52 -0
  59. package/src/hub.ts +82 -14
  60. package/src/oauth-handlers.ts +298 -21
  61. package/src/oauth-ui.ts +10 -0
  62. package/src/operator-token.ts +151 -0
  63. package/src/pending-login.ts +116 -0
  64. package/src/rate-limit.ts +51 -0
  65. package/src/resource-binding.ts +134 -0
  66. package/src/scope-explanations.ts +46 -18
  67. package/src/services-manifest.ts +112 -0
  68. package/src/setup-wizard.ts +77 -7
  69. package/src/tailscale/run.ts +28 -11
  70. package/src/totp.ts +201 -0
  71. package/src/two-factor-handlers.ts +287 -0
  72. package/src/two-factor-store.ts +181 -0
  73. package/src/two-factor-ui.ts +462 -0
  74. package/src/users.ts +58 -0
  75. package/src/vault/auth-status.ts +71 -19
  76. package/src/vault-hub-origin-env.ts +163 -0
  77. package/web/ui/dist/assets/index-BiBlvEaj.css +1 -0
  78. package/web/ui/dist/assets/index-CIN3mnmf.js +61 -0
  79. package/web/ui/dist/index.html +2 -2
  80. package/src/__tests__/vault-tokens-create-interactive.test.ts +0 -183
  81. package/src/commands/vault-tokens-create-interactive.ts +0 -143
  82. package/web/ui/dist/assets/index-7DtAXz7y.css +0 -1
  83. package/web/ui/dist/assets/index-tRmPbbC7.js +0 -61
@@ -23,6 +23,7 @@ import {
23
23
  getDefaultOperationsRegistry,
24
24
  } from "../api-modules-ops.ts";
25
25
  import { CSRF_COOKIE_NAME, CSRF_FIELD_NAME } from "../csrf.ts";
26
+ import { type ExposeState, readExposeState, writeExposeState } from "../expose-state.ts";
26
27
  import { hubDbPath, openHubDb } from "../hub-db.ts";
27
28
  import { hubFetch } from "../hub-server.ts";
28
29
  import { getSetting, setSetting } from "../hub-settings.ts";
@@ -44,6 +45,20 @@ import { createUser, getUserByUsername, userCount } from "../users.ts";
44
45
  interface Harness {
45
46
  dir: string;
46
47
  manifestPath: string;
48
+ /**
49
+ * Hermetic expose-state reader scoped to the harness's tmp dir. The
50
+ * production `readExposeState()` defaults to the operator's real
51
+ * `~/.parachute/expose-state.json` (a module-load constant), so a
52
+ * wizard test that omits an injected reader would auto-seed
53
+ * `setup_expose_mode` from the developer's LIVE exposure (hub#406) and
54
+ * flip expose-step assertions nondeterministically. Threading this
55
+ * harness reader keeps every wizard test isolated from the real
56
+ * filesystem — same isolation the harness already gives DB + manifest.
57
+ * Defaults to "no live exposure" (the tmp file doesn't exist) unless a
58
+ * test writes one via `writeExposeState(state, h.exposeStatePath)`.
59
+ */
60
+ exposeStatePath: string;
61
+ readExposeStateFn: () => ExposeState | undefined;
47
62
  cleanup: () => void;
48
63
  }
49
64
 
@@ -52,9 +67,12 @@ function makeHarness(): Harness {
52
67
  writeFileSync(join(dir, "hub.html"), "<html>discovery</html>");
53
68
  const manifestPath = join(dir, "services.json");
54
69
  writeManifest({ services: [] }, manifestPath);
70
+ const exposeStatePath = join(dir, "expose-state.json");
55
71
  return {
56
72
  dir,
57
73
  manifestPath,
74
+ exposeStatePath,
75
+ readExposeStateFn: () => readExposeState(exposeStatePath),
58
76
  cleanup: () => rmSync(dir, { recursive: true, force: true }),
59
77
  };
60
78
  }
@@ -124,7 +142,11 @@ describe("deriveWizardState", () => {
124
142
  test("welcome step when no admin and no vault", () => {
125
143
  const db = openHubDb(hubDbPath(h.dir));
126
144
  try {
127
- const s = deriveWizardState({ db, manifestPath: h.manifestPath });
145
+ const s = deriveWizardState({
146
+ db,
147
+ manifestPath: h.manifestPath,
148
+ readExposeStateFn: h.readExposeStateFn,
149
+ });
128
150
  expect(s.step).toBe("welcome");
129
151
  expect(s.hasAdmin).toBe(false);
130
152
  expect(s.hasVault).toBe(false);
@@ -137,7 +159,11 @@ describe("deriveWizardState", () => {
137
159
  const db = openHubDb(hubDbPath(h.dir));
138
160
  try {
139
161
  await createUser(db, "owner", "pw");
140
- const s = deriveWizardState({ db, manifestPath: h.manifestPath });
162
+ const s = deriveWizardState({
163
+ db,
164
+ manifestPath: h.manifestPath,
165
+ readExposeStateFn: h.readExposeStateFn,
166
+ });
141
167
  expect(s.step).toBe("vault");
142
168
  expect(s.hasAdmin).toBe(true);
143
169
  expect(s.hasVault).toBe(false);
@@ -164,7 +190,11 @@ describe("deriveWizardState", () => {
164
190
  },
165
191
  h.manifestPath,
166
192
  );
167
- const s = deriveWizardState({ db, manifestPath: h.manifestPath });
193
+ const s = deriveWizardState({
194
+ db,
195
+ manifestPath: h.manifestPath,
196
+ readExposeStateFn: h.readExposeStateFn,
197
+ });
168
198
  expect(s.step).toBe("expose");
169
199
  expect(s.hasAdmin).toBe(true);
170
200
  expect(s.hasVault).toBe(true);
@@ -201,7 +231,12 @@ describe("deriveWizardState", () => {
201
231
  );
202
232
  // Simulate Render env. detectAutoExposeMode reads RENDER_EXTERNAL_URL.
203
233
  const renderEnv = { RENDER_EXTERNAL_URL: "https://parachute-hub.onrender.com" };
204
- const s = deriveWizardState({ db, manifestPath: h.manifestPath, env: renderEnv });
234
+ const s = deriveWizardState({
235
+ db,
236
+ manifestPath: h.manifestPath,
237
+ env: renderEnv,
238
+ readExposeStateFn: h.readExposeStateFn,
239
+ });
205
240
  expect(s.step).toBe("done");
206
241
  expect(s.hasExposeMode).toBe(true);
207
242
  } finally {
@@ -227,7 +262,12 @@ describe("deriveWizardState", () => {
227
262
  },
228
263
  h.manifestPath,
229
264
  );
230
- const s = deriveWizardState({ db, manifestPath: h.manifestPath, env: {} });
265
+ const s = deriveWizardState({
266
+ db,
267
+ manifestPath: h.manifestPath,
268
+ env: {},
269
+ readExposeStateFn: h.readExposeStateFn,
270
+ });
231
271
  // Local install path — the operator still gets to choose
232
272
  expect(s.step).toBe("expose");
233
273
  expect(s.hasExposeMode).toBe(false);
@@ -236,6 +276,188 @@ describe("deriveWizardState", () => {
236
276
  }
237
277
  });
238
278
 
279
+ test("auto-seeds expose mode from a live `parachute expose tailnet` (hub#406 team-onboarding bug)", async () => {
280
+ // Team-onboarding bug: an operator ran `parachute expose tailnet`
281
+ // BEFORE opening the wizard. That writes expose-state.json
282
+ // (layer=tailnet) but never the `setup_expose_mode` hub_setting —
283
+ // the two are orthogonal axes. Pre-fix, the wizard consulted only
284
+ // the setting and re-rendered "How will this hub be reached?" though
285
+ // tailnet was already live. deriveWizardState now reads the live
286
+ // exposure layer and auto-seeds the setting, so the expose step is
287
+ // treated as satisfied and the wizard advances to done.
288
+ const db = openHubDb(hubDbPath(h.dir));
289
+ try {
290
+ await createUser(db, "owner", "pw");
291
+ writeManifest(
292
+ {
293
+ services: [
294
+ {
295
+ name: "parachute-vault",
296
+ version: "0.1.0",
297
+ port: 1940,
298
+ paths: ["/vault/default"],
299
+ health: "/health",
300
+ },
301
+ ],
302
+ },
303
+ h.manifestPath,
304
+ );
305
+ // Simulate `parachute expose tailnet`: write a real expose-state
306
+ // file (round-trips through readExposeState's validator) into the
307
+ // harness tmp path. No env signal (not Render/Fly), no setting.
308
+ writeExposeState(
309
+ {
310
+ version: 1,
311
+ layer: "tailnet",
312
+ mode: "path",
313
+ canonicalFqdn: "my-mac.tailnet-name.ts.net",
314
+ port: 1939,
315
+ funnel: false,
316
+ entries: [],
317
+ },
318
+ h.exposeStatePath,
319
+ );
320
+ const s = deriveWizardState({
321
+ db,
322
+ manifestPath: h.manifestPath,
323
+ env: {},
324
+ readExposeStateFn: h.readExposeStateFn,
325
+ });
326
+ expect(s.step).toBe("done");
327
+ expect(s.hasExposeMode).toBe(true);
328
+ // The setting was auto-seeded from the live exposure layer.
329
+ expect(getSetting(db, "setup_expose_mode")).toBe("tailnet");
330
+ } finally {
331
+ db.close();
332
+ }
333
+ });
334
+
335
+ test("auto-seeds expose mode = public from a live public exposure", async () => {
336
+ const db = openHubDb(hubDbPath(h.dir));
337
+ try {
338
+ await createUser(db, "owner", "pw");
339
+ writeManifest(
340
+ {
341
+ services: [
342
+ {
343
+ name: "parachute-vault",
344
+ version: "0.1.0",
345
+ port: 1940,
346
+ paths: ["/vault/default"],
347
+ health: "/health",
348
+ },
349
+ ],
350
+ },
351
+ h.manifestPath,
352
+ );
353
+ writeExposeState(
354
+ {
355
+ version: 1,
356
+ layer: "public",
357
+ mode: "path",
358
+ canonicalFqdn: "hub.example.com",
359
+ port: 1939,
360
+ funnel: true,
361
+ entries: [],
362
+ },
363
+ h.exposeStatePath,
364
+ );
365
+ const s = deriveWizardState({
366
+ db,
367
+ manifestPath: h.manifestPath,
368
+ env: {},
369
+ readExposeStateFn: h.readExposeStateFn,
370
+ });
371
+ expect(s.step).toBe("done");
372
+ expect(s.hasExposeMode).toBe(true);
373
+ expect(getSetting(db, "setup_expose_mode")).toBe("public");
374
+ } finally {
375
+ db.close();
376
+ }
377
+ });
378
+
379
+ test("still asks the expose step when no live exposure + no setting (unchanged)", async () => {
380
+ const db = openHubDb(hubDbPath(h.dir));
381
+ try {
382
+ await createUser(db, "owner", "pw");
383
+ writeManifest(
384
+ {
385
+ services: [
386
+ {
387
+ name: "parachute-vault",
388
+ version: "0.1.0",
389
+ port: 1940,
390
+ paths: ["/vault/default"],
391
+ health: "/health",
392
+ },
393
+ ],
394
+ },
395
+ h.manifestPath,
396
+ );
397
+ // No env signal, no expose-state file written (reader returns
398
+ // undefined), no setting → the operator still gets the expose step.
399
+ const s = deriveWizardState({
400
+ db,
401
+ manifestPath: h.manifestPath,
402
+ env: {},
403
+ readExposeStateFn: h.readExposeStateFn,
404
+ });
405
+ expect(s.step).toBe("expose");
406
+ expect(s.hasExposeMode).toBe(false);
407
+ expect(getSetting(db, "setup_expose_mode")).toBeUndefined();
408
+ } finally {
409
+ db.close();
410
+ }
411
+ });
412
+
413
+ test("an explicit setup_expose_mode wins over a live exposure (no clobber)", async () => {
414
+ // If the operator already answered the expose step (or it was seeded
415
+ // by a prior call), a later live-exposure read must not overwrite the
416
+ // recorded answer. Guards the `=== undefined` gate.
417
+ const db = openHubDb(hubDbPath(h.dir));
418
+ try {
419
+ await createUser(db, "owner", "pw");
420
+ writeManifest(
421
+ {
422
+ services: [
423
+ {
424
+ name: "parachute-vault",
425
+ version: "0.1.0",
426
+ port: 1940,
427
+ paths: ["/vault/default"],
428
+ health: "/health",
429
+ },
430
+ ],
431
+ },
432
+ h.manifestPath,
433
+ );
434
+ setSetting(db, "setup_expose_mode", "localhost");
435
+ writeExposeState(
436
+ {
437
+ version: 1,
438
+ layer: "public",
439
+ mode: "path",
440
+ canonicalFqdn: "hub.example.com",
441
+ port: 1939,
442
+ funnel: true,
443
+ entries: [],
444
+ },
445
+ h.exposeStatePath,
446
+ );
447
+ const s = deriveWizardState({
448
+ db,
449
+ manifestPath: h.manifestPath,
450
+ env: {},
451
+ readExposeStateFn: h.readExposeStateFn,
452
+ });
453
+ expect(s.step).toBe("done");
454
+ // Recorded answer is preserved, not overwritten by the live layer.
455
+ expect(getSetting(db, "setup_expose_mode")).toBe("localhost");
456
+ } finally {
457
+ db.close();
458
+ }
459
+ });
460
+
239
461
  test("done step once admin + vault + expose mode all exist", async () => {
240
462
  const db = openHubDb(hubDbPath(h.dir));
241
463
  try {
@@ -255,7 +477,11 @@ describe("deriveWizardState", () => {
255
477
  h.manifestPath,
256
478
  );
257
479
  setSetting(db, "setup_expose_mode", "localhost");
258
- const s = deriveWizardState({ db, manifestPath: h.manifestPath });
480
+ const s = deriveWizardState({
481
+ db,
482
+ manifestPath: h.manifestPath,
483
+ readExposeStateFn: h.readExposeStateFn,
484
+ });
259
485
  expect(s.step).toBe("done");
260
486
  expect(s.hasAdmin).toBe(true);
261
487
  expect(s.hasVault).toBe(true);
@@ -283,6 +509,7 @@ describe("handleSetupGet", () => {
283
509
  db,
284
510
  manifestPath: h.manifestPath,
285
511
  configDir: h.dir,
512
+ readExposeStateFn: h.readExposeStateFn,
286
513
  issuer: "https://hub.example",
287
514
  registry: getDefaultOperationsRegistry(),
288
515
  });
@@ -304,6 +531,7 @@ describe("handleSetupGet", () => {
304
531
  db,
305
532
  manifestPath: h.manifestPath,
306
533
  configDir: h.dir,
534
+ readExposeStateFn: h.readExposeStateFn,
307
535
  issuer: "https://hub.example",
308
536
  registry: getDefaultOperationsRegistry(),
309
537
  });
@@ -354,6 +582,7 @@ describe("handleSetupGet", () => {
354
582
  db,
355
583
  manifestPath: h.manifestPath,
356
584
  configDir: h.dir,
585
+ readExposeStateFn: h.readExposeStateFn,
357
586
  issuer: "https://hub.example",
358
587
  registry: getDefaultOperationsRegistry(),
359
588
  });
@@ -386,6 +615,7 @@ describe("handleSetupGet", () => {
386
615
  db,
387
616
  manifestPath: h.manifestPath,
388
617
  configDir: h.dir,
618
+ readExposeStateFn: h.readExposeStateFn,
389
619
  issuer: "https://hub.example",
390
620
  registry: getDefaultOperationsRegistry(),
391
621
  });
@@ -434,6 +664,7 @@ describe("handleSetupGet", () => {
434
664
  db,
435
665
  manifestPath: h.manifestPath,
436
666
  configDir: h.dir,
667
+ readExposeStateFn: h.readExposeStateFn,
437
668
  issuer: "https://hub.example",
438
669
  registry: getDefaultOperationsRegistry(),
439
670
  },
@@ -483,6 +714,7 @@ describe("handleSetupGet", () => {
483
714
  db,
484
715
  manifestPath: h.manifestPath,
485
716
  configDir: h.dir,
717
+ readExposeStateFn: h.readExposeStateFn,
486
718
  issuer: "https://hub.example",
487
719
  registry: getDefaultOperationsRegistry(),
488
720
  },
@@ -524,6 +756,7 @@ describe("handleSetupGet", () => {
524
756
  db,
525
757
  manifestPath: h.manifestPath,
526
758
  configDir: h.dir,
759
+ readExposeStateFn: h.readExposeStateFn,
527
760
  issuer: "https://hub.example",
528
761
  registry: getDefaultOperationsRegistry(),
529
762
  },
@@ -548,6 +781,7 @@ describe("handleSetupGet", () => {
548
781
  db,
549
782
  manifestPath: h.manifestPath,
550
783
  configDir: h.dir,
784
+ readExposeStateFn: h.readExposeStateFn,
551
785
  issuer: "https://hub.example",
552
786
  registry: reg,
553
787
  });
@@ -577,6 +811,7 @@ describe("handleSetupGet", () => {
577
811
  db,
578
812
  manifestPath: h.manifestPath,
579
813
  configDir: h.dir,
814
+ readExposeStateFn: h.readExposeStateFn,
580
815
  issuer: "https://hub.example",
581
816
  registry: reg,
582
817
  });
@@ -608,6 +843,7 @@ describe("handleSetupAccountPost", () => {
608
843
  db,
609
844
  manifestPath: h.manifestPath,
610
845
  configDir: h.dir,
846
+ readExposeStateFn: h.readExposeStateFn,
611
847
  issuer: "https://hub.example",
612
848
  registry: getDefaultOperationsRegistry(),
613
849
  });
@@ -632,6 +868,7 @@ describe("handleSetupAccountPost", () => {
632
868
  db,
633
869
  manifestPath: h.manifestPath,
634
870
  configDir: h.dir,
871
+ readExposeStateFn: h.readExposeStateFn,
635
872
  issuer: "https://hub.example",
636
873
  registry: getDefaultOperationsRegistry(),
637
874
  },
@@ -660,6 +897,7 @@ describe("handleSetupAccountPost", () => {
660
897
  db,
661
898
  manifestPath: h.manifestPath,
662
899
  configDir: h.dir,
900
+ readExposeStateFn: h.readExposeStateFn,
663
901
  issuer: "https://hub.example",
664
902
  registry: getDefaultOperationsRegistry(),
665
903
  });
@@ -680,6 +918,7 @@ describe("handleSetupAccountPost", () => {
680
918
  db,
681
919
  manifestPath: h.manifestPath,
682
920
  configDir: h.dir,
921
+ readExposeStateFn: h.readExposeStateFn,
683
922
  issuer: "https://hub.example",
684
923
  registry: getDefaultOperationsRegistry(),
685
924
  },
@@ -712,6 +951,7 @@ describe("handleSetupAccountPost", () => {
712
951
  db,
713
952
  manifestPath: h.manifestPath,
714
953
  configDir: h.dir,
954
+ readExposeStateFn: h.readExposeStateFn,
715
955
  issuer: "https://hub.example",
716
956
  registry: getDefaultOperationsRegistry(),
717
957
  },
@@ -731,6 +971,7 @@ describe("handleSetupAccountPost", () => {
731
971
  db,
732
972
  manifestPath: h.manifestPath,
733
973
  configDir: h.dir,
974
+ readExposeStateFn: h.readExposeStateFn,
734
975
  issuer: "https://hub.example",
735
976
  registry: getDefaultOperationsRegistry(),
736
977
  });
@@ -751,6 +992,7 @@ describe("handleSetupAccountPost", () => {
751
992
  db,
752
993
  manifestPath: h.manifestPath,
753
994
  configDir: h.dir,
995
+ readExposeStateFn: h.readExposeStateFn,
754
996
  issuer: "https://hub.example",
755
997
  registry: getDefaultOperationsRegistry(),
756
998
  },
@@ -794,6 +1036,7 @@ describe("handleSetupVaultPost", () => {
794
1036
  db,
795
1037
  manifestPath: h.manifestPath,
796
1038
  configDir: h.dir,
1039
+ readExposeStateFn: h.readExposeStateFn,
797
1040
  issuer: "https://hub.example",
798
1041
  registry: getDefaultOperationsRegistry(),
799
1042
  },
@@ -815,6 +1058,7 @@ describe("handleSetupVaultPost", () => {
815
1058
  db,
816
1059
  manifestPath: h.manifestPath,
817
1060
  configDir: h.dir,
1061
+ readExposeStateFn: h.readExposeStateFn,
818
1062
  issuer: "https://hub.example",
819
1063
  registry: getDefaultOperationsRegistry(),
820
1064
  });
@@ -834,6 +1078,7 @@ describe("handleSetupVaultPost", () => {
834
1078
  db,
835
1079
  manifestPath: h.manifestPath,
836
1080
  configDir: h.dir,
1081
+ readExposeStateFn: h.readExposeStateFn,
837
1082
  issuer: "https://hub.example",
838
1083
  supervisor: makeSupervisor(),
839
1084
  registry: getDefaultOperationsRegistry(),
@@ -863,6 +1108,7 @@ describe("handleSetupVaultPost", () => {
863
1108
  db,
864
1109
  manifestPath: h.manifestPath,
865
1110
  configDir: h.dir,
1111
+ readExposeStateFn: h.readExposeStateFn,
866
1112
  issuer: "https://hub.example",
867
1113
  registry: getDefaultOperationsRegistry(),
868
1114
  });
@@ -887,6 +1133,7 @@ describe("handleSetupVaultPost", () => {
887
1133
  db,
888
1134
  manifestPath: h.manifestPath,
889
1135
  configDir: h.dir,
1136
+ readExposeStateFn: h.readExposeStateFn,
890
1137
  issuer: "https://hub.example",
891
1138
  supervisor: makeSupervisor(),
892
1139
  registry: getDefaultOperationsRegistry(),
@@ -940,6 +1187,7 @@ describe("handleSetupVaultPost", () => {
940
1187
  db,
941
1188
  manifestPath: h.manifestPath,
942
1189
  configDir: h.dir,
1190
+ readExposeStateFn: h.readExposeStateFn,
943
1191
  issuer: "https://hub.example",
944
1192
  registry: getDefaultOperationsRegistry(),
945
1193
  });
@@ -966,6 +1214,7 @@ describe("handleSetupVaultPost", () => {
966
1214
  db,
967
1215
  manifestPath: h.manifestPath,
968
1216
  configDir: h.dir,
1217
+ readExposeStateFn: h.readExposeStateFn,
969
1218
  issuer: "https://hub.example",
970
1219
  supervisor: makeSupervisor(),
971
1220
  registry: getDefaultOperationsRegistry(),
@@ -1009,6 +1258,7 @@ describe("handleSetupVaultPost", () => {
1009
1258
  db,
1010
1259
  manifestPath: h.manifestPath,
1011
1260
  configDir: h.dir,
1261
+ readExposeStateFn: h.readExposeStateFn,
1012
1262
  issuer: "https://hub.example",
1013
1263
  registry: getDefaultOperationsRegistry(),
1014
1264
  });
@@ -1034,6 +1284,7 @@ describe("handleSetupVaultPost", () => {
1034
1284
  db,
1035
1285
  manifestPath: h.manifestPath,
1036
1286
  configDir: h.dir,
1287
+ readExposeStateFn: h.readExposeStateFn,
1037
1288
  issuer: "https://hub.example",
1038
1289
  supervisor: makeSupervisor(),
1039
1290
  registry: getDefaultOperationsRegistry(),
@@ -1070,6 +1321,7 @@ describe("handleSetupVaultPost", () => {
1070
1321
  db,
1071
1322
  manifestPath: h.manifestPath,
1072
1323
  configDir: h.dir,
1324
+ readExposeStateFn: h.readExposeStateFn,
1073
1325
  issuer: "https://hub.example",
1074
1326
  registry: getDefaultOperationsRegistry(),
1075
1327
  });
@@ -1099,6 +1351,7 @@ describe("handleSetupVaultPost", () => {
1099
1351
  db,
1100
1352
  manifestPath: h.manifestPath,
1101
1353
  configDir: h.dir,
1354
+ readExposeStateFn: h.readExposeStateFn,
1102
1355
  issuer: "https://hub.example",
1103
1356
  supervisor,
1104
1357
  registry: getDefaultOperationsRegistry(),
@@ -1153,6 +1406,7 @@ describe("handleSetupVaultPost", () => {
1153
1406
  db,
1154
1407
  manifestPath: h.manifestPath,
1155
1408
  configDir: h.dir,
1409
+ readExposeStateFn: h.readExposeStateFn,
1156
1410
  issuer: "https://hub.example",
1157
1411
  registry: getDefaultOperationsRegistry(),
1158
1412
  });
@@ -1178,6 +1432,7 @@ describe("handleSetupVaultPost", () => {
1178
1432
  db,
1179
1433
  manifestPath: h.manifestPath,
1180
1434
  configDir: h.dir,
1435
+ readExposeStateFn: h.readExposeStateFn,
1181
1436
  issuer: "https://hub.example",
1182
1437
  supervisor: makeSupervisor(),
1183
1438
  registry: getDefaultOperationsRegistry(),
@@ -1432,6 +1687,7 @@ describe("handleSetupExposePost", () => {
1432
1687
  db,
1433
1688
  manifestPath: h.manifestPath,
1434
1689
  configDir: h.dir,
1690
+ readExposeStateFn: h.readExposeStateFn,
1435
1691
  issuer: "https://hub.example",
1436
1692
  registry: getDefaultOperationsRegistry(),
1437
1693
  });
@@ -1460,6 +1716,7 @@ describe("handleSetupExposePost", () => {
1460
1716
  db,
1461
1717
  manifestPath: h.manifestPath,
1462
1718
  configDir: h.dir,
1719
+ readExposeStateFn: h.readExposeStateFn,
1463
1720
  issuer: "https://hub.example",
1464
1721
  registry: getDefaultOperationsRegistry(),
1465
1722
  },
@@ -1495,6 +1752,7 @@ describe("handleSetupExposePost", () => {
1495
1752
  db,
1496
1753
  manifestPath: h.manifestPath,
1497
1754
  configDir: h.dir,
1755
+ readExposeStateFn: h.readExposeStateFn,
1498
1756
  issuer: "https://hub.example",
1499
1757
  registry: getDefaultOperationsRegistry(),
1500
1758
  },
@@ -1533,6 +1791,7 @@ describe("handleSetupExposePost", () => {
1533
1791
  db,
1534
1792
  manifestPath: h.manifestPath,
1535
1793
  configDir: h.dir,
1794
+ readExposeStateFn: h.readExposeStateFn,
1536
1795
  issuer: "https://hub.example",
1537
1796
  registry: getDefaultOperationsRegistry(),
1538
1797
  },
@@ -1567,6 +1826,7 @@ describe("handleSetupExposePost", () => {
1567
1826
  db,
1568
1827
  manifestPath: h.manifestPath,
1569
1828
  configDir: h.dir,
1829
+ readExposeStateFn: h.readExposeStateFn,
1570
1830
  issuer: "https://hub.example",
1571
1831
  registry: getDefaultOperationsRegistry(),
1572
1832
  },
@@ -1603,6 +1863,7 @@ describe("handleSetupExposePost", () => {
1603
1863
  db,
1604
1864
  manifestPath: h.manifestPath,
1605
1865
  configDir: h.dir,
1866
+ readExposeStateFn: h.readExposeStateFn,
1606
1867
  issuer: "https://hub.example",
1607
1868
  registry: getDefaultOperationsRegistry(),
1608
1869
  },
@@ -1653,6 +1914,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1653
1914
  db,
1654
1915
  manifestPath: h.manifestPath,
1655
1916
  configDir: h.dir,
1917
+ readExposeStateFn: h.readExposeStateFn,
1656
1918
  issuer: "https://hub.example",
1657
1919
  registry: getDefaultOperationsRegistry(),
1658
1920
  });
@@ -1681,6 +1943,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1681
1943
  db,
1682
1944
  manifestPath: h.manifestPath,
1683
1945
  configDir: h.dir,
1946
+ readExposeStateFn: h.readExposeStateFn,
1684
1947
  issuer: "https://hub.example",
1685
1948
  registry: getDefaultOperationsRegistry(),
1686
1949
  },
@@ -1727,6 +1990,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1727
1990
  db,
1728
1991
  manifestPath: h.manifestPath,
1729
1992
  configDir: h.dir,
1993
+ readExposeStateFn: h.readExposeStateFn,
1730
1994
  issuer: "https://hub.example",
1731
1995
  registry: getDefaultOperationsRegistry(),
1732
1996
  },
@@ -1792,16 +2056,21 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1792
2056
  db,
1793
2057
  manifestPath: h.manifestPath,
1794
2058
  configDir: h.dir,
2059
+ readExposeStateFn: h.readExposeStateFn,
1795
2060
  issuer: "https://hub.example",
1796
2061
  registry: getDefaultOperationsRegistry(),
1797
2062
  },
1798
2063
  );
1799
2064
  const html = await res.text();
1800
2065
  expect(html).toContain("claude mcp add --transport http parachute-default");
1801
- // The fallback explanatory text mentions `pvt_...` as a placeholder
1802
- // but the actual `--header` flag must NOT be appended to the
1803
- // command line itself.
1804
- expect(html).toContain("Bearer pvt_");
2066
+ // The fallback explanatory text leads with the OAuth path (no token
2067
+ // needed) and, for headless clients, references a hub JWT placeholder
2068
+ // NOT the retired `pvt_*` format (gap #4). The `--header` flag must
2069
+ // also NOT be appended to the command line itself.
2070
+ expect(html).toContain("browser OAuth");
2071
+ expect(html).toContain("Bearer &lt;token&gt;");
2072
+ expect(html).not.toContain("pvt_");
2073
+ expect(html).toContain("parachute auth mint-token");
1805
2074
  expect(html).toContain("/admin/tokens");
1806
2075
  // Specifically no Copy button — that's a token-present surface.
1807
2076
  expect(html).not.toContain('id="mcp-cmd"');
@@ -1836,6 +2105,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1836
2105
  db,
1837
2106
  manifestPath: h.manifestPath,
1838
2107
  configDir: h.dir,
2108
+ readExposeStateFn: h.readExposeStateFn,
1839
2109
  issuer: "https://hub.example",
1840
2110
  registry: getDefaultOperationsRegistry(),
1841
2111
  };
@@ -1892,6 +2162,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1892
2162
  db,
1893
2163
  manifestPath: h.manifestPath,
1894
2164
  configDir: h.dir,
2165
+ readExposeStateFn: h.readExposeStateFn,
1895
2166
  issuer: "https://hub.example",
1896
2167
  registry: getDefaultOperationsRegistry(),
1897
2168
  },
@@ -1957,6 +2228,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
1957
2228
  db,
1958
2229
  manifestPath: h.manifestPath,
1959
2230
  configDir: h.dir,
2231
+ readExposeStateFn: h.readExposeStateFn,
1960
2232
  issuer: "https://hub.example",
1961
2233
  registry: getDefaultOperationsRegistry(),
1962
2234
  },
@@ -2015,6 +2287,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
2015
2287
  db,
2016
2288
  manifestPath: h.manifestPath,
2017
2289
  configDir: h.dir,
2290
+ readExposeStateFn: h.readExposeStateFn,
2018
2291
  issuer: "https://hub.example",
2019
2292
  registry: getDefaultOperationsRegistry(),
2020
2293
  },
@@ -2076,6 +2349,7 @@ describe("done screen auto-minted token (hub#272 Item A)", () => {
2076
2349
  db,
2077
2350
  manifestPath: h.manifestPath,
2078
2351
  configDir: h.dir,
2352
+ readExposeStateFn: h.readExposeStateFn,
2079
2353
  issuer: "https://hub.example",
2080
2354
  registry: getDefaultOperationsRegistry(),
2081
2355
  });
@@ -2139,6 +2413,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2139
2413
  db,
2140
2414
  manifestPath: h.manifestPath,
2141
2415
  configDir: h.dir,
2416
+ readExposeStateFn: h.readExposeStateFn,
2142
2417
  issuer: "https://hub.example",
2143
2418
  registry: getDefaultOperationsRegistry(),
2144
2419
  },
@@ -2204,6 +2479,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2204
2479
  db,
2205
2480
  manifestPath: h.manifestPath,
2206
2481
  configDir: h.dir,
2482
+ readExposeStateFn: h.readExposeStateFn,
2207
2483
  issuer: "https://hub.example",
2208
2484
  registry: getDefaultOperationsRegistry(),
2209
2485
  },
@@ -2255,6 +2531,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2255
2531
  db,
2256
2532
  manifestPath: h.manifestPath,
2257
2533
  configDir: h.dir,
2534
+ readExposeStateFn: h.readExposeStateFn,
2258
2535
  issuer: "https://hub.example",
2259
2536
  registry: reg,
2260
2537
  },
@@ -2294,6 +2571,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2294
2571
  db,
2295
2572
  manifestPath: h.manifestPath,
2296
2573
  configDir: h.dir,
2574
+ readExposeStateFn: h.readExposeStateFn,
2297
2575
  issuer: "https://hub.example",
2298
2576
  registry: getDefaultOperationsRegistry(),
2299
2577
  });
@@ -2317,6 +2595,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2317
2595
  db,
2318
2596
  manifestPath: h.manifestPath,
2319
2597
  configDir: h.dir,
2598
+ readExposeStateFn: h.readExposeStateFn,
2320
2599
  issuer: "https://hub.example",
2321
2600
  supervisor: makeSupervisor(),
2322
2601
  registry: getDefaultOperationsRegistry(),
@@ -2345,6 +2624,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2345
2624
  db,
2346
2625
  manifestPath: h.manifestPath,
2347
2626
  configDir: h.dir,
2627
+ readExposeStateFn: h.readExposeStateFn,
2348
2628
  issuer: "https://hub.example",
2349
2629
  registry: getDefaultOperationsRegistry(),
2350
2630
  });
@@ -2363,6 +2643,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2363
2643
  db,
2364
2644
  manifestPath: h.manifestPath,
2365
2645
  configDir: h.dir,
2646
+ readExposeStateFn: h.readExposeStateFn,
2366
2647
  issuer: "https://hub.example",
2367
2648
  supervisor: makeSupervisor(),
2368
2649
  registry: getDefaultOperationsRegistry(),
@@ -2390,6 +2671,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2390
2671
  db,
2391
2672
  manifestPath: h.manifestPath,
2392
2673
  configDir: h.dir,
2674
+ readExposeStateFn: h.readExposeStateFn,
2393
2675
  issuer: "https://hub.example",
2394
2676
  supervisor: makeSupervisor(),
2395
2677
  registry: getDefaultOperationsRegistry(),
@@ -2411,6 +2693,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2411
2693
  db,
2412
2694
  manifestPath: h.manifestPath,
2413
2695
  configDir: h.dir,
2696
+ readExposeStateFn: h.readExposeStateFn,
2414
2697
  issuer: "https://hub.example",
2415
2698
  registry: getDefaultOperationsRegistry(),
2416
2699
  });
@@ -2429,6 +2712,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2429
2712
  db,
2430
2713
  manifestPath: h.manifestPath,
2431
2714
  configDir: h.dir,
2715
+ readExposeStateFn: h.readExposeStateFn,
2432
2716
  issuer: "https://hub.example",
2433
2717
  supervisor: makeSupervisor(),
2434
2718
  registry: getDefaultOperationsRegistry(),
@@ -2457,6 +2741,7 @@ describe("done screen install tiles (hub#272 Item B)", () => {
2457
2741
  db,
2458
2742
  manifestPath: h.manifestPath,
2459
2743
  configDir: h.dir,
2744
+ readExposeStateFn: h.readExposeStateFn,
2460
2745
  issuer: "https://hub.example",
2461
2746
  registry: getDefaultOperationsRegistry(),
2462
2747
  },
@@ -2490,6 +2775,7 @@ describe("typed vault name (hub#267)", () => {
2490
2775
  db,
2491
2776
  manifestPath: h.manifestPath,
2492
2777
  configDir: h.dir,
2778
+ readExposeStateFn: h.readExposeStateFn,
2493
2779
  issuer: "https://hub.example",
2494
2780
  registry: getDefaultOperationsRegistry(),
2495
2781
  });
@@ -2532,6 +2818,7 @@ describe("typed vault name (hub#267)", () => {
2532
2818
  db,
2533
2819
  manifestPath: h.manifestPath,
2534
2820
  configDir: h.dir,
2821
+ readExposeStateFn: h.readExposeStateFn,
2535
2822
  issuer: "https://hub.example",
2536
2823
  supervisor,
2537
2824
  registry: getDefaultOperationsRegistry(),
@@ -2565,6 +2852,7 @@ describe("typed vault name (hub#267)", () => {
2565
2852
  db,
2566
2853
  manifestPath: h.manifestPath,
2567
2854
  configDir: h.dir,
2855
+ readExposeStateFn: h.readExposeStateFn,
2568
2856
  issuer: "https://hub.example",
2569
2857
  registry: getDefaultOperationsRegistry(),
2570
2858
  });
@@ -2585,6 +2873,7 @@ describe("typed vault name (hub#267)", () => {
2585
2873
  db,
2586
2874
  manifestPath: h.manifestPath,
2587
2875
  configDir: h.dir,
2876
+ readExposeStateFn: h.readExposeStateFn,
2588
2877
  issuer: "https://hub.example",
2589
2878
  supervisor: makeSupervisor(),
2590
2879
  registry: getDefaultOperationsRegistry(),
@@ -2610,6 +2899,7 @@ describe("typed vault name (hub#267)", () => {
2610
2899
  db,
2611
2900
  manifestPath: h.manifestPath,
2612
2901
  configDir: h.dir,
2902
+ readExposeStateFn: h.readExposeStateFn,
2613
2903
  issuer: "https://hub.example",
2614
2904
  registry: getDefaultOperationsRegistry(),
2615
2905
  });
@@ -2650,6 +2940,7 @@ describe("typed vault name (hub#267)", () => {
2650
2940
  db,
2651
2941
  manifestPath: h.manifestPath,
2652
2942
  configDir: h.dir,
2943
+ readExposeStateFn: h.readExposeStateFn,
2653
2944
  issuer: "https://hub.example",
2654
2945
  supervisor,
2655
2946
  registry: getDefaultOperationsRegistry(),
@@ -2712,6 +3003,7 @@ describe("typed vault name (hub#267)", () => {
2712
3003
  db,
2713
3004
  manifestPath: h.manifestPath,
2714
3005
  configDir: h.dir,
3006
+ readExposeStateFn: h.readExposeStateFn,
2715
3007
  issuer: "https://hub.example",
2716
3008
  registry: getDefaultOperationsRegistry(),
2717
3009
  },
@@ -2763,6 +3055,7 @@ describe("typed vault name (hub#267)", () => {
2763
3055
  db,
2764
3056
  manifestPath: h.manifestPath,
2765
3057
  configDir: h.dir,
3058
+ readExposeStateFn: h.readExposeStateFn,
2766
3059
  issuer: "https://hub.example",
2767
3060
  registry: getDefaultOperationsRegistry(),
2768
3061
  },
@@ -2814,6 +3107,7 @@ describe("typed vault name (hub#267)", () => {
2814
3107
  db,
2815
3108
  manifestPath: h.manifestPath,
2816
3109
  configDir: h.dir,
3110
+ readExposeStateFn: h.readExposeStateFn,
2817
3111
  issuer: "https://hub.example",
2818
3112
  registry: getDefaultOperationsRegistry(),
2819
3113
  },
@@ -2884,6 +3178,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
2884
3178
  db,
2885
3179
  manifestPath: h.manifestPath,
2886
3180
  configDir: h.dir,
3181
+ readExposeStateFn: h.readExposeStateFn,
2887
3182
  issuer: "https://hub.example",
2888
3183
  registry: getDefaultOperationsRegistry(),
2889
3184
  });
@@ -2908,6 +3203,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
2908
3203
  db,
2909
3204
  manifestPath: h.manifestPath,
2910
3205
  configDir: h.dir,
3206
+ readExposeStateFn: h.readExposeStateFn,
2911
3207
  issuer: "https://hub.example",
2912
3208
  registry: getDefaultOperationsRegistry(),
2913
3209
  });
@@ -2933,6 +3229,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
2933
3229
  db,
2934
3230
  manifestPath: h.manifestPath,
2935
3231
  configDir: h.dir,
3232
+ readExposeStateFn: h.readExposeStateFn,
2936
3233
  issuer: "https://hub.example",
2937
3234
  registry: getDefaultOperationsRegistry(),
2938
3235
  });
@@ -2954,6 +3251,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
2954
3251
  db,
2955
3252
  manifestPath: h.manifestPath,
2956
3253
  configDir: h.dir,
3254
+ readExposeStateFn: h.readExposeStateFn,
2957
3255
  issuer: "https://hub.example",
2958
3256
  registry: getDefaultOperationsRegistry(),
2959
3257
  },
@@ -2976,6 +3274,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
2976
3274
  db,
2977
3275
  manifestPath: h.manifestPath,
2978
3276
  configDir: h.dir,
3277
+ readExposeStateFn: h.readExposeStateFn,
2979
3278
  issuer: "https://hub.example",
2980
3279
  registry: getDefaultOperationsRegistry(),
2981
3280
  });
@@ -2997,6 +3296,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
2997
3296
  db,
2998
3297
  manifestPath: h.manifestPath,
2999
3298
  configDir: h.dir,
3299
+ readExposeStateFn: h.readExposeStateFn,
3000
3300
  issuer: "https://hub.example",
3001
3301
  registry: getDefaultOperationsRegistry(),
3002
3302
  },
@@ -3026,6 +3326,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3026
3326
  db,
3027
3327
  manifestPath: h.manifestPath,
3028
3328
  configDir: h.dir,
3329
+ readExposeStateFn: h.readExposeStateFn,
3029
3330
  issuer: "https://hub.example",
3030
3331
  registry: getDefaultOperationsRegistry(),
3031
3332
  });
@@ -3047,6 +3348,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3047
3348
  db,
3048
3349
  manifestPath: h.manifestPath,
3049
3350
  configDir: h.dir,
3351
+ readExposeStateFn: h.readExposeStateFn,
3050
3352
  issuer: "https://hub.example",
3051
3353
  registry: getDefaultOperationsRegistry(),
3052
3354
  },
@@ -3074,6 +3376,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3074
3376
  db,
3075
3377
  manifestPath: h.manifestPath,
3076
3378
  configDir: h.dir,
3379
+ readExposeStateFn: h.readExposeStateFn,
3077
3380
  issuer: "https://hub.example",
3078
3381
  registry: getDefaultOperationsRegistry(),
3079
3382
  });
@@ -3095,6 +3398,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3095
3398
  db,
3096
3399
  manifestPath: h.manifestPath,
3097
3400
  configDir: h.dir,
3401
+ readExposeStateFn: h.readExposeStateFn,
3098
3402
  issuer: "https://hub.example",
3099
3403
  registry: getDefaultOperationsRegistry(),
3100
3404
  },
@@ -3120,6 +3424,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3120
3424
  db,
3121
3425
  manifestPath: h.manifestPath,
3122
3426
  configDir: h.dir,
3427
+ readExposeStateFn: h.readExposeStateFn,
3123
3428
  issuer: "https://hub.example",
3124
3429
  registry: getDefaultOperationsRegistry(),
3125
3430
  });
@@ -3140,6 +3445,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3140
3445
  db,
3141
3446
  manifestPath: h.manifestPath,
3142
3447
  configDir: h.dir,
3448
+ readExposeStateFn: h.readExposeStateFn,
3143
3449
  issuer: "https://hub.example",
3144
3450
  registry: getDefaultOperationsRegistry(),
3145
3451
  },
@@ -3186,6 +3492,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3186
3492
  db,
3187
3493
  manifestPath: h.manifestPath,
3188
3494
  configDir: h.dir,
3495
+ readExposeStateFn: h.readExposeStateFn,
3189
3496
  issuer: "https://hub.example",
3190
3497
  registry: getDefaultOperationsRegistry(),
3191
3498
  });
@@ -3207,6 +3514,7 @@ describe("bootstrap token gate (handleSetupAccountPost)", () => {
3207
3514
  db,
3208
3515
  manifestPath: h.manifestPath,
3209
3516
  configDir: h.dir,
3517
+ readExposeStateFn: h.readExposeStateFn,
3210
3518
  issuer: "https://hub.example",
3211
3519
  registry: getDefaultOperationsRegistry(),
3212
3520
  };
@@ -3290,6 +3598,7 @@ describe("done screen — 'Start using your vault' tile (hub#342)", () => {
3290
3598
  db,
3291
3599
  manifestPath: h.manifestPath,
3292
3600
  configDir: h.dir,
3601
+ readExposeStateFn: h.readExposeStateFn,
3293
3602
  issuer: "https://hub.example",
3294
3603
  registry: getDefaultOperationsRegistry(),
3295
3604
  },
@@ -3353,6 +3662,7 @@ describe("done screen — 'Start using your vault' tile (hub#342)", () => {
3353
3662
  db,
3354
3663
  manifestPath: h.manifestPath,
3355
3664
  configDir: h.dir,
3665
+ readExposeStateFn: h.readExposeStateFn,
3356
3666
  issuer: "https://hub.example",
3357
3667
  registry: getDefaultOperationsRegistry(),
3358
3668
  },
@@ -3411,6 +3721,7 @@ describe("done screen — 'Start using your vault' tile (hub#342)", () => {
3411
3721
  db,
3412
3722
  manifestPath: h.manifestPath,
3413
3723
  configDir: h.dir,
3724
+ readExposeStateFn: h.readExposeStateFn,
3414
3725
  issuer: "https://hub.example",
3415
3726
  registry: reg,
3416
3727
  },
@@ -3470,6 +3781,7 @@ describe("done screen — 'Start using your vault' tile (hub#342)", () => {
3470
3781
  db,
3471
3782
  manifestPath: h.manifestPath,
3472
3783
  configDir: h.dir,
3784
+ readExposeStateFn: h.readExposeStateFn,
3473
3785
  issuer: "https://hub.example",
3474
3786
  registry: getDefaultOperationsRegistry(),
3475
3787
  },
@@ -3497,6 +3809,7 @@ describe("done screen — 'Start using your vault' tile (hub#342)", () => {
3497
3809
  db,
3498
3810
  manifestPath: h.manifestPath,
3499
3811
  configDir: h.dir,
3812
+ readExposeStateFn: h.readExposeStateFn,
3500
3813
  issuer: "https://hub.example",
3501
3814
  registry: getDefaultOperationsRegistry(),
3502
3815
  });
@@ -3593,6 +3906,7 @@ describe("setup-wizard JSON surface (hub#168 Cuts 2/3)", () => {
3593
3906
  db,
3594
3907
  manifestPath: h.manifestPath,
3595
3908
  configDir: h.dir,
3909
+ readExposeStateFn: h.readExposeStateFn,
3596
3910
  issuer: "http://127.0.0.1:1939",
3597
3911
  registry: getDefaultOperationsRegistry(),
3598
3912
  };
@@ -3618,6 +3932,7 @@ describe("setup-wizard JSON surface (hub#168 Cuts 2/3)", () => {
3618
3932
  db,
3619
3933
  manifestPath: h.manifestPath,
3620
3934
  configDir: h.dir,
3935
+ readExposeStateFn: h.readExposeStateFn,
3621
3936
  issuer: "http://127.0.0.1:1939",
3622
3937
  registry: getDefaultOperationsRegistry(),
3623
3938
  supervisor,
@@ -3657,7 +3972,11 @@ describe("setup-wizard JSON surface (hub#168 Cuts 2/3)", () => {
3657
3972
  // The skip flag is persisted.
3658
3973
  expect(getSetting(db, "setup_vault_skipped")).toBe("true");
3659
3974
  // deriveWizardState advances past the vault step.
3660
- const s = deriveWizardState({ db, manifestPath: h.manifestPath });
3975
+ const s = deriveWizardState({
3976
+ db,
3977
+ manifestPath: h.manifestPath,
3978
+ readExposeStateFn: h.readExposeStateFn,
3979
+ });
3661
3980
  expect(s.hasVault).toBe(true);
3662
3981
  expect(s.step).toBe("expose");
3663
3982
  } finally {
@@ -3674,6 +3993,7 @@ describe("setup-wizard JSON surface (hub#168 Cuts 2/3)", () => {
3674
3993
  db,
3675
3994
  manifestPath: h.manifestPath,
3676
3995
  configDir: h.dir,
3996
+ readExposeStateFn: h.readExposeStateFn,
3677
3997
  issuer: "http://127.0.0.1:1939",
3678
3998
  registry: getDefaultOperationsRegistry(),
3679
3999
  supervisor,
@@ -3767,10 +4087,10 @@ describe("setup-wizard JSON surface (hub#168 Cuts 2/3)", () => {
3767
4087
  let capturedBody: unknown;
3768
4088
  const stubFetch = (async (_: string | URL | Request, init?: RequestInit) => {
3769
4089
  capturedBody = JSON.parse((init?.body as string) ?? "{}");
3770
- return new Response(
3771
- JSON.stringify({ notes_imported: 1 }),
3772
- { status: 200, headers: { "content-type": "application/json" } },
3773
- );
4090
+ return new Response(JSON.stringify({ notes_imported: 1 }), {
4091
+ status: 200,
4092
+ headers: { "content-type": "application/json" },
4093
+ });
3774
4094
  }) as typeof fetch;
3775
4095
 
3776
4096
  await postVaultImportImpl({