@endora-commerce/mod-admin-roles 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/admin/index.d.ts +47 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +27 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/backend/demo/reset.d.ts +20 -0
  8. package/dist/backend/demo/reset.d.ts.map +1 -0
  9. package/dist/backend/demo/reset.js +13 -0
  10. package/dist/backend/demo/reset.js.map +1 -0
  11. package/dist/backend/demo/rows.d.ts +64 -0
  12. package/dist/backend/demo/rows.d.ts.map +1 -0
  13. package/dist/backend/demo/rows.js +74 -0
  14. package/dist/backend/demo/rows.js.map +1 -0
  15. package/dist/backend/demo/seed.d.ts +20 -0
  16. package/dist/backend/demo/seed.d.ts.map +1 -0
  17. package/dist/backend/demo/seed.js +29 -0
  18. package/dist/backend/demo/seed.js.map +1 -0
  19. package/dist/backend/entities/admin-role.entity.d.ts +20 -0
  20. package/dist/backend/entities/admin-role.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/admin-role.entity.js +65 -0
  22. package/dist/backend/entities/admin-role.entity.js.map +1 -0
  23. package/dist/backend/index.d.ts +96 -0
  24. package/dist/backend/index.d.ts.map +1 -0
  25. package/dist/backend/index.js +114 -0
  26. package/dist/backend/index.js.map +1 -0
  27. package/dist/backend/permission-inventory.d.ts +117 -0
  28. package/dist/backend/permission-inventory.d.ts.map +1 -0
  29. package/dist/backend/permission-inventory.js +585 -0
  30. package/dist/backend/permission-inventory.js.map +1 -0
  31. package/dist/backend/services/admin-role-ports.d.ts +39 -0
  32. package/dist/backend/services/admin-role-ports.d.ts.map +1 -0
  33. package/dist/backend/services/admin-role-ports.js +72 -0
  34. package/dist/backend/services/admin-role-ports.js.map +1 -0
  35. package/dist/backend/services/admin-role-service.d.ts +77 -0
  36. package/dist/backend/services/admin-role-service.d.ts.map +1 -0
  37. package/dist/backend/services/admin-role-service.js +219 -0
  38. package/dist/backend/services/admin-role-service.js.map +1 -0
  39. package/dist/backend/services/permission-catalogue.service.d.ts +130 -0
  40. package/dist/backend/services/permission-catalogue.service.d.ts.map +1 -0
  41. package/dist/backend/services/permission-catalogue.service.js +175 -0
  42. package/dist/backend/services/permission-catalogue.service.js.map +1 -0
  43. package/dist/backend/services/permission-service.d.ts +24 -0
  44. package/dist/backend/services/permission-service.d.ts.map +1 -0
  45. package/dist/backend/services/permission-service.js +40 -0
  46. package/dist/backend/services/permission-service.js.map +1 -0
  47. package/dist/manifest.d.ts +169 -0
  48. package/dist/manifest.d.ts.map +1 -0
  49. package/dist/manifest.js +192 -0
  50. package/dist/manifest.js.map +1 -0
  51. package/docs/admin_roles.md +38 -0
  52. package/i18n/en.json +10 -0
  53. package/i18n/pl.json +10 -0
  54. package/package.json +73 -0
  55. package/tailwind.css +14 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ <!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
2
+
3
+ # @endora-commerce/mod-admin-roles
4
+
5
+ Admin RBAC — roles, permissions, and policy enforcement for admin sessions.
6
+
7
+ ## What this is
8
+
9
+ An **Endora Commerce module package**. Its module id is `admin_roles` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
10
+
11
+ A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
12
+
13
+ ## Entry points
14
+
15
+ | Import | Contents |
16
+ | --- | --- |
17
+ | `@endora-commerce/mod-admin-roles` | the module manifest — its id, version, dependencies, settings and activation |
18
+ | `@endora-commerce/mod-admin-roles/admin` | the Admin UI contribution: the screens, navigation and palette actions it adds |
19
+ | `@endora-commerce/mod-admin-roles/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
20
+ | `@endora-commerce/mod-admin-roles/tailwind.css` | the Tailwind source declaration for the admin code above |
21
+
22
+ ## Depends on
23
+
24
+ Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
25
+
26
+ **Endora packages**
27
+
28
+ - `@endora-commerce/admin-kit` — *optional*
29
+ - `@endora-commerce/contracts`
30
+ - `@endora-commerce/platform`
31
+
32
+ **Third-party**
33
+
34
+ - `@mikro-orm/core` ^6
35
+ - `@mikro-orm/postgresql` ^6
36
+
37
+ ## What the tarball carries
38
+
39
+ - `dist/` — the compiled JavaScript and its type declarations
40
+ - `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
41
+ - `docs/` — the package’s own pages in the Endora Commerce documentation site
42
+ - `tailwind.css` — the Tailwind source declaration
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pnpm add @endora-commerce/mod-admin-roles
48
+ ```
49
+
50
+ ## Licence
51
+
52
+ MIT — the text is in `LICENSE`, beside this file.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `admin_roles`' admin surface — **one sidebar entry and no route of its own**,
3
+ * declared by the module that owns it (feature 091, Phase 4, batch four;
4
+ * `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
5
+ *
6
+ * **This module ships a nav entry pointing at a route another module
7
+ * declares**, which the registry supports by design —
8
+ * `AdminContributions`' three arrays are each optional, and
9
+ * `admin/test/lib/module-registry.test.ts` reads *"a module that contributes
10
+ * only nav as contributing no route"* as legal. It is not a shortcut taken to
11
+ * make a move cheap; it is the honest shape of a screen the two hand-written
12
+ * registries have disagreed about since it was written: `App.tsx` imported
13
+ * `AdminRolesPage` out of `admin_users`' surface directory while
14
+ * `AppShell.tsx`'s row declared `module: 'admin_roles'`, and
15
+ * `backend/scripts/ledgers/admin-registrations.ts` records both facts rather
16
+ * than picking one.
17
+ *
18
+ * `plan.md`'s open question 3 recommended keeping exactly that attribution and
19
+ * this is where the recommendation is made explicit. The **route** belongs to
20
+ * `admin_users`, because the screen is served by
21
+ * `GET /api/v1/admin/admin-roles` in that module's `routes.admin.ts` and the
22
+ * component lives in its package; the **sidebar entry and the palette action**
23
+ * belong here, because they advertise the roles capability, which is this
24
+ * module's whole subject. Both modules declare `nonDeactivatable`, so nothing
25
+ * an operator can do makes the split observable today. It stops being inert the
26
+ * day either is unlocked: switching `admin_roles` off would withdraw the
27
+ * advertisement and leave the deep link working, which is the right answer for
28
+ * a module that owns the *roles* capability and not the screen that edits it.
29
+ *
30
+ * **`admin_users:manage`, not an `admin_roles:*` code.** This module registers
31
+ * no admin route and declares no permission of its own, and the endpoints the
32
+ * screen calls all enforce that one code
33
+ * (`packages/modules/admin_users/src/backend/routes.admin.ts`). The comment the
34
+ * hand-written NAV entry carried said so, and it survives the move rather than
35
+ * being re-derived by whoever reads the declaration next. The module
36
+ * attribution and the permission answer two different questions here, which is
37
+ * why they are two fields.
38
+ *
39
+ * **This entry exports data and nothing else** (R2), like every `./admin`
40
+ * layer: `check:module-boundary`'s D-171 rule designates a subpath as contract
41
+ * surface when it emits no runtime binding, and `./admin` deliberately does not
42
+ * qualify, so a consumer reaching into another module's `./admin` stays a
43
+ * counted boundary reach.
44
+ */
45
+ import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
46
+ export declare const contributions: AdminContributions;
47
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAKnF,eAAO,MAAM,aAAa,EAAE,kBAuB3B,CAAC"}
@@ -0,0 +1,27 @@
1
+ /** The roles editor. `admin_users` declares the route; this is where it goes. */
2
+ const ROLES_PATH = '/admin-roles';
3
+ export const contributions = {
4
+ nav: [
5
+ {
6
+ to: ROLES_PATH,
7
+ // Module-relative (R8), resolved in this module's own namespace out of
8
+ // `packages/modules/admin_roles/i18n/`. It was `appShell.nav.roles` in
9
+ // the shared `_i18n` bundle, one of the four shared files a module author
10
+ // had to edit.
11
+ labelKey: 'nav.adminRoles.label',
12
+ // The icon the hand-written NAV entry carried. It joins
13
+ // `KnownIconNameSchema` and `icon-map.ts` in this merge request: a nav
14
+ // entry names its glyph now, so keeping the one the sidebar already drew
15
+ // meant adding the name rather than substituting one already on the list.
16
+ icon: 'ShieldCheck',
17
+ section: 'system',
18
+ // The hand-written table's position times a hundred — see the note in
19
+ // `packages/modules/audit_logs/src/admin/index.ts`. `/admin-roles` was
20
+ // third of thirteen, directly after `/admin-users` (200), and that
21
+ // relative order is preserved.
22
+ weight: 300,
23
+ requiredPermission: 'admin_users:manage',
24
+ },
25
+ ],
26
+ };
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AA8CA,iFAAiF;AACjF,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,uEAAuE;YACvE,0EAA0E;YAC1E,eAAe;YACf,QAAQ,EAAE,sBAAsB;YAChC,wDAAwD;YACxD,uEAAuE;YACvE,yEAAyE;YACzE,0EAA0E;YAC1E,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,QAAQ;YACjB,sEAAsE;YACtE,uEAAuE;YACvE,mEAAmE;YACnE,+BAA+B;YAC/B,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,oBAAoB;SACzC;KACF;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Withdraw `admin_roles`' demo data (feature 113, T222 — contract §2.5).
3
+ *
4
+ * By the fixed codes `seed` assigns, never by a predicate over the table. That
5
+ * is not a precaution here, it is a measured one: `blog` and `cms` seed a
6
+ * `blog_manager` and a `content_manager` role from their own boot hooks, and an
7
+ * operator's own role is indistinguishable from a demo one by every other
8
+ * column. This is the withdrawal that replaces `admin_roles`' line in the host's
9
+ * `truncate … cascade`, which took all four with it — and, through the cascade,
10
+ * the `admin_users` rows pointing at them.
11
+ *
12
+ * The demo administrators' **assignment** to these roles is withdrawn first, by
13
+ * the composition: `reset` runs the composition's withdrawal before any module's
14
+ * (§5.5), which is also what leaves no `admin_users` row referencing a role
15
+ * deleted below.
16
+ */
17
+ import type { DemoResetResult, ModuleDemoContext } from '@endora-commerce/contracts';
18
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
19
+ export declare function resetDemo(context: ModuleDemoContext<ModuleContext>): Promise<DemoResetResult>;
20
+ //# sourceMappingURL=reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../../src/backend/demo/reset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAStE,wBAAsB,SAAS,CAC7B,OAAO,EAAE,iBAAiB,CAAC,aAAa,CAAC,GACxC,OAAO,CAAC,eAAe,CAAC,CAS1B"}
@@ -0,0 +1,13 @@
1
+ import { AdminRole } from '../entities/admin-role.entity.js';
2
+ import { DEMO_ADMIN_ROLE_CODES } from './rows.js';
3
+ export async function resetDemo(context) {
4
+ // command-coverage-ignore: the withdrawal half of the demo data above. Same
5
+ // entry point, same `mustBeNonProduction()` guard, same absence of an
6
+ // operator to attribute the write to (contract §2.7).
7
+ const em = context.ctx.cradle().emFactory();
8
+ const removed = await em.nativeDelete(AdminRole, {
9
+ code: { $in: [...DEMO_ADMIN_ROLE_CODES] },
10
+ });
11
+ return { removed: [{ entity: 'AdminRole', count: removed }] };
12
+ }
13
+ //# sourceMappingURL=reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../../src/backend/demo/reset.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAMlD,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAyC;IAEzC,4EAA4E;IAC5E,sEAAsE;IACtE,sDAAsD;IACtD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAwB,CAAC,SAAS,EAAE,CAAC;IAClE,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE;QAC/C,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,qBAAqB,CAAC,EAAE;KAC1C,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AAChE,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The admin roles this module's demo data creates (feature 113, T222).
3
+ *
4
+ * One place holding the data, imported by both bodies: `seed.ts` creates these
5
+ * roles and `reset.ts` withdraws exactly them, by the `code` they are keyed on
6
+ * (contract §2.5). A predicate over the table would take `blog_manager` and
7
+ * `content_manager` with it — two roles `blog` and `cms` seed from their own
8
+ * boot hooks, which share this table with the demo and are not the demo's.
9
+ *
10
+ * The values are the host seed's verbatim, moved rather than rewritten. The
11
+ * frozen copy the parity comparison read them against is deleted with that
12
+ * comparison (T226); what holds them now is
13
+ * `test/integration/demo/demo-shop.test.ts`' recorded delta.
14
+ *
15
+ * ## Why the sales representative's permission list is here and is exported
16
+ *
17
+ * It was `backend/src/seeds/seeded-role-permissions.ts`, and it is a constant
18
+ * rather than eight lines inside a seed for a reason that survives the move
19
+ * unchanged: `platform_admin` and the bootstrap admin hold `['*']`, which
20
+ * short-circuits in `PermissionService.hasPermission` before any code is
21
+ * compared, and the two content roles hold their own module's codes. So a change
22
+ * to what a catalogue code grants is felt by exactly this list, and the six
23
+ * `permission-authority` contract tests that assert what the seeded sales
24
+ * representative may reach have to read **the list the seed
25
+ * actually writes** rather than a copy of it — a copy agrees with the seed on
26
+ * the day it is written and never again.
27
+ *
28
+ * Which list that is moved with this batch. The composed path writes this one,
29
+ * so it is re-exported from `../index.js` — the package's own `./backend`
30
+ * subpath — and the six tests name it there. The frozen reference under
31
+ * `backend/src/seeds/` keeps a literal copy of its own on purpose: the two sides
32
+ * of the parity comparison have to be different code, or the comparison cannot
33
+ * see a change to either.
34
+ */
35
+ /** One demo admin role, in `AdminRole`'s own field names. */
36
+ export interface DemoAdminRoleRow {
37
+ readonly code: string;
38
+ readonly name: string;
39
+ readonly permissions: readonly string[];
40
+ }
41
+ /**
42
+ * What the seeded `sales_representative` role may reach.
43
+ *
44
+ * Assignment-scoped visibility (feature 008): the quickstart signs in as this
45
+ * role to exercise it.
46
+ */
47
+ export declare const SALES_REPRESENTATIVE_PERMISSIONS: readonly string[];
48
+ /**
49
+ * The two roles the demo shop has.
50
+ *
51
+ * `['*']` on `platform_admin` is the wildcard `PermissionService` answers before
52
+ * it compares a code, which is what makes the demo administrator able to open
53
+ * every screen a fresh install ships.
54
+ *
55
+ * **Which account holds which role is not here.** An `admin_users` row carrying
56
+ * an `admin_roles` id is two modules' rows in one statement, so the assignment
57
+ * is a composition step (contract §5.1) and lives in
58
+ * `backend/src/seeds/demo-composition.ts`. This body creates the roles that step
59
+ * assigns.
60
+ */
61
+ export declare const DEMO_ADMIN_ROLES: readonly DemoAdminRoleRow[];
62
+ /** The codes `reset` withdraws — derived from the rows, never a second list. */
63
+ export declare const DEMO_ADMIN_ROLE_CODES: readonly string[];
64
+ //# sourceMappingURL=rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../src/backend/demo/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,EAAE,SAAS,MAAM,EAS7D,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAOvD,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAA4C,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The admin roles this module's demo data creates (feature 113, T222).
3
+ *
4
+ * One place holding the data, imported by both bodies: `seed.ts` creates these
5
+ * roles and `reset.ts` withdraws exactly them, by the `code` they are keyed on
6
+ * (contract §2.5). A predicate over the table would take `blog_manager` and
7
+ * `content_manager` with it — two roles `blog` and `cms` seed from their own
8
+ * boot hooks, which share this table with the demo and are not the demo's.
9
+ *
10
+ * The values are the host seed's verbatim, moved rather than rewritten. The
11
+ * frozen copy the parity comparison read them against is deleted with that
12
+ * comparison (T226); what holds them now is
13
+ * `test/integration/demo/demo-shop.test.ts`' recorded delta.
14
+ *
15
+ * ## Why the sales representative's permission list is here and is exported
16
+ *
17
+ * It was `backend/src/seeds/seeded-role-permissions.ts`, and it is a constant
18
+ * rather than eight lines inside a seed for a reason that survives the move
19
+ * unchanged: `platform_admin` and the bootstrap admin hold `['*']`, which
20
+ * short-circuits in `PermissionService.hasPermission` before any code is
21
+ * compared, and the two content roles hold their own module's codes. So a change
22
+ * to what a catalogue code grants is felt by exactly this list, and the six
23
+ * `permission-authority` contract tests that assert what the seeded sales
24
+ * representative may reach have to read **the list the seed
25
+ * actually writes** rather than a copy of it — a copy agrees with the seed on
26
+ * the day it is written and never again.
27
+ *
28
+ * Which list that is moved with this batch. The composed path writes this one,
29
+ * so it is re-exported from `../index.js` — the package's own `./backend`
30
+ * subpath — and the six tests name it there. The frozen reference under
31
+ * `backend/src/seeds/` keeps a literal copy of its own on purpose: the two sides
32
+ * of the parity comparison have to be different code, or the comparison cannot
33
+ * see a change to either.
34
+ */
35
+ /**
36
+ * What the seeded `sales_representative` role may reach.
37
+ *
38
+ * Assignment-scoped visibility (feature 008): the quickstart signs in as this
39
+ * role to exercise it.
40
+ */
41
+ export const SALES_REPRESENTATIVE_PERMISSIONS = [
42
+ 'rfqs:handle',
43
+ 'organizations:read.assigned',
44
+ 'catalog:read',
45
+ // D-173 — the RFQ create screen prefills the agreed unit price from
46
+ // `GET /admin/products/:id/resolved-price`, which is `price_lists`' own
47
+ // endpoint and is gated on `price_lists:read` rather than on
48
+ // `rfqs:handle`. Without this code the prefill answers 403.
49
+ 'price_lists:read',
50
+ ];
51
+ /**
52
+ * The two roles the demo shop has.
53
+ *
54
+ * `['*']` on `platform_admin` is the wildcard `PermissionService` answers before
55
+ * it compares a code, which is what makes the demo administrator able to open
56
+ * every screen a fresh install ships.
57
+ *
58
+ * **Which account holds which role is not here.** An `admin_users` row carrying
59
+ * an `admin_roles` id is two modules' rows in one statement, so the assignment
60
+ * is a composition step (contract §5.1) and lives in
61
+ * `backend/src/seeds/demo-composition.ts`. This body creates the roles that step
62
+ * assigns.
63
+ */
64
+ export const DEMO_ADMIN_ROLES = [
65
+ { code: 'platform_admin', name: 'Platform Admin', permissions: ['*'] },
66
+ {
67
+ code: 'sales_representative',
68
+ name: 'Sales representative',
69
+ permissions: SALES_REPRESENTATIVE_PERMISSIONS,
70
+ },
71
+ ];
72
+ /** The codes `reset` withdraws — derived from the rows, never a second list. */
73
+ export const DEMO_ADMIN_ROLE_CODES = DEMO_ADMIN_ROLES.map((row) => row.code);
74
+ //# sourceMappingURL=rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.js","sourceRoot":"","sources":["../../../src/backend/demo/rows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AASH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAsB;IACjE,aAAa;IACb,6BAA6B;IAC7B,cAAc;IACd,oEAAoE;IACpE,wEAAwE;IACxE,6DAA6D;IAC7D,4DAA4D;IAC5D,kBAAkB;CACnB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAgC;IAC3D,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE;IACtE;QACE,IAAI,EAAE,sBAAsB;QAC5B,IAAI,EAAE,sBAAsB;QAC5B,WAAW,EAAE,gCAAgC;KAC9C;CACF,CAAC;AAEF,gFAAgF;AAChF,MAAM,CAAC,MAAM,qBAAqB,GAAsB,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `admin_roles`' demo data (feature 113, T222 — contract §2).
3
+ *
4
+ * The two roles the demo shop signs in with, in the module that owns the table.
5
+ * It writes `admin_roles` and nothing else (§2.1), reads no other module's table
6
+ * and resolves no port (§2.2), so declaring it added no entry to this module's
7
+ * manifest `dependencies` (§2.3).
8
+ *
9
+ * **Who holds which role is not this body's.** An `admin_users` row carrying an
10
+ * `admin_roles` id is two modules' rows in one statement — a composition step
11
+ * (§5.1), in `backend/src/seeds/demo-composition.ts`, which runs after every
12
+ * module's `seed` and so finds both sides of the assignment already there.
13
+ *
14
+ * Idempotent by an existence probe on the natural key (§2.4): a second run
15
+ * creates nothing and reports the same count.
16
+ */
17
+ import type { DemoSeedResult, ModuleDemoContext } from '@endora-commerce/contracts';
18
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
19
+ export declare function seedDemo(context: ModuleDemoContext<ModuleContext>): Promise<DemoSeedResult>;
20
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../src/backend/demo/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAUtE,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,iBAAiB,CAAC,aAAa,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CA0BzB"}
@@ -0,0 +1,29 @@
1
+ import { AdminRole } from '../entities/admin-role.entity.js';
2
+ import { DEMO_ADMIN_ROLES } from './rows.js';
3
+ export async function seedDemo(context) {
4
+ // command-coverage-ignore: demo data, reached only by `endora demo seed`,
5
+ // whose entry point calls `mustBeNonProduction()` as its first statement and
6
+ // outside every `try` (contract §2.7, §3.3). The guard is the enforcement:
7
+ // this write has no operator, no tenant and no audit reader, and the command
8
+ // refuses to run against a production database at all.
9
+ const em = context.ctx.cradle().emFactory();
10
+ for (const row of DEMO_ADMIN_ROLES) {
11
+ const existing = await em.findOne(AdminRole, { code: row.code });
12
+ // Present already: left exactly as it is. An operator may have narrowed
13
+ // what the demo sales representative may reach, and overwriting it here
14
+ // would be this body deciding its own literal outranks their change —
15
+ // which is `reset`'s question, not `seed`'s.
16
+ if (existing)
17
+ continue;
18
+ em.create(AdminRole, {
19
+ code: row.code,
20
+ name: row.name,
21
+ permissions: [...row.permissions],
22
+ });
23
+ }
24
+ await em.flush();
25
+ // What the demo *holds* after the run, not what this call inserted: seeding
26
+ // twice must report the same counts (`DemoEntityCount`).
27
+ return { created: [{ entity: 'AdminRole', count: DEMO_ADMIN_ROLES.length }] };
28
+ }
29
+ //# sourceMappingURL=seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../../src/backend/demo/seed.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAO7C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAyC;IAEzC,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,6EAA6E;IAC7E,uDAAuD;IACvD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAwB,CAAC,SAAS,EAAE,CAAC;IAElE,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjE,wEAAwE;QACxE,wEAAwE;QACxE,sEAAsE;QACtE,6CAA6C;QAC7C,IAAI,QAAQ;YAAE,SAAS;QACvB,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;YACnB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,CAAC,GAAG,GAAG,CAAC,WAAW,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IACD,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IAEjB,4EAA4E;IAC5E,yDAAyD;IACzD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;AAChF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { OptionalProps } from '@mikro-orm/core';
2
+ /**
3
+ * AdminRole — a named permission bundle assigned to AdminUsers. Permissions
4
+ * are screaming-snake `module:action` strings (e.g. `catalog:write`,
5
+ * `orders:read`, `customers:impersonate`). Stored as JSONB array.
6
+ *
7
+ * `requiresTwoFactor` flips the requireAdmin pre-handler into a strict mode
8
+ * (T188 hook) — accounts mapped to such a Role MUST have 2FA confirmed.
9
+ */
10
+ export declare class AdminRole {
11
+ [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'requiresTwoFactor' | 'permissions';
12
+ id: string;
13
+ code: string;
14
+ name: string;
15
+ permissions: string[];
16
+ requiresTwoFactor: boolean;
17
+ createdAt: Date;
18
+ updatedAt: Date;
19
+ }
20
+ //# sourceMappingURL=admin-role.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-role.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/admin-role.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAItF;;;;;;;GAOG;AACH,qBAEa,SAAS;IACpB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,mBAAmB,GACnB,aAAa,CAAC;IAGlB,EAAE,EAAE,MAAM,CAAgB;IAI1B,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAGd,WAAW,EAAE,MAAM,EAAE,CAAM;IAG3B,iBAAiB,EAAE,OAAO,CAAS;IAGnC,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
@@ -0,0 +1,65 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * AdminRole — a named permission bundle assigned to AdminUsers. Permissions
15
+ * are screaming-snake `module:action` strings (e.g. `catalog:write`,
16
+ * `orders:read`, `customers:impersonate`). Stored as JSONB array.
17
+ *
18
+ * `requiresTwoFactor` flips the requireAdmin pre-handler into a strict mode
19
+ * (T188 hook) — accounts mapped to such a Role MUST have 2FA confirmed.
20
+ */
21
+ let AdminRole = class AdminRole {
22
+ [OptionalProps];
23
+ id = randomUUID();
24
+ code;
25
+ name;
26
+ permissions = [];
27
+ requiresTwoFactor = false;
28
+ createdAt = new Date();
29
+ updatedAt = new Date();
30
+ };
31
+ __decorate([
32
+ PrimaryKey({ type: 'uuid' }),
33
+ __metadata("design:type", String)
34
+ ], AdminRole.prototype, "id", void 0);
35
+ __decorate([
36
+ Property({ type: 'string', length: 64 }),
37
+ Unique(),
38
+ __metadata("design:type", String)
39
+ ], AdminRole.prototype, "code", void 0);
40
+ __decorate([
41
+ Property({ type: 'string', length: 160 }),
42
+ __metadata("design:type", String)
43
+ ], AdminRole.prototype, "name", void 0);
44
+ __decorate([
45
+ Property({ type: 'json' }),
46
+ __metadata("design:type", Array)
47
+ ], AdminRole.prototype, "permissions", void 0);
48
+ __decorate([
49
+ Property({ type: 'boolean' }),
50
+ __metadata("design:type", Boolean)
51
+ ], AdminRole.prototype, "requiresTwoFactor", void 0);
52
+ __decorate([
53
+ Property({ type: 'datetime', onCreate: () => new Date() }),
54
+ __metadata("design:type", Date)
55
+ ], AdminRole.prototype, "createdAt", void 0);
56
+ __decorate([
57
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
58
+ __metadata("design:type", Date)
59
+ ], AdminRole.prototype, "updatedAt", void 0);
60
+ AdminRole = __decorate([
61
+ GlobalEntity(),
62
+ Entity({ tableName: 'admin_roles' })
63
+ ], AdminRole);
64
+ export { AdminRole };
65
+ //# sourceMappingURL=admin-role.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-role.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/admin-role.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;GAOG;AAGI,IAAM,SAAS,GAAf,MAAM,SAAS;IACpB,CAAC,aAAa,CAAC,CAKG;IAGlB,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,IAAI,CAAU;IAGd,IAAI,CAAU;IAGd,WAAW,GAAa,EAAE,CAAC;IAG3B,iBAAiB,GAAY,KAAK,CAAC;IAGnC,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AApBC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;qCACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,MAAM,EAAE;;uCACK;AAGd;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;uCAC5B;AAGd;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;8CACA;AAG3B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;oDACK;AAGnC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;4CAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;4CAAc;AA5BlB,SAAS;IAFrB,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;GACxB,SAAS,CA6BrB"}
@@ -0,0 +1,96 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
3
+ import type { ModuleManifest, SystemRoleCodePort } from '@endora-commerce/contracts';
4
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
5
+ import { AdminRoleService } from './services/admin-role-service.js';
6
+ import { PermissionCatalogueService } from './services/permission-catalogue.service.js';
7
+ import { AdminRole } from './entities/admin-role.entity.js';
8
+ /**
9
+ * `admin_roles` — the permission model behind every admin guard (feature 072,
10
+ * wave 1).
11
+ *
12
+ * It owns no routes. Its admin surface is registered by `admin_users`, which is
13
+ * shape D's usual shape seen from the other side: the module that owns the
14
+ * behaviour and the module that owns the URL are different, and only the second
15
+ * one has a `plugin.ts`. Converting it needs no route seam, because there is
16
+ * nothing to gate here — what moves is which composition root constructs the
17
+ * three services, and the answer is now "neither".
18
+ *
19
+ * `permissionService` is the reason this module goes first in wave 1. `auth`
20
+ * resolves it to build `requireAdmin`, and until now both composition roots
21
+ * registered it as a **host value** on `admin_roles`' behalf — a declared edge
22
+ * (D-32) standing in for a registration. That entry leaves
23
+ * `HOST_REGISTERED_PORTS` with this commit.
24
+ *
25
+ * The catalogue takes the **resolved** registry — core manifests plus the
26
+ * active deployment's overlay modules — which is a composition-root input, not
27
+ * something this module can see. It stays a host value for that reason, and the
28
+ * reason is durable rather than temporary: which modules a deployment ships is
29
+ * exactly the kind of thing a module must not decide for itself.
30
+ */
31
+ export interface AdminRolesCradle {
32
+ readonly emFactory: () => EntityManager;
33
+ readonly auditLogService: AuditPort;
34
+ /**
35
+ * Core manifests + this deployment's overlay modules (feature 057).
36
+ *
37
+ * Typed by what this module reads rather than by `_lifecycle`'s
38
+ * `RegisteredManifestEntry` (feature 075, Phase C). The entries the root
39
+ * contributes carry a `filePath` and the install hooks as well, and none of
40
+ * that is any of this module's business: the catalogue walks
41
+ * `manifest.permissions` and nothing else, and it already declared that
42
+ * shape for itself.
43
+ */
44
+ readonly resolvedModuleRegistry: ReadonlyArray<{
45
+ manifest: ModuleManifest;
46
+ }>;
47
+ readonly permissionCatalogueService: PermissionCatalogueService;
48
+ readonly adminRoleService: AdminRoleService;
49
+ /** Feature 075, Phase P — the deletion-protection contribution seam. */
50
+ readonly systemRoleCodePort: SystemRoleCodePort;
51
+ }
52
+ export declare function registerModule(ctx: ModuleContext): void;
53
+ /**
54
+ * The module's persisted entity classes, on the `./backend` subpath, as one
55
+ * array and **no named class export** (D-168).
56
+ *
57
+ * This is the shape the platform reads when the package is *installed*: the
58
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
59
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
60
+ * which is the third source of `check:module-boundary`'s `table->owner` map and
61
+ * the package pass of `check-entity-tenant-classification`. A missing array is
62
+ * answered with `[]` — zero entities registered, no error anywhere.
63
+ *
64
+ * The order is the one `db/entities-registry.generated.ts` declared before this
65
+ * module became a package, so the registered set is the same list in the same
66
+ * sequence.
67
+ */
68
+ export declare const entities: (typeof AdminRole)[];
69
+ /**
70
+ * The permission catalogue and the inventory scanner, published **by name**.
71
+ *
72
+ * Three host programs read them — the acceptance instance probe,
73
+ * `check:action-route-permissions`, and the permission-inventory contract test
74
+ * — and each of those processes also holds this package's published artefact,
75
+ * so a filesystem path into this module's source would give them a second copy
76
+ * of everything on that file's graph (D-160.6.1). None of these is an entity,
77
+ * which is the one thing D-168 keeps off this door; what they are is the
78
+ * host-facing half of the permission estate, and it has to be the same half the
79
+ * platform composed or a scanner reports on a catalogue nobody enforces.
80
+ */
81
+ /**
82
+ * The permission list the demo's `sales_representative` role carries (feature
83
+ * 113, T222).
84
+ *
85
+ * It was `backend/src/seeds/seeded-role-permissions.ts`, and it is published
86
+ * here because six `test/contract/*\/permission-authority.test.ts` files assert
87
+ * what that role may reach and must read **the list the seed actually writes**,
88
+ * not a copy of it. Since T222 the seed that writes it is this module's own
89
+ * demo body, so this is where the list lives and this is the door those tests
90
+ * name. It is a `readonly string[]` of catalogue codes — no entity, so D-168
91
+ * does not bar it — and `demo/rows.ts` carries nothing else on its graph.
92
+ */
93
+ export { SALES_REPRESENTATIVE_PERMISSIONS } from './demo/rows.js';
94
+ export { PermissionCatalogueService, listAssignablePermissionCodes } from './services/permission-catalogue.service.js';
95
+ export { ConstantResolver, defaultScanRoots, scanEnforcedPermissionCodes, scanEnforcedPermissionGates } from './permission-inventory.js';
96
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAGV,cAAc,EAGd,kBAAkB,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAC;AAExF,OAAO,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,sBAAsB,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;IAC7E,QAAQ,CAAC,0BAA0B,EAAE,0BAA0B,CAAC;IAChE,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;CACjD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA2FvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,sBAEpB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,gCAAgC,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,0BAA0B,EAAE,6BAA6B,EAAE,MAAM,4CAA4C,CAAC;AACvH,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC"}