@openparachute/hub 0.6.5-rc.8 → 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 (50) hide show
  1. package/package.json +1 -1
  2. package/src/__tests__/account-setup.test.ts +34 -0
  3. package/src/__tests__/account-vault-admin-token.test.ts +35 -3
  4. package/src/__tests__/admin-channel-token.test.ts +173 -0
  5. package/src/__tests__/admin-connections.test.ts +1154 -0
  6. package/src/__tests__/admin-csrf-belt.test.ts +346 -0
  7. package/src/__tests__/admin-module-token.test.ts +311 -0
  8. package/src/__tests__/admin-vaults.test.ts +590 -0
  9. package/src/__tests__/api-modules-ops.test.ts +70 -5
  10. package/src/__tests__/api-modules.test.ts +262 -79
  11. package/src/__tests__/hub-server.test.ts +319 -21
  12. package/src/__tests__/invites.test.ts +27 -0
  13. package/src/__tests__/module-manifest.test.ts +305 -8
  14. package/src/__tests__/serve-boot.test.ts +133 -2
  15. package/src/__tests__/service-spec-discovery.test.ts +109 -0
  16. package/src/__tests__/setup-gate.test.ts +13 -7
  17. package/src/__tests__/setup-wizard.test.ts +228 -1
  18. package/src/__tests__/vault-name.test.ts +20 -5
  19. package/src/__tests__/well-known.test.ts +44 -8
  20. package/src/account-vault-admin-token.ts +43 -14
  21. package/src/admin-channel-token.ts +135 -0
  22. package/src/admin-connections.ts +980 -0
  23. package/src/admin-module-token.ts +197 -0
  24. package/src/admin-vaults.ts +390 -12
  25. package/src/api-hub-upgrade.ts +4 -3
  26. package/src/api-modules-ops.ts +41 -16
  27. package/src/api-modules.ts +238 -116
  28. package/src/api-tokens.ts +8 -5
  29. package/src/commands/serve-boot.ts +80 -3
  30. package/src/commands/setup.ts +4 -4
  31. package/src/connections-store.ts +161 -0
  32. package/src/grants.ts +50 -0
  33. package/src/hub-server.ts +349 -59
  34. package/src/invites.ts +22 -0
  35. package/src/jwt-sign.ts +41 -1
  36. package/src/module-manifest.ts +429 -23
  37. package/src/origin-check.ts +106 -0
  38. package/src/proxy-error-ui.ts +1 -1
  39. package/src/service-spec.ts +132 -41
  40. package/src/setup-wizard.ts +68 -6
  41. package/src/users.ts +11 -0
  42. package/src/vault-name.ts +27 -7
  43. package/src/well-known.ts +41 -33
  44. package/web/ui/dist/assets/index-C-XzMVqN.js +61 -0
  45. package/web/ui/dist/assets/index-E_9wqjEm.css +1 -0
  46. package/web/ui/dist/index.html +2 -2
  47. package/src/__tests__/api-modules-config.test.ts +0 -882
  48. package/src/api-modules-config.ts +0 -421
  49. package/web/ui/dist/assets/index-BYYUeLGA.css +0 -1
  50. package/web/ui/dist/assets/index-D3cDUOOj.js +0 -61
package/src/well-known.ts CHANGED
@@ -169,12 +169,17 @@ export interface BuildWellKnownOpts {
169
169
  /**
170
170
  * Optional resolver mapping a `ServiceEntry` to its `module.json:uiUrl`,
171
171
  * if any. Same shape as `managementUrlFor`. Returning `undefined` means
172
- * "no user-facing UI" and discovery omits the Services tile. For vault
173
- * entries, the declared `uiUrl` is the per-instance path (e.g. "/admin/")
174
- * `buildWellKnown` prefixes it with the per-instance mount path on
175
- * emission, yielding one tile per vault instance pointing at
176
- * `<origin>/vault/<name>/admin/`. See patterns#96
177
- * `module-ui-declaration.md` §"Multi-instance services (vault)".
172
+ * "no user-facing UI" and discovery omits the Services tile.
173
+ *
174
+ * Resolution follows the B4 unified semantics (2026-06-09
175
+ * hub-module-boundary): http(s):// verbatim; leading-`/`
176
+ * ORIGIN-ABSOLUTE against the canonical origin (vault's daemon-level
177
+ * `/vault/admin/` emits as-is, once per row); RELATIVE (no leading slash,
178
+ * e.g. `"admin/"`) → the per-instance form, mount-joined per emitted path
179
+ * — for a multi-path vault entry that yields one tile per instance at
180
+ * `<origin>/vault/<name>/admin/`. The literal legacy `"/admin/"` on a
181
+ * vault entry rides the one-release compat shim (mount-join + deprecation
182
+ * log). See `buildWellKnown`'s uiUrl branch.
178
183
  */
179
184
  uiUrlFor?: (entry: ServiceEntry) => string | undefined;
180
185
  /**
@@ -227,6 +232,9 @@ function buildUisArray(uis: Record<string, UiSubUnit>, base: string): WellKnownU
227
232
  });
228
233
  }
229
234
 
235
+ /** One-time deprecation log for the legacy vault `"/admin/"` uiUrl (B4 compat shim). */
236
+ let warnedLegacyVaultUiUrl = false;
237
+
230
238
  export function buildWellKnown(opts: BuildWellKnownOpts): WellKnownDocument {
231
239
  const base = opts.canonicalOrigin.replace(/\/$/, "");
232
240
  const doc: WellKnownDocument = { vaults: [], services: [] };
@@ -257,43 +265,43 @@ export function buildWellKnown(opts: BuildWellKnownOpts): WellKnownDocument {
257
265
  // entry — no installDir round-trip needed since it's already
258
266
  // persisted server-side and reasonably stable across reboots.
259
267
  if (s.tagline !== undefined) entry.tagline = s.tagline;
260
- // Resolve uiUrl. Three forms (per patterns#96
261
- // `module-ui-declaration.md` §"Shape"):
268
+ // Resolve uiUrl. Unified URL-resolution semantics (B4 of the 2026-06-09
269
+ // hub-module-boundary migration — same doctrine as api-modules.ts
270
+ // `resolveModuleUrl` and the `resolveManagementUrl` pair):
262
271
  // - Absolute http(s) URL → verbatim.
263
- // - Path on a non-vault entry joined onto `base` directly.
264
- // - Path on a vault entry joined onto `base` AFTER prefixing
265
- // with the per-instance mount path. Vault is the only
266
- // multi-instance service today; its declared `uiUrl: "/admin/"`
267
- // resolves to `<base>/vault/<name>/admin/` (one tile per
268
- // instance). The mount path is whichever `path` we're iterating
269
- // this loop turn (vault's `pathsToEmit` is its `paths[]`,
270
- // fanning one row per instance).
272
+ // - Leading-`/` path ORIGIN-ABSOLUTE: resolved against `base`
273
+ // directly (`/scribe/admin``<base>/scribe/admin`; vault's
274
+ // daemon-level `/vault/admin/` `<base>/vault/admin/`, once,
275
+ // NOT per instance).
276
+ // - Relative path (no leading slash) → MOUNT-JOINED: the
277
+ // per-instance form. Vault is the only multi-instance service
278
+ // today; a declared `uiUrl: "admin/"` resolves to
279
+ // `<base>/vault/<name>/admin/` (one tile per instance). The mount
280
+ // is whichever `path` we're iterating this loop turn (vault's
281
+ // `pathsToEmit` is its `paths[]`, fanning one row per instance).
271
282
  //
272
- // Path concatenation: `path` is the canonical per-instance mount
273
- // ("/vault/default", no trailing slash from services.json). `uiUrlRaw`
274
- // starts with "/" per pattern rule. Direct concatenation yields the
275
- // correct join ("/vault/default" + "/admin/" "/vault/default/admin/").
283
+ // COMPAT SHIM (one release remove once vault's new manifest reaches
284
+ // @latest): the literal legacy `"/admin"`/`"/admin/"` on a VAULT entry
285
+ // is the OLD per-instance relative declaration that deployed vaults
286
+ // still ship; it mount-joins (the pre-B4 behavior) with a deprecation
287
+ // log instead of resolving origin-absolute.
276
288
  const uiUrlRaw = opts.uiUrlFor?.(s);
277
289
  if (uiUrlRaw !== undefined) {
290
+ const mount = path.replace(/\/+$/, "");
278
291
  if (/^https?:\/\//i.test(uiUrlRaw)) {
279
292
  entry.uiUrl = uiUrlRaw;
280
- } else if (isVault) {
281
- // Defensive guard: vault uiUrl MUST start with "/" per the
282
- // multi-instance pattern (see module-ui-declaration.md). A bare
283
- // "admin/" (no leading slash) would concatenate into
284
- // "/vault/defaultadmin/" — a silent malformed URL that 404s.
285
- // Warn loudly instead of emitting garbage; the entry just
286
- // omits its uiUrl rather than poisoning the well-known doc.
287
- if (!uiUrlRaw.startsWith("/")) {
293
+ } else if (isVault && (uiUrlRaw === "/admin" || uiUrlRaw === "/admin/")) {
294
+ if (!warnedLegacyVaultUiUrl) {
295
+ warnedLegacyVaultUiUrl = true;
288
296
  console.warn(
289
- `[well-known] vault entry "${s.name}" declares uiUrl=${JSON.stringify(uiUrlRaw)} without a leading slash; skipping uiUrl emission. Per module-ui-declaration.md, multi-instance uiUrl must be a path-form starting with "/".`,
297
+ `[well-known] vault entry "${s.name}" declares the legacy per-instance uiUrl ${JSON.stringify(uiUrlRaw)}; mount-joining for one release. New semantics: relative ("admin/") = per-instance mount-join, leading-"/" = origin-absolute. Upgrade the vault module to clear this.`,
290
298
  );
291
- } else {
292
- const mount = path.replace(/\/$/, "");
293
- entry.uiUrl = new URL(`${mount}${uiUrlRaw}`, `${base}/`).toString();
294
299
  }
295
- } else {
300
+ entry.uiUrl = new URL(`${mount}${uiUrlRaw}`, `${base}/`).toString();
301
+ } else if (uiUrlRaw.startsWith("/")) {
296
302
  entry.uiUrl = new URL(uiUrlRaw, `${base}/`).toString();
303
+ } else {
304
+ entry.uiUrl = new URL(`${mount}/${uiUrlRaw}`, `${base}/`).toString();
297
305
  }
298
306
  }
299
307
  // Hierarchical sub-units (hub#313 / parachute-app design doc §12). The