@aglyn/aglyn 1.0.0-beta.285 → 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.
Files changed (91) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/account-acquisition.d.ts +14 -0
  3. package/src/lib/app-utils/account-acquisition.js +19 -0
  4. package/src/lib/app-utils/account-acquisition.js.map +1 -1
  5. package/src/lib/app-utils/api-plugins.d.ts +25 -0
  6. package/src/lib/app-utils/api-plugins.js +13 -0
  7. package/src/lib/app-utils/api-plugins.js.map +1 -1
  8. package/src/lib/app-utils/business-profile-values.d.ts +15 -3
  9. package/src/lib/app-utils/business-profile-values.js +19 -3
  10. package/src/lib/app-utils/business-profile-values.js.map +1 -1
  11. package/src/lib/app-utils/business-profile.d.ts +5 -3
  12. package/src/lib/app-utils/business-profile.js +2 -1
  13. package/src/lib/app-utils/business-profile.js.map +1 -1
  14. package/src/lib/app-utils/contacts.d.ts +11 -0
  15. package/src/lib/app-utils/contacts.js +18 -0
  16. package/src/lib/app-utils/contacts.js.map +1 -1
  17. package/src/lib/app-utils/docs-deflection.d.ts +1 -1
  18. package/src/lib/app-utils/docs-deflection.js +17 -0
  19. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  20. package/src/lib/app-utils/docs-help-sections.generated.js +5 -2
  21. package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
  22. package/src/lib/app-utils/docs-help.generated.d.ts +9 -9
  23. package/src/lib/app-utils/docs-help.generated.js +20 -0
  24. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  25. package/src/lib/app-utils/docs-index.generated.js +168 -63
  26. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  27. package/src/lib/app-utils/docs-retrieval.d.ts +17 -0
  28. package/src/lib/app-utils/docs-retrieval.js +28 -0
  29. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  30. package/src/lib/app-utils/host-naming.d.ts +2 -1
  31. package/src/lib/app-utils/host-naming.js +65 -2
  32. package/src/lib/app-utils/host-naming.js.map +1 -1
  33. package/src/lib/app-utils/host-permissions.d.ts +159 -3
  34. package/src/lib/app-utils/host-permissions.js +384 -12
  35. package/src/lib/app-utils/host-permissions.js.map +1 -1
  36. package/src/lib/app-utils/org-permission-editor.d.ts +128 -1
  37. package/src/lib/app-utils/org-permission-editor.js +176 -1
  38. package/src/lib/app-utils/org-permission-editor.js.map +1 -1
  39. package/src/lib/app-utils/org-permissions.d.ts +62 -1
  40. package/src/lib/app-utils/org-permissions.js +55 -5
  41. package/src/lib/app-utils/org-permissions.js.map +1 -1
  42. package/src/lib/app-utils/org-role-grants.d.ts +28 -0
  43. package/src/lib/app-utils/org-role-grants.js +180 -2
  44. package/src/lib/app-utils/org-role-grants.js.map +1 -1
  45. package/src/lib/app-utils/org-roles.js +11 -2
  46. package/src/lib/app-utils/org-roles.js.map +1 -1
  47. package/src/lib/app-utils/redirect-query.d.ts +39 -0
  48. package/src/lib/app-utils/redirect-query.js +57 -0
  49. package/src/lib/app-utils/redirect-query.js.map +1 -0
  50. package/src/lib/app-utils/release-flags.js +2 -2
  51. package/src/lib/app-utils/release-flags.js.map +1 -1
  52. package/src/lib/app-utils/site-starter-links.d.ts +42 -0
  53. package/src/lib/app-utils/site-starter-links.js +79 -0
  54. package/src/lib/app-utils/site-starter-links.js.map +1 -0
  55. package/src/lib/app-utils/site-starter-nodes.d.ts +91 -0
  56. package/src/lib/app-utils/site-starter-nodes.js +210 -0
  57. package/src/lib/app-utils/site-starter-nodes.js.map +1 -0
  58. package/src/lib/app-utils/site-starter-theme.d.ts +90 -0
  59. package/src/lib/app-utils/site-starter-theme.js +241 -0
  60. package/src/lib/app-utils/site-starter-theme.js.map +1 -0
  61. package/src/lib/app-utils/webhook-delivery.js +6 -1
  62. package/src/lib/app-utils/webhook-delivery.js.map +1 -1
  63. package/src/lib/foundation/definitions/organization.types.d.ts +12 -2
  64. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  65. package/src/lib/plugin-manager/first-party-plugins.generated.js +39 -0
  66. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  67. package/src/lib/plugin-manager/plugin-closure-notices.d.ts +88 -0
  68. package/src/lib/plugin-manager/plugin-closure-notices.js +123 -0
  69. package/src/lib/plugin-manager/plugin-closure-notices.js.map +1 -0
  70. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +15 -0
  71. package/src/lib/plugin-manager/plugin-contact-capture.js +24 -1
  72. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  73. package/src/lib/plugin-manager/plugin-customer-display-verifier.d.ts +77 -0
  74. package/src/lib/plugin-manager/plugin-customer-display-verifier.js +74 -0
  75. package/src/lib/plugin-manager/plugin-customer-display-verifier.js.map +1 -0
  76. package/src/lib/plugin-manager/plugin-customer-display.d.ts +135 -0
  77. package/src/lib/plugin-manager/plugin-customer-display.js +189 -0
  78. package/src/lib/plugin-manager/plugin-customer-display.js.map +1 -0
  79. package/src/lib/plugin-manager/plugin-entitlements.d.ts +59 -0
  80. package/src/lib/plugin-manager/plugin-entitlements.js +32 -3
  81. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  82. package/src/lib/plugin-manager/plugin-events.d.ts +45 -0
  83. package/src/lib/plugin-manager/plugin-events.js.map +1 -1
  84. package/src/lib/plugin-manager/plugin-permissions.d.ts +74 -0
  85. package/src/lib/plugin-manager/plugin-permissions.js +91 -1
  86. package/src/lib/plugin-manager/plugin-permissions.js.map +1 -1
  87. package/src/lib/plugin-manager/plugin-site-starters.d.ts +7 -0
  88. package/src/lib/plugin-manager/plugin-site-starters.js.map +1 -1
  89. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +87 -4
  90. package/src/lib/plugin-manager/plugin-tax-return-sources.js +155 -35
  91. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -1
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * A PLUGIN'S SCREEN ON A PAIRED CUSTOMER DISPLAY (AGL-4042).
19
+ *
20
+ * A register can pair a screen that faces the customer (AGL-3608). That
21
+ * screen belongs to the plugin that runs the register, and until now it
22
+ * could show only that plugin's own prompts — a basket, a tip, a receipt
23
+ * choice. Another plugin that needs the customer's own hands on a screen
24
+ * (a form to fill in, a document to sign) had no way to ask for it.
25
+ *
26
+ * So the seam has two halves, and neither names the other plugin:
27
+ *
28
+ * 1. A **display host**: the page running a register with a connected
29
+ * display registers itself here ({@link registerCustomerDisplayHost}) while
30
+ * it can show something, and unregisters when it can't. Any plugin's
31
+ * console widget on that page asks {@link customerDisplayHost} and, when
32
+ * there is one, hands it a {@link CustomerDisplayScreen}.
33
+ * 2. A **screen** is one of the asking plugin's own public console pages
34
+ * (`ConsolePublicPage`, served at `/kiosk/{pluginId}{path}` with no staff
35
+ * session), with a few short parameters. The display opens it, and the
36
+ * page sends the display back to where it came from
37
+ * ({@link customerDisplayReturnPath}) when the customer is done.
38
+ *
39
+ * THE PAIRED DISPLAY IS THE AUTHORIZATION (AGL-4042). A screen's page has no
40
+ * staff session, and needs none: the display it runs on was paired to one
41
+ * store by staff, and that pairing is what it shows. The display's page hands
42
+ * its credential to core ({@link rememberCustomerDisplayCredential}); a
43
+ * screen's page reads it ({@link customerDisplayCredential}) and sends it to
44
+ * its own plugin's server, which asks core who the display is
45
+ * (`plugin-customer-display-verifier`): the store it is paired to, the screen
46
+ * it is showing now, and who put that screen there. Nothing is minted per
47
+ * screen, nothing is opened or closed, and nothing names the plugin on the
48
+ * other side.
49
+ *
50
+ * The display opens only a page the plugin DECLARED public: the console's
51
+ * public route refuses any other before a byte of the plugin loads. The
52
+ * parameters are capped and plain, because they ride the display's state and
53
+ * its address bar; they must never carry a customer's details, and never the
54
+ * display's credential.
55
+ *
56
+ * A screen has no timer (AGL-4042): it stays, however long the customer
57
+ * takes, until the register shows something else on the display, the plugin
58
+ * takes it off ({@link CustomerDisplayHost.close}), or its page sends the
59
+ * display back once the customer is done. A screen's page follows the
60
+ * register by asking its server whether its screen is still the one showing.
61
+ *
62
+ * Client-only, React-free, and reached by its own subpath, never the barrel:
63
+ * a published page has no use for it.
64
+ */
65
+ /** What a plugin asks a paired display to show. */
66
+ export interface CustomerDisplayScreen {
67
+ /** The plugin whose public page it is. */
68
+ pluginId: string;
69
+ /** The page's path beneath `/kiosk/{pluginId}`, with its leading slash: `/display-sign`. */
70
+ path: string;
71
+ /** Short plain values the page reads from its query string. Never a customer's details. */
72
+ params: Record<string, string>;
73
+ }
74
+ /** The query key a screen's page reads the display's own address from. */
75
+ export declare const CUSTOMER_DISPLAY_RETURN_PARAM = "return";
76
+ /**
77
+ * A screen as a register may store it, or `null` for anything else: a plugin
78
+ * id and a public page path in their own shapes, and at most eight short
79
+ * string parameters with plain keys. The return address is never one of
80
+ * them; the display adds its own.
81
+ */
82
+ export declare function sanitizeCustomerDisplayScreen(raw: unknown): CustomerDisplayScreen | null;
83
+ /** Whether a path is a display's own address, which a screen may send it back to. */
84
+ export declare function isCustomerDisplayReturnPath(path: unknown): path is string;
85
+ /**
86
+ * Where a display opens a screen: the plugin's public page with the screen's
87
+ * parameters and, under {@link CUSTOMER_DISPLAY_RETURN_PARAM}, the display's
88
+ * own address to come back to. `null` for a screen or a return address out
89
+ * of shape.
90
+ */
91
+ export declare function customerDisplayScreenHref(screen: CustomerDisplayScreen, returnTo: string): string | null;
92
+ /** The address a screen's page sends the display back to, from its query string, or `null`. */
93
+ export declare function customerDisplayReturnPath(search: string): string | null;
94
+ /** What the page running a register offers while it has a display to show things on. */
95
+ export interface CustomerDisplayHost {
96
+ /** Puts a plugin's screen on the display; `false` when it could not. */
97
+ show(screen: CustomerDisplayScreen): Promise<boolean>;
98
+ /**
99
+ * Takes this plugin's screen off the display, when it is the one the
100
+ * register last put there, so the display shows the register's own screen
101
+ * again; `false` when it could not. A screen of another plugin, or one the
102
+ * register already replaced, is left alone (and answers `true`: this
103
+ * plugin's screen is not showing). A display already on the plugin's page
104
+ * goes back when that page sees its screen is no longer the one showing.
105
+ */
106
+ close?(pluginId: string): Promise<boolean>;
107
+ }
108
+ /**
109
+ * Offers a display host to every plugin on the page, replacing any other;
110
+ * answers the function that withdraws it (and only it).
111
+ */
112
+ export declare function registerCustomerDisplayHost(host: CustomerDisplayHost): () => void;
113
+ /** The display host on this page, or `null` when no register here has a display to show on. */
114
+ export declare function customerDisplayHost(): CustomerDisplayHost | null;
115
+ /** Calls `listener` whenever a host arrives or leaves; answers the unsubscribe. */
116
+ export declare function subscribeCustomerDisplayHost(listener: () => void): () => void;
117
+ /**
118
+ * Where the display's credential is kept on the display itself: the
119
+ * browser's own storage for the console's public pages, which the display's
120
+ * page and every screen's page share.
121
+ */
122
+ export declare const CUSTOMER_DISPLAY_CREDENTIAL_KEY = "aglyn.customerDisplay.credential";
123
+ /**
124
+ * Called by the page that RUNS a paired display (AGL-4042): keeps the
125
+ * credential its pairing gave it where a screen's page can read it, or
126
+ * forgets it (`null`) when the display is unpaired. Best effort: a browser
127
+ * that refuses storage keeps nothing, and a screen's page then signs nothing.
128
+ */
129
+ export declare function rememberCustomerDisplayCredential(credential: string | null): void;
130
+ /**
131
+ * The credential of the paired display this page is running on, for a
132
+ * screen's page to send to its own plugin's server, which asks core's
133
+ * verifier who the display is; `null` on a device that is not one.
134
+ */
135
+ export declare function customerDisplayCredential(): string | null;
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * A PLUGIN'S SCREEN ON A PAIRED CUSTOMER DISPLAY (AGL-4042).
18
+ *
19
+ * A register can pair a screen that faces the customer (AGL-3608). That
20
+ * screen belongs to the plugin that runs the register, and until now it
21
+ * could show only that plugin's own prompts — a basket, a tip, a receipt
22
+ * choice. Another plugin that needs the customer's own hands on a screen
23
+ * (a form to fill in, a document to sign) had no way to ask for it.
24
+ *
25
+ * So the seam has two halves, and neither names the other plugin:
26
+ *
27
+ * 1. A **display host**: the page running a register with a connected
28
+ * display registers itself here ({@link registerCustomerDisplayHost}) while
29
+ * it can show something, and unregisters when it can't. Any plugin's
30
+ * console widget on that page asks {@link customerDisplayHost} and, when
31
+ * there is one, hands it a {@link CustomerDisplayScreen}.
32
+ * 2. A **screen** is one of the asking plugin's own public console pages
33
+ * (`ConsolePublicPage`, served at `/kiosk/{pluginId}{path}` with no staff
34
+ * session), with a few short parameters. The display opens it, and the
35
+ * page sends the display back to where it came from
36
+ * ({@link customerDisplayReturnPath}) when the customer is done.
37
+ *
38
+ * THE PAIRED DISPLAY IS THE AUTHORIZATION (AGL-4042). A screen's page has no
39
+ * staff session, and needs none: the display it runs on was paired to one
40
+ * store by staff, and that pairing is what it shows. The display's page hands
41
+ * its credential to core ({@link rememberCustomerDisplayCredential}); a
42
+ * screen's page reads it ({@link customerDisplayCredential}) and sends it to
43
+ * its own plugin's server, which asks core who the display is
44
+ * (`plugin-customer-display-verifier`): the store it is paired to, the screen
45
+ * it is showing now, and who put that screen there. Nothing is minted per
46
+ * screen, nothing is opened or closed, and nothing names the plugin on the
47
+ * other side.
48
+ *
49
+ * The display opens only a page the plugin DECLARED public: the console's
50
+ * public route refuses any other before a byte of the plugin loads. The
51
+ * parameters are capped and plain, because they ride the display's state and
52
+ * its address bar; they must never carry a customer's details, and never the
53
+ * display's credential.
54
+ *
55
+ * A screen has no timer (AGL-4042): it stays, however long the customer
56
+ * takes, until the register shows something else on the display, the plugin
57
+ * takes it off ({@link CustomerDisplayHost.close}), or its page sends the
58
+ * display back once the customer is done. A screen's page follows the
59
+ * register by asking its server whether its screen is still the one showing.
60
+ *
61
+ * Client-only, React-free, and reached by its own subpath, never the barrel:
62
+ * a published page has no use for it.
63
+ */ /** What a plugin asks a paired display to show. */ import { _ as _extends } from "@swc/helpers/_/_extends";
64
+ var _globalScope_REGISTRY_KEY;
65
+ /** The query key a screen's page reads the display's own address from. */ export const CUSTOMER_DISPLAY_RETURN_PARAM = 'return';
66
+ const PLUGIN_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
67
+ const PATH = /^(\/[a-z0-9][a-z0-9-]{0,63}){1,4}$/;
68
+ const PARAM_KEY = /^[a-zA-Z][a-zA-Z0-9]{0,31}$/;
69
+ const MAX_PARAMS = 8;
70
+ const MAX_PARAM_LENGTH = 2000;
71
+ /** A display's own address: a public console page, and nothing that leaves it. */ const RETURN_PATH = /^\/kiosk(\/[a-z0-9][a-z0-9-]{0,63}){1,5}$/;
72
+ /**
73
+ * A screen as a register may store it, or `null` for anything else: a plugin
74
+ * id and a public page path in their own shapes, and at most eight short
75
+ * string parameters with plain keys. The return address is never one of
76
+ * them; the display adds its own.
77
+ */ export function sanitizeCustomerDisplayScreen(raw) {
78
+ var _input_pluginId, _input_path;
79
+ const input = raw && typeof raw === 'object' ? raw : {};
80
+ const pluginId = String((_input_pluginId = input['pluginId']) != null ? _input_pluginId : '');
81
+ const path = String((_input_path = input['path']) != null ? _input_path : '');
82
+ if (!PLUGIN_ID.test(pluginId) || !PATH.test(path)) return null;
83
+ const rawParams = input['params'] && typeof input['params'] === 'object' ? input['params'] : {};
84
+ const entries = Object.entries(rawParams);
85
+ if (entries.length > MAX_PARAMS) return null;
86
+ const params = {};
87
+ for (const [key, value] of entries){
88
+ if (!PARAM_KEY.test(key) || key === CUSTOMER_DISPLAY_RETURN_PARAM || typeof value !== 'string') return null;
89
+ if (value.length > MAX_PARAM_LENGTH) return null;
90
+ params[key] = value;
91
+ }
92
+ return {
93
+ pluginId,
94
+ path,
95
+ params
96
+ };
97
+ }
98
+ /** Whether a path is a display's own address, which a screen may send it back to. */ export function isCustomerDisplayReturnPath(path) {
99
+ return typeof path === 'string' && RETURN_PATH.test(path);
100
+ }
101
+ /**
102
+ * Where a display opens a screen: the plugin's public page with the screen's
103
+ * parameters and, under {@link CUSTOMER_DISPLAY_RETURN_PARAM}, the display's
104
+ * own address to come back to. `null` for a screen or a return address out
105
+ * of shape.
106
+ */ export function customerDisplayScreenHref(screen, returnTo) {
107
+ const clean = sanitizeCustomerDisplayScreen(screen);
108
+ if (!clean || !isCustomerDisplayReturnPath(returnTo)) return null;
109
+ const query = new URLSearchParams(_extends({}, clean.params, {
110
+ [CUSTOMER_DISPLAY_RETURN_PARAM]: returnTo
111
+ }));
112
+ return `/kiosk/${clean.pluginId}${clean.path}?${query.toString()}`;
113
+ }
114
+ /** The address a screen's page sends the display back to, from its query string, or `null`. */ export function customerDisplayReturnPath(search) {
115
+ const value = new URLSearchParams(search).get(CUSTOMER_DISPLAY_RETURN_PARAM);
116
+ return isCustomerDisplayReturnPath(value) ? value : null;
117
+ }
118
+ /**
119
+ * One registry per JavaScript realm, on `globalThis` under `Symbol.for`
120
+ * (AGL-3947), so two copies of this module in one bundle agree on whether a
121
+ * display is there.
122
+ */ const REGISTRY_KEY = Symbol.for('@aglyn/aglyn:customer-display-host');
123
+ const globalScope = globalThis;
124
+ const registry = (_globalScope_REGISTRY_KEY = globalScope[REGISTRY_KEY]) != null ? _globalScope_REGISTRY_KEY : globalScope[REGISTRY_KEY] = {
125
+ host: null,
126
+ listeners: new Set()
127
+ };
128
+ const notify = ()=>{
129
+ for (const listener of [
130
+ ...registry.listeners
131
+ ])listener();
132
+ };
133
+ /**
134
+ * Offers a display host to every plugin on the page, replacing any other;
135
+ * answers the function that withdraws it (and only it).
136
+ */ export function registerCustomerDisplayHost(host) {
137
+ registry.host = host;
138
+ notify();
139
+ return ()=>{
140
+ if (registry.host !== host) return;
141
+ registry.host = null;
142
+ notify();
143
+ };
144
+ }
145
+ /** The display host on this page, or `null` when no register here has a display to show on. */ export function customerDisplayHost() {
146
+ return registry.host;
147
+ }
148
+ /** Calls `listener` whenever a host arrives or leaves; answers the unsubscribe. */ export function subscribeCustomerDisplayHost(listener) {
149
+ registry.listeners.add(listener);
150
+ return ()=>{
151
+ registry.listeners.delete(listener);
152
+ };
153
+ }
154
+ /**
155
+ * Where the display's credential is kept on the display itself: the
156
+ * browser's own storage for the console's public pages, which the display's
157
+ * page and every screen's page share.
158
+ */ export const CUSTOMER_DISPLAY_CREDENTIAL_KEY = 'aglyn.customerDisplay.credential';
159
+ const CREDENTIAL = /^[A-Za-z0-9_.~-]{16,512}$/;
160
+ /**
161
+ * Called by the page that RUNS a paired display (AGL-4042): keeps the
162
+ * credential its pairing gave it where a screen's page can read it, or
163
+ * forgets it (`null`) when the display is unpaired. Best effort: a browser
164
+ * that refuses storage keeps nothing, and a screen's page then signs nothing.
165
+ */ export function rememberCustomerDisplayCredential(credential) {
166
+ try {
167
+ if (credential && CREDENTIAL.test(credential)) {
168
+ window.localStorage.setItem(CUSTOMER_DISPLAY_CREDENTIAL_KEY, credential);
169
+ } else {
170
+ window.localStorage.removeItem(CUSTOMER_DISPLAY_CREDENTIAL_KEY);
171
+ }
172
+ } catch (unused) {
173
+ // No storage: nothing to keep, and nothing kept.
174
+ }
175
+ }
176
+ /**
177
+ * The credential of the paired display this page is running on, for a
178
+ * screen's page to send to its own plugin's server, which asks core's
179
+ * verifier who the display is; `null` on a device that is not one.
180
+ */ export function customerDisplayCredential() {
181
+ try {
182
+ const value = window.localStorage.getItem(CUSTOMER_DISPLAY_CREDENTIAL_KEY);
183
+ return value && CREDENTIAL.test(value) ? value : null;
184
+ } catch (unused) {
185
+ return null;
186
+ }
187
+ }
188
+
189
+ //# sourceMappingURL=plugin-customer-display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-customer-display.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 * A PLUGIN'S SCREEN ON A PAIRED CUSTOMER DISPLAY (AGL-4042).\n *\n * A register can pair a screen that faces the customer (AGL-3608). That\n * screen belongs to the plugin that runs the register, and until now it\n * could show only that plugin's own prompts — a basket, a tip, a receipt\n * choice. Another plugin that needs the customer's own hands on a screen\n * (a form to fill in, a document to sign) had no way to ask for it.\n *\n * So the seam has two halves, and neither names the other plugin:\n *\n * 1. A **display host**: the page running a register with a connected\n * display registers itself here ({@link registerCustomerDisplayHost}) while\n * it can show something, and unregisters when it can't. Any plugin's\n * console widget on that page asks {@link customerDisplayHost} and, when\n * there is one, hands it a {@link CustomerDisplayScreen}.\n * 2. A **screen** is one of the asking plugin's own public console pages\n * (`ConsolePublicPage`, served at `/kiosk/{pluginId}{path}` with no staff\n * session), with a few short parameters. The display opens it, and the\n * page sends the display back to where it came from\n * ({@link customerDisplayReturnPath}) when the customer is done.\n *\n * THE PAIRED DISPLAY IS THE AUTHORIZATION (AGL-4042). A screen's page has no\n * staff session, and needs none: the display it runs on was paired to one\n * store by staff, and that pairing is what it shows. The display's page hands\n * its credential to core ({@link rememberCustomerDisplayCredential}); a\n * screen's page reads it ({@link customerDisplayCredential}) and sends it to\n * its own plugin's server, which asks core who the display is\n * (`plugin-customer-display-verifier`): the store it is paired to, the screen\n * it is showing now, and who put that screen there. Nothing is minted per\n * screen, nothing is opened or closed, and nothing names the plugin on the\n * other side.\n *\n * The display opens only a page the plugin DECLARED public: the console's\n * public route refuses any other before a byte of the plugin loads. The\n * parameters are capped and plain, because they ride the display's state and\n * its address bar; they must never carry a customer's details, and never the\n * display's credential.\n *\n * A screen has no timer (AGL-4042): it stays, however long the customer\n * takes, until the register shows something else on the display, the plugin\n * takes it off ({@link CustomerDisplayHost.close}), or its page sends the\n * display back once the customer is done. A screen's page follows the\n * register by asking its server whether its screen is still the one showing.\n *\n * Client-only, React-free, and reached by its own subpath, never the barrel:\n * a published page has no use for it.\n */\n\n/** What a plugin asks a paired display to show. */\nexport interface CustomerDisplayScreen {\n /** The plugin whose public page it is. */\n pluginId: string\n /** The page's path beneath `/kiosk/{pluginId}`, with its leading slash: `/display-sign`. */\n path: string\n /** Short plain values the page reads from its query string. Never a customer's details. */\n params: Record<string, string>\n}\n\n/** The query key a screen's page reads the display's own address from. */\nexport const CUSTOMER_DISPLAY_RETURN_PARAM = 'return'\n\nconst PLUGIN_ID = /^[a-z0-9][a-z0-9-]{0,63}$/\nconst PATH = /^(\\/[a-z0-9][a-z0-9-]{0,63}){1,4}$/\nconst PARAM_KEY = /^[a-zA-Z][a-zA-Z0-9]{0,31}$/\nconst MAX_PARAMS = 8\nconst MAX_PARAM_LENGTH = 2000\n/** A display's own address: a public console page, and nothing that leaves it. */\nconst RETURN_PATH = /^\\/kiosk(\\/[a-z0-9][a-z0-9-]{0,63}){1,5}$/\n\n/**\n * A screen as a register may store it, or `null` for anything else: a plugin\n * id and a public page path in their own shapes, and at most eight short\n * string parameters with plain keys. The return address is never one of\n * them; the display adds its own.\n */\nexport function sanitizeCustomerDisplayScreen(raw: unknown): CustomerDisplayScreen | null {\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const pluginId = String(input['pluginId'] ?? '')\n const path = String(input['path'] ?? '')\n if (!PLUGIN_ID.test(pluginId) || !PATH.test(path)) return null\n const rawParams = (input['params'] && typeof input['params'] === 'object' ? input['params'] : {}) as Record<\n string,\n unknown\n >\n const entries = Object.entries(rawParams)\n if (entries.length > MAX_PARAMS) return null\n const params: Record<string, string> = {}\n for (const [key, value] of entries) {\n if (!PARAM_KEY.test(key) || key === CUSTOMER_DISPLAY_RETURN_PARAM || typeof value !== 'string') return null\n if (value.length > MAX_PARAM_LENGTH) return null\n params[key] = value\n }\n return { pluginId, path, params }\n}\n\n/** Whether a path is a display's own address, which a screen may send it back to. */\nexport function isCustomerDisplayReturnPath(path: unknown): path is string {\n return typeof path === 'string' && RETURN_PATH.test(path)\n}\n\n/**\n * Where a display opens a screen: the plugin's public page with the screen's\n * parameters and, under {@link CUSTOMER_DISPLAY_RETURN_PARAM}, the display's\n * own address to come back to. `null` for a screen or a return address out\n * of shape.\n */\nexport function customerDisplayScreenHref(screen: CustomerDisplayScreen, returnTo: string): string | null {\n const clean = sanitizeCustomerDisplayScreen(screen)\n if (!clean || !isCustomerDisplayReturnPath(returnTo)) return null\n const query = new URLSearchParams({ ...clean.params, [CUSTOMER_DISPLAY_RETURN_PARAM]: returnTo })\n return `/kiosk/${clean.pluginId}${clean.path}?${query.toString()}`\n}\n\n/** The address a screen's page sends the display back to, from its query string, or `null`. */\nexport function customerDisplayReturnPath(search: string): string | null {\n const value = new URLSearchParams(search).get(CUSTOMER_DISPLAY_RETURN_PARAM)\n return isCustomerDisplayReturnPath(value) ? value : null\n}\n\n/** What the page running a register offers while it has a display to show things on. */\nexport interface CustomerDisplayHost {\n /** Puts a plugin's screen on the display; `false` when it could not. */\n show(screen: CustomerDisplayScreen): Promise<boolean>\n /**\n * Takes this plugin's screen off the display, when it is the one the\n * register last put there, so the display shows the register's own screen\n * again; `false` when it could not. A screen of another plugin, or one the\n * register already replaced, is left alone (and answers `true`: this\n * plugin's screen is not showing). A display already on the plugin's page\n * goes back when that page sees its screen is no longer the one showing.\n */\n close?(pluginId: string): Promise<boolean>\n}\n\ninterface HostRegistry {\n host: CustomerDisplayHost | null\n listeners: Set<() => void>\n}\n\n/**\n * One registry per JavaScript realm, on `globalThis` under `Symbol.for`\n * (AGL-3947), so two copies of this module in one bundle agree on whether a\n * display is there.\n */\nconst REGISTRY_KEY = Symbol.for('@aglyn/aglyn:customer-display-host')\nconst globalScope = globalThis as typeof globalThis & { [REGISTRY_KEY]?: HostRegistry }\nconst registry: HostRegistry =\n globalScope[REGISTRY_KEY] ?? (globalScope[REGISTRY_KEY] = { host: null, listeners: new Set() })\n\nconst notify = () => {\n for (const listener of [...registry.listeners]) listener()\n}\n\n/**\n * Offers a display host to every plugin on the page, replacing any other;\n * answers the function that withdraws it (and only it).\n */\nexport function registerCustomerDisplayHost(host: CustomerDisplayHost): () => void {\n registry.host = host\n notify()\n return () => {\n if (registry.host !== host) return\n registry.host = null\n notify()\n }\n}\n\n/** The display host on this page, or `null` when no register here has a display to show on. */\nexport function customerDisplayHost(): CustomerDisplayHost | null {\n return registry.host\n}\n\n/** Calls `listener` whenever a host arrives or leaves; answers the unsubscribe. */\nexport function subscribeCustomerDisplayHost(listener: () => void): () => void {\n registry.listeners.add(listener)\n return () => {\n registry.listeners.delete(listener)\n }\n}\n\n/**\n * Where the display's credential is kept on the display itself: the\n * browser's own storage for the console's public pages, which the display's\n * page and every screen's page share.\n */\nexport const CUSTOMER_DISPLAY_CREDENTIAL_KEY = 'aglyn.customerDisplay.credential'\n\nconst CREDENTIAL = /^[A-Za-z0-9_.~-]{16,512}$/\n\n/**\n * Called by the page that RUNS a paired display (AGL-4042): keeps the\n * credential its pairing gave it where a screen's page can read it, or\n * forgets it (`null`) when the display is unpaired. Best effort: a browser\n * that refuses storage keeps nothing, and a screen's page then signs nothing.\n */\nexport function rememberCustomerDisplayCredential(credential: string | null): void {\n try {\n if (credential && CREDENTIAL.test(credential)) {\n window.localStorage.setItem(CUSTOMER_DISPLAY_CREDENTIAL_KEY, credential)\n } else {\n window.localStorage.removeItem(CUSTOMER_DISPLAY_CREDENTIAL_KEY)\n }\n } catch {\n // No storage: nothing to keep, and nothing kept.\n }\n}\n\n/**\n * The credential of the paired display this page is running on, for a\n * screen's page to send to its own plugin's server, which asks core's\n * verifier who the display is; `null` on a device that is not one.\n */\nexport function customerDisplayCredential(): string | null {\n try {\n const value = window.localStorage.getItem(CUSTOMER_DISPLAY_CREDENTIAL_KEY)\n return value && CREDENTIAL.test(value) ? value : null\n } catch {\n return null\n }\n}\n"],"names":["globalScope","CUSTOMER_DISPLAY_RETURN_PARAM","PLUGIN_ID","PATH","PARAM_KEY","MAX_PARAMS","MAX_PARAM_LENGTH","RETURN_PATH","sanitizeCustomerDisplayScreen","raw","input","pluginId","String","path","test","rawParams","entries","Object","length","params","key","value","isCustomerDisplayReturnPath","customerDisplayScreenHref","screen","returnTo","clean","query","URLSearchParams","toString","customerDisplayReturnPath","search","get","REGISTRY_KEY","Symbol","for","globalThis","registry","host","listeners","Set","notify","listener","registerCustomerDisplayHost","customerDisplayHost","subscribeCustomerDisplayHost","add","delete","CUSTOMER_DISPLAY_CREDENTIAL_KEY","CREDENTIAL","rememberCustomerDisplayCredential","credential","window","localStorage","setItem","removeItem","customerDisplayCredential","getItem"],"mappings":"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"}
@@ -169,6 +169,52 @@ export interface PluginOrgPermissionDeclaration {
169
169
  group?: string;
170
170
  /** The heading of a group of the plugin's own; absent: the plugin's catalog label. */
171
171
  groupLabel?: string;
172
+ /**
173
+ * A broader key this one is a finer part of (AGL-4047): never granted
174
+ * without it, and following it where nothing sets this key. See
175
+ * `PluginPermission.refines`.
176
+ */
177
+ refines?: string;
178
+ /**
179
+ * A key this one takes its verdict from where nothing sets it, without
180
+ * needing it (AGL-4047). See `PluginPermission.follows`.
181
+ */
182
+ follows?: string;
183
+ /**
184
+ * A site can narrow this key, and the built-in roles' default for whether
185
+ * it does (AGL-4047). See `PluginPermission.narrowsPerSite`. Ignored on a
186
+ * key whose preset is the site role (`presetFromSiteRole`).
187
+ */
188
+ narrowsPerSite?: boolean | Partial<Record<OrgRole, boolean>>;
189
+ /**
190
+ * The key's preset is the member's role ON THE SITE, for every member
191
+ * (AGL-4047), not only for a site collaborator: the owner and admins are
192
+ * admins of every site, a workspace-wide editor or viewer holds their
193
+ * workspace role on a site unless the site gives them other access there
194
+ * (narrower or wider), and a collaborator holds their site role. So the
195
+ * key reads `hostRoleDefaults` for whatever access the member has on the
196
+ * site a request names, exactly as a gate on that host role did, and a
197
+ * custom role, the member's own permissions and that site's toggle then
198
+ * grant or refuse it like any other key. Needs `hostRoleDefaults`, and
199
+ * `roleDefaults` that match them tier for tier (the owner and admins on
200
+ * Admin, editors on Editor, viewers on Viewer), which is the answer with
201
+ * no site named.
202
+ *
203
+ * For a key that replaces a gate on the site role (who may edit a site's
204
+ * stays, say), so the built-in roles keep exactly the access they had
205
+ * while the key becomes grantable and deniable like every other.
206
+ */
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;
172
218
  }
173
219
  /**
174
220
  * One key of the ONE permission catalog that a plugin contributes (AGL-4047),
@@ -187,6 +233,19 @@ export interface PluginCatalogPermission {
187
233
  group: string;
188
234
  groupLabel: string;
189
235
  source: 'catalog' | 'tier';
236
+ /** The broader key this one refines, when it does. */
237
+ refines?: string;
238
+ /** The key this one follows without needing it, when it does. */
239
+ follows?: string;
240
+ /**
241
+ * Present when a site can narrow the key (AGL-4047): each built-in role's
242
+ * default for whether it does, normalized from the declaration.
243
+ */
244
+ narrowsPerSite?: Readonly<Record<OrgRole, boolean>>;
245
+ /** The key's preset is the member's role on the site (AGL-4047); see the declaration's. */
246
+ presetFromSiteRole?: true;
247
+ /** That preset holds for members of the whole workspace only (AGL-4047); see the declaration's. */
248
+ workspaceMembersOnly?: true;
190
249
  }
191
250
  export interface PluginEntitlementRegistration {
192
251
  pluginId: string;
@@ -173,6 +173,23 @@ const reportedCatalogRefusals = new Set();
173
173
  var _FIRST_PARTY_PLUGINS_find;
174
174
  return (_ref = (_FIRST_PARTY_PLUGINS_find = FIRST_PARTY_PLUGINS.find((plugin)=>plugin.id === pluginId)) == null ? void 0 : _FIRST_PARTY_PLUGINS_find.label) != null ? _ref : pluginId;
175
175
  }
176
+ /**
177
+ * A declaration's `narrowsPerSite`, normalized to each built-in role's
178
+ * default (AGL-4047): `true` or `false` names every role, a map names each,
179
+ * a role it leaves out keeps. Absent: the key is not one a site can narrow.
180
+ */ function narrowingDefaultsOf(declared) {
181
+ if (declared === undefined || declared === null) return {};
182
+ const every = typeof declared === 'boolean' ? declared : undefined;
183
+ const map = typeof declared === 'object' ? declared : {};
184
+ return {
185
+ narrowsPerSite: {
186
+ owner: every != null ? every : map.owner === true,
187
+ admin: every != null ? every : map.admin === true,
188
+ editor: every != null ? every : map.editor === true,
189
+ viewer: every != null ? every : map.viewer === true
190
+ }
191
+ };
192
+ }
176
193
  /**
177
194
  * Every key plugins contribute to the permission catalog, through EITHER
178
195
  * declaration API, in catalog order (AGL-4047): per plugin, its
@@ -218,11 +235,19 @@ const reportedCatalogRefusals = new Set();
218
235
  group: (_permission_group = permission.group) != null ? _permission_group : permission.pluginId,
219
236
  groupLabel: (_permission_groupLabel = permission.groupLabel) != null ? _permission_groupLabel : pluginLabel(permission.pluginId),
220
237
  source: 'catalog'
221
- }));
238
+ }, permission.refines ? {
239
+ refines: permission.refines
240
+ } : {}, permission.follows ? {
241
+ follows: permission.follows
242
+ } : {}, permission.presetFromSiteRole && permission.hostRoleDefaults ? _extends({
243
+ presetFromSiteRole: true
244
+ }, permission.workspaceMembersOnly ? {
245
+ workspaceMembersOnly: true
246
+ } : {}) : narrowingDefaultsOf(permission.narrowsPerSite)));
222
247
  }
223
248
  for (const permission of listPluginPermissions()){
224
249
  var _permission_description, _permission_group1, _permission_groupLabel1;
225
- add({
250
+ add(_extends({
226
251
  key: permission.key,
227
252
  label: permission.label,
228
253
  description: (_permission_description = permission.description) != null ? _permission_description : '',
@@ -231,7 +256,11 @@ const reportedCatalogRefusals = new Set();
231
256
  group: (_permission_group1 = permission.group) != null ? _permission_group1 : permission.pluginId,
232
257
  groupLabel: (_permission_groupLabel1 = permission.groupLabel) != null ? _permission_groupLabel1 : pluginLabel(permission.pluginId),
233
258
  source: 'tier'
234
- });
259
+ }, permission.refines ? {
260
+ refines: permission.refines
261
+ } : {}, permission.follows ? {
262
+ follows: permission.follows
263
+ } : {}, narrowingDefaultsOf(permission.narrowsPerSite)));
235
264
  }
236
265
  return [
237
266
  ...byPlugin.keys()
@@ -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\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}\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 * 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 })\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 })\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","listPluginCatalogPermissions","byPlugin","incumbent","refusal","has","console","error","list","push","listPluginOrgPermissions","description","roleDefaults","hostRoleDefaults","group","groupLabel","source","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"}