@aglyn/aglyn 1.0.0-beta.287 → 1.0.0-beta.288
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.
- package/package.json +11 -11
- package/src/lib/app-utils/business-profile-values.d.ts +15 -3
- package/src/lib/app-utils/business-profile-values.js +19 -3
- package/src/lib/app-utils/business-profile-values.js.map +1 -1
- package/src/lib/app-utils/business-profile.d.ts +5 -3
- package/src/lib/app-utils/business-profile.js +2 -1
- package/src/lib/app-utils/business-profile.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +16 -16
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/host-permissions.d.ts +31 -1
- package/src/lib/app-utils/host-permissions.js +45 -5
- package/src/lib/app-utils/host-permissions.js.map +1 -1
- package/src/lib/app-utils/org-permissions.d.ts +8 -0
- package/src/lib/app-utils/org-permissions.js +2 -0
- package/src/lib/app-utils/org-permissions.js.map +1 -1
- package/src/lib/app-utils/org-role-grants.js +3 -3
- package/src/lib/app-utils/org-role-grants.js.map +1 -1
- package/src/lib/plugin-manager/plugin-entitlements.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-entitlements.js +4 -2
- package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/org-role-grants.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WHAT EACH ORG ROLE MAY DO, AS DATA (AGL-243, AGL-3950).\n *\n * The core permission catalog and the core keys each org role holds when\n * nothing refines it. `org-permissions.ts` builds the console's live\n * catalog from these (adding the keys plugins declare, with their own role\n * defaults), and the native apps read the same grants through the generated\n * contracts, so a native gate (`hosts.create`, `data.manage`) answers as the\n * console does for a member row that carries no `resolvedPermissions` stamp.\n *\n * `ORG_ROLE_GRANTS` in the contracts is this table with every plugin's\n * declared catalog keys added (`assembleOrgRoleGrants`, run by\n * `generate:native-contracts` over each plugin's declarations), which is\n * exactly the set the console's `DEFAULT_ROLE_PERMISSIONS` holds once the\n * plugins have registered.\n *\n * NO IMPORTS, on purpose: the native apps read it through the generated\n * contracts.\n */\n\n/** The four org roles. */\nexport type OrgRoleName = 'owner' | 'admin' | 'editor' | 'viewer'\n\n/**\n * Where a key is decided (AGL-4047). `org`: by the member's org role, custom\n * role and overrides, the same on every site. `site`: as `org` for a member\n * who reaches the whole workspace, and per site for a site collaborator,\n * from the host role they hold there and that site's toggle.\n */\nexport type OrgPermissionScope = 'org' | 'site'\n\n/** One key of the org permission catalog. */\nexport interface OrgPermissionEntry {\n key: string\n label: string\n description: string\n /** The plugin that declared it; absent for a core key. */\n pluginId?: string\n /** The group the role editor files it under (`ORG_PERMISSION_GROUPS`, or the declaring plugin's id). */\n group?: string\n /** Where it is decided; absent reads as `org`. */\n scope?: OrgPermissionScope\n /**\n * The broader key this one is a finer part of (AGL-4047): never granted\n * without it, and following it where no custom role or override sets this\n * key. Absent for a key that refines nothing.\n */\n refines?: string\n /**\n * The key this one follows where no custom role or override sets it,\n * without needing it (AGL-4047): granted on its own, it holds while that\n * key is refused. Absent for a key that follows nothing, and for one that\n * refines.\n */\n follows?: string\n}\n\n/** A heading the role editor groups keys under. */\nexport interface OrgPermissionGroup {\n id: string\n label: string\n /** The plugin whose keys it holds; absent for a core group. */\n pluginId?: string\n}\n\n/** The catalog and each role's default grants. */\nexport interface OrgRoleGrants {\n /** Every key, in the order the role editor lists them. */\n permissions: OrgPermissionEntry[]\n /** The keys each role holds when no custom role or per-member override sets them. */\n grants: Record<OrgRoleName, string[]>\n /** The groups the keys are filed under, in display order; absent in a catalog written before them. */\n groups?: OrgPermissionGroup[]\n}\n\n/**\n * A key a plugin declares into the catalog: `PluginOrgPermissionDeclaration`\n * with org-role defaults, or a `registerPluginPermissions` key (`managePos`),\n * whose `defaults` are per role tier, the owner holding the admin tier's.\n */\nexport interface OrgPermissionDeclaration {\n key: string\n label: string\n description?: string\n roleDefaults?: Record<OrgRoleName, boolean>\n /** The tier defaults of a `registerPluginPermissions` key, used when `roleDefaults` is absent. */\n defaults?: { admin: boolean; editor: boolean; viewer: boolean }\n /** The host-role defaults of a key a site collaborator holds per site. */\n hostRoleDefaults?: Record<string, boolean>\n /** The group it is filed under; absent: a group named by the declaring plugin's id. */\n group?: string\n /** That group's heading, for a group the core does not name. */\n groupLabel?: string\n pluginId?: string\n /** The broader key it refines (AGL-4047). */\n refines?: string\n /** The key it follows without needing it (AGL-4047). */\n follows?: string\n}\n\n/** A custom role (`orgs/{orgId}/roles/{roleId}`) or a member's overrides: explicit verdicts per key. */\nexport interface OrgPermissionLayer {\n permissions?: Record<string, boolean>\n}\n\nexport const ORG_ROLE_NAMES: readonly OrgRoleName[] = ['owner', 'admin', 'editor', 'viewer']\n\n/**\n * The core groups, in display order. A plugin's keys are filed under a group\n * named by the plugin's id unless its declaration names one of these, so no\n * core group id is a plugin id.\n */\nexport const CORE_ORG_PERMISSION_GROUPS: readonly OrgPermissionGroup[] = [\n { id: 'organization', label: 'Organization' },\n { id: 'billing', label: 'Billing' },\n { id: 'team', label: 'Team' },\n { id: 'sites', label: 'Sites' },\n { id: 'datasets', label: 'Data' },\n { id: 'extensions', label: 'Marketplace and plugins' },\n]\n\n/**\n * A declaration's verdict for each org role: its `roleDefaults`, or, for a\n * `registerPluginPermissions` key, its tier `defaults` with the owner on the\n * admin tier (the tier `ORG_ROLE_TIER` resolves the owner onto).\n */\nexport function declarationRoleDefaults(\n declaration: Pick<OrgPermissionDeclaration, 'roleDefaults' | 'defaults'>,\n): Record<OrgRoleName, boolean> {\n if (declaration.roleDefaults) {\n return {\n owner: declaration.roleDefaults.owner === true,\n admin: declaration.roleDefaults.admin === true,\n editor: declaration.roleDefaults.editor === true,\n viewer: declaration.roleDefaults.viewer === true,\n }\n }\n const tiers = declaration.defaults\n return {\n owner: tiers?.admin === true,\n admin: tiers?.admin === true,\n editor: tiers?.editor === true,\n viewer: tiers?.viewer === true,\n }\n}\n\n/**\n * The core keys, in display order for role editors. Every key here is\n * enforced server-side (`apps/console/specs/org-permissions-are-enforced.spec.ts`).\n */\nexport const CORE_ORG_PERMISSION_ENTRIES: readonly OrgPermissionEntry[] = [\n {\n key: 'org.settings',\n group: 'organization',\n label: 'Organization settings',\n description: 'Rename the organization, change its slug, transfer it.',\n },\n {\n key: 'org.auditLog',\n group: 'organization',\n label: 'Activity & audit log',\n description: 'See the organization activity feed.',\n },\n {\n key: 'org.apiKeys',\n group: 'organization',\n label: 'Manage API keys',\n description: 'Create and revoke the workspace’s REST API keys.',\n },\n {\n key: 'org.privacy',\n group: 'organization',\n label: 'Privacy requests',\n description: 'Erase a person from every site in the workspace when they ask.',\n },\n {\n key: 'org.holds',\n group: 'organization',\n label: 'Holds and reviews',\n description: 'See what is held, flagged or locked on the workspace, and request a review.',\n },\n // Workspace acts that asked for the owner or admin role (AGL-4047), each\n // a key of its own whose default is exactly those roles.\n {\n key: 'org.export',\n group: 'organization',\n label: 'Export workspace data',\n description: 'Download a copy of everything the workspace holds.',\n },\n {\n key: 'org.sendingDomains',\n group: 'organization',\n label: 'Sending domains',\n description: 'Add, verify and remove the domains the workspace’s mail is sent from.',\n },\n {\n key: 'org.businessProfile',\n group: 'organization',\n label: 'Business profile',\n description: 'Change the services, audience and tone every site starts from.',\n },\n {\n key: 'billing.view',\n group: 'billing',\n label: 'View billing',\n description: 'See the plan, usage meters, and invoices.',\n },\n {\n key: 'billing.manage',\n group: 'billing',\n label: 'Manage billing',\n description: 'Change plans, buy add-ons, update payment details.',\n },\n {\n key: 'members.manage',\n group: 'team',\n label: 'Manage members',\n description: 'Invite, remove, and re-role organization members.',\n },\n {\n key: 'hosts.create',\n group: 'sites',\n label: 'Create sites',\n description: 'Create new sites in the organization.',\n },\n {\n key: 'hosts.delete',\n group: 'sites',\n label: 'Delete sites',\n description: 'Delete sites the member can access.',\n },\n // The site keys (AGL-4047): each replaced a gate on the member's role on a\n // site, and keeps that role as its preset (`CORE_SITE_ROLE_PRESETS`).\n {\n key: 'site.edit',\n group: 'sites',\n scope: 'site',\n label: 'Edit site content',\n description: 'Create and change pages, content and everything else on a site, without making it live.',\n },\n {\n key: 'site.publish',\n group: 'sites',\n scope: 'site',\n label: 'Publish site content',\n description: 'Make pages and content live, take them down or schedule them, and change redirects.',\n },\n {\n key: 'site.sensitive',\n group: 'sites',\n scope: 'site',\n label: 'See orders and webhooks',\n description: 'Read a site’s orders and returns, and set up its webhooks.',\n },\n {\n key: 'site.manage',\n group: 'sites',\n scope: 'site',\n label: 'Manage site settings',\n description: 'Manage a site’s people, domain, address and plugins, change its plugin settings and security allow-lists, import, export and delete it.',\n },\n {\n key: 'data.manage',\n group: 'datasets',\n label: 'Manage data',\n description: 'Create, edit, and delete organization datasets.',\n },\n // Workspace content that asked for the owner, admin or editor role\n // (AGL-4047), each a key whose default is exactly those roles.\n {\n key: 'org.media',\n group: 'datasets',\n label: 'Edit the workspace media library',\n description: 'Upload, change, move and remove the files in the media library every site shares.',\n },\n {\n key: 'org.eraseRecords',\n group: 'datasets',\n label: 'Delete workspace records',\n description: 'Delete records the whole workspace shares, such as dataset rows, contacts and lists.',\n },\n {\n key: 'marketplace.publish',\n group: 'extensions',\n label: 'Publish to marketplace',\n description: 'Publish listings under the organization profile.',\n },\n {\n key: 'plugins.install',\n group: 'extensions',\n label: 'Install plugins',\n description: 'Install or remove marketplace plugins.',\n },\n]\n\nconst CORE_KEYS = CORE_ORG_PERMISSION_ENTRIES.map((entry) => entry.key)\n\n/**\n * The core keys whose preset is the member's role ON THE SITE (AGL-4047), and\n * each site role's verdict: the sets the rules' `canWriteHostContent`,\n * `canPublishHostContent`, `canReadHostSensitive` and the site-admin checks\n * asked before they asked these keys. A member's preset on a site is the\n * verdict for the role they hold there (the owner and admins are Admin on\n * every site); a custom role, their own permissions and that site's toggle\n * then grant or refuse it. Each workspace role's default below is the verdict\n * for its own site role, which is the answer with no site named.\n */\nexport const CORE_SITE_ROLE_PRESETS: Readonly<Record<string, Readonly<Record<string, boolean>>>> = {\n 'site.edit': { admin: true, editor: true, author: true, viewer: false },\n 'site.publish': { admin: true, editor: true, author: false, viewer: false },\n 'site.sensitive': { admin: true, editor: true, author: false, viewer: false },\n 'site.manage': { admin: true, editor: false, author: false, viewer: false },\n}\n\n/** The workspace editor's site keys: its own site role's preset. */\nconst EDITOR_SITE_KEYS = Object.keys(CORE_SITE_ROLE_PRESETS).filter((key) => CORE_SITE_ROLE_PRESETS[key].editor)\n\n/**\n * The core keys each org role holds when nothing refines it: owners and\n * admins hold every core key (owner-only acts like deleting the\n * organization stay role-checked, not permission-checked); editors work on\n * content and see no money or roster controls; viewers read only.\n */\nexport const CORE_ORG_ROLE_GRANTS: Readonly<Record<OrgRoleName, readonly string[]>> = {\n owner: CORE_KEYS,\n admin: CORE_KEYS,\n editor: ['data.manage', 'org.media', 'org.eraseRecords', 'marketplace.publish', 'plugins.install', ...EDITOR_SITE_KEYS],\n viewer: [],\n}\n\n/** The core's catalog and grants; the contracts' value adds the plugins' keys. */\nexport const ORG_ROLE_GRANTS: OrgRoleGrants = {\n permissions: CORE_ORG_PERMISSION_ENTRIES.map((entry) => ({ ...entry })),\n groups: CORE_ORG_PERMISSION_GROUPS.map((group) => ({ ...group })),\n grants: {\n owner: [...CORE_ORG_ROLE_GRANTS.owner],\n admin: [...CORE_ORG_ROLE_GRANTS.admin],\n editor: [...CORE_ORG_ROLE_GRANTS.editor],\n viewer: [...CORE_ORG_ROLE_GRANTS.viewer],\n },\n}\n\n/**\n * The catalog with plugins' declared keys after the core ones, each role\n * holding a declared key when its declaration's defaults say so. A\n * declaration naming a key already taken is refused, as the console's\n * catalog refuses it: the first declaration keeps its meaning and defaults.\n * Every declaration list after the first is folded in the same way, so the\n * org catalog keys (`PluginOrgPermissionDeclaration`) and the\n * `registerPluginPermissions` keys land in one catalog (AGL-4047).\n */\nexport function assembleOrgRoleGrants(\n core: OrgRoleGrants,\n ...declarationLists: ReadonlyArray<readonly OrgPermissionDeclaration[]>\n): OrgRoleGrants {\n const permissions = [...core.permissions]\n const groups = [...(core.groups ?? [])]\n const grants = Object.fromEntries(ORG_ROLE_NAMES.map((role) => [role, [...core.grants[role]]])) as Record<\n OrgRoleName,\n string[]\n >\n const taken = new Set(permissions.map((entry) => entry.key))\n const groupIds = new Set(groups.map((group) => group.id))\n for (const declaration of declarationLists.flat()) {\n if (!declaration?.key || taken.has(declaration.key)) continue\n taken.add(declaration.key)\n const group = declaration.group ?? declaration.pluginId\n permissions.push({\n key: declaration.key,\n label: declaration.label,\n description: declaration.description ?? '',\n ...(declaration.pluginId ? { pluginId: declaration.pluginId } : {}),\n ...(group ? { group } : {}),\n scope: declaration.hostRoleDefaults ? 'site' : 'org',\n ...(declaration.refines && declaration.refines !== declaration.key ? { refines: declaration.refines } : {}),\n ...(declaration.follows && declaration.follows !== declaration.key && !declaration.refines\n ? { follows: declaration.follows }\n : {}),\n })\n if (group && !groupIds.has(group)) {\n groupIds.add(group)\n groups.push({\n id: group,\n label: declaration.groupLabel ?? group,\n ...(declaration.pluginId ? { pluginId: declaration.pluginId } : {}),\n })\n } else if (group && declaration.pluginId) {\n // A plugin's group opened by a declaration that names no plugin (an org\n // catalog declaration, AGL-4047) is still that plugin's: the first of\n // its declarations that names one says so.\n const index = groups.findIndex((entry) => entry.id === group)\n if (index >= 0 && !groups[index].pluginId) groups[index] = { ...groups[index], pluginId: declaration.pluginId }\n }\n const defaults = declarationRoleDefaults(declaration)\n for (const role of ORG_ROLE_NAMES) {\n if (defaults[role]) grants[role].push(declaration.key)\n }\n }\n return { permissions, grants, groups }\n}\n\n/**\n * A member's effective permissions, as the console's `resolveOrgPermissions`\n * answers them: the role's defaults, then the custom role's explicit\n * verdicts (when the member has one), then the member's own overrides,\n * later layers winning key by key, over the catalog's keys only. A missing\n * role reads as viewer; a role the catalog does not know holds nothing.\n */\nexport function rolePermissions(\n catalog: OrgRoleGrants,\n role: string | null | undefined,\n customRole?: OrgPermissionLayer | null,\n overrides?: OrgPermissionLayer | null,\n): Record<string, boolean> {\n const keys = catalog.permissions.map((entry) => entry.key)\n const name = role ?? 'viewer'\n const held = new Set((catalog.grants as Record<string, string[] | undefined>)[name] ?? [])\n const out: Record<string, boolean> = Object.fromEntries(keys.map((key) => [key, held.has(key)]))\n for (const layer of [customRole, overrides]) {\n const values = layer?.permissions\n if (!values) continue\n for (const key of keys) {\n if (typeof values[key] === 'boolean') out[key] = values[key]\n }\n }\n // A key that refines a broader one (AGL-4047) is never held without it, and\n // follows it where neither layer set the key; a key that only follows one\n // takes its verdict where neither layer set the key, and nothing more.\n // Applied broader keys first, so a chain settles in one pass; a refinement\n // that loops is left alone.\n const broader = new Map<string, string>()\n const followsOnly = new Set<string>()\n for (const entry of catalog.permissions) {\n if (entry.refines && entry.refines in out) broader.set(entry.key, entry.refines)\n else if (entry.follows && entry.follows in out) {\n broader.set(entry.key, entry.follows)\n followsOnly.add(entry.key)\n }\n }\n const depth = (key: string): number => {\n const seen = new Set<string>()\n let steps = 0\n let current = key\n while (broader.has(current)) {\n if (seen.has(current)) return -1\n seen.add(current)\n current = broader.get(current) as string\n steps += 1\n }\n return steps\n }\n const ordered = [...broader.keys()]\n .map((key) => ({ key, steps: depth(key) }))\n .filter((entry) => entry.steps > 0)\n .sort((a, b) => a.steps - b.steps)\n for (const { key } of ordered) {\n let stored: boolean | undefined\n for (const layer of [customRole, overrides]) {\n const value = layer?.permissions?.[key]\n if (typeof value === 'boolean') stored = value\n }\n const held = out[broader.get(key) as string] === true\n out[key] = followsOnly.has(key) ? (stored === undefined ? held : stored) : held && (stored === undefined ? true : stored)\n }\n return out\n}\n"],"names":["ORG_ROLE_NAMES","CORE_ORG_PERMISSION_GROUPS","id","label","declarationRoleDefaults","declaration","roleDefaults","owner","admin","editor","viewer","tiers","defaults","CORE_ORG_PERMISSION_ENTRIES","key","group","description","scope","CORE_KEYS","map","entry","CORE_SITE_ROLE_PRESETS","author","EDITOR_SITE_KEYS","Object","keys","filter","CORE_ORG_ROLE_GRANTS","ORG_ROLE_GRANTS","permissions","groups","grants","assembleOrgRoleGrants","core","declarationLists","fromEntries","role","taken","Set","groupIds","flat","has","add","pluginId","push","hostRoleDefaults","refines","follows","groupLabel","index","findIndex","rolePermissions","catalog","customRole","overrides","name","held","out","layer","values","broader","Map","followsOnly","set","depth","seen","steps","current","get","ordered","sort","a","b","stored","value","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/org-role-grants.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * WHAT EACH ORG ROLE MAY DO, AS DATA (AGL-243, AGL-3950).\n *\n * The core permission catalog and the core keys each org role holds when\n * nothing refines it. `org-permissions.ts` builds the console's live\n * catalog from these (adding the keys plugins declare, with their own role\n * defaults), and the native apps read the same grants through the generated\n * contracts, so a native gate (`hosts.create`, `data.manage`) answers as the\n * console does for a member row that carries no `resolvedPermissions` stamp.\n *\n * `ORG_ROLE_GRANTS` in the contracts is this table with every plugin's\n * declared catalog keys added (`assembleOrgRoleGrants`, run by\n * `generate:native-contracts` over each plugin's declarations), which is\n * exactly the set the console's `DEFAULT_ROLE_PERMISSIONS` holds once the\n * plugins have registered.\n *\n * NO IMPORTS, on purpose: the native apps read it through the generated\n * contracts.\n */\n\n/** The four org roles. */\nexport type OrgRoleName = 'owner' | 'admin' | 'editor' | 'viewer'\n\n/**\n * Where a key is decided (AGL-4047). `org`: by the member's org role, custom\n * role and overrides, the same on every site. `site`: as `org` for a member\n * who reaches the whole workspace, and per site for a site collaborator,\n * from the host role they hold there and that site's toggle.\n */\nexport type OrgPermissionScope = 'org' | 'site'\n\n/** One key of the org permission catalog. */\nexport interface OrgPermissionEntry {\n key: string\n label: string\n description: string\n /** The plugin that declared it; absent for a core key. */\n pluginId?: string\n /** The group the role editor files it under (`ORG_PERMISSION_GROUPS`, or the declaring plugin's id). */\n group?: string\n /** Where it is decided; absent reads as `org`. */\n scope?: OrgPermissionScope\n /**\n * The broader key this one is a finer part of (AGL-4047): never granted\n * without it, and following it where no custom role or override sets this\n * key. Absent for a key that refines nothing.\n */\n refines?: string\n /**\n * The key this one follows where no custom role or override sets it,\n * without needing it (AGL-4047): granted on its own, it holds while that\n * key is refused. Absent for a key that follows nothing, and for one that\n * refines.\n */\n follows?: string\n}\n\n/** A heading the role editor groups keys under. */\nexport interface OrgPermissionGroup {\n id: string\n label: string\n /** The plugin whose keys it holds; absent for a core group. */\n pluginId?: string\n}\n\n/** The catalog and each role's default grants. */\nexport interface OrgRoleGrants {\n /** Every key, in the order the role editor lists them. */\n permissions: OrgPermissionEntry[]\n /** The keys each role holds when no custom role or per-member override sets them. */\n grants: Record<OrgRoleName, string[]>\n /** The groups the keys are filed under, in display order; absent in a catalog written before them. */\n groups?: OrgPermissionGroup[]\n}\n\n/**\n * A key a plugin declares into the catalog: `PluginOrgPermissionDeclaration`\n * with org-role defaults, or a `registerPluginPermissions` key (`managePos`),\n * whose `defaults` are per role tier, the owner holding the admin tier's.\n */\nexport interface OrgPermissionDeclaration {\n key: string\n label: string\n description?: string\n roleDefaults?: Record<OrgRoleName, boolean>\n /** The tier defaults of a `registerPluginPermissions` key, used when `roleDefaults` is absent. */\n defaults?: { admin: boolean; editor: boolean; viewer: boolean }\n /** The host-role defaults of a key a site collaborator holds per site. */\n hostRoleDefaults?: Record<string, boolean>\n /** The group it is filed under; absent: a group named by the declaring plugin's id. */\n group?: string\n /** That group's heading, for a group the core does not name. */\n groupLabel?: string\n pluginId?: string\n /** The broader key it refines (AGL-4047). */\n refines?: string\n /** The key it follows without needing it (AGL-4047). */\n follows?: string\n}\n\n/** A custom role (`orgs/{orgId}/roles/{roleId}`) or a member's overrides: explicit verdicts per key. */\nexport interface OrgPermissionLayer {\n permissions?: Record<string, boolean>\n}\n\nexport const ORG_ROLE_NAMES: readonly OrgRoleName[] = ['owner', 'admin', 'editor', 'viewer']\n\n/**\n * The core groups, in display order. A plugin's keys are filed under a group\n * named by the plugin's id unless its declaration names one of these, so no\n * core group id is a plugin id.\n */\nexport const CORE_ORG_PERMISSION_GROUPS: readonly OrgPermissionGroup[] = [\n { id: 'organization', label: 'Organization' },\n { id: 'billing', label: 'Billing' },\n { id: 'team', label: 'Team' },\n { id: 'sites', label: 'Sites' },\n { id: 'datasets', label: 'Data' },\n { id: 'extensions', label: 'Marketplace and plugins' },\n]\n\n/**\n * A declaration's verdict for each org role: its `roleDefaults`, or, for a\n * `registerPluginPermissions` key, its tier `defaults` with the owner on the\n * admin tier (the tier `ORG_ROLE_TIER` resolves the owner onto).\n */\nexport function declarationRoleDefaults(\n declaration: Pick<OrgPermissionDeclaration, 'roleDefaults' | 'defaults'>,\n): Record<OrgRoleName, boolean> {\n if (declaration.roleDefaults) {\n return {\n owner: declaration.roleDefaults.owner === true,\n admin: declaration.roleDefaults.admin === true,\n editor: declaration.roleDefaults.editor === true,\n viewer: declaration.roleDefaults.viewer === true,\n }\n }\n const tiers = declaration.defaults\n return {\n owner: tiers?.admin === true,\n admin: tiers?.admin === true,\n editor: tiers?.editor === true,\n viewer: tiers?.viewer === true,\n }\n}\n\n/**\n * The core keys, in display order for role editors. Every key here is\n * enforced server-side (`apps/console/specs/org-permissions-are-enforced.spec.ts`).\n */\nexport const CORE_ORG_PERMISSION_ENTRIES: readonly OrgPermissionEntry[] = [\n {\n key: 'org.settings',\n group: 'organization',\n label: 'Organization settings',\n description: 'Rename the organization, change its slug, transfer it.',\n },\n {\n key: 'org.auditLog',\n group: 'organization',\n label: 'Activity & audit log',\n description: 'See the organization activity feed.',\n },\n {\n key: 'org.apiKeys',\n group: 'organization',\n label: 'Manage API keys',\n description: 'Create and revoke the workspace’s REST API keys.',\n },\n {\n key: 'org.privacy',\n group: 'organization',\n label: 'Privacy requests',\n description: 'Erase a person from every site in the workspace when they ask.',\n },\n {\n key: 'org.holds',\n group: 'organization',\n label: 'Holds and reviews',\n description: 'See what is held, flagged or locked on the workspace, and request a review.',\n },\n // Workspace acts that asked for the owner or admin role (AGL-4047), each\n // a key of its own whose default is exactly those roles.\n {\n key: 'org.export',\n group: 'organization',\n label: 'Export workspace data',\n description: 'Download a copy of everything the workspace holds.',\n },\n {\n key: 'org.sendingDomains',\n group: 'organization',\n label: 'Sending domains',\n description: 'Add, verify and remove the domains the workspace’s mail is sent from.',\n },\n {\n key: 'org.businessProfile',\n group: 'organization',\n label: 'Business profile',\n description: 'Change the services, audience and tone every site starts from.',\n },\n {\n key: 'billing.view',\n group: 'billing',\n label: 'View billing',\n description: 'See the plan, usage meters, and invoices.',\n },\n {\n key: 'billing.manage',\n group: 'billing',\n label: 'Manage billing',\n description: 'Change plans, buy add-ons, update payment details.',\n },\n {\n key: 'members.manage',\n group: 'team',\n label: 'Manage members',\n description: 'Invite, remove, and re-role organization members.',\n },\n {\n key: 'hosts.create',\n group: 'sites',\n label: 'Create sites',\n description: 'Create new sites in the organization.',\n },\n {\n key: 'hosts.delete',\n group: 'sites',\n label: 'Delete sites',\n description: 'Delete sites the member can access.',\n },\n // The site keys (AGL-4047): each replaced a gate on the member's role on a\n // site, and keeps that role as its preset (`CORE_SITE_ROLE_PRESETS`).\n {\n key: 'site.edit',\n group: 'sites',\n scope: 'site',\n label: 'Edit site content',\n description: 'Create and change pages, content and everything else on a site, without making it live.',\n },\n {\n key: 'site.publish',\n group: 'sites',\n scope: 'site',\n label: 'Publish site content',\n description: 'Make pages and content live, take them down or schedule them, change redirects and change the site’s fonts.',\n },\n {\n key: 'site.sensitive',\n group: 'sites',\n scope: 'site',\n label: 'See orders and webhooks',\n description: 'Read a site’s orders, returns and sending address, and set up its webhooks.',\n },\n {\n key: 'site.manage',\n group: 'sites',\n scope: 'site',\n label: 'Manage site settings',\n description: 'Manage a site’s people, domain, address and plugins, change its plugin settings (live chat, Zapier) and security allow-lists, import, export and delete it.',\n },\n {\n key: 'data.manage',\n group: 'datasets',\n label: 'Manage data',\n description: 'Create, edit, and delete organization datasets.',\n },\n // Workspace content that asked for the owner, admin or editor role\n // (AGL-4047), each a key whose default is exactly those roles.\n {\n key: 'org.media',\n group: 'datasets',\n label: 'Edit the workspace media library',\n description: 'Upload, change, move and remove the files in the media library every site shares.',\n },\n {\n key: 'org.eraseRecords',\n group: 'datasets',\n label: 'Delete workspace records',\n description: 'Delete records the whole workspace shares, such as dataset rows, contacts and lists.',\n },\n {\n key: 'marketplace.publish',\n group: 'extensions',\n label: 'Publish to marketplace',\n description: 'Publish listings under the organization profile.',\n },\n {\n key: 'plugins.install',\n group: 'extensions',\n label: 'Install plugins',\n description: 'Install or remove marketplace plugins.',\n },\n]\n\nconst CORE_KEYS = CORE_ORG_PERMISSION_ENTRIES.map((entry) => entry.key)\n\n/**\n * The core keys whose preset is the member's role ON THE SITE (AGL-4047), and\n * each site role's verdict: the sets the rules' `canWriteHostContent`,\n * `canPublishHostContent`, `canReadHostSensitive` and the site-admin checks\n * asked before they asked these keys. A member's preset on a site is the\n * verdict for the role they hold there (the owner and admins are Admin on\n * every site); a custom role, their own permissions and that site's toggle\n * then grant or refuse it. Each workspace role's default below is the verdict\n * for its own site role, which is the answer with no site named.\n */\nexport const CORE_SITE_ROLE_PRESETS: Readonly<Record<string, Readonly<Record<string, boolean>>>> = {\n 'site.edit': { admin: true, editor: true, author: true, viewer: false },\n 'site.publish': { admin: true, editor: true, author: false, viewer: false },\n 'site.sensitive': { admin: true, editor: true, author: false, viewer: false },\n 'site.manage': { admin: true, editor: false, author: false, viewer: false },\n}\n\n/** The workspace editor's site keys: its own site role's preset. */\nconst EDITOR_SITE_KEYS = Object.keys(CORE_SITE_ROLE_PRESETS).filter((key) => CORE_SITE_ROLE_PRESETS[key].editor)\n\n/**\n * The core keys each org role holds when nothing refines it: owners and\n * admins hold every core key (owner-only acts like deleting the\n * organization stay role-checked, not permission-checked); editors work on\n * content and see no money or roster controls; viewers read only.\n */\nexport const CORE_ORG_ROLE_GRANTS: Readonly<Record<OrgRoleName, readonly string[]>> = {\n owner: CORE_KEYS,\n admin: CORE_KEYS,\n editor: ['data.manage', 'org.media', 'org.eraseRecords', 'marketplace.publish', 'plugins.install', ...EDITOR_SITE_KEYS],\n viewer: [],\n}\n\n/** The core's catalog and grants; the contracts' value adds the plugins' keys. */\nexport const ORG_ROLE_GRANTS: OrgRoleGrants = {\n permissions: CORE_ORG_PERMISSION_ENTRIES.map((entry) => ({ ...entry })),\n groups: CORE_ORG_PERMISSION_GROUPS.map((group) => ({ ...group })),\n grants: {\n owner: [...CORE_ORG_ROLE_GRANTS.owner],\n admin: [...CORE_ORG_ROLE_GRANTS.admin],\n editor: [...CORE_ORG_ROLE_GRANTS.editor],\n viewer: [...CORE_ORG_ROLE_GRANTS.viewer],\n },\n}\n\n/**\n * The catalog with plugins' declared keys after the core ones, each role\n * holding a declared key when its declaration's defaults say so. A\n * declaration naming a key already taken is refused, as the console's\n * catalog refuses it: the first declaration keeps its meaning and defaults.\n * Every declaration list after the first is folded in the same way, so the\n * org catalog keys (`PluginOrgPermissionDeclaration`) and the\n * `registerPluginPermissions` keys land in one catalog (AGL-4047).\n */\nexport function assembleOrgRoleGrants(\n core: OrgRoleGrants,\n ...declarationLists: ReadonlyArray<readonly OrgPermissionDeclaration[]>\n): OrgRoleGrants {\n const permissions = [...core.permissions]\n const groups = [...(core.groups ?? [])]\n const grants = Object.fromEntries(ORG_ROLE_NAMES.map((role) => [role, [...core.grants[role]]])) as Record<\n OrgRoleName,\n string[]\n >\n const taken = new Set(permissions.map((entry) => entry.key))\n const groupIds = new Set(groups.map((group) => group.id))\n for (const declaration of declarationLists.flat()) {\n if (!declaration?.key || taken.has(declaration.key)) continue\n taken.add(declaration.key)\n const group = declaration.group ?? declaration.pluginId\n permissions.push({\n key: declaration.key,\n label: declaration.label,\n description: declaration.description ?? '',\n ...(declaration.pluginId ? { pluginId: declaration.pluginId } : {}),\n ...(group ? { group } : {}),\n scope: declaration.hostRoleDefaults ? 'site' : 'org',\n ...(declaration.refines && declaration.refines !== declaration.key ? { refines: declaration.refines } : {}),\n ...(declaration.follows && declaration.follows !== declaration.key && !declaration.refines\n ? { follows: declaration.follows }\n : {}),\n })\n if (group && !groupIds.has(group)) {\n groupIds.add(group)\n groups.push({\n id: group,\n label: declaration.groupLabel ?? group,\n ...(declaration.pluginId ? { pluginId: declaration.pluginId } : {}),\n })\n } else if (group && declaration.pluginId) {\n // A plugin's group opened by a declaration that names no plugin (an org\n // catalog declaration, AGL-4047) is still that plugin's: the first of\n // its declarations that names one says so.\n const index = groups.findIndex((entry) => entry.id === group)\n if (index >= 0 && !groups[index].pluginId) groups[index] = { ...groups[index], pluginId: declaration.pluginId }\n }\n const defaults = declarationRoleDefaults(declaration)\n for (const role of ORG_ROLE_NAMES) {\n if (defaults[role]) grants[role].push(declaration.key)\n }\n }\n return { permissions, grants, groups }\n}\n\n/**\n * A member's effective permissions, as the console's `resolveOrgPermissions`\n * answers them: the role's defaults, then the custom role's explicit\n * verdicts (when the member has one), then the member's own overrides,\n * later layers winning key by key, over the catalog's keys only. A missing\n * role reads as viewer; a role the catalog does not know holds nothing.\n */\nexport function rolePermissions(\n catalog: OrgRoleGrants,\n role: string | null | undefined,\n customRole?: OrgPermissionLayer | null,\n overrides?: OrgPermissionLayer | null,\n): Record<string, boolean> {\n const keys = catalog.permissions.map((entry) => entry.key)\n const name = role ?? 'viewer'\n const held = new Set((catalog.grants as Record<string, string[] | undefined>)[name] ?? [])\n const out: Record<string, boolean> = Object.fromEntries(keys.map((key) => [key, held.has(key)]))\n for (const layer of [customRole, overrides]) {\n const values = layer?.permissions\n if (!values) continue\n for (const key of keys) {\n if (typeof values[key] === 'boolean') out[key] = values[key]\n }\n }\n // A key that refines a broader one (AGL-4047) is never held without it, and\n // follows it where neither layer set the key; a key that only follows one\n // takes its verdict where neither layer set the key, and nothing more.\n // Applied broader keys first, so a chain settles in one pass; a refinement\n // that loops is left alone.\n const broader = new Map<string, string>()\n const followsOnly = new Set<string>()\n for (const entry of catalog.permissions) {\n if (entry.refines && entry.refines in out) broader.set(entry.key, entry.refines)\n else if (entry.follows && entry.follows in out) {\n broader.set(entry.key, entry.follows)\n followsOnly.add(entry.key)\n }\n }\n const depth = (key: string): number => {\n const seen = new Set<string>()\n let steps = 0\n let current = key\n while (broader.has(current)) {\n if (seen.has(current)) return -1\n seen.add(current)\n current = broader.get(current) as string\n steps += 1\n }\n return steps\n }\n const ordered = [...broader.keys()]\n .map((key) => ({ key, steps: depth(key) }))\n .filter((entry) => entry.steps > 0)\n .sort((a, b) => a.steps - b.steps)\n for (const { key } of ordered) {\n let stored: boolean | undefined\n for (const layer of [customRole, overrides]) {\n const value = layer?.permissions?.[key]\n if (typeof value === 'boolean') stored = value\n }\n const held = out[broader.get(key) as string] === true\n out[key] = followsOnly.has(key) ? (stored === undefined ? held : stored) : held && (stored === undefined ? true : stored)\n }\n return out\n}\n"],"names":["ORG_ROLE_NAMES","CORE_ORG_PERMISSION_GROUPS","id","label","declarationRoleDefaults","declaration","roleDefaults","owner","admin","editor","viewer","tiers","defaults","CORE_ORG_PERMISSION_ENTRIES","key","group","description","scope","CORE_KEYS","map","entry","CORE_SITE_ROLE_PRESETS","author","EDITOR_SITE_KEYS","Object","keys","filter","CORE_ORG_ROLE_GRANTS","ORG_ROLE_GRANTS","permissions","groups","grants","assembleOrgRoleGrants","core","declarationLists","fromEntries","role","taken","Set","groupIds","flat","has","add","pluginId","push","hostRoleDefaults","refines","follows","groupLabel","index","findIndex","rolePermissions","catalog","customRole","overrides","name","held","out","layer","values","broader","Map","followsOnly","set","depth","seen","steps","current","get","ordered","sort","a","b","stored","value","undefined"],"mappings":"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"}
|
|
@@ -205,6 +205,16 @@ export interface PluginOrgPermissionDeclaration {
|
|
|
205
205
|
* while the key becomes grantable and deniable like every other.
|
|
206
206
|
*/
|
|
207
207
|
presetFromSiteRole?: boolean;
|
|
208
|
+
/**
|
|
209
|
+
* With `presetFromSiteRole`: the site role is the preset only for a member
|
|
210
|
+
* of the WHOLE workspace (AGL-4047). Someone added to one site only holds
|
|
211
|
+
* the key there when the site's toggle grants it, whatever their site role,
|
|
212
|
+
* and `hostRoleDefaults` is not their default. For a key that replaces a
|
|
213
|
+
* gate asking for an admin of the whole workspace who is also this site's
|
|
214
|
+
* admin (refunds, say): a site's Members card can make a contractor the
|
|
215
|
+
* admin of one site, and that never carried those acts.
|
|
216
|
+
*/
|
|
217
|
+
workspaceMembersOnly?: boolean;
|
|
208
218
|
}
|
|
209
219
|
/**
|
|
210
220
|
* One key of the ONE permission catalog that a plugin contributes (AGL-4047),
|
|
@@ -234,6 +244,8 @@ export interface PluginCatalogPermission {
|
|
|
234
244
|
narrowsPerSite?: Readonly<Record<OrgRole, boolean>>;
|
|
235
245
|
/** The key's preset is the member's role on the site (AGL-4047); see the declaration's. */
|
|
236
246
|
presetFromSiteRole?: true;
|
|
247
|
+
/** That preset holds for members of the whole workspace only (AGL-4047); see the declaration's. */
|
|
248
|
+
workspaceMembersOnly?: true;
|
|
237
249
|
}
|
|
238
250
|
export interface PluginEntitlementRegistration {
|
|
239
251
|
pluginId: string;
|
|
@@ -239,9 +239,11 @@ const reportedCatalogRefusals = new Set();
|
|
|
239
239
|
refines: permission.refines
|
|
240
240
|
} : {}, permission.follows ? {
|
|
241
241
|
follows: permission.follows
|
|
242
|
-
} : {}, permission.presetFromSiteRole && permission.hostRoleDefaults ? {
|
|
242
|
+
} : {}, permission.presetFromSiteRole && permission.hostRoleDefaults ? _extends({
|
|
243
243
|
presetFromSiteRole: true
|
|
244
|
-
}
|
|
244
|
+
}, permission.workspaceMembersOnly ? {
|
|
245
|
+
workspaceMembersOnly: true
|
|
246
|
+
} : {}) : narrowingDefaultsOf(permission.narrowsPerSite)));
|
|
245
247
|
}
|
|
246
248
|
for (const permission of listPluginPermissions()){
|
|
247
249
|
var _permission_description, _permission_group1, _permission_groupLabel1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Plugin-declared billing and access keys (AGL-2940).\n *\n * A plugin that sells something, gates something, or can be switched off in\n * an incident declares the KEYS here — a seat add-on, a feature flag, a\n * lockdown lever, a permission — and the core catalogs that used to be\n * edited for each one read the declarations instead:\n *\n * - `plan-entitlements` folds every registered seat add-on into\n * `resolveOrgEntitlements` (a quota raised per unit, features switched on)\n * and answers a registered feature's plan defaults;\n * - `lockdown` lists a registered lever on the staff page, carries its label,\n * its staff-bypass rule and its visitor notice, and maps the API paths it\n * names onto it for the plugin dispatcher;\n * - `plugin-permissions` receives the permissions with the owner filled in;\n * - `org-permissions` folds every declared catalog key into the org\n * permission catalog — the role editor, custom roles, per-member overrides\n * and the rules stamp — and `host-permissions` decides the keys that\n * declare host-role defaults for a site collaborator, per site.\n *\n * The NUMBERS stay where they are checked. A seat add-on's price is a row in\n * `PLAN_PRICING`, read by the Stripe wiring and reconciled by\n * `check-pricing-drift`; a declaration names the quota it widens and by how\n * much per plan, and nothing here restates a price.\n *\n * Registration order is deterministic: the catalog order of\n * `FIRST_PARTY_PLUGINS`, then any other id alphabetically. Plugin modules\n * load in parallel, so the order they REGISTER in is whatever the network\n * gave, and a list that followed it would put the staff lockdown checklist\n * in a different order per session.\n */\n\nimport type { AglynOrgBilling, HostAccessRole, OrgPlan, OrgRole } from '../foundation'\nimport { declarationRoleDefaults } from '../app-utils/org-role-grants'\nimport { FIRST_PARTY_PLUGINS } from './enabled-plugins'\nimport {\n listPluginPermissions,\n registerPluginPermissions,\n type PluginPermission,\n} from './plugin-permissions'\n\nexport interface PluginSeatAddonDeclaration {\n /** The `org.seatAddons` key the purchase is stored under (`aiAddon`). */\n key: string\n label: string\n /**\n * How many purchased units count. `1` for an org-wide add-on that is\n * bought once, where a doubled webhook item or a hand edit above one is\n * still one purchase; absent to count the stored quantity.\n */\n maxUnits?: number\n /**\n * The quota each unit widens: the resolved entitlement named by `key`\n * gains `perUnit(org, plan) × units` when the figure depends on WHAT was\n * bought (the Aglyn AI tier, AGL-3965), else `perUnitByPlan[plan] × units`.\n * A plan the add-on is not sold on answers 0.\n */\n quota?: {\n key: string\n perUnitByPlan?: Readonly<Record<OrgPlan, number>>\n /**\n * The per-unit figure read off the org itself. `org` is the billing\n * document as `resolveOrgEntitlements` received it and `plan` its\n * effective plan.\n */\n perUnit?: (org: Readonly<Partial<AglynOrgBilling>>, plan: OrgPlan) => number\n }\n /** Feature flags a purchased unit switches on. */\n features?: readonly string[]\n /**\n * Whether a plan sells the add-on, when `perUnitByPlan` cannot say so\n * (a `perUnit` quota, AGL-3965). Absent: sold where `perUnitByPlan` names\n * a figure above zero, or everywhere for an add-on with no quota.\n */\n soldOn?: (plan: OrgPlan) => boolean\n}\n\n/**\n * Whether a plan sells a declared seat add-on: its own `soldOn`, else a\n * `perUnitByPlan` figure above zero for the plan, else (no quota) yes.\n */\nexport function pluginSeatAddonSoldOn(\n addon: PluginSeatAddonDeclaration,\n plan: OrgPlan,\n): boolean {\n if (addon.soldOn) return addon.soldOn(plan)\n if (!addon.quota) return true\n return (addon.quota.perUnitByPlan?.[plan] ?? 0) > 0\n}\n\nexport interface PluginFeatureDeclaration {\n /** The `OrgFeatureFlags` key. */\n key: string\n label: string\n /**\n * The plan defaults, when the key is not one the plan tables already\n * carry. Absent means the plan tables decide, which is the case for every\n * key that predates this registry.\n */\n defaultByPlan?: Readonly<Partial<Record<OrgPlan, boolean>>>\n}\n\nexport interface PluginLockdownFeatureDeclaration {\n /** The lever's wire identity (`ai-generate`). */\n key: string\n /** The staff checklist's label. */\n label: string\n /**\n * What the lever is called in the customer's mail when staff pull it for\n * their workspace (`media uploads`, `AI generation`). A noun phrase that\n * reads right mid-sentence (\"our team paused media uploads\") and after a\n * colon, so lowercase unless it starts with a name or an acronym. The\n * staff `label` never reaches a customer.\n */\n customerName: string\n /**\n * Whether a verified staff claim passes the lock. Granted where a staff\n * action aids incident response — one real call proves a provider is back\n * — and withheld where the staff action would BE the incident.\n */\n staffBypass: boolean\n /**\n * What a visitor reads while the lever is pulled: what is paused AND what\n * still works. The expected-back window is appended by the notice builder.\n */\n notice: { title: string; body: string }\n /**\n * Plugin API paths the lever gates at the dispatcher: exact paths, and\n * prefixes matched on a segment boundary (`ai/generate` also gates\n * `ai/generate/section`, never `ai/generated-report`).\n */\n apiPaths?: { exact?: readonly string[]; prefixes?: readonly string[] }\n}\n\n/**\n * A key a plugin adds to the org permission CATALOG (AGL-2984): the granular\n * map custom roles and per-member overrides are stored in, that\n * `resolveOrgPermissions` layers and the rules stamp carries, and that the\n * role editor lists. A declared key is stored, layered and resolved exactly\n * like a core key; the declaration supplies what a core key's catalog entry\n * and role table supply.\n */\nexport interface PluginOrgPermissionDeclaration {\n /** The dotted key a custom role and a per-member override store (`reviews.reply`). */\n key: string\n /** The role editor's line for the key, and the word a refusal names. */\n label: string\n /** What the key opens, under the label in the role editor. */\n description: string\n /** Each org role's verdict when no custom role or per-member override sets the key. */\n roleDefaults: Readonly<Record<OrgRole, boolean>>\n /**\n * Each HOST role's verdict, for a key a site collaborator holds per site.\n *\n * A collaborator's membership carries no org standing to refine — it\n * exists only to carry access to named sites — so a key with this field is\n * decided for them on the site a request names: the host role they hold\n * there, refined by the per-site toggle on their member document. A key\n * without it is never held by a collaborator.\n */\n hostRoleDefaults?: Readonly<Record<HostAccessRole, boolean>>\n /**\n * The role editor's group for the key (AGL-4047): a core group id\n * (`CORE_ORG_PERMISSION_GROUPS`) or, absent, a group of the plugin's own.\n */\n group?: string\n /** The heading of a group of the plugin's own; absent: the plugin's catalog label. */\n groupLabel?: string\n /**\n * A broader key this one is a finer part of (AGL-4047): never granted\n * without it, and following it where nothing sets this key. See\n * `PluginPermission.refines`.\n */\n refines?: string\n /**\n * A key this one takes its verdict from where nothing sets it, without\n * needing it (AGL-4047). See `PluginPermission.follows`.\n */\n follows?: string\n /**\n * A site can narrow this key, and the built-in roles' default for whether\n * it does (AGL-4047). See `PluginPermission.narrowsPerSite`. Ignored on a\n * key whose preset is the site role (`presetFromSiteRole`).\n */\n narrowsPerSite?: boolean | Partial<Record<OrgRole, boolean>>\n /**\n * The key's preset is the member's role ON THE SITE, for every member\n * (AGL-4047), not only for a site collaborator: the owner and admins are\n * admins of every site, a workspace-wide editor or viewer holds their\n * workspace role on a site unless the site gives them other access there\n * (narrower or wider), and a collaborator holds their site role. So the\n * key reads `hostRoleDefaults` for whatever access the member has on the\n * site a request names, exactly as a gate on that host role did, and a\n * custom role, the member's own permissions and that site's toggle then\n * grant or refuse it like any other key. Needs `hostRoleDefaults`, and\n * `roleDefaults` that match them tier for tier (the owner and admins on\n * Admin, editors on Editor, viewers on Viewer), which is the answer with\n * no site named.\n *\n * For a key that replaces a gate on the site role (who may edit a site's\n * stays, say), so the built-in roles keep exactly the access they had\n * while the key becomes grantable and deniable like every other.\n */\n presetFromSiteRole?: boolean\n}\n\n/**\n * One key of the ONE permission catalog that a plugin contributes (AGL-4047),\n * whichever of the two declaration APIs it came through:\n * `registerPluginEntitlements({ orgPermissions })` (`source: 'catalog'`) or\n * `registerPluginPermissions` (`source: 'tier'`, its per-tier defaults read\n * as org-role defaults, the owner on the admin tier).\n */\nexport interface PluginCatalogPermission {\n key: string\n label: string\n description: string\n pluginId: string\n roleDefaults: Readonly<Record<OrgRole, boolean>>\n hostRoleDefaults?: Readonly<Record<HostAccessRole, boolean>>\n group: string\n groupLabel: string\n source: 'catalog' | 'tier'\n /** The broader key this one refines, when it does. */\n refines?: string\n /** The key this one follows without needing it, when it does. */\n follows?: string\n /**\n * Present when a site can narrow the key (AGL-4047): each built-in role's\n * default for whether it does, normalized from the declaration.\n */\n narrowsPerSite?: Readonly<Record<OrgRole, boolean>>\n /** The key's preset is the member's role on the site (AGL-4047); see the declaration's. */\n presetFromSiteRole?: true\n}\n\nexport interface PluginEntitlementRegistration {\n pluginId: string\n seatAddons?: readonly PluginSeatAddonDeclaration[]\n features?: readonly PluginFeatureDeclaration[]\n lockdownFeatures?: readonly PluginLockdownFeatureDeclaration[]\n /** Per-tier keys a plugin's own pages read (`registerPluginPermissions`). */\n permissions?: readonly Omit<PluginPermission, 'pluginId'>[]\n /** Keys added to the org permission catalog — see {@link PluginOrgPermissionDeclaration}. */\n orgPermissions?: readonly PluginOrgPermissionDeclaration[]\n}\n\n/**\n * The registrations and their listeners, held on `globalThis` rather than in\n * module `const`s (AGL-3596), as the custom field registry and the\n * declarations repair hook are (AGL-3412).\n *\n * An app registers the plugins' declarations from `instrumentation.ts`, which\n * Next compiles into a different module graph from the routes. A module-scoped\n * map was filled in the instrumentation's copy and read empty in a route's, so\n * a core route that never loaded a plugin knew none of its keys: the site\n * create door asked for `ai.generate`, read `undefined` from a catalog that\n * had never heard of it, and every guided AI start was born with the starter.\n * One map per process, under a `Symbol.for` key, is the same map in every\n * copy.\n */\nconst REGISTRY_KEY = Symbol.for('@aglyn/aglyn:plugin-entitlements')\n\ninterface PluginEntitlementsRegistry {\n registrations: Map<string, PluginEntitlementRegistration>\n /**\n * Readers of the registrations that keep a derived view in step with them —\n * every copy of the org permission catalog rebuilds itself on every\n * registration and reset, because a plugin's declarations can register\n * after a module that imported the catalog was evaluated, and in another\n * module graph's copy of this file.\n */\n listeners: Set<() => void>\n}\n\nconst globalScope = globalThis as typeof globalThis & {\n [REGISTRY_KEY]?: PluginEntitlementsRegistry\n}\n\nconst registry: PluginEntitlementsRegistry = (globalScope[REGISTRY_KEY] ??= {\n registrations: new Map(),\n listeners: new Set(),\n})\n\nconst registrations = registry.registrations\nconst listeners = registry.listeners\n\n/** Calls `listener` after every registration and reset; answers the unsubscribe. */\nexport function subscribePluginEntitlements(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\nfunction notifyListeners(): void {\n for (const listener of [...listeners]) listener()\n}\n\nconst CATALOG_ORDER = new Map(\n FIRST_PARTY_PLUGINS.map((plugin, index) => [plugin.id, index]),\n)\n\nfunction catalogRank(pluginId: string): number {\n return CATALOG_ORDER.get(pluginId) ?? Number.MAX_SAFE_INTEGER\n}\n\nfunction orderedRegistrations(): PluginEntitlementRegistration[] {\n return [...registrations.values()].sort(\n (a, b) =>\n catalogRank(a.pluginId) - catalogRank(b.pluginId) ||\n a.pluginId.localeCompare(b.pluginId),\n )\n}\n\nfunction refuseStolenKeys(\n pluginId: string,\n kind: 'seat add-on' | 'lockdown feature' | 'org permission',\n keys: readonly string[],\n ownerOf: (key: string) => string | undefined,\n): void {\n for (const key of keys) {\n const owner = ownerOf(key)\n if (owner && owner !== pluginId) {\n throw new Error(\n `${kind} \"${key}\" is already declared by \"${owner}\"; refused \"${pluginId}\"`,\n )\n }\n }\n}\n\n/**\n * Idempotent per plugin — re-registration replaces that plugin's\n * declarations. A seat add-on, lockdown or catalog permission key another\n * plugin already owns refuses the whole registration: one key, one owner,\n * or the two declarations disagree about what the key means.\n */\nexport function registerPluginEntitlements(\n registration: PluginEntitlementRegistration,\n): void {\n const pluginId = registration.pluginId.trim()\n if (!pluginId) throw new Error('plugin entitlements need a pluginId')\n refuseStolenKeys(\n pluginId,\n 'seat add-on',\n (registration.seatAddons ?? []).map((entry) => entry.key),\n (key) => pluginSeatAddonOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'lockdown feature',\n (registration.lockdownFeatures ?? []).map((entry) => entry.key),\n (key) => pluginLockdownFeatureOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'org permission',\n (registration.orgPermissions ?? []).map((entry) => entry.key),\n (key) => pluginOrgPermissionOwner(key) ?? pluginTierPermissionOwner(key),\n )\n registrations.set(pluginId, { ...registration, pluginId })\n if (registration.permissions?.length) {\n registerPluginPermissions(\n registration.permissions.map((permission) => ({ ...permission, pluginId })),\n )\n }\n notifyListeners()\n}\n\nfunction pluginSeatAddonOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.seatAddons?.some((addon) => addon.key === key)) return entry.pluginId\n }\n return undefined\n}\n\nfunction pluginLockdownFeatureOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.lockdownFeatures?.some((feature) => feature.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\nfunction pluginOrgPermissionOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.orgPermissions?.some((permission) => permission.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\nfunction pluginTierPermissionOwner(key: string): string | undefined {\n return listPluginPermissions().find((permission) => permission.key === key)?.pluginId\n}\n\n/** Refusals already logged by `listPluginCatalogPermissions`, so each is reported once. */\n// boot-registry: per copy — it only de-duplicates a warning; a second compiled copy logging the same refusal once more is harmless, and nothing reads it to decide anything.\nconst reportedCatalogRefusals = new Set<string>()\n\n/** A plugin's catalog label, the heading its own permission group carries. */\nfunction pluginLabel(pluginId: string): string {\n return FIRST_PARTY_PLUGINS.find((plugin) => plugin.id === pluginId)?.label ?? pluginId\n}\n\n/**\n * A declaration's `narrowsPerSite`, normalized to each built-in role's\n * default (AGL-4047): `true` or `false` names every role, a map names each,\n * a role it leaves out keeps. Absent: the key is not one a site can narrow.\n */\nfunction narrowingDefaultsOf(\n declared: boolean | Partial<Record<OrgRole, boolean>> | undefined,\n): { narrowsPerSite?: Readonly<Record<OrgRole, boolean>> } {\n if (declared === undefined || declared === null) return {}\n const every = typeof declared === 'boolean' ? declared : undefined\n const map = typeof declared === 'object' ? declared : {}\n return {\n narrowsPerSite: {\n owner: every ?? map.owner === true,\n admin: every ?? map.admin === true,\n editor: every ?? map.editor === true,\n viewer: every ?? map.viewer === true,\n },\n }\n}\n\n/**\n * Every key plugins contribute to the permission catalog, through EITHER\n * declaration API, in catalog order (AGL-4047): per plugin, its\n * `orgPermissions` declarations and then its `registerPluginPermissions`\n * keys. A key both APIs carry for the same plugin is the catalog\n * declaration's; one a DIFFERENT plugin already declared is refused and\n * logged once, the first declaration keeping its meaning and defaults.\n *\n * Read live: both registries fill at plugin module scope, which can be after\n * any reader of this was evaluated.\n */\nexport function listPluginCatalogPermissions(): PluginCatalogPermission[] {\n const byPlugin = new Map<string, PluginCatalogPermission[]>()\n const owner = new Map<string, string>()\n const add = (entry: PluginCatalogPermission): void => {\n const incumbent = owner.get(entry.key)\n if (incumbent !== undefined) {\n if (incumbent !== entry.pluginId) {\n const refusal = `${entry.pluginId}\\x00${entry.key}`\n if (!reportedCatalogRefusals.has(refusal)) {\n reportedCatalogRefusals.add(refusal)\n console.error(\n `[plugins] refused permission key \"${entry.key}\" to \"${entry.pluginId}\": ` +\n `already declared by \"${incumbent}\"`,\n )\n }\n }\n return\n }\n owner.set(entry.key, entry.pluginId)\n const list = byPlugin.get(entry.pluginId) ?? []\n list.push(entry)\n byPlugin.set(entry.pluginId, list)\n }\n for (const permission of listPluginOrgPermissions()) {\n add({\n key: permission.key,\n label: permission.label,\n description: permission.description,\n pluginId: permission.pluginId,\n roleDefaults: declarationRoleDefaults(permission),\n ...(permission.hostRoleDefaults ? { hostRoleDefaults: permission.hostRoleDefaults } : {}),\n group: permission.group ?? permission.pluginId,\n groupLabel: permission.groupLabel ?? pluginLabel(permission.pluginId),\n source: 'catalog',\n ...(permission.refines ? { refines: permission.refines } : {}),\n ...(permission.follows ? { follows: permission.follows } : {}),\n // A key whose preset is the site role follows every site's access\n // already, so it offers no narrowing control (AGL-4047).\n ...(permission.presetFromSiteRole && permission.hostRoleDefaults\n ? { presetFromSiteRole: true as const }\n : narrowingDefaultsOf(permission.narrowsPerSite)),\n })\n }\n for (const permission of listPluginPermissions()) {\n add({\n key: permission.key,\n label: permission.label,\n description: permission.description ?? '',\n pluginId: permission.pluginId,\n roleDefaults: declarationRoleDefaults(permission),\n group: permission.group ?? permission.pluginId,\n groupLabel: permission.groupLabel ?? pluginLabel(permission.pluginId),\n source: 'tier',\n ...(permission.refines ? { refines: permission.refines } : {}),\n ...(permission.follows ? { follows: permission.follows } : {}),\n ...narrowingDefaultsOf(permission.narrowsPerSite),\n })\n }\n return [...byPlugin.keys()]\n .sort((a, b) => catalogRank(a) - catalogRank(b) || a.localeCompare(b))\n .flatMap((pluginId) => byPlugin.get(pluginId) ?? [])\n}\n\n/** Every declared catalog permission, in catalog order, with the plugin that declared it. */\nexport function listPluginOrgPermissions(): Array<\n PluginOrgPermissionDeclaration & { pluginId: string }\n> {\n return orderedRegistrations().flatMap((entry) =>\n (entry.orgPermissions ?? []).map((permission) => ({\n ...permission,\n pluginId: entry.pluginId,\n })),\n )\n}\n\n/** Every declared seat add-on, in catalog order. */\nexport function listPluginSeatAddons(): PluginSeatAddonDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.seatAddons ?? [])])\n}\n\n/** One declared seat add-on by its `seatAddons` key. */\nexport function pluginSeatAddon(key: string): PluginSeatAddonDeclaration | undefined {\n return listPluginSeatAddons().find((entry) => entry.key === key)\n}\n\n/** Every declared feature, in catalog order. */\nexport function listPluginFeatures(): PluginFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.features ?? [])])\n}\n\n/** Every declared lockdown lever, in catalog order. */\nexport function listPluginLockdownFeatures(): PluginLockdownFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [\n ...(entry.lockdownFeatures ?? []),\n ])\n}\n\n/** One declared lockdown lever by key. */\nexport function pluginLockdownFeature(\n key: string,\n): PluginLockdownFeatureDeclaration | undefined {\n return listPluginLockdownFeatures().find((entry) => entry.key === key)\n}\n\n/** The plugin that declared a lockdown lever, for attribution. */\nexport function pluginIdForLockdownFeature(key: string): string | undefined {\n return pluginLockdownFeatureOwner(key)\n}\n\n/** Every registration, in catalog order. */\nexport function listPluginEntitlementRegistrations(): PluginEntitlementRegistration[] {\n return orderedRegistrations()\n}\n\n/**\n * Test seam: forget every registration. The per-tier permissions stay\n * registered; the catalog permissions go with their registrations.\n */\nexport function resetPluginEntitlementsForTests(): void {\n registrations.clear()\n notifyListeners()\n}\n"],"names":["globalScope","REGISTRY_KEY","declarationRoleDefaults","FIRST_PARTY_PLUGINS","listPluginPermissions","registerPluginPermissions","pluginSeatAddonSoldOn","addon","plan","soldOn","quota","perUnitByPlan","Symbol","for","globalThis","registry","registrations","Map","listeners","Set","subscribePluginEntitlements","listener","add","delete","notifyListeners","CATALOG_ORDER","map","plugin","index","id","catalogRank","pluginId","get","Number","MAX_SAFE_INTEGER","orderedRegistrations","values","sort","a","b","localeCompare","refuseStolenKeys","kind","keys","ownerOf","key","owner","Error","registerPluginEntitlements","registration","trim","seatAddons","entry","pluginSeatAddonOwner","lockdownFeatures","pluginLockdownFeatureOwner","orgPermissions","pluginOrgPermissionOwner","pluginTierPermissionOwner","set","permissions","length","permission","some","undefined","feature","find","reportedCatalogRefusals","pluginLabel","label","narrowingDefaultsOf","declared","every","narrowsPerSite","admin","editor","viewer","listPluginCatalogPermissions","byPlugin","incumbent","refusal","has","console","error","list","push","listPluginOrgPermissions","description","roleDefaults","hostRoleDefaults","group","groupLabel","source","refines","follows","presetFromSiteRole","flatMap","listPluginSeatAddons","pluginSeatAddon","listPluginFeatures","features","listPluginLockdownFeatures","pluginLockdownFeature","pluginIdForLockdownFeature","listPluginEntitlementRegistrations","resetPluginEntitlementsForTests","clear"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-entitlements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Plugin-declared billing and access keys (AGL-2940).\n *\n * A plugin that sells something, gates something, or can be switched off in\n * an incident declares the KEYS here — a seat add-on, a feature flag, a\n * lockdown lever, a permission — and the core catalogs that used to be\n * edited for each one read the declarations instead:\n *\n * - `plan-entitlements` folds every registered seat add-on into\n * `resolveOrgEntitlements` (a quota raised per unit, features switched on)\n * and answers a registered feature's plan defaults;\n * - `lockdown` lists a registered lever on the staff page, carries its label,\n * its staff-bypass rule and its visitor notice, and maps the API paths it\n * names onto it for the plugin dispatcher;\n * - `plugin-permissions` receives the permissions with the owner filled in;\n * - `org-permissions` folds every declared catalog key into the org\n * permission catalog — the role editor, custom roles, per-member overrides\n * and the rules stamp — and `host-permissions` decides the keys that\n * declare host-role defaults for a site collaborator, per site.\n *\n * The NUMBERS stay where they are checked. A seat add-on's price is a row in\n * `PLAN_PRICING`, read by the Stripe wiring and reconciled by\n * `check-pricing-drift`; a declaration names the quota it widens and by how\n * much per plan, and nothing here restates a price.\n *\n * Registration order is deterministic: the catalog order of\n * `FIRST_PARTY_PLUGINS`, then any other id alphabetically. Plugin modules\n * load in parallel, so the order they REGISTER in is whatever the network\n * gave, and a list that followed it would put the staff lockdown checklist\n * in a different order per session.\n */\n\nimport type { AglynOrgBilling, HostAccessRole, OrgPlan, OrgRole } from '../foundation'\nimport { declarationRoleDefaults } from '../app-utils/org-role-grants'\nimport { FIRST_PARTY_PLUGINS } from './enabled-plugins'\nimport {\n listPluginPermissions,\n registerPluginPermissions,\n type PluginPermission,\n} from './plugin-permissions'\n\nexport interface PluginSeatAddonDeclaration {\n /** The `org.seatAddons` key the purchase is stored under (`aiAddon`). */\n key: string\n label: string\n /**\n * How many purchased units count. `1` for an org-wide add-on that is\n * bought once, where a doubled webhook item or a hand edit above one is\n * still one purchase; absent to count the stored quantity.\n */\n maxUnits?: number\n /**\n * The quota each unit widens: the resolved entitlement named by `key`\n * gains `perUnit(org, plan) × units` when the figure depends on WHAT was\n * bought (the Aglyn AI tier, AGL-3965), else `perUnitByPlan[plan] × units`.\n * A plan the add-on is not sold on answers 0.\n */\n quota?: {\n key: string\n perUnitByPlan?: Readonly<Record<OrgPlan, number>>\n /**\n * The per-unit figure read off the org itself. `org` is the billing\n * document as `resolveOrgEntitlements` received it and `plan` its\n * effective plan.\n */\n perUnit?: (org: Readonly<Partial<AglynOrgBilling>>, plan: OrgPlan) => number\n }\n /** Feature flags a purchased unit switches on. */\n features?: readonly string[]\n /**\n * Whether a plan sells the add-on, when `perUnitByPlan` cannot say so\n * (a `perUnit` quota, AGL-3965). Absent: sold where `perUnitByPlan` names\n * a figure above zero, or everywhere for an add-on with no quota.\n */\n soldOn?: (plan: OrgPlan) => boolean\n}\n\n/**\n * Whether a plan sells a declared seat add-on: its own `soldOn`, else a\n * `perUnitByPlan` figure above zero for the plan, else (no quota) yes.\n */\nexport function pluginSeatAddonSoldOn(\n addon: PluginSeatAddonDeclaration,\n plan: OrgPlan,\n): boolean {\n if (addon.soldOn) return addon.soldOn(plan)\n if (!addon.quota) return true\n return (addon.quota.perUnitByPlan?.[plan] ?? 0) > 0\n}\n\nexport interface PluginFeatureDeclaration {\n /** The `OrgFeatureFlags` key. */\n key: string\n label: string\n /**\n * The plan defaults, when the key is not one the plan tables already\n * carry. Absent means the plan tables decide, which is the case for every\n * key that predates this registry.\n */\n defaultByPlan?: Readonly<Partial<Record<OrgPlan, boolean>>>\n}\n\nexport interface PluginLockdownFeatureDeclaration {\n /** The lever's wire identity (`ai-generate`). */\n key: string\n /** The staff checklist's label. */\n label: string\n /**\n * What the lever is called in the customer's mail when staff pull it for\n * their workspace (`media uploads`, `AI generation`). A noun phrase that\n * reads right mid-sentence (\"our team paused media uploads\") and after a\n * colon, so lowercase unless it starts with a name or an acronym. The\n * staff `label` never reaches a customer.\n */\n customerName: string\n /**\n * Whether a verified staff claim passes the lock. Granted where a staff\n * action aids incident response — one real call proves a provider is back\n * — and withheld where the staff action would BE the incident.\n */\n staffBypass: boolean\n /**\n * What a visitor reads while the lever is pulled: what is paused AND what\n * still works. The expected-back window is appended by the notice builder.\n */\n notice: { title: string; body: string }\n /**\n * Plugin API paths the lever gates at the dispatcher: exact paths, and\n * prefixes matched on a segment boundary (`ai/generate` also gates\n * `ai/generate/section`, never `ai/generated-report`).\n */\n apiPaths?: { exact?: readonly string[]; prefixes?: readonly string[] }\n}\n\n/**\n * A key a plugin adds to the org permission CATALOG (AGL-2984): the granular\n * map custom roles and per-member overrides are stored in, that\n * `resolveOrgPermissions` layers and the rules stamp carries, and that the\n * role editor lists. A declared key is stored, layered and resolved exactly\n * like a core key; the declaration supplies what a core key's catalog entry\n * and role table supply.\n */\nexport interface PluginOrgPermissionDeclaration {\n /** The dotted key a custom role and a per-member override store (`reviews.reply`). */\n key: string\n /** The role editor's line for the key, and the word a refusal names. */\n label: string\n /** What the key opens, under the label in the role editor. */\n description: string\n /** Each org role's verdict when no custom role or per-member override sets the key. */\n roleDefaults: Readonly<Record<OrgRole, boolean>>\n /**\n * Each HOST role's verdict, for a key a site collaborator holds per site.\n *\n * A collaborator's membership carries no org standing to refine — it\n * exists only to carry access to named sites — so a key with this field is\n * decided for them on the site a request names: the host role they hold\n * there, refined by the per-site toggle on their member document. A key\n * without it is never held by a collaborator.\n */\n hostRoleDefaults?: Readonly<Record<HostAccessRole, boolean>>\n /**\n * The role editor's group for the key (AGL-4047): a core group id\n * (`CORE_ORG_PERMISSION_GROUPS`) or, absent, a group of the plugin's own.\n */\n group?: string\n /** The heading of a group of the plugin's own; absent: the plugin's catalog label. */\n groupLabel?: string\n /**\n * A broader key this one is a finer part of (AGL-4047): never granted\n * without it, and following it where nothing sets this key. See\n * `PluginPermission.refines`.\n */\n refines?: string\n /**\n * A key this one takes its verdict from where nothing sets it, without\n * needing it (AGL-4047). See `PluginPermission.follows`.\n */\n follows?: string\n /**\n * A site can narrow this key, and the built-in roles' default for whether\n * it does (AGL-4047). See `PluginPermission.narrowsPerSite`. Ignored on a\n * key whose preset is the site role (`presetFromSiteRole`).\n */\n narrowsPerSite?: boolean | Partial<Record<OrgRole, boolean>>\n /**\n * The key's preset is the member's role ON THE SITE, for every member\n * (AGL-4047), not only for a site collaborator: the owner and admins are\n * admins of every site, a workspace-wide editor or viewer holds their\n * workspace role on a site unless the site gives them other access there\n * (narrower or wider), and a collaborator holds their site role. So the\n * key reads `hostRoleDefaults` for whatever access the member has on the\n * site a request names, exactly as a gate on that host role did, and a\n * custom role, the member's own permissions and that site's toggle then\n * grant or refuse it like any other key. Needs `hostRoleDefaults`, and\n * `roleDefaults` that match them tier for tier (the owner and admins on\n * Admin, editors on Editor, viewers on Viewer), which is the answer with\n * no site named.\n *\n * For a key that replaces a gate on the site role (who may edit a site's\n * stays, say), so the built-in roles keep exactly the access they had\n * while the key becomes grantable and deniable like every other.\n */\n presetFromSiteRole?: boolean\n /**\n * With `presetFromSiteRole`: the site role is the preset only for a member\n * of the WHOLE workspace (AGL-4047). Someone added to one site only holds\n * the key there when the site's toggle grants it, whatever their site role,\n * and `hostRoleDefaults` is not their default. For a key that replaces a\n * gate asking for an admin of the whole workspace who is also this site's\n * admin (refunds, say): a site's Members card can make a contractor the\n * admin of one site, and that never carried those acts.\n */\n workspaceMembersOnly?: boolean\n}\n\n/**\n * One key of the ONE permission catalog that a plugin contributes (AGL-4047),\n * whichever of the two declaration APIs it came through:\n * `registerPluginEntitlements({ orgPermissions })` (`source: 'catalog'`) or\n * `registerPluginPermissions` (`source: 'tier'`, its per-tier defaults read\n * as org-role defaults, the owner on the admin tier).\n */\nexport interface PluginCatalogPermission {\n key: string\n label: string\n description: string\n pluginId: string\n roleDefaults: Readonly<Record<OrgRole, boolean>>\n hostRoleDefaults?: Readonly<Record<HostAccessRole, boolean>>\n group: string\n groupLabel: string\n source: 'catalog' | 'tier'\n /** The broader key this one refines, when it does. */\n refines?: string\n /** The key this one follows without needing it, when it does. */\n follows?: string\n /**\n * Present when a site can narrow the key (AGL-4047): each built-in role's\n * default for whether it does, normalized from the declaration.\n */\n narrowsPerSite?: Readonly<Record<OrgRole, boolean>>\n /** The key's preset is the member's role on the site (AGL-4047); see the declaration's. */\n presetFromSiteRole?: true\n /** That preset holds for members of the whole workspace only (AGL-4047); see the declaration's. */\n workspaceMembersOnly?: true\n}\n\nexport interface PluginEntitlementRegistration {\n pluginId: string\n seatAddons?: readonly PluginSeatAddonDeclaration[]\n features?: readonly PluginFeatureDeclaration[]\n lockdownFeatures?: readonly PluginLockdownFeatureDeclaration[]\n /** Per-tier keys a plugin's own pages read (`registerPluginPermissions`). */\n permissions?: readonly Omit<PluginPermission, 'pluginId'>[]\n /** Keys added to the org permission catalog — see {@link PluginOrgPermissionDeclaration}. */\n orgPermissions?: readonly PluginOrgPermissionDeclaration[]\n}\n\n/**\n * The registrations and their listeners, held on `globalThis` rather than in\n * module `const`s (AGL-3596), as the custom field registry and the\n * declarations repair hook are (AGL-3412).\n *\n * An app registers the plugins' declarations from `instrumentation.ts`, which\n * Next compiles into a different module graph from the routes. A module-scoped\n * map was filled in the instrumentation's copy and read empty in a route's, so\n * a core route that never loaded a plugin knew none of its keys: the site\n * create door asked for `ai.generate`, read `undefined` from a catalog that\n * had never heard of it, and every guided AI start was born with the starter.\n * One map per process, under a `Symbol.for` key, is the same map in every\n * copy.\n */\nconst REGISTRY_KEY = Symbol.for('@aglyn/aglyn:plugin-entitlements')\n\ninterface PluginEntitlementsRegistry {\n registrations: Map<string, PluginEntitlementRegistration>\n /**\n * Readers of the registrations that keep a derived view in step with them —\n * every copy of the org permission catalog rebuilds itself on every\n * registration and reset, because a plugin's declarations can register\n * after a module that imported the catalog was evaluated, and in another\n * module graph's copy of this file.\n */\n listeners: Set<() => void>\n}\n\nconst globalScope = globalThis as typeof globalThis & {\n [REGISTRY_KEY]?: PluginEntitlementsRegistry\n}\n\nconst registry: PluginEntitlementsRegistry = (globalScope[REGISTRY_KEY] ??= {\n registrations: new Map(),\n listeners: new Set(),\n})\n\nconst registrations = registry.registrations\nconst listeners = registry.listeners\n\n/** Calls `listener` after every registration and reset; answers the unsubscribe. */\nexport function subscribePluginEntitlements(listener: () => void): () => void {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\nfunction notifyListeners(): void {\n for (const listener of [...listeners]) listener()\n}\n\nconst CATALOG_ORDER = new Map(\n FIRST_PARTY_PLUGINS.map((plugin, index) => [plugin.id, index]),\n)\n\nfunction catalogRank(pluginId: string): number {\n return CATALOG_ORDER.get(pluginId) ?? Number.MAX_SAFE_INTEGER\n}\n\nfunction orderedRegistrations(): PluginEntitlementRegistration[] {\n return [...registrations.values()].sort(\n (a, b) =>\n catalogRank(a.pluginId) - catalogRank(b.pluginId) ||\n a.pluginId.localeCompare(b.pluginId),\n )\n}\n\nfunction refuseStolenKeys(\n pluginId: string,\n kind: 'seat add-on' | 'lockdown feature' | 'org permission',\n keys: readonly string[],\n ownerOf: (key: string) => string | undefined,\n): void {\n for (const key of keys) {\n const owner = ownerOf(key)\n if (owner && owner !== pluginId) {\n throw new Error(\n `${kind} \"${key}\" is already declared by \"${owner}\"; refused \"${pluginId}\"`,\n )\n }\n }\n}\n\n/**\n * Idempotent per plugin — re-registration replaces that plugin's\n * declarations. A seat add-on, lockdown or catalog permission key another\n * plugin already owns refuses the whole registration: one key, one owner,\n * or the two declarations disagree about what the key means.\n */\nexport function registerPluginEntitlements(\n registration: PluginEntitlementRegistration,\n): void {\n const pluginId = registration.pluginId.trim()\n if (!pluginId) throw new Error('plugin entitlements need a pluginId')\n refuseStolenKeys(\n pluginId,\n 'seat add-on',\n (registration.seatAddons ?? []).map((entry) => entry.key),\n (key) => pluginSeatAddonOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'lockdown feature',\n (registration.lockdownFeatures ?? []).map((entry) => entry.key),\n (key) => pluginLockdownFeatureOwner(key),\n )\n refuseStolenKeys(\n pluginId,\n 'org permission',\n (registration.orgPermissions ?? []).map((entry) => entry.key),\n (key) => pluginOrgPermissionOwner(key) ?? pluginTierPermissionOwner(key),\n )\n registrations.set(pluginId, { ...registration, pluginId })\n if (registration.permissions?.length) {\n registerPluginPermissions(\n registration.permissions.map((permission) => ({ ...permission, pluginId })),\n )\n }\n notifyListeners()\n}\n\nfunction pluginSeatAddonOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.seatAddons?.some((addon) => addon.key === key)) return entry.pluginId\n }\n return undefined\n}\n\nfunction pluginLockdownFeatureOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.lockdownFeatures?.some((feature) => feature.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\nfunction pluginOrgPermissionOwner(key: string): string | undefined {\n for (const entry of registrations.values()) {\n if (entry.orgPermissions?.some((permission) => permission.key === key)) {\n return entry.pluginId\n }\n }\n return undefined\n}\n\nfunction pluginTierPermissionOwner(key: string): string | undefined {\n return listPluginPermissions().find((permission) => permission.key === key)?.pluginId\n}\n\n/** Refusals already logged by `listPluginCatalogPermissions`, so each is reported once. */\n// boot-registry: per copy — it only de-duplicates a warning; a second compiled copy logging the same refusal once more is harmless, and nothing reads it to decide anything.\nconst reportedCatalogRefusals = new Set<string>()\n\n/** A plugin's catalog label, the heading its own permission group carries. */\nfunction pluginLabel(pluginId: string): string {\n return FIRST_PARTY_PLUGINS.find((plugin) => plugin.id === pluginId)?.label ?? pluginId\n}\n\n/**\n * A declaration's `narrowsPerSite`, normalized to each built-in role's\n * default (AGL-4047): `true` or `false` names every role, a map names each,\n * a role it leaves out keeps. Absent: the key is not one a site can narrow.\n */\nfunction narrowingDefaultsOf(\n declared: boolean | Partial<Record<OrgRole, boolean>> | undefined,\n): { narrowsPerSite?: Readonly<Record<OrgRole, boolean>> } {\n if (declared === undefined || declared === null) return {}\n const every = typeof declared === 'boolean' ? declared : undefined\n const map = typeof declared === 'object' ? declared : {}\n return {\n narrowsPerSite: {\n owner: every ?? map.owner === true,\n admin: every ?? map.admin === true,\n editor: every ?? map.editor === true,\n viewer: every ?? map.viewer === true,\n },\n }\n}\n\n/**\n * Every key plugins contribute to the permission catalog, through EITHER\n * declaration API, in catalog order (AGL-4047): per plugin, its\n * `orgPermissions` declarations and then its `registerPluginPermissions`\n * keys. A key both APIs carry for the same plugin is the catalog\n * declaration's; one a DIFFERENT plugin already declared is refused and\n * logged once, the first declaration keeping its meaning and defaults.\n *\n * Read live: both registries fill at plugin module scope, which can be after\n * any reader of this was evaluated.\n */\nexport function listPluginCatalogPermissions(): PluginCatalogPermission[] {\n const byPlugin = new Map<string, PluginCatalogPermission[]>()\n const owner = new Map<string, string>()\n const add = (entry: PluginCatalogPermission): void => {\n const incumbent = owner.get(entry.key)\n if (incumbent !== undefined) {\n if (incumbent !== entry.pluginId) {\n const refusal = `${entry.pluginId}\\x00${entry.key}`\n if (!reportedCatalogRefusals.has(refusal)) {\n reportedCatalogRefusals.add(refusal)\n console.error(\n `[plugins] refused permission key \"${entry.key}\" to \"${entry.pluginId}\": ` +\n `already declared by \"${incumbent}\"`,\n )\n }\n }\n return\n }\n owner.set(entry.key, entry.pluginId)\n const list = byPlugin.get(entry.pluginId) ?? []\n list.push(entry)\n byPlugin.set(entry.pluginId, list)\n }\n for (const permission of listPluginOrgPermissions()) {\n add({\n key: permission.key,\n label: permission.label,\n description: permission.description,\n pluginId: permission.pluginId,\n roleDefaults: declarationRoleDefaults(permission),\n ...(permission.hostRoleDefaults ? { hostRoleDefaults: permission.hostRoleDefaults } : {}),\n group: permission.group ?? permission.pluginId,\n groupLabel: permission.groupLabel ?? pluginLabel(permission.pluginId),\n source: 'catalog',\n ...(permission.refines ? { refines: permission.refines } : {}),\n ...(permission.follows ? { follows: permission.follows } : {}),\n // A key whose preset is the site role follows every site's access\n // already, so it offers no narrowing control (AGL-4047).\n ...(permission.presetFromSiteRole && permission.hostRoleDefaults\n ? {\n presetFromSiteRole: true as const,\n ...(permission.workspaceMembersOnly ? { workspaceMembersOnly: true as const } : {}),\n }\n : narrowingDefaultsOf(permission.narrowsPerSite)),\n })\n }\n for (const permission of listPluginPermissions()) {\n add({\n key: permission.key,\n label: permission.label,\n description: permission.description ?? '',\n pluginId: permission.pluginId,\n roleDefaults: declarationRoleDefaults(permission),\n group: permission.group ?? permission.pluginId,\n groupLabel: permission.groupLabel ?? pluginLabel(permission.pluginId),\n source: 'tier',\n ...(permission.refines ? { refines: permission.refines } : {}),\n ...(permission.follows ? { follows: permission.follows } : {}),\n ...narrowingDefaultsOf(permission.narrowsPerSite),\n })\n }\n return [...byPlugin.keys()]\n .sort((a, b) => catalogRank(a) - catalogRank(b) || a.localeCompare(b))\n .flatMap((pluginId) => byPlugin.get(pluginId) ?? [])\n}\n\n/** Every declared catalog permission, in catalog order, with the plugin that declared it. */\nexport function listPluginOrgPermissions(): Array<\n PluginOrgPermissionDeclaration & { pluginId: string }\n> {\n return orderedRegistrations().flatMap((entry) =>\n (entry.orgPermissions ?? []).map((permission) => ({\n ...permission,\n pluginId: entry.pluginId,\n })),\n )\n}\n\n/** Every declared seat add-on, in catalog order. */\nexport function listPluginSeatAddons(): PluginSeatAddonDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.seatAddons ?? [])])\n}\n\n/** One declared seat add-on by its `seatAddons` key. */\nexport function pluginSeatAddon(key: string): PluginSeatAddonDeclaration | undefined {\n return listPluginSeatAddons().find((entry) => entry.key === key)\n}\n\n/** Every declared feature, in catalog order. */\nexport function listPluginFeatures(): PluginFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [...(entry.features ?? [])])\n}\n\n/** Every declared lockdown lever, in catalog order. */\nexport function listPluginLockdownFeatures(): PluginLockdownFeatureDeclaration[] {\n return orderedRegistrations().flatMap((entry) => [\n ...(entry.lockdownFeatures ?? []),\n ])\n}\n\n/** One declared lockdown lever by key. */\nexport function pluginLockdownFeature(\n key: string,\n): PluginLockdownFeatureDeclaration | undefined {\n return listPluginLockdownFeatures().find((entry) => entry.key === key)\n}\n\n/** The plugin that declared a lockdown lever, for attribution. */\nexport function pluginIdForLockdownFeature(key: string): string | undefined {\n return pluginLockdownFeatureOwner(key)\n}\n\n/** Every registration, in catalog order. */\nexport function listPluginEntitlementRegistrations(): PluginEntitlementRegistration[] {\n return orderedRegistrations()\n}\n\n/**\n * Test seam: forget every registration. The per-tier permissions stay\n * registered; the catalog permissions go with their registrations.\n */\nexport function resetPluginEntitlementsForTests(): void {\n registrations.clear()\n notifyListeners()\n}\n"],"names":["globalScope","REGISTRY_KEY","declarationRoleDefaults","FIRST_PARTY_PLUGINS","listPluginPermissions","registerPluginPermissions","pluginSeatAddonSoldOn","addon","plan","soldOn","quota","perUnitByPlan","Symbol","for","globalThis","registry","registrations","Map","listeners","Set","subscribePluginEntitlements","listener","add","delete","notifyListeners","CATALOG_ORDER","map","plugin","index","id","catalogRank","pluginId","get","Number","MAX_SAFE_INTEGER","orderedRegistrations","values","sort","a","b","localeCompare","refuseStolenKeys","kind","keys","ownerOf","key","owner","Error","registerPluginEntitlements","registration","trim","seatAddons","entry","pluginSeatAddonOwner","lockdownFeatures","pluginLockdownFeatureOwner","orgPermissions","pluginOrgPermissionOwner","pluginTierPermissionOwner","set","permissions","length","permission","some","undefined","feature","find","reportedCatalogRefusals","pluginLabel","label","narrowingDefaultsOf","declared","every","narrowsPerSite","admin","editor","viewer","listPluginCatalogPermissions","byPlugin","incumbent","refusal","has","console","error","list","push","listPluginOrgPermissions","description","roleDefaults","hostRoleDefaults","group","groupLabel","source","refines","follows","presetFromSiteRole","workspaceMembersOnly","flatMap","listPluginSeatAddons","pluginSeatAddon","listPluginFeatures","features","listPluginLockdownFeatures","pluginLockdownFeature","pluginIdForLockdownFeature","listPluginEntitlementRegistrations","resetPluginEntitlementsForTests","clear"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC;IAqQ6CA,cAAYC,eAAZD;AAlQ9C,SAASE,uBAAuB,QAAQ,kCAA8B;AACtE,SAASC,mBAAmB,QAAQ,uBAAmB;AACvD,SACEC,qBAAqB,EACrBC,yBAAyB,QAEpB,0BAAsB;AAsC7B;;;CAGC,GACD,OAAO,SAASC,sBACdC,KAAiC,EACjCC,IAAa;;QAILD;IAFR,IAAIA,MAAME,MAAM,EAAE,OAAOF,MAAME,MAAM,CAACD;IACtC,IAAI,CAACD,MAAMG,KAAK,EAAE,OAAO;IACzB,OAAO,UAACH,6BAAAA,MAAMG,KAAK,CAACC,aAAa,qBAAzBJ,0BAA2B,CAACC,KAAK,mBAAI,KAAK;AACpD;AA2KA;;;;;;;;;;;;;CAaC,GACD,MAAMP,eAAeW,OAAOC,GAAG,CAAC;AAchC,MAAMb,cAAcc;AAIpB,MAAMC,YAAwCf,IAAAA,CAAAA,eAAAA,YAAW,CAACC,gBAAAA,aAAa,YAAzBD,IAAAA,YAAW,CAACC,cAAa,GAAK;IAC1Ee,eAAe,IAAIC;IACnBC,WAAW,IAAIC;AACjB;AAEA,MAAMH,gBAAgBD,SAASC,aAAa;AAC5C,MAAME,YAAYH,SAASG,SAAS;AAEpC,kFAAkF,GAClF,OAAO,SAASE,4BAA4BC,QAAoB;IAC9DH,UAAUI,GAAG,CAACD;IACd,OAAO;QACLH,UAAUK,MAAM,CAACF;IACnB;AACF;AAEA,SAASG;IACP,KAAK,MAAMH,YAAY;WAAIH;KAAU,CAAEG;AACzC;AAEA,MAAMI,gBAAgB,IAAIR,IACxBd,oBAAoBuB,GAAG,CAAC,CAACC,QAAQC,QAAU;QAACD,OAAOE,EAAE;QAAED;KAAM;AAG/D,SAASE,YAAYC,QAAgB;QAC5BN;IAAP,QAAOA,qBAAAA,cAAcO,GAAG,CAACD,qBAAlBN,qBAA+BQ,OAAOC,gBAAgB;AAC/D;AAEA,SAASC;IACP,OAAO;WAAInB,cAAcoB,MAAM;KAAG,CAACC,IAAI,CACrC,CAACC,GAAGC,IACFT,YAAYQ,EAAEP,QAAQ,IAAID,YAAYS,EAAER,QAAQ,KAChDO,EAAEP,QAAQ,CAACS,aAAa,CAACD,EAAER,QAAQ;AAEzC;AAEA,SAASU,iBACPV,QAAgB,EAChBW,IAA2D,EAC3DC,IAAuB,EACvBC,OAA4C;IAE5C,KAAK,MAAMC,OAAOF,KAAM;QACtB,MAAMG,QAAQF,QAAQC;QACtB,IAAIC,SAASA,UAAUf,UAAU;YAC/B,MAAM,IAAIgB,MACR,GAAGL,KAAK,EAAE,EAAEG,IAAI,0BAA0B,EAAEC,MAAM,YAAY,EAAEf,SAAS,CAAC,CAAC;QAE/E;IACF;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASiB,2BACdC,YAA2C;QAOxCA,0BAMAA,gCAMAA;QAICA;IArBJ,MAAMlB,WAAWkB,aAAalB,QAAQ,CAACmB,IAAI;IAC3C,IAAI,CAACnB,UAAU,MAAM,IAAIgB,MAAM;IAC/BN,iBACEV,UACA,eACA,EAACkB,2BAAAA,aAAaE,UAAU,YAAvBF,2BAA2B,EAAE,EAAEvB,GAAG,CAAC,CAAC0B,QAAUA,MAAMP,GAAG,GACxD,CAACA,MAAQQ,qBAAqBR;IAEhCJ,iBACEV,UACA,oBACA,EAACkB,iCAAAA,aAAaK,gBAAgB,YAA7BL,iCAAiC,EAAE,EAAEvB,GAAG,CAAC,CAAC0B,QAAUA,MAAMP,GAAG,GAC9D,CAACA,MAAQU,2BAA2BV;IAEtCJ,iBACEV,UACA,kBACA,EAACkB,+BAAAA,aAAaO,cAAc,YAA3BP,+BAA+B,EAAE,EAAEvB,GAAG,CAAC,CAAC0B,QAAUA,MAAMP,GAAG,GAC5D,CAACA;YAAQY;gBAAAA,4BAAAA,yBAAyBZ,gBAAzBY,4BAAiCC,0BAA0Bb;;IAEtE7B,cAAc2C,GAAG,CAAC5B,UAAU,aAAKkB;QAAclB;;IAC/C,KAAIkB,4BAAAA,aAAaW,WAAW,qBAAxBX,0BAA0BY,MAAM,EAAE;QACpCxD,0BACE4C,aAAaW,WAAW,CAAClC,GAAG,CAAC,CAACoC,aAAgB,aAAKA;gBAAY/B;;IAEnE;IACAP;AACF;AAEA,SAAS6B,qBAAqBR,GAAW;IACvC,KAAK,MAAMO,SAASpC,cAAcoB,MAAM,GAAI;YACtCgB;QAAJ,KAAIA,oBAAAA,MAAMD,UAAU,qBAAhBC,kBAAkBW,IAAI,CAAC,CAACxD,QAAUA,MAAMsC,GAAG,KAAKA,MAAM,OAAOO,MAAMrB,QAAQ;IACjF;IACA,OAAOiC;AACT;AAEA,SAAST,2BAA2BV,GAAW;IAC7C,KAAK,MAAMO,SAASpC,cAAcoB,MAAM,GAAI;YACtCgB;QAAJ,KAAIA,0BAAAA,MAAME,gBAAgB,qBAAtBF,wBAAwBW,IAAI,CAAC,CAACE,UAAYA,QAAQpB,GAAG,KAAKA,MAAM;YAClE,OAAOO,MAAMrB,QAAQ;QACvB;IACF;IACA,OAAOiC;AACT;AAEA,SAASP,yBAAyBZ,GAAW;IAC3C,KAAK,MAAMO,SAASpC,cAAcoB,MAAM,GAAI;YACtCgB;QAAJ,KAAIA,wBAAAA,MAAMI,cAAc,qBAApBJ,sBAAsBW,IAAI,CAAC,CAACD,aAAeA,WAAWjB,GAAG,KAAKA,MAAM;YACtE,OAAOO,MAAMrB,QAAQ;QACvB;IACF;IACA,OAAOiC;AACT;AAEA,SAASN,0BAA0Bb,GAAW;QACrCzC;IAAP,QAAOA,8BAAAA,wBAAwB8D,IAAI,CAAC,CAACJ,aAAeA,WAAWjB,GAAG,KAAKA,yBAAhEzC,4BAAsE2B,QAAQ;AACvF;AAEA,yFAAyF,GACzF,6KAA6K;AAC7K,MAAMoC,0BAA0B,IAAIhD;AAEpC,4EAA4E,GAC5E,SAASiD,YAAYrC,QAAgB;;QAC5B5B;IAAP,gBAAOA,4BAAAA,oBAAoB+D,IAAI,CAAC,CAACvC,SAAWA,OAAOE,EAAE,KAAKE,8BAAnD5B,0BAA8DkE,KAAK,mBAAItC;AAChF;AAEA;;;;CAIC,GACD,SAASuC,oBACPC,QAAiE;IAEjE,IAAIA,aAAaP,aAAaO,aAAa,MAAM,OAAO,CAAC;IACzD,MAAMC,QAAQ,OAAOD,aAAa,YAAYA,WAAWP;IACzD,MAAMtC,MAAM,OAAO6C,aAAa,WAAWA,WAAW,CAAC;IACvD,OAAO;QACLE,gBAAgB;YACd3B,KAAK,EAAE0B,gBAAAA,QAAS9C,IAAIoB,KAAK,KAAK;YAC9B4B,KAAK,EAAEF,gBAAAA,QAAS9C,IAAIgD,KAAK,KAAK;YAC9BC,MAAM,EAAEH,gBAAAA,QAAS9C,IAAIiD,MAAM,KAAK;YAChCC,MAAM,EAAEJ,gBAAAA,QAAS9C,IAAIkD,MAAM,KAAK;QAClC;IACF;AACF;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC;IACd,MAAMC,WAAW,IAAI7D;IACrB,MAAM6B,QAAQ,IAAI7B;IAClB,MAAMK,MAAM,CAAC8B;YAgBE0B;QAfb,MAAMC,YAAYjC,MAAMd,GAAG,CAACoB,MAAMP,GAAG;QACrC,IAAIkC,cAAcf,WAAW;YAC3B,IAAIe,cAAc3B,MAAMrB,QAAQ,EAAE;gBAChC,MAAMiD,UAAU,GAAG5B,MAAMrB,QAAQ,CAAC,IAAI,EAAEqB,MAAMP,GAAG,EAAE;gBACnD,IAAI,CAACsB,wBAAwBc,GAAG,CAACD,UAAU;oBACzCb,wBAAwB7C,GAAG,CAAC0D;oBAC5BE,QAAQC,KAAK,CACX,CAAC,kCAAkC,EAAE/B,MAAMP,GAAG,CAAC,MAAM,EAAEO,MAAMrB,QAAQ,CAAC,GAAG,CAAC,GACxE,CAAC,qBAAqB,EAAEgD,UAAU,CAAC,CAAC;gBAE1C;YACF;YACA;QACF;QACAjC,MAAMa,GAAG,CAACP,MAAMP,GAAG,EAAEO,MAAMrB,QAAQ;QACnC,MAAMqD,QAAON,gBAAAA,SAAS9C,GAAG,CAACoB,MAAMrB,QAAQ,aAA3B+C,gBAAgC,EAAE;QAC/CM,KAAKC,IAAI,CAACjC;QACV0B,SAASnB,GAAG,CAACP,MAAMrB,QAAQ,EAAEqD;IAC/B;IACA,KAAK,MAAMtB,cAAcwB,2BAA4B;YAQ1CxB,mBACKA;QARdxC,IAAI;YACFuB,KAAKiB,WAAWjB,GAAG;YACnBwB,OAAOP,WAAWO,KAAK;YACvBkB,aAAazB,WAAWyB,WAAW;YACnCxD,UAAU+B,WAAW/B,QAAQ;YAC7ByD,cAActF,wBAAwB4D;WAClCA,WAAW2B,gBAAgB,GAAG;YAAEA,kBAAkB3B,WAAW2B,gBAAgB;QAAC,IAAI,CAAC;YACvFC,KAAK,GAAE5B,oBAAAA,WAAW4B,KAAK,YAAhB5B,oBAAoBA,WAAW/B,QAAQ;YAC9C4D,UAAU,GAAE7B,yBAAAA,WAAW6B,UAAU,YAArB7B,yBAAyBM,YAAYN,WAAW/B,QAAQ;YACpE6D,QAAQ;WACJ9B,WAAW+B,OAAO,GAAG;YAAEA,SAAS/B,WAAW+B,OAAO;QAAC,IAAI,CAAC,GACxD/B,WAAWgC,OAAO,GAAG;YAAEA,SAAShC,WAAWgC,OAAO;QAAC,IAAI,CAAC,GAGxDhC,WAAWiC,kBAAkB,IAAIjC,WAAW2B,gBAAgB,GAC5D;YACEM,oBAAoB;WAChBjC,WAAWkC,oBAAoB,GAAG;YAAEA,sBAAsB;QAAc,IAAI,CAAC,KAEnF1B,oBAAoBR,WAAWW,cAAc;IAErD;IACA,KAAK,MAAMX,cAAc1D,wBAAyB;YAIjC0D,yBAGNA,oBACKA;QAPdxC,IAAI;YACFuB,KAAKiB,WAAWjB,GAAG;YACnBwB,OAAOP,WAAWO,KAAK;YACvBkB,WAAW,GAAEzB,0BAAAA,WAAWyB,WAAW,YAAtBzB,0BAA0B;YACvC/B,UAAU+B,WAAW/B,QAAQ;YAC7ByD,cAActF,wBAAwB4D;YACtC4B,KAAK,GAAE5B,qBAAAA,WAAW4B,KAAK,YAAhB5B,qBAAoBA,WAAW/B,QAAQ;YAC9C4D,UAAU,GAAE7B,0BAAAA,WAAW6B,UAAU,YAArB7B,0BAAyBM,YAAYN,WAAW/B,QAAQ;YACpE6D,QAAQ;WACJ9B,WAAW+B,OAAO,GAAG;YAAEA,SAAS/B,WAAW+B,OAAO;QAAC,IAAI,CAAC,GACxD/B,WAAWgC,OAAO,GAAG;YAAEA,SAAShC,WAAWgC,OAAO;QAAC,IAAI,CAAC,GACzDxB,oBAAoBR,WAAWW,cAAc;IAEpD;IACA,OAAO;WAAIK,SAASnC,IAAI;KAAG,CACxBN,IAAI,CAAC,CAACC,GAAGC,IAAMT,YAAYQ,KAAKR,YAAYS,MAAMD,EAAEE,aAAa,CAACD,IAClE0D,OAAO,CAAC,CAAClE;YAAa+C;gBAAAA,gBAAAA,SAAS9C,GAAG,CAACD,qBAAb+C,gBAA0B,EAAE;;AACvD;AAEA,2FAA2F,GAC3F,OAAO,SAASQ;IAGd,OAAOnD,uBAAuB8D,OAAO,CAAC,CAAC7C;YACpCA;eAAD,EAACA,wBAAAA,MAAMI,cAAc,YAApBJ,wBAAwB,EAAE,EAAE1B,GAAG,CAAC,CAACoC,aAAgB,aAC7CA;gBACH/B,UAAUqB,MAAMrB,QAAQ;;;AAG9B;AAEA,kDAAkD,GAClD,OAAO,SAASmE;IACd,OAAO/D,uBAAuB8D,OAAO,CAAC,CAAC7C;YAAeA;eAAL;gBAAKA,oBAAAA,MAAMD,UAAU,YAAhBC,oBAAoB,EAAE;SAAE;;AAChF;AAEA,sDAAsD,GACtD,OAAO,SAAS+C,gBAAgBtD,GAAW;IACzC,OAAOqD,uBAAuBhC,IAAI,CAAC,CAACd,QAAUA,MAAMP,GAAG,KAAKA;AAC9D;AAEA,8CAA8C,GAC9C,OAAO,SAASuD;IACd,OAAOjE,uBAAuB8D,OAAO,CAAC,CAAC7C;YAAeA;eAAL;gBAAKA,kBAAAA,MAAMiD,QAAQ,YAAdjD,kBAAkB,EAAE;SAAE;;AAC9E;AAEA,qDAAqD,GACrD,OAAO,SAASkD;IACd,OAAOnE,uBAAuB8D,OAAO,CAAC,CAAC7C;YACjCA;eAD2C;gBAC3CA,0BAAAA,MAAME,gBAAgB,YAAtBF,0BAA0B,EAAE;SACjC;;AACH;AAEA,wCAAwC,GACxC,OAAO,SAASmD,sBACd1D,GAAW;IAEX,OAAOyD,6BAA6BpC,IAAI,CAAC,CAACd,QAAUA,MAAMP,GAAG,KAAKA;AACpE;AAEA,gEAAgE,GAChE,OAAO,SAAS2D,2BAA2B3D,GAAW;IACpD,OAAOU,2BAA2BV;AACpC;AAEA,0CAA0C,GAC1C,OAAO,SAAS4D;IACd,OAAOtE;AACT;AAEA;;;CAGC,GACD,OAAO,SAASuE;IACd1F,cAAc2F,KAAK;IACnBnF;AACF"}
|