every-plugin 3.0.0-rc.15 → 3.0.0-rc.17

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 (75) hide show
  1. package/dist/build/artifact-names.cjs +8 -5
  2. package/dist/build/artifact-names.cjs.map +1 -1
  3. package/dist/build/artifact-names.d.cts +7 -5
  4. package/dist/build/artifact-names.d.cts.map +1 -1
  5. package/dist/build/artifact-names.d.mts +7 -5
  6. package/dist/build/artifact-names.d.mts.map +1 -1
  7. package/dist/build/artifact-names.mjs +8 -5
  8. package/dist/build/artifact-names.mjs.map +1 -1
  9. package/dist/build/ui/stubs.cjs +2 -7
  10. package/dist/build/ui/stubs.cjs.map +1 -1
  11. package/dist/build/ui/stubs.d.cts.map +1 -1
  12. package/dist/build/ui/stubs.d.mts.map +1 -1
  13. package/dist/build/ui/stubs.mjs +2 -7
  14. package/dist/build/ui/stubs.mjs.map +1 -1
  15. package/dist/client.cjs +15 -63
  16. package/dist/client.cjs.map +1 -1
  17. package/dist/client.d.cts +12 -11
  18. package/dist/client.d.cts.map +1 -1
  19. package/dist/client.d.mts +12 -11
  20. package/dist/client.d.mts.map +1 -1
  21. package/dist/client.mjs +16 -64
  22. package/dist/client.mjs.map +1 -1
  23. package/dist/errors.cjs +11 -1
  24. package/dist/errors.cjs.map +1 -1
  25. package/dist/errors.d.cts +15 -1
  26. package/dist/errors.d.cts.map +1 -1
  27. package/dist/errors.d.mts +15 -1
  28. package/dist/errors.d.mts.map +1 -1
  29. package/dist/errors.mjs +11 -2
  30. package/dist/errors.mjs.map +1 -1
  31. package/dist/index.cjs +1 -0
  32. package/dist/index.d.cts +4 -4
  33. package/dist/index.d.mts +4 -4
  34. package/dist/index.mjs +2 -2
  35. package/dist/runtime/mf-config.cjs +1 -1
  36. package/dist/runtime/mf-config.mjs +1 -1
  37. package/dist/ui/manifest/construct.cjs +13 -38
  38. package/dist/ui/manifest/construct.cjs.map +1 -1
  39. package/dist/ui/manifest/construct.d.cts +4 -13
  40. package/dist/ui/manifest/construct.d.cts.map +1 -1
  41. package/dist/ui/manifest/construct.d.mts +4 -13
  42. package/dist/ui/manifest/construct.d.mts.map +1 -1
  43. package/dist/ui/manifest/construct.mjs +14 -39
  44. package/dist/ui/manifest/construct.mjs.map +1 -1
  45. package/dist/ui/manifest/descriptor.cjs +1 -1
  46. package/dist/ui/manifest/descriptor.mjs +1 -1
  47. package/dist/ui/manifest/index.d.cts +3 -3
  48. package/dist/ui/manifest/index.d.mts +3 -3
  49. package/dist/ui/manifest/mount-registry.cjs +6 -13
  50. package/dist/ui/manifest/mount-registry.cjs.map +1 -1
  51. package/dist/ui/manifest/mount-registry.d.cts +29 -36
  52. package/dist/ui/manifest/mount-registry.d.cts.map +1 -1
  53. package/dist/ui/manifest/mount-registry.d.mts +29 -36
  54. package/dist/ui/manifest/mount-registry.d.mts.map +1 -1
  55. package/dist/ui/manifest/mount-registry.mjs +6 -13
  56. package/dist/ui/manifest/mount-registry.mjs.map +1 -1
  57. package/dist/version-manifest.cjs +70 -5
  58. package/dist/version-manifest.cjs.map +1 -1
  59. package/dist/version-manifest.d.cts +41 -1
  60. package/dist/version-manifest.d.cts.map +1 -1
  61. package/dist/version-manifest.d.mts +41 -1
  62. package/dist/version-manifest.d.mts.map +1 -1
  63. package/dist/version-manifest.mjs +66 -6
  64. package/dist/version-manifest.mjs.map +1 -1
  65. package/package.json +1 -1
  66. package/skills/plugin-client/SKILL.md +3 -3
  67. package/src/build/artifact-names.ts +8 -5
  68. package/src/build/ui/stubs.ts +2 -7
  69. package/src/client.ts +14 -111
  70. package/src/errors.ts +10 -0
  71. package/src/index.ts +2 -0
  72. package/src/ui/manifest/construct.ts +20 -65
  73. package/src/ui/manifest/index.ts +0 -2
  74. package/src/ui/manifest/mount-registry.ts +20 -20
  75. package/src/version-manifest.ts +77 -10
@@ -3,16 +3,17 @@ import { MOUNT_REGISTRY, resolveMountSegment } from "./mount-registry.mjs";
3
3
  import { digestOf } from "./descriptor.mjs";
4
4
  import { compareNavItems } from "./nav.mjs";
5
5
  import { ROUTE_OPTION_KEYS } from "./route-config.mjs";
6
- import { Outlet, createRootRoute, createRoute, redirect } from "@tanstack/react-router";
6
+ import { Outlet, createRootRoute, createRoute } from "@tanstack/react-router";
7
7
 
8
8
  //#region src/ui/manifest/construct.ts
9
9
  /**
10
10
  * Host-side route-graph construction (ADR 0008 §1): manifests + route
11
11
  * configs in → host-built tree out. The host constructs ITS OWN Route
12
12
  * objects from validated manifest data via public `createRoute` APIs; no
13
- * foreign route object is ever mutated. Gates are attached HOST-side from
14
- * the mount registry — plugins cannot ship a route that escapes its
15
- * mount's gate because plugins never construct route objects.
13
+ * foreign route object is ever mutated. Mount guards are ordinary
14
+ * beforeLoads attached HOST-side from ConstructInput.guards — plugins
15
+ * cannot ship a route that escapes its mount's guard because plugins never
16
+ * construct route objects.
16
17
  *
17
18
  * Mounts are the ONLY cross-source parenting points: a root-level
18
19
  * pathless layout file (`_public.tsx`) declares the mount for the whole
@@ -31,33 +32,6 @@ import { Outlet, createRootRoute, createRoute, redirect } from "@tanstack/react-
31
32
  function toAnyRoute(route) {
32
33
  return route;
33
34
  }
34
- /**
35
- * Gate redirects are absolute: gates attach to root-level mount routes, and
36
- * relatives would silently shift if a mount ever moved deeper in the tree.
37
- * The login surface is plugin-owned, never core-known.
38
- */
39
- const LOGIN_TO = "/login";
40
- const HOME_TO = "/";
41
- /**
42
- * Gate implementations by mount gate kind. Only `session` and `admin` are
43
- * implemented (ADR 0008 §3); unimplemented mounts are rejected during mount
44
- * declaration before any gate lookup, and gates are optional by type — a
45
- * mount without an entry (or `none`) renders ungated.
46
- */
47
- const GATES = {
48
- session: ({ context, location }) => {
49
- if (!userOf(context)) throw redirect({
50
- to: LOGIN_TO,
51
- search: { redirect: location.pathname + location.searchStr }
52
- });
53
- },
54
- admin: ({ context }) => {
55
- const user = userOf(context);
56
- if (!user) throw redirect({ to: LOGIN_TO });
57
- if (user.role !== "admin") throw redirect({ to: HOME_TO });
58
- }
59
- };
60
- const userOf = (context) => context?.user ?? context?.session?.user;
61
35
  function joinPath(a, b) {
62
36
  const left = a && a !== "/" ? a.replace(/\/+$/, "") : "";
63
37
  const right = b && b !== "/" ? b.replace(/^\/+/, "") : "";
@@ -70,14 +44,14 @@ const navOf = (options) => {
70
44
  if (nav && typeof nav === "object" && typeof nav.label === "string") return nav;
71
45
  };
72
46
  /** Layout routes (mount or pathless) pass their authored options through,
73
- * like every other route kind. On mount routes the registry gate composes
47
+ * like every other route kind. On mount routes the mount's guard composes
74
48
  * BEFORE the declaration's own beforeLoad so a source can never escape its
75
- * mount's gate (ADR 0008 §3), while the declaration's return value still
49
+ * mount's guard (ADR 0008 §3), while the declaration's return value still
76
50
  * flows to children as route context. */
77
- function layoutBeforeLoad(gate, declared) {
78
- if (!gate) return declared;
79
- return (args) => {
80
- gate(args);
51
+ function layoutBeforeLoad(guard, declared) {
52
+ if (!guard) return declared;
53
+ return async (args) => {
54
+ await guard(args);
81
55
  return declared?.(args);
82
56
  };
83
57
  }
@@ -161,12 +135,13 @@ async function constructTree(input) {
161
135
  const declaration = declarations.get(mountId);
162
136
  if (!declaration) continue;
163
137
  const def = MOUNT_REGISTRY[mountId];
164
- const gate = GATES[def.gate];
138
+ const guard = input.guards?.[mountId];
139
+ if (def.guarded && !guard) throw new Error(`mount "${mountId}" requires a guard — add one to ConstructInput.guards`);
165
140
  const options = declaration.options;
166
141
  const route = createRoute({
167
142
  id: `__mount_${mountId}`,
168
143
  getParentRoute: () => rootRoute,
169
- ...gate || options.beforeLoad ? { beforeLoad: layoutBeforeLoad(gate, options.beforeLoad) } : {},
144
+ ...guard || options.beforeLoad ? { beforeLoad: layoutBeforeLoad(guard, options.beforeLoad) } : {},
170
145
  ...authoredOptions(options),
171
146
  component: options.component ?? Outlet
172
147
  });
@@ -1 +1 @@
1
- {"version":3,"file":"construct.mjs","names":[],"sources":["../../../src/ui/manifest/construct.ts"],"sourcesContent":["import type { AnyRoute } from \"@tanstack/react-router\";\nimport { createRootRoute, createRoute, Outlet, redirect } from \"@tanstack/react-router\";\nimport { CORE_UI_PLUGIN_KEY } from \"./contract\";\nimport { digestOf } from \"./descriptor\";\nimport type { PluginManifest, RouteRecord } from \"./manifest-schema\";\nimport { MOUNT_REGISTRY, type MountDef, type MountId, resolveMountSegment } from \"./mount-registry\";\nimport { compareNavItems, type NavDeclaration, type NavItem, type NavManifest } from \"./nav\";\nimport { ROUTE_OPTION_KEYS, type RouteConfigModule, type RouteOptionsBundle } from \"./route-config\";\n\n/**\n * Host-side route-graph construction (ADR 0008 §1): manifests + route\n * configs in → host-built tree out. The host constructs ITS OWN Route\n * objects from validated manifest data via public `createRoute` APIs; no\n * foreign route object is ever mutated. Gates are attached HOST-side from\n * the mount registry — plugins cannot ship a route that escapes its\n * mount's gate because plugins never construct route objects.\n *\n * Mounts are the ONLY cross-source parenting points: a root-level\n * pathless layout file (`_public.tsx`) declares the mount for the whole\n * composition (exactly one declaring source per mount); every other\n * source's routes live under the same `_mount/` directory prefix and\n * parent onto the shared mount route. This module executes inside the\n * core ui's module graph (production MF expose `./compose`, dev source\n * import) so constructed routes are minted by the same react/router\n * instance that renders them.\n */\n\n/**\n * TanStack's Route generics are intentionally invariant — routes built from\n * manifest data can't satisfy them structurally. One named boundary for\n * handing a constructed route to the tree-building APIs.\n */\nfunction toAnyRoute(route: unknown): AnyRoute {\n return route as AnyRoute;\n}\n\nexport interface GateUser {\n id: string;\n name?: string;\n /** Better Auth user role — \"admin\" gates the admin mount. */\n role?: string;\n}\n\nexport interface HostContext {\n user?: GateUser;\n session?: { user?: GateUser };\n}\n\ntype GateFn = (args: {\n context: HostContext;\n location: { pathname: string; searchStr: string };\n}) => void;\n\n/**\n * Gate redirects are absolute: gates attach to root-level mount routes, and\n * relatives would silently shift if a mount ever moved deeper in the tree.\n * The login surface is plugin-owned, never core-known.\n */\nconst LOGIN_TO: string = \"/login\";\nconst HOME_TO: string = \"/\";\n\n/**\n * Gate implementations by mount gate kind. Only `session` and `admin` are\n * implemented (ADR 0008 §3); unimplemented mounts are rejected during mount\n * declaration before any gate lookup, and gates are optional by type — a\n * mount without an entry (or `none`) renders ungated.\n */\nconst GATES: Partial<Record<MountDef[\"gate\"], GateFn>> = {\n session: ({ context, location }) => {\n const user = userOf(context);\n if (!user) {\n throw redirect({\n to: LOGIN_TO,\n search: { redirect: location.pathname + location.searchStr } as never,\n });\n }\n },\n admin: ({ context }) => {\n const user = userOf(context);\n if (!user) throw redirect({ to: LOGIN_TO });\n if (user.role !== \"admin\") throw redirect({ to: HOME_TO });\n },\n};\n\nconst userOf = (context: HostContext | undefined): GateUser | undefined =>\n context?.user ?? context?.session?.user;\n\nexport interface ConstructPluginRef {\n /** composition identity — plugin key; participates in the digest */\n key: string;\n /** deployment identity — the MF container name loadRemote targets */\n mfName: string;\n}\n\nexport interface ResolvedPlugin {\n key: string;\n manifest: PluginManifest;\n routeConfig: RouteConfigModule;\n}\n\nexport interface ConstructInput {\n name: string;\n plugins: ReadonlyArray<ConstructPluginRef>;\n resolve: (ref: ConstructPluginRef) => Promise<ResolvedPlugin>;\n /** core ui `__root` options — component/error/notFound + head baseline */\n rootOptions?: RouteOptionsBundle;\n}\n\nexport interface ConstructedTree {\n rootRoute: AnyRoute;\n routeTree: AnyRoute;\n digest: string;\n manifests: PluginManifest[];\n nav: NavManifest;\n /** constructed route count per mount id */\n mountCounts: Record<string, number>;\n}\n\ninterface MountDeclaration {\n key: string;\n record: RouteRecord;\n options: RouteOptionsBundle;\n}\n\nfunction joinPath(a: string | undefined, b: string | undefined): string {\n const left = a && a !== \"/\" ? a.replace(/\\/+$/, \"\") : \"\";\n const right = b && b !== \"/\" ? b.replace(/^\\/+/, \"\") : \"\";\n if (!left) return right ? `/${right}` : \"/\";\n if (!right) return left;\n return `${left}/${right}`;\n}\n\nconst navOf = (options: RouteOptionsBundle): NavDeclaration | undefined => {\n const nav = options.staticData?.nav;\n if (nav && typeof nav === \"object\" && typeof (nav as NavDeclaration).label === \"string\") {\n return nav as NavDeclaration;\n }\n return undefined;\n};\n\n/** Layout routes (mount or pathless) pass their authored options through,\n * like every other route kind. On mount routes the registry gate composes\n * BEFORE the declaration's own beforeLoad so a source can never escape its\n * mount's gate (ADR 0008 §3), while the declaration's return value still\n * flows to children as route context. */\nfunction layoutBeforeLoad(\n gate: GateFn | undefined,\n declared: RouteOptionsBundle[\"beforeLoad\"],\n): RouteOptionsBundle[\"beforeLoad\"] {\n if (!gate) return declared;\n return (args) => {\n gate(args);\n return declared?.(args);\n };\n}\n\nconst AUTHORED_OPTION_KEYS = ROUTE_OPTION_KEYS.filter(\n (key) => key !== \"beforeLoad\" && key !== \"component\",\n);\n\nfunction authoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const authored: Partial<Record<keyof RouteOptionsBundle, unknown>> = {};\n for (const key of AUTHORED_OPTION_KEYS) {\n if (options[key] !== undefined) authored[key] = options[key];\n }\n return authored as Partial<RouteOptionsBundle>;\n}\n\nfunction rootAuthoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const { params: _params, ...authored } = authoredOptions(options);\n return authored;\n}\n\nexport async function constructTree(input: ConstructInput): Promise<ConstructedTree> {\n const refs = [...input.plugins].sort((a, b) => a.key.localeCompare(b.key));\n const resolved: ResolvedPlugin[] = [];\n for (const ref of refs) {\n resolved.push(await input.resolve(ref));\n }\n\n const ordered = [\n ...resolved.filter((plugin) => plugin.key === CORE_UI_PLUGIN_KEY),\n ...resolved.filter((plugin) => plugin.key !== CORE_UI_PLUGIN_KEY),\n ];\n\n const optionsByPlugin = new Map<string, Map<string, RouteOptionsBundle>>();\n const skippedByPlugin = new Map<string, Set<string>>();\n for (const plugin of resolved) {\n // A plugin with no manifest at all contributes no routes — skip it rather\n // than crashing SSR composition (same contract as the missing-loader skip\n // below).\n if (!plugin.manifest?.routes) continue;\n const optionsById = new Map<string, RouteOptionsBundle>();\n const skipped = new Set<string>();\n await Promise.all(\n plugin.manifest.routes.map(async (record) => {\n const loader = plugin.routeConfig.routeConfigLoaders[record.id];\n if (!loader) {\n console.log(\n `[Compose] skipping route \"${record.id}\" in \"${plugin.key}\" — no routeConfig loader`,\n );\n skipped.add(record.id);\n return;\n }\n optionsById.set(record.id, await loader());\n }),\n );\n // cascade: any route whose parent is skipped is also skipped\n let changed = true;\n while (changed) {\n changed = false;\n for (const record of plugin.manifest.routes) {\n if (skipped.has(record.id)) continue;\n if (record.parentId && skipped.has(record.parentId)) {\n skipped.add(record.id);\n changed = true;\n }\n }\n }\n optionsByPlugin.set(plugin.key, optionsById);\n skippedByPlugin.set(plugin.key, skipped);\n }\n\n const declarations = new Map<MountId, MountDeclaration>();\n const mountByLayoutId = new Map<string, MountId>();\n for (const plugin of ordered) {\n if (!plugin.manifest?.routes) continue;\n for (const record of plugin.manifest.routes) {\n if (!record.mount) continue;\n const mountId = record.mount as MountId;\n const def = MOUNT_REGISTRY[mountId];\n if (!def.implemented) {\n throw new Error(\n `mount \"${record.mount}\" is declared vocabulary but unimplemented (ADR 0008 §3) — declared by \"${plugin.key}\"`,\n );\n }\n const existing = declarations.get(mountId);\n if (existing) {\n if (existing.key === CORE_UI_PLUGIN_KEY && plugin.key !== CORE_UI_PLUGIN_KEY) {\n console.log(\n `[Compose] mount declaration \"${existing.key}:${existing.record.id}\" (${mountId}) shadows \"${plugin.key}:${record.id}\" — core ui wins; shadowed declaration skipped`,\n );\n mountByLayoutId.set(record.id, mountId);\n continue;\n }\n throw new Error(\n `mount \"${mountId}\" declared by multiple sources: \"${existing.key}\" (${existing.record.file}) and \"${plugin.key}\" (${record.file})`,\n );\n }\n if (skippedByPlugin.get(plugin.key)!.has(record.id)) continue;\n declarations.set(mountId, {\n key: plugin.key,\n record,\n options: optionsByPlugin.get(plugin.key)!.get(record.id)!,\n });\n mountByLayoutId.set(record.id, mountId);\n }\n }\n\n const rootRoute = toAnyRoute(\n createRootRoute({\n ...(input.rootOptions ? rootAuthoredOptions(input.rootOptions) : {}),\n component: input.rootOptions?.component ?? Outlet,\n ...(input.rootOptions?.beforeLoad ? { beforeLoad: input.rootOptions.beforeLoad } : {}),\n }),\n );\n\n const mountRoutes = new Map<MountId, AnyRoute>();\n for (const mountId of Object.keys(MOUNT_REGISTRY) as MountId[]) {\n const declaration = declarations.get(mountId);\n if (!declaration) continue;\n const def = MOUNT_REGISTRY[mountId]!;\n const gate = GATES[def.gate];\n const options = declaration.options;\n const route = createRoute({\n id: `__mount_${mountId}`,\n getParentRoute: () => rootRoute,\n ...(gate || options.beforeLoad\n ? { beforeLoad: layoutBeforeLoad(gate, options.beforeLoad) }\n : {}),\n ...authoredOptions(options),\n component: options.component ?? Outlet,\n });\n mountRoutes.set(mountId, toAnyRoute(route));\n }\n\n const nav: NavItem[] = [];\n const mountCounts: Record<string, number> = {};\n const childrenByParent = new Map<AnyRoute, AnyRoute[]>();\n /** cross-plugin path-collision guard: claimed paths per parent route */\n const claimedPathsByParent = new Map<AnyRoute, Map<string, string>>();\n const attach = (parent: AnyRoute, child: AnyRoute) => {\n const list = childrenByParent.get(parent) ?? [];\n list.push(child);\n childrenByParent.set(parent, list);\n };\n\n for (const plugin of ordered) {\n if (!plugin.manifest?.routes) continue;\n const optionsById = optionsByPlugin.get(plugin.key)!;\n const fullPathById = new Map<string, string>();\n const mountById = new Map<string, MountId>();\n const byId = new Map<string, { record: RouteRecord; route: AnyRoute }>();\n\n const parentFor = (\n record: RouteRecord,\n ): { route: AnyRoute; fullPath: string; mount: MountId } => {\n if (record.parentId) {\n const local = byId.get(record.parentId);\n if (local) {\n return {\n route: local.route,\n fullPath: fullPathById.get(record.parentId)!,\n mount: mountById.get(record.parentId)!,\n };\n }\n const mount = mountByLayoutId.get(record.parentId);\n if (mount) return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n throw new Error(\n `unresolvable parent \"${record.parentId}\" for \"${record.id}\" in \"${plugin.key}\" — mounts are the only cross-source parenting points`,\n );\n }\n const firstSegment = record.id.split(\"/\")[0] ?? \"\";\n if (firstSegment.startsWith(\"_\")) {\n const mount = resolveMountSegment(firstSegment.slice(1));\n if (mount && mountRoutes.has(mount))\n return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n if (mount) {\n throw new Error(\n `mount \"${mount}\" is not declared by any source (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(\n `unknown mount \"_${firstSegment.slice(1)}\" (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(`route \"${record.id}\" in \"${plugin.key}\" must live under a mount`);\n };\n\n const pending = plugin.manifest.routes.filter(\n (record) => !record.mount && !skippedByPlugin.get(plugin.key)!.has(record.id),\n );\n const shadowedIds = new Set<string>();\n let progressed = true;\n while (pending.length > 0 && progressed) {\n progressed = false;\n for (let i = 0; i < pending.length; i += 1) {\n const record = pending[i]!;\n const parentId = record.parentId;\n if (parentId) {\n if (shadowedIds.has(parentId)) {\n shadowedIds.add(record.id);\n pending.splice(i, 1);\n i -= 1;\n progressed = true;\n continue;\n }\n if (!byId.has(parentId) && !mountByLayoutId.has(parentId)) continue;\n }\n pending.splice(i, 1);\n progressed = true;\n\n const options = optionsById.get(record.id)!;\n const parent = parentFor(record);\n const routePath = record.path;\n const isLayoutRoute = record.type === \"layout\" || routePath === undefined;\n if (!isLayoutRoute) {\n const siblings = claimedPathsByParent.get(parent.route) ?? new Map<string, string>();\n const claimant = siblings.get(routePath);\n if (claimant) {\n if (\n plugin.key !== CORE_UI_PLUGIN_KEY &&\n claimant.startsWith(`${CORE_UI_PLUGIN_KEY}:`)\n ) {\n console.log(\n `[Compose] route claim \"${claimant}\" (${routePath}) shadows \"${plugin.key}:${record.id}\" — core ui wins; shadowed route skipped`,\n );\n shadowedIds.add(record.id);\n break;\n }\n throw new Error(\n `path collision: \"${plugin.key}\" route \"${record.id}\" declares \"${routePath}\" which is already claimed by \"${claimant}\" under the same parent`,\n );\n }\n siblings.set(routePath, `${plugin.key}:${record.id}`);\n claimedPathsByParent.set(parent.route, siblings);\n }\n const fullPath = joinPath(parent.fullPath, routePath);\n let route: AnyRoute;\n if (isLayoutRoute) {\n route = createRoute({\n id: `${plugin.key}__${record.id}`,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n } else {\n route = createRoute({\n path: routePath,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n }\n\n byId.set(record.id, { record, route: toAnyRoute(route) });\n fullPathById.set(record.id, fullPath);\n mountById.set(record.id, parent.mount);\n attach(parent.route, toAnyRoute(route));\n mountCounts[parent.mount] = (mountCounts[parent.mount] ?? 0) + 1;\n\n const navDeclaration = navOf(options);\n if (navDeclaration && navDeclaration.hidden !== true) {\n nav.push({\n id: `${plugin.key}:${record.id}`,\n label: navDeclaration.label,\n ...(typeof navDeclaration.icon === \"string\" ? { icon: navDeclaration.icon } : {}),\n ...(typeof navDeclaration.group === \"string\" ? { group: navDeclaration.group } : {}),\n ...(typeof navDeclaration.order === \"number\" ? { order: navDeclaration.order } : {}),\n to: navDeclaration.to ?? fullPath,\n plugin: plugin.key,\n mount: parent.mount,\n });\n }\n break;\n }\n }\n if (pending.length > 0) {\n throw new Error(\n `unresolvable parentage in \"${plugin.key}\": ${pending.map((record) => record.id).join(\", \")}`,\n );\n }\n }\n\n for (const [parent, children] of childrenByParent) {\n (parent as { addChildren: (children: AnyRoute[]) => void }).addChildren(children);\n }\n rootRoute.addChildren([...mountRoutes.values()] as AnyRoute[]);\n nav.sort(compareNavItems);\n\n const digest = await digestOf({\n plugins: refs.map((ref) => ({ key: ref.key, mfName: ref.mfName })),\n manifests: resolved.map((plugin) => plugin.manifest),\n });\n\n return {\n rootRoute,\n routeTree: rootRoute,\n digest,\n manifests: resolved.map((plugin) => plugin.manifest),\n nav: { items: nav },\n mountCounts,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"construct.mjs","names":[],"sources":["../../../src/ui/manifest/construct.ts"],"sourcesContent":["import type { AnyRoute } from \"@tanstack/react-router\";\nimport { createRootRoute, createRoute, Outlet } from \"@tanstack/react-router\";\nimport { CORE_UI_PLUGIN_KEY } from \"./contract\";\nimport { digestOf } from \"./descriptor\";\nimport type { PluginManifest, RouteRecord } from \"./manifest-schema\";\nimport { MOUNT_REGISTRY, type MountId, resolveMountSegment } from \"./mount-registry\";\nimport { compareNavItems, type NavDeclaration, type NavItem, type NavManifest } from \"./nav\";\nimport { ROUTE_OPTION_KEYS, type RouteConfigModule, type RouteOptionsBundle } from \"./route-config\";\n\n/**\n * Host-side route-graph construction (ADR 0008 §1): manifests + route\n * configs in → host-built tree out. The host constructs ITS OWN Route\n * objects from validated manifest data via public `createRoute` APIs; no\n * foreign route object is ever mutated. Mount guards are ordinary\n * beforeLoads attached HOST-side from ConstructInput.guards — plugins\n * cannot ship a route that escapes its mount's guard because plugins never\n * construct route objects.\n *\n * Mounts are the ONLY cross-source parenting points: a root-level\n * pathless layout file (`_public.tsx`) declares the mount for the whole\n * composition (exactly one declaring source per mount); every other\n * source's routes live under the same `_mount/` directory prefix and\n * parent onto the shared mount route. This module executes inside the\n * core ui's module graph (production MF expose `./compose`, dev source\n * import) so constructed routes are minted by the same react/router\n * instance that renders them.\n */\n\n/**\n * TanStack's Route generics are intentionally invariant — routes built from\n * manifest data can't satisfy them structurally. One named boundary for\n * handing a constructed route to the tree-building APIs.\n */\nfunction toAnyRoute(route: unknown): AnyRoute {\n return route as AnyRoute;\n}\n\nexport interface ConstructPluginRef {\n /** composition identity — plugin key; participates in the digest */\n key: string;\n /** deployment identity — the MF container name loadRemote targets */\n mfName: string;\n}\n\nexport interface ResolvedPlugin {\n key: string;\n manifest: PluginManifest;\n routeConfig: RouteConfigModule;\n}\n\nexport interface ConstructInput {\n name: string;\n plugins: ReadonlyArray<ConstructPluginRef>;\n resolve: (ref: ConstructPluginRef) => Promise<ResolvedPlugin>;\n /** core ui `__root` options — component/error/notFound + head baseline */\n rootOptions?: RouteOptionsBundle;\n /** Guard per mount id — beforeLoads supplied by the composing framework. */\n guards?: Partial<Record<MountId, RouteOptionsBundle[\"beforeLoad\"]>>;\n}\n\nexport interface ConstructedTree {\n rootRoute: AnyRoute;\n routeTree: AnyRoute;\n digest: string;\n manifests: PluginManifest[];\n nav: NavManifest;\n /** constructed route count per mount id */\n mountCounts: Record<string, number>;\n}\n\ninterface MountDeclaration {\n key: string;\n record: RouteRecord;\n options: RouteOptionsBundle;\n}\n\nfunction joinPath(a: string | undefined, b: string | undefined): string {\n const left = a && a !== \"/\" ? a.replace(/\\/+$/, \"\") : \"\";\n const right = b && b !== \"/\" ? b.replace(/^\\/+/, \"\") : \"\";\n if (!left) return right ? `/${right}` : \"/\";\n if (!right) return left;\n return `${left}/${right}`;\n}\n\nconst navOf = (options: RouteOptionsBundle): NavDeclaration | undefined => {\n const nav = options.staticData?.nav;\n if (nav && typeof nav === \"object\" && typeof (nav as NavDeclaration).label === \"string\") {\n return nav as NavDeclaration;\n }\n return undefined;\n};\n\n/** Layout routes (mount or pathless) pass their authored options through,\n * like every other route kind. On mount routes the mount's guard composes\n * BEFORE the declaration's own beforeLoad so a source can never escape its\n * mount's guard (ADR 0008 §3), while the declaration's return value still\n * flows to children as route context. */\nfunction layoutBeforeLoad(\n guard: RouteOptionsBundle[\"beforeLoad\"],\n declared: RouteOptionsBundle[\"beforeLoad\"],\n): RouteOptionsBundle[\"beforeLoad\"] {\n if (!guard) return declared;\n return async (args) => {\n await guard(args);\n return declared?.(args);\n };\n}\n\nconst AUTHORED_OPTION_KEYS = ROUTE_OPTION_KEYS.filter(\n (key) => key !== \"beforeLoad\" && key !== \"component\",\n);\n\nfunction authoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const authored: Partial<Record<keyof RouteOptionsBundle, unknown>> = {};\n for (const key of AUTHORED_OPTION_KEYS) {\n if (options[key] !== undefined) authored[key] = options[key];\n }\n return authored as Partial<RouteOptionsBundle>;\n}\n\nfunction rootAuthoredOptions(options: RouteOptionsBundle): Partial<RouteOptionsBundle> {\n const { params: _params, ...authored } = authoredOptions(options);\n return authored;\n}\n\nexport async function constructTree(input: ConstructInput): Promise<ConstructedTree> {\n const refs = [...input.plugins].sort((a, b) => a.key.localeCompare(b.key));\n const resolved: ResolvedPlugin[] = [];\n for (const ref of refs) {\n resolved.push(await input.resolve(ref));\n }\n\n const ordered = [\n ...resolved.filter((plugin) => plugin.key === CORE_UI_PLUGIN_KEY),\n ...resolved.filter((plugin) => plugin.key !== CORE_UI_PLUGIN_KEY),\n ];\n\n const optionsByPlugin = new Map<string, Map<string, RouteOptionsBundle>>();\n const skippedByPlugin = new Map<string, Set<string>>();\n for (const plugin of resolved) {\n // A plugin with no manifest at all contributes no routes — skip it rather\n // than crashing SSR composition (same contract as the missing-loader skip\n // below).\n if (!plugin.manifest?.routes) continue;\n const optionsById = new Map<string, RouteOptionsBundle>();\n const skipped = new Set<string>();\n await Promise.all(\n plugin.manifest.routes.map(async (record) => {\n const loader = plugin.routeConfig.routeConfigLoaders[record.id];\n if (!loader) {\n console.log(\n `[Compose] skipping route \"${record.id}\" in \"${plugin.key}\" — no routeConfig loader`,\n );\n skipped.add(record.id);\n return;\n }\n optionsById.set(record.id, await loader());\n }),\n );\n // cascade: any route whose parent is skipped is also skipped\n let changed = true;\n while (changed) {\n changed = false;\n for (const record of plugin.manifest.routes) {\n if (skipped.has(record.id)) continue;\n if (record.parentId && skipped.has(record.parentId)) {\n skipped.add(record.id);\n changed = true;\n }\n }\n }\n optionsByPlugin.set(plugin.key, optionsById);\n skippedByPlugin.set(plugin.key, skipped);\n }\n\n const declarations = new Map<MountId, MountDeclaration>();\n const mountByLayoutId = new Map<string, MountId>();\n for (const plugin of ordered) {\n if (!plugin.manifest?.routes) continue;\n for (const record of plugin.manifest.routes) {\n if (!record.mount) continue;\n const mountId = record.mount as MountId;\n const def = MOUNT_REGISTRY[mountId];\n if (!def.implemented) {\n throw new Error(\n `mount \"${record.mount}\" is declared vocabulary but unimplemented (ADR 0008 §3) — declared by \"${plugin.key}\"`,\n );\n }\n const existing = declarations.get(mountId);\n if (existing) {\n if (existing.key === CORE_UI_PLUGIN_KEY && plugin.key !== CORE_UI_PLUGIN_KEY) {\n console.log(\n `[Compose] mount declaration \"${existing.key}:${existing.record.id}\" (${mountId}) shadows \"${plugin.key}:${record.id}\" — core ui wins; shadowed declaration skipped`,\n );\n mountByLayoutId.set(record.id, mountId);\n continue;\n }\n throw new Error(\n `mount \"${mountId}\" declared by multiple sources: \"${existing.key}\" (${existing.record.file}) and \"${plugin.key}\" (${record.file})`,\n );\n }\n if (skippedByPlugin.get(plugin.key)!.has(record.id)) continue;\n declarations.set(mountId, {\n key: plugin.key,\n record,\n options: optionsByPlugin.get(plugin.key)!.get(record.id)!,\n });\n mountByLayoutId.set(record.id, mountId);\n }\n }\n\n const rootRoute = toAnyRoute(\n createRootRoute({\n ...(input.rootOptions ? rootAuthoredOptions(input.rootOptions) : {}),\n component: input.rootOptions?.component ?? Outlet,\n ...(input.rootOptions?.beforeLoad ? { beforeLoad: input.rootOptions.beforeLoad } : {}),\n }),\n );\n\n const mountRoutes = new Map<MountId, AnyRoute>();\n for (const mountId of Object.keys(MOUNT_REGISTRY) as MountId[]) {\n const declaration = declarations.get(mountId);\n if (!declaration) continue;\n const def = MOUNT_REGISTRY[mountId]!;\n const guard = input.guards?.[mountId];\n if (def.guarded && !guard) {\n throw new Error(`mount \"${mountId}\" requires a guard — add one to ConstructInput.guards`);\n }\n const options = declaration.options;\n const route = createRoute({\n id: `__mount_${mountId}`,\n getParentRoute: () => rootRoute,\n ...(guard || options.beforeLoad\n ? { beforeLoad: layoutBeforeLoad(guard, options.beforeLoad) }\n : {}),\n ...authoredOptions(options),\n component: options.component ?? Outlet,\n });\n mountRoutes.set(mountId, toAnyRoute(route));\n }\n\n const nav: NavItem[] = [];\n const mountCounts: Record<string, number> = {};\n const childrenByParent = new Map<AnyRoute, AnyRoute[]>();\n /** cross-plugin path-collision guard: claimed paths per parent route */\n const claimedPathsByParent = new Map<AnyRoute, Map<string, string>>();\n const attach = (parent: AnyRoute, child: AnyRoute) => {\n const list = childrenByParent.get(parent) ?? [];\n list.push(child);\n childrenByParent.set(parent, list);\n };\n\n for (const plugin of ordered) {\n if (!plugin.manifest?.routes) continue;\n const optionsById = optionsByPlugin.get(plugin.key)!;\n const fullPathById = new Map<string, string>();\n const mountById = new Map<string, MountId>();\n const byId = new Map<string, { record: RouteRecord; route: AnyRoute }>();\n\n const parentFor = (\n record: RouteRecord,\n ): { route: AnyRoute; fullPath: string; mount: MountId } => {\n if (record.parentId) {\n const local = byId.get(record.parentId);\n if (local) {\n return {\n route: local.route,\n fullPath: fullPathById.get(record.parentId)!,\n mount: mountById.get(record.parentId)!,\n };\n }\n const mount = mountByLayoutId.get(record.parentId);\n if (mount) return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n throw new Error(\n `unresolvable parent \"${record.parentId}\" for \"${record.id}\" in \"${plugin.key}\" — mounts are the only cross-source parenting points`,\n );\n }\n const firstSegment = record.id.split(\"/\")[0] ?? \"\";\n if (firstSegment.startsWith(\"_\")) {\n const mount = resolveMountSegment(firstSegment.slice(1));\n if (mount && mountRoutes.has(mount))\n return { route: mountRoutes.get(mount)!, fullPath: \"\", mount };\n if (mount) {\n throw new Error(\n `mount \"${mount}\" is not declared by any source (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(\n `unknown mount \"_${firstSegment.slice(1)}\" (route \"${record.id}\" in \"${plugin.key}\")`,\n );\n }\n throw new Error(`route \"${record.id}\" in \"${plugin.key}\" must live under a mount`);\n };\n\n const pending = plugin.manifest.routes.filter(\n (record) => !record.mount && !skippedByPlugin.get(plugin.key)!.has(record.id),\n );\n const shadowedIds = new Set<string>();\n let progressed = true;\n while (pending.length > 0 && progressed) {\n progressed = false;\n for (let i = 0; i < pending.length; i += 1) {\n const record = pending[i]!;\n const parentId = record.parentId;\n if (parentId) {\n if (shadowedIds.has(parentId)) {\n shadowedIds.add(record.id);\n pending.splice(i, 1);\n i -= 1;\n progressed = true;\n continue;\n }\n if (!byId.has(parentId) && !mountByLayoutId.has(parentId)) continue;\n }\n pending.splice(i, 1);\n progressed = true;\n\n const options = optionsById.get(record.id)!;\n const parent = parentFor(record);\n const routePath = record.path;\n const isLayoutRoute = record.type === \"layout\" || routePath === undefined;\n if (!isLayoutRoute) {\n const siblings = claimedPathsByParent.get(parent.route) ?? new Map<string, string>();\n const claimant = siblings.get(routePath);\n if (claimant) {\n if (\n plugin.key !== CORE_UI_PLUGIN_KEY &&\n claimant.startsWith(`${CORE_UI_PLUGIN_KEY}:`)\n ) {\n console.log(\n `[Compose] route claim \"${claimant}\" (${routePath}) shadows \"${plugin.key}:${record.id}\" — core ui wins; shadowed route skipped`,\n );\n shadowedIds.add(record.id);\n break;\n }\n throw new Error(\n `path collision: \"${plugin.key}\" route \"${record.id}\" declares \"${routePath}\" which is already claimed by \"${claimant}\" under the same parent`,\n );\n }\n siblings.set(routePath, `${plugin.key}:${record.id}`);\n claimedPathsByParent.set(parent.route, siblings);\n }\n const fullPath = joinPath(parent.fullPath, routePath);\n let route: AnyRoute;\n if (isLayoutRoute) {\n route = createRoute({\n id: `${plugin.key}__${record.id}`,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n } else {\n route = createRoute({\n path: routePath,\n getParentRoute: () => parent.route,\n ...authoredOptions(options),\n ...(options.beforeLoad ? { beforeLoad: options.beforeLoad } : {}),\n component: options.component ?? Outlet,\n });\n }\n\n byId.set(record.id, { record, route: toAnyRoute(route) });\n fullPathById.set(record.id, fullPath);\n mountById.set(record.id, parent.mount);\n attach(parent.route, toAnyRoute(route));\n mountCounts[parent.mount] = (mountCounts[parent.mount] ?? 0) + 1;\n\n const navDeclaration = navOf(options);\n if (navDeclaration && navDeclaration.hidden !== true) {\n nav.push({\n id: `${plugin.key}:${record.id}`,\n label: navDeclaration.label,\n ...(typeof navDeclaration.icon === \"string\" ? { icon: navDeclaration.icon } : {}),\n ...(typeof navDeclaration.group === \"string\" ? { group: navDeclaration.group } : {}),\n ...(typeof navDeclaration.order === \"number\" ? { order: navDeclaration.order } : {}),\n to: navDeclaration.to ?? fullPath,\n plugin: plugin.key,\n mount: parent.mount,\n });\n }\n break;\n }\n }\n if (pending.length > 0) {\n throw new Error(\n `unresolvable parentage in \"${plugin.key}\": ${pending.map((record) => record.id).join(\", \")}`,\n );\n }\n }\n\n for (const [parent, children] of childrenByParent) {\n (parent as { addChildren: (children: AnyRoute[]) => void }).addChildren(children);\n }\n rootRoute.addChildren([...mountRoutes.values()] as AnyRoute[]);\n nav.sort(compareNavItems);\n\n const digest = await digestOf({\n plugins: refs.map((ref) => ({ key: ref.key, mfName: ref.mfName })),\n manifests: resolved.map((plugin) => plugin.manifest),\n });\n\n return {\n rootRoute,\n routeTree: rootRoute,\n digest,\n manifests: resolved.map((plugin) => plugin.manifest),\n nav: { items: nav },\n mountCounts,\n };\n}\n"],"mappings":"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"}
@@ -50,7 +50,7 @@ function resolveApp(input, apps) {
50
50
  async function digestOf(input) {
51
51
  const bytes = new TextEncoder().encode(JSON.stringify({
52
52
  ...input,
53
- mountRegistryVersion: 5
53
+ mountRegistryVersion: 6
54
54
  }));
55
55
  const hash = await crypto.subtle.digest("SHA-256", bytes);
56
56
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
@@ -50,7 +50,7 @@ function resolveApp(input, apps) {
50
50
  async function digestOf(input) {
51
51
  const bytes = new TextEncoder().encode(JSON.stringify({
52
52
  ...input,
53
- mountRegistryVersion: 5
53
+ mountRegistryVersion: 6
54
54
  }));
55
55
  const hash = await crypto.subtle.digest("SHA-256", bytes);
56
56
  return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
@@ -1,9 +1,9 @@
1
1
  import { containerName } from "../../identity.cjs";
2
2
  import { CORE_UI_PLUGIN_KEY, MANIFEST_FILENAME, PLUGIN_UI_SHARED_EXPOSES, ROUTE_CONFIG_FILENAME, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME } from "./contract.cjs";
3
3
  import { ComposePayload, ComposePayloadSchema, ComposeRemote, MANIFEST_VERSION, ManifestSchema, PluginManifest, PluginManifestSchema, RouteRecord, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, parsePluginManifest } from "./manifest-schema.cjs";
4
+ import { MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, MountDef, MountId, resolveMountSegment } from "./mount-registry.cjs";
4
5
  import { NavDeclaration, NavItem, NavManifest, compareNavItems } from "./nav.cjs";
5
6
  import { RouteConfigModule, RouteConfigRef, RouteOptionsBundle } from "./route-config.cjs";
6
- import { ConstructInput, ConstructPluginRef, ConstructedTree, GateUser, HostContext, ResolvedPlugin, constructTree } from "./construct.cjs";
7
+ import { ConstructInput, ConstructPluginRef, ConstructedTree, ResolvedPlugin, constructTree } from "./construct.cjs";
7
8
  import { App, AppInput, CompositionDigestInput, KnownPlugins, Plugin, PluginRef, PluginRefInput, RemoteSource, ResolvedApp, digestOf, resolveApp } from "./descriptor.cjs";
8
- import { MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, MountDef, MountId, resolveMountSegment } from "./mount-registry.cjs";
9
- export { App, type AppInput, CORE_UI_PLUGIN_KEY, type ComposePayload, ComposePayloadSchema, type ComposeRemote, type CompositionDigestInput, type ConstructInput, type ConstructPluginRef, type ConstructedTree, type GateUser, type HostContext, type KnownPlugins, MANIFEST_FILENAME, MANIFEST_VERSION, MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, ManifestSchema, type MountDef, type MountId, type NavDeclaration, type NavItem, type NavManifest, PLUGIN_UI_SHARED_EXPOSES, Plugin, type PluginManifest, PluginManifestSchema, type PluginRef, type PluginRefInput, ROUTE_CONFIG_FILENAME, type RemoteSource, type ResolvedApp, type ResolvedPlugin, type RouteConfigModule, type RouteConfigRef, type RouteOptionsBundle, type RouteRecord, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME, compareNavItems, constructTree, digestOf, parsePluginManifest, resolveApp, resolveMountSegment, containerName as sanitizeContainerName };
9
+ export { App, type AppInput, CORE_UI_PLUGIN_KEY, type ComposePayload, ComposePayloadSchema, type ComposeRemote, type CompositionDigestInput, type ConstructInput, type ConstructPluginRef, type ConstructedTree, type KnownPlugins, MANIFEST_FILENAME, MANIFEST_VERSION, MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, ManifestSchema, type MountDef, type MountId, type NavDeclaration, type NavItem, type NavManifest, PLUGIN_UI_SHARED_EXPOSES, Plugin, type PluginManifest, PluginManifestSchema, type PluginRef, type PluginRefInput, ROUTE_CONFIG_FILENAME, type RemoteSource, type ResolvedApp, type ResolvedPlugin, type RouteConfigModule, type RouteConfigRef, type RouteOptionsBundle, type RouteRecord, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME, compareNavItems, constructTree, digestOf, parsePluginManifest, resolveApp, resolveMountSegment, containerName as sanitizeContainerName };
@@ -1,9 +1,9 @@
1
1
  import { containerName } from "../../identity.mjs";
2
2
  import { CORE_UI_PLUGIN_KEY, MANIFEST_FILENAME, PLUGIN_UI_SHARED_EXPOSES, ROUTE_CONFIG_FILENAME, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME } from "./contract.mjs";
3
3
  import { ComposePayload, ComposePayloadSchema, ComposeRemote, MANIFEST_VERSION, ManifestSchema, PluginManifest, PluginManifestSchema, RouteRecord, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, parsePluginManifest } from "./manifest-schema.mjs";
4
+ import { MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, MountDef, MountId, resolveMountSegment } from "./mount-registry.mjs";
4
5
  import { NavDeclaration, NavItem, NavManifest, compareNavItems } from "./nav.mjs";
5
6
  import { RouteConfigModule, RouteConfigRef, RouteOptionsBundle } from "./route-config.mjs";
6
- import { ConstructInput, ConstructPluginRef, ConstructedTree, GateUser, HostContext, ResolvedPlugin, constructTree } from "./construct.mjs";
7
+ import { ConstructInput, ConstructPluginRef, ConstructedTree, ResolvedPlugin, constructTree } from "./construct.mjs";
7
8
  import { App, AppInput, CompositionDigestInput, KnownPlugins, Plugin, PluginRef, PluginRefInput, RemoteSource, ResolvedApp, digestOf, resolveApp } from "./descriptor.mjs";
8
- import { MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, MountDef, MountId, resolveMountSegment } from "./mount-registry.mjs";
9
- export { App, type AppInput, CORE_UI_PLUGIN_KEY, type ComposePayload, ComposePayloadSchema, type ComposeRemote, type CompositionDigestInput, type ConstructInput, type ConstructPluginRef, type ConstructedTree, type GateUser, type HostContext, type KnownPlugins, MANIFEST_FILENAME, MANIFEST_VERSION, MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, ManifestSchema, type MountDef, type MountId, type NavDeclaration, type NavItem, type NavManifest, PLUGIN_UI_SHARED_EXPOSES, Plugin, type PluginManifest, PluginManifestSchema, type PluginRef, type PluginRefInput, ROUTE_CONFIG_FILENAME, type RemoteSource, type ResolvedApp, type ResolvedPlugin, type RouteConfigModule, type RouteConfigRef, type RouteOptionsBundle, type RouteRecord, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME, compareNavItems, constructTree, digestOf, parsePluginManifest, resolveApp, resolveMountSegment, containerName as sanitizeContainerName };
9
+ export { App, type AppInput, CORE_UI_PLUGIN_KEY, type ComposePayload, ComposePayloadSchema, type ComposeRemote, type CompositionDigestInput, type ConstructInput, type ConstructPluginRef, type ConstructedTree, type KnownPlugins, MANIFEST_FILENAME, MANIFEST_VERSION, MOUNTS, MOUNT_ALIASES, MOUNT_REGISTRY, MOUNT_REGISTRY_VERSION, ManifestSchema, type MountDef, type MountId, type NavDeclaration, type NavItem, type NavManifest, PLUGIN_UI_SHARED_EXPOSES, Plugin, type PluginManifest, PluginManifestSchema, type PluginRef, type PluginRefInput, ROUTE_CONFIG_FILENAME, type RemoteSource, type ResolvedApp, type ResolvedPlugin, type RouteConfigModule, type RouteConfigRef, type RouteOptionsBundle, type RouteRecord, RouteRecordSchema, SUPPORTED_MANIFEST_VERSION, UI_EXPOSES, UI_REMOTE_ENTRY_FILENAME, UI_REMOTE_SERVER_ENTRY_FILENAME, compareNavItems, constructTree, digestOf, parsePluginManifest, resolveApp, resolveMountSegment, containerName as sanitizeContainerName };
@@ -1,36 +1,29 @@
1
1
  //#region src/ui/manifest/mount-registry.ts
2
2
  const MOUNT_REGISTRY = {
3
3
  public: {
4
- label: "public",
5
- gate: "none",
4
+ guarded: false,
6
5
  implemented: true
7
6
  },
8
7
  authenticated: {
9
- label: "authenticated",
10
- gate: "session",
8
+ guarded: true,
11
9
  implemented: true
12
10
  },
13
11
  admin: {
14
- label: "admin",
15
- gate: "admin",
12
+ guarded: true,
16
13
  implemented: true
17
14
  },
18
15
  org: {
19
- label: "org",
20
- gate: "organization",
21
- parameterized: true,
16
+ guarded: true,
22
17
  implemented: false
23
18
  },
24
19
  team: {
25
- label: "team",
26
- gate: "team",
27
- parameterized: true,
20
+ guarded: true,
28
21
  implemented: false
29
22
  }
30
23
  };
31
24
  const MOUNTS = Object.keys(MOUNT_REGISTRY);
32
25
  /** Bump when the registry shape changes — invalidates all compose digests. */
33
- const MOUNT_REGISTRY_VERSION = 5;
26
+ const MOUNT_REGISTRY_VERSION = 6;
34
27
  /** Migration aliases: legacy `_`-segment names → canonical mounts. */
35
28
  const MOUNT_ALIASES = {
36
29
  auth: "authenticated",
@@ -1 +1 @@
1
- {"version":3,"file":"mount-registry.cjs","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"sourcesContent":["/**\n * Mount registry v2 — gates only, shape-free (ADR 0008 §3).\n *\n * A mount is a gate + a host-owned pathless layout position. It carries no\n * product semantics (no \"dashboard\", no shell, no chrome) — plugins bring\n * their own layouts and route freely inside their subtree. A mount is\n * declared by a ROOT-level pathless layout file whose `_`-prefixed segment\n * names the mount (`_public.tsx` → `public`); the generator derives it and\n * validates it against this registry; gates are attached HOST-side at\n * construction, so a plugin cannot ship a route that escapes its mount's\n * gate (plugins never construct route objects).\n *\n * `org`/`team` are declared vocabulary (parameterized membership gates) but\n * unimplemented — construction rejects them until they land.\n */\n\nexport interface MountDef {\n label: string;\n gate: \"none\" | \"session\" | \"admin\" | \"organization\" | \"team\";\n parameterized?: boolean;\n implemented: boolean;\n}\n\nexport const MOUNT_REGISTRY = {\n public: { label: \"public\", gate: \"none\", implemented: true },\n authenticated: { label: \"authenticated\", gate: \"session\", implemented: true },\n admin: { label: \"admin\", gate: \"admin\", implemented: true },\n org: { label: \"org\", gate: \"organization\", parameterized: true, implemented: false },\n team: { label: \"team\", gate: \"team\", parameterized: true, implemented: false },\n} satisfies Record<string, MountDef>;\n\nexport type MountId = Extract<keyof typeof MOUNT_REGISTRY, string>;\n\nexport const MOUNTS = Object.keys(MOUNT_REGISTRY) as MountId[];\n\n/** Bump when the registry shape changes — invalidates all compose digests. */\nexport const MOUNT_REGISTRY_VERSION = 5;\n\n/** Migration aliases: legacy `_`-segment names → canonical mounts. */\nexport const MOUNT_ALIASES: Record<string, MountId> = {\n auth: \"authenticated\",\n authed: \"authenticated\",\n dashboard: \"authenticated\",\n organization: \"org\",\n};\n\nexport function resolveMountSegment(segment: string): MountId | undefined {\n if (segment in MOUNT_REGISTRY) return segment as MountId;\n return MOUNT_ALIASES[segment];\n}\n"],"mappings":";AAuBA,MAAa,iBAAiB;CAC5B,QAAQ;EAAE,OAAO;EAAU,MAAM;EAAQ,aAAa;CAAK;CAC3D,eAAe;EAAE,OAAO;EAAiB,MAAM;EAAW,aAAa;CAAK;CAC5E,OAAO;EAAE,OAAO;EAAS,MAAM;EAAS,aAAa;CAAK;CAC1D,KAAK;EAAE,OAAO;EAAO,MAAM;EAAgB,eAAe;EAAM,aAAa;CAAM;CACnF,MAAM;EAAE,OAAO;EAAQ,MAAM;EAAQ,eAAe;EAAM,aAAa;CAAM;AAC/E;AAIA,MAAa,SAAS,OAAO,KAAK,cAAc;;AAGhD,MAAa,yBAAyB;;AAGtC,MAAa,gBAAyC;CACpD,MAAM;CACN,QAAQ;CACR,WAAW;CACX,cAAc;AAChB;AAEA,SAAgB,oBAAoB,SAAsC;CACxE,IAAI,WAAW,gBAAgB,OAAO;CACtC,OAAO,cAAc;AACvB"}
1
+ {"version":3,"file":"mount-registry.cjs","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"sourcesContent":["/**\n * Mount registry v2 — shape-free (ADR 0008 §3).\n *\n * A mount is a host-owned pathless layout position. It carries no product\n * semantics (no \"dashboard\", no shell, no chrome) — plugins bring their own\n * layouts and route freely inside their subtree. A mount is declared by a\n * ROOT-level pathless layout file whose `_`-prefixed segment names the mount\n * (`_public.tsx` → `public`); the generator derives it and validates it\n * against this registry; guards are ordinary beforeLoads attached HOST-side\n * at construction from ConstructInput.guards, so a plugin cannot ship a\n * route that escapes its mount's guard (plugins never construct route\n * objects).\n *\n * `org`/`team` are declared vocabulary (parameterized membership) but\n * unimplemented — construction rejects them until they land.\n */\n\nexport interface MountDef {\n /** The composing framework supplies this mount's guard via ConstructInput.guards. */\n guarded: boolean;\n implemented: boolean;\n}\n\nexport const MOUNT_REGISTRY = {\n public: { guarded: false, implemented: true },\n authenticated: { guarded: true, implemented: true },\n admin: { guarded: true, implemented: true },\n org: { guarded: true, implemented: false },\n team: { guarded: true, implemented: false },\n} as const satisfies Record<string, MountDef>;\n\nexport type MountId = Extract<keyof typeof MOUNT_REGISTRY, string>;\n\nexport const MOUNTS = Object.keys(MOUNT_REGISTRY) as MountId[];\n\n/** Bump when the registry shape changes — invalidates all compose digests. */\nexport const MOUNT_REGISTRY_VERSION = 6;\n\n/** Migration aliases: legacy `_`-segment names → canonical mounts. */\nexport const MOUNT_ALIASES: Record<string, MountId> = {\n auth: \"authenticated\",\n authed: \"authenticated\",\n dashboard: \"authenticated\",\n organization: \"org\",\n};\n\nexport function resolveMountSegment(segment: string): MountId | undefined {\n if (segment in MOUNT_REGISTRY) return segment as MountId;\n return MOUNT_ALIASES[segment];\n}\n"],"mappings":";AAuBA,MAAa,iBAAiB;CAC5B,QAAQ;EAAE,SAAS;EAAO,aAAa;CAAK;CAC5C,eAAe;EAAE,SAAS;EAAM,aAAa;CAAK;CAClD,OAAO;EAAE,SAAS;EAAM,aAAa;CAAK;CAC1C,KAAK;EAAE,SAAS;EAAM,aAAa;CAAM;CACzC,MAAM;EAAE,SAAS;EAAM,aAAa;CAAM;AAC5C;AAIA,MAAa,SAAS,OAAO,KAAK,cAAc;;AAGhD,MAAa,yBAAyB;;AAGtC,MAAa,gBAAyC;CACpD,MAAM;CACN,QAAQ;CACR,WAAW;CACX,cAAc;AAChB;AAEA,SAAgB,oBAAoB,SAAsC;CACxE,IAAI,WAAW,gBAAgB,OAAO;CACtC,OAAO,cAAc;AACvB"}
@@ -1,58 +1,51 @@
1
1
  //#region src/ui/manifest/mount-registry.d.ts
2
2
  /**
3
- * Mount registry v2 — gates only, shape-free (ADR 0008 §3).
3
+ * Mount registry v2 — shape-free (ADR 0008 §3).
4
4
  *
5
- * A mount is a gate + a host-owned pathless layout position. It carries no
6
- * product semantics (no "dashboard", no shell, no chrome) — plugins bring
7
- * their own layouts and route freely inside their subtree. A mount is
8
- * declared by a ROOT-level pathless layout file whose `_`-prefixed segment
9
- * names the mount (`_public.tsx` → `public`); the generator derives it and
10
- * validates it against this registry; gates are attached HOST-side at
11
- * construction, so a plugin cannot ship a route that escapes its mount's
12
- * gate (plugins never construct route objects).
5
+ * A mount is a host-owned pathless layout position. It carries no product
6
+ * semantics (no "dashboard", no shell, no chrome) — plugins bring their own
7
+ * layouts and route freely inside their subtree. A mount is declared by a
8
+ * ROOT-level pathless layout file whose `_`-prefixed segment names the mount
9
+ * (`_public.tsx` → `public`); the generator derives it and validates it
10
+ * against this registry; guards are ordinary beforeLoads attached HOST-side
11
+ * at construction from ConstructInput.guards, so a plugin cannot ship a
12
+ * route that escapes its mount's guard (plugins never construct route
13
+ * objects).
13
14
  *
14
- * `org`/`team` are declared vocabulary (parameterized membership gates) but
15
+ * `org`/`team` are declared vocabulary (parameterized membership) but
15
16
  * unimplemented — construction rejects them until they land.
16
17
  */
17
18
  interface MountDef {
18
- label: string;
19
- gate: "none" | "session" | "admin" | "organization" | "team";
20
- parameterized?: boolean;
19
+ /** The composing framework supplies this mount's guard via ConstructInput.guards. */
20
+ guarded: boolean;
21
21
  implemented: boolean;
22
22
  }
23
23
  declare const MOUNT_REGISTRY: {
24
- public: {
25
- label: string;
26
- gate: "none";
27
- implemented: true;
24
+ readonly public: {
25
+ readonly guarded: false;
26
+ readonly implemented: true;
28
27
  };
29
- authenticated: {
30
- label: string;
31
- gate: "session";
32
- implemented: true;
28
+ readonly authenticated: {
29
+ readonly guarded: true;
30
+ readonly implemented: true;
33
31
  };
34
- admin: {
35
- label: string;
36
- gate: "admin";
37
- implemented: true;
32
+ readonly admin: {
33
+ readonly guarded: true;
34
+ readonly implemented: true;
38
35
  };
39
- org: {
40
- label: string;
41
- gate: "organization";
42
- parameterized: true;
43
- implemented: false;
36
+ readonly org: {
37
+ readonly guarded: true;
38
+ readonly implemented: false;
44
39
  };
45
- team: {
46
- label: string;
47
- gate: "team";
48
- parameterized: true;
49
- implemented: false;
40
+ readonly team: {
41
+ readonly guarded: true;
42
+ readonly implemented: false;
50
43
  };
51
44
  };
52
45
  type MountId = Extract<keyof typeof MOUNT_REGISTRY, string>;
53
46
  declare const MOUNTS: MountId[];
54
47
  /** Bump when the registry shape changes — invalidates all compose digests. */
55
- declare const MOUNT_REGISTRY_VERSION = 5;
48
+ declare const MOUNT_REGISTRY_VERSION = 6;
56
49
  /** Migration aliases: legacy `_`-segment names → canonical mounts. */
57
50
  declare const MOUNT_ALIASES: Record<string, MountId>;
58
51
  declare function resolveMountSegment(segment: string): MountId | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"mount-registry.d.cts","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"mappings":";;;;;;;;;;;;;;;;UAgBiB;EACf;EACA;EACA;EACA;;cAGW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAQD,UAAU,qBAAqB;cAE9B,QAAwC;;cAGxC;;cAGA,eAAe,eAAe;iBAO3B,oBAAoB,kBAAkB"}
1
+ {"version":3,"file":"mount-registry.d.cts","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"mappings":";;;;;;;;;;;;;;;;;UAiBiB;;EAEf;EACA;;cAGW;;aACD;aAAgB;;;aACT;aAAe;;;aACvB;aAAe;;;aACjB;aAAe;;;aACd;aAAe;;;KAGb,UAAU,qBAAqB;cAE9B,QAAwC;;cAGxC;;cAGA,eAAe,eAAe;iBAO3B,oBAAoB,kBAAkB"}
@@ -1,58 +1,51 @@
1
1
  //#region src/ui/manifest/mount-registry.d.ts
2
2
  /**
3
- * Mount registry v2 — gates only, shape-free (ADR 0008 §3).
3
+ * Mount registry v2 — shape-free (ADR 0008 §3).
4
4
  *
5
- * A mount is a gate + a host-owned pathless layout position. It carries no
6
- * product semantics (no "dashboard", no shell, no chrome) — plugins bring
7
- * their own layouts and route freely inside their subtree. A mount is
8
- * declared by a ROOT-level pathless layout file whose `_`-prefixed segment
9
- * names the mount (`_public.tsx` → `public`); the generator derives it and
10
- * validates it against this registry; gates are attached HOST-side at
11
- * construction, so a plugin cannot ship a route that escapes its mount's
12
- * gate (plugins never construct route objects).
5
+ * A mount is a host-owned pathless layout position. It carries no product
6
+ * semantics (no "dashboard", no shell, no chrome) — plugins bring their own
7
+ * layouts and route freely inside their subtree. A mount is declared by a
8
+ * ROOT-level pathless layout file whose `_`-prefixed segment names the mount
9
+ * (`_public.tsx` → `public`); the generator derives it and validates it
10
+ * against this registry; guards are ordinary beforeLoads attached HOST-side
11
+ * at construction from ConstructInput.guards, so a plugin cannot ship a
12
+ * route that escapes its mount's guard (plugins never construct route
13
+ * objects).
13
14
  *
14
- * `org`/`team` are declared vocabulary (parameterized membership gates) but
15
+ * `org`/`team` are declared vocabulary (parameterized membership) but
15
16
  * unimplemented — construction rejects them until they land.
16
17
  */
17
18
  interface MountDef {
18
- label: string;
19
- gate: "none" | "session" | "admin" | "organization" | "team";
20
- parameterized?: boolean;
19
+ /** The composing framework supplies this mount's guard via ConstructInput.guards. */
20
+ guarded: boolean;
21
21
  implemented: boolean;
22
22
  }
23
23
  declare const MOUNT_REGISTRY: {
24
- public: {
25
- label: string;
26
- gate: "none";
27
- implemented: true;
24
+ readonly public: {
25
+ readonly guarded: false;
26
+ readonly implemented: true;
28
27
  };
29
- authenticated: {
30
- label: string;
31
- gate: "session";
32
- implemented: true;
28
+ readonly authenticated: {
29
+ readonly guarded: true;
30
+ readonly implemented: true;
33
31
  };
34
- admin: {
35
- label: string;
36
- gate: "admin";
37
- implemented: true;
32
+ readonly admin: {
33
+ readonly guarded: true;
34
+ readonly implemented: true;
38
35
  };
39
- org: {
40
- label: string;
41
- gate: "organization";
42
- parameterized: true;
43
- implemented: false;
36
+ readonly org: {
37
+ readonly guarded: true;
38
+ readonly implemented: false;
44
39
  };
45
- team: {
46
- label: string;
47
- gate: "team";
48
- parameterized: true;
49
- implemented: false;
40
+ readonly team: {
41
+ readonly guarded: true;
42
+ readonly implemented: false;
50
43
  };
51
44
  };
52
45
  type MountId = Extract<keyof typeof MOUNT_REGISTRY, string>;
53
46
  declare const MOUNTS: MountId[];
54
47
  /** Bump when the registry shape changes — invalidates all compose digests. */
55
- declare const MOUNT_REGISTRY_VERSION = 5;
48
+ declare const MOUNT_REGISTRY_VERSION = 6;
56
49
  /** Migration aliases: legacy `_`-segment names → canonical mounts. */
57
50
  declare const MOUNT_ALIASES: Record<string, MountId>;
58
51
  declare function resolveMountSegment(segment: string): MountId | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"mount-registry.d.mts","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"mappings":";;;;;;;;;;;;;;;;UAgBiB;EACf;EACA;EACA;EACA;;cAGW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAQD,UAAU,qBAAqB;cAE9B,QAAwC;;cAGxC;;cAGA,eAAe,eAAe;iBAO3B,oBAAoB,kBAAkB"}
1
+ {"version":3,"file":"mount-registry.d.mts","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"mappings":";;;;;;;;;;;;;;;;;UAiBiB;;EAEf;EACA;;cAGW;;aACD;aAAgB;;;aACT;aAAe;;;aACvB;aAAe;;;aACjB;aAAe;;;aACd;aAAe;;;KAGb,UAAU,qBAAqB;cAE9B,QAAwC;;cAGxC;;cAGA,eAAe,eAAe;iBAO3B,oBAAoB,kBAAkB"}
@@ -1,36 +1,29 @@
1
1
  //#region src/ui/manifest/mount-registry.ts
2
2
  const MOUNT_REGISTRY = {
3
3
  public: {
4
- label: "public",
5
- gate: "none",
4
+ guarded: false,
6
5
  implemented: true
7
6
  },
8
7
  authenticated: {
9
- label: "authenticated",
10
- gate: "session",
8
+ guarded: true,
11
9
  implemented: true
12
10
  },
13
11
  admin: {
14
- label: "admin",
15
- gate: "admin",
12
+ guarded: true,
16
13
  implemented: true
17
14
  },
18
15
  org: {
19
- label: "org",
20
- gate: "organization",
21
- parameterized: true,
16
+ guarded: true,
22
17
  implemented: false
23
18
  },
24
19
  team: {
25
- label: "team",
26
- gate: "team",
27
- parameterized: true,
20
+ guarded: true,
28
21
  implemented: false
29
22
  }
30
23
  };
31
24
  const MOUNTS = Object.keys(MOUNT_REGISTRY);
32
25
  /** Bump when the registry shape changes — invalidates all compose digests. */
33
- const MOUNT_REGISTRY_VERSION = 5;
26
+ const MOUNT_REGISTRY_VERSION = 6;
34
27
  /** Migration aliases: legacy `_`-segment names → canonical mounts. */
35
28
  const MOUNT_ALIASES = {
36
29
  auth: "authenticated",
@@ -1 +1 @@
1
- {"version":3,"file":"mount-registry.mjs","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"sourcesContent":["/**\n * Mount registry v2 — gates only, shape-free (ADR 0008 §3).\n *\n * A mount is a gate + a host-owned pathless layout position. It carries no\n * product semantics (no \"dashboard\", no shell, no chrome) — plugins bring\n * their own layouts and route freely inside their subtree. A mount is\n * declared by a ROOT-level pathless layout file whose `_`-prefixed segment\n * names the mount (`_public.tsx` → `public`); the generator derives it and\n * validates it against this registry; gates are attached HOST-side at\n * construction, so a plugin cannot ship a route that escapes its mount's\n * gate (plugins never construct route objects).\n *\n * `org`/`team` are declared vocabulary (parameterized membership gates) but\n * unimplemented — construction rejects them until they land.\n */\n\nexport interface MountDef {\n label: string;\n gate: \"none\" | \"session\" | \"admin\" | \"organization\" | \"team\";\n parameterized?: boolean;\n implemented: boolean;\n}\n\nexport const MOUNT_REGISTRY = {\n public: { label: \"public\", gate: \"none\", implemented: true },\n authenticated: { label: \"authenticated\", gate: \"session\", implemented: true },\n admin: { label: \"admin\", gate: \"admin\", implemented: true },\n org: { label: \"org\", gate: \"organization\", parameterized: true, implemented: false },\n team: { label: \"team\", gate: \"team\", parameterized: true, implemented: false },\n} satisfies Record<string, MountDef>;\n\nexport type MountId = Extract<keyof typeof MOUNT_REGISTRY, string>;\n\nexport const MOUNTS = Object.keys(MOUNT_REGISTRY) as MountId[];\n\n/** Bump when the registry shape changes — invalidates all compose digests. */\nexport const MOUNT_REGISTRY_VERSION = 5;\n\n/** Migration aliases: legacy `_`-segment names → canonical mounts. */\nexport const MOUNT_ALIASES: Record<string, MountId> = {\n auth: \"authenticated\",\n authed: \"authenticated\",\n dashboard: \"authenticated\",\n organization: \"org\",\n};\n\nexport function resolveMountSegment(segment: string): MountId | undefined {\n if (segment in MOUNT_REGISTRY) return segment as MountId;\n return MOUNT_ALIASES[segment];\n}\n"],"mappings":";AAuBA,MAAa,iBAAiB;CAC5B,QAAQ;EAAE,OAAO;EAAU,MAAM;EAAQ,aAAa;CAAK;CAC3D,eAAe;EAAE,OAAO;EAAiB,MAAM;EAAW,aAAa;CAAK;CAC5E,OAAO;EAAE,OAAO;EAAS,MAAM;EAAS,aAAa;CAAK;CAC1D,KAAK;EAAE,OAAO;EAAO,MAAM;EAAgB,eAAe;EAAM,aAAa;CAAM;CACnF,MAAM;EAAE,OAAO;EAAQ,MAAM;EAAQ,eAAe;EAAM,aAAa;CAAM;AAC/E;AAIA,MAAa,SAAS,OAAO,KAAK,cAAc;;AAGhD,MAAa,yBAAyB;;AAGtC,MAAa,gBAAyC;CACpD,MAAM;CACN,QAAQ;CACR,WAAW;CACX,cAAc;AAChB;AAEA,SAAgB,oBAAoB,SAAsC;CACxE,IAAI,WAAW,gBAAgB,OAAO;CACtC,OAAO,cAAc;AACvB"}
1
+ {"version":3,"file":"mount-registry.mjs","names":[],"sources":["../../../src/ui/manifest/mount-registry.ts"],"sourcesContent":["/**\n * Mount registry v2 — shape-free (ADR 0008 §3).\n *\n * A mount is a host-owned pathless layout position. It carries no product\n * semantics (no \"dashboard\", no shell, no chrome) — plugins bring their own\n * layouts and route freely inside their subtree. A mount is declared by a\n * ROOT-level pathless layout file whose `_`-prefixed segment names the mount\n * (`_public.tsx` → `public`); the generator derives it and validates it\n * against this registry; guards are ordinary beforeLoads attached HOST-side\n * at construction from ConstructInput.guards, so a plugin cannot ship a\n * route that escapes its mount's guard (plugins never construct route\n * objects).\n *\n * `org`/`team` are declared vocabulary (parameterized membership) but\n * unimplemented — construction rejects them until they land.\n */\n\nexport interface MountDef {\n /** The composing framework supplies this mount's guard via ConstructInput.guards. */\n guarded: boolean;\n implemented: boolean;\n}\n\nexport const MOUNT_REGISTRY = {\n public: { guarded: false, implemented: true },\n authenticated: { guarded: true, implemented: true },\n admin: { guarded: true, implemented: true },\n org: { guarded: true, implemented: false },\n team: { guarded: true, implemented: false },\n} as const satisfies Record<string, MountDef>;\n\nexport type MountId = Extract<keyof typeof MOUNT_REGISTRY, string>;\n\nexport const MOUNTS = Object.keys(MOUNT_REGISTRY) as MountId[];\n\n/** Bump when the registry shape changes — invalidates all compose digests. */\nexport const MOUNT_REGISTRY_VERSION = 6;\n\n/** Migration aliases: legacy `_`-segment names → canonical mounts. */\nexport const MOUNT_ALIASES: Record<string, MountId> = {\n auth: \"authenticated\",\n authed: \"authenticated\",\n dashboard: \"authenticated\",\n organization: \"org\",\n};\n\nexport function resolveMountSegment(segment: string): MountId | undefined {\n if (segment in MOUNT_REGISTRY) return segment as MountId;\n return MOUNT_ALIASES[segment];\n}\n"],"mappings":";AAuBA,MAAa,iBAAiB;CAC5B,QAAQ;EAAE,SAAS;EAAO,aAAa;CAAK;CAC5C,eAAe;EAAE,SAAS;EAAM,aAAa;CAAK;CAClD,OAAO;EAAE,SAAS;EAAM,aAAa;CAAK;CAC1C,KAAK;EAAE,SAAS;EAAM,aAAa;CAAM;CACzC,MAAM;EAAE,SAAS;EAAM,aAAa;CAAM;AAC5C;AAIA,MAAa,SAAS,OAAO,KAAK,cAAc;;AAGhD,MAAa,yBAAyB;;AAGtC,MAAa,gBAAyC;CACpD,MAAM;CACN,QAAQ;CACR,WAAW;CACX,cAAc;AAChB;AAEA,SAAgB,oBAAoB,SAAsC;CACxE,IAAI,WAAW,gBAAgB,OAAO;CACtC,OAAO,cAAc;AACvB"}