import { CORE_PERMISSION_DECLARATIONS, createPermissionRegistry, type PermissionDeclaration, type PermissionRegistry, } from "../authorization/index.js"; import type { DiscoveredModule } from "./discovery.js"; const CORE_PERMISSION_NAMESPACE_PREFIX = "core."; export type ModulePermissionCompositionErrorCode = "MODULE_PERMISSION_RESERVED_NAMESPACE"; /** Thrown only at composition time, never per-request. Carries the offending module and * permission id deliberately — like `PermissionRegistryError`, this is a startup-time * configuration error, not a runtime response, and neither value is sensitive. Never carries a * module directory, manifest path, caught error, or stack detail. */ export class ModulePermissionCompositionError extends Error { readonly code: ModulePermissionCompositionErrorCode; readonly moduleId: string; readonly permissionId: string; constructor(moduleId: string, permissionId: string) { super( `Module "${moduleId}" cannot declare permission "${permissionId}": the "core." namespace is reserved for Core's own permissions.`, ); this.name = "ModulePermissionCompositionError"; this.code = "MODULE_PERMISSION_RESERVED_NAMESPACE"; this.moduleId = moduleId; this.permissionId = permissionId; this.stack = `${this.name}: ${this.message}`; Object.freeze(this); } } /** * Core's module-integration-layer composition helper: builds the one platform permission * registry from Core's own declarations (`CORE_PERMISSION_DECLARATIONS`) plus every already * discovered module's manifest-declared permissions. Deliberately lives in `core/src/modules`, * not `core/src/authorization` — the dependency runs one way only, this layer depends on * authorization's `createPermissionRegistry`, and authorization never imports module discovery * or runtime. This is pure, synchronous, in-memory composition over already-discovered, * already-validated data: no filesystem, environment, database, network, or module-code * execution happens here, and the caller (not this function) is responsible for having already * produced a complete, successfully discovered module list (for example via `discoverModules`). * * Declaration order is deterministic and meaningful for duplicate detection: Core's own * declarations first, then each module in the exact order it appears in `modules`, and within a * module its declarations in manifest-array order. Duplicate exact permission ids fail via the * same `PermissionRegistryError` `createPermissionRegistry` already throws for any duplicate; * this function does not reimplement or duplicate that detection. * * `core.*` is reserved for Core's own permissions. A discovered module contributing any * `core.`-prefixed permission id — regardless of that module's own id, and regardless of whether * it happens to exactly collide with an existing declaration — fails composition with * `ModulePermissionCompositionError` rather than silently registering or silently colliding. This * check runs before a declaration is ever added to the list `createPermissionRegistry` sees, so it * is also what actually catches a module attempting to collide with a Core-owned id specifically: * every Core-owned id is itself `core.`-prefixed, module ids may never contain a dot (so only a * module whose own id is literally `"core"` can produce a `core.`-prefixed permission at all), and * a genuine module-vs-module collision is structurally unreachable through real discovery — module * ids are unique (discovery itself rejects a duplicate) and every permission id is namespaced by * its own module's id. `PermissionRegistryError` therefore never fires for a real, validly * discovered installed-module set; `createPermissionRegistry`'s duplicate rejection remains here as * a defensive, unconditional guarantee regardless, and is exercised directly with deliberately * conflicting test data rather than through a scenario real discovery could ever produce. */ export function composePlatformPermissionRegistry( modules: readonly DiscoveredModule[], ): PermissionRegistry { const declarations: PermissionDeclaration[] = [...CORE_PERMISSION_DECLARATIONS]; for (const module of modules) { for (const declaration of module.manifest.permissions) { if (declaration.id.startsWith(CORE_PERMISSION_NAMESPACE_PREFIX)) { throw new ModulePermissionCompositionError(module.manifest.id, declaration.id); } declarations.push(declaration); } } return createPermissionRegistry(declarations); }