@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
@@ -0,0 +1,175 @@
1
+ import { PERMISSION_CATALOGUE, } from '@endora-commerce/contracts';
2
+ /**
3
+ * Merges the core `PERMISSION_CATALOGUE` with the `permissions` arrays of every
4
+ * registered module manifest. Powers `GET /admin/permissions` and role upsert
5
+ * validation (feature 026).
6
+ *
7
+ * **There is no memo, and that is the design** (issue #213). `listAssignable()`
8
+ * cached its result into a field that only an explicit `invalidate()` cleared,
9
+ * and the two things that had to call it — a Redis pub/sub listener in each
10
+ * composition root — were a duplicated wiring step rather than a property of the
11
+ * service. That is the shape of issue #33 (an admin "clear cache" action that
12
+ * missed the per-process LRU) and issue #45 (an invalidation that depended on
13
+ * subscriber registration order) for the third time. Now that the value tracks a
14
+ * Setting an operator flips from the Admin UI at runtime, a per-process memo can
15
+ * only be wrong between the flip and whatever drops it.
16
+ *
17
+ * Nothing is lost by dropping it: every input is already in memory — the
18
+ * manifests are fixed at composition and the presence predicate reads the
19
+ * registry cache, which does its own refreshing — so a call is a map build and a
20
+ * sort over a few hundred rows, on two admin-only paths. There is no ordering
21
+ * trap to test because there is no invalidation to order.
22
+ */
23
+ export class PermissionCatalogueService {
24
+ options;
25
+ isModulePresent;
26
+ constructor(options) {
27
+ this.options = options;
28
+ this.isModulePresent = options.isModulePresent ?? (() => true);
29
+ }
30
+ /**
31
+ * The grantable set: what `/admin-roles` renders and what an operator may
32
+ * newly grant. Presence-filtered, so a module that is off contributes nothing
33
+ * (Constitution XVII item 5).
34
+ */
35
+ listAssignable() {
36
+ return this.#merge()
37
+ .filter((row) => [...row.owners].some((owner) => this.isModulePresent(owner)))
38
+ .map((row) => row.entry);
39
+ }
40
+ listAssignableCodes() {
41
+ return this.listAssignable().map((e) => e.code);
42
+ }
43
+ /**
44
+ * Every code the platform knows, present or not — the **vocabulary**, not the
45
+ * grantable set.
46
+ *
47
+ * Role upsert validates against this rather than against `listAssignable()`,
48
+ * and the difference is what keeps "off is non-destructive and reversible"
49
+ * true. An "Editor" role holds `blog.read`; the operator switches `blog` off
50
+ * and then renames the role. Validating the submitted list against the
51
+ * grantable set would answer 400 `Unknown permission(s): blog.read` — the
52
+ * operator either loses the role or silently drops a grant that must come back
53
+ * when `blog` does. A code of an absent module grants access to nothing anyway:
54
+ * the routes behind it answer 503 `MODULE_DISABLED` at their own seam.
55
+ *
56
+ * This is what the role editor already relies on: `AdminRolesPage` seeds its
57
+ * selection from the **role's** codes and renders a checkbox only for the ones
58
+ * the catalogue offers, so an absent module's grant is carried through the
59
+ * round trip untouched and simply has no checkbox to un-tick while the module
60
+ * is off. Narrowing this method would turn that into a silent revocation.
61
+ */
62
+ listKnownCodes() {
63
+ return this.#merge().map((row) => row.entry.code);
64
+ }
65
+ /**
66
+ * Every code the platform knows, mapped to the modules whose presence keeps
67
+ * it grantable — {@link MergedRow.owners}, published.
68
+ *
69
+ * D-173's sweep reads it, and it is a method rather than a derivation of its
70
+ * own because the whole question is *"does `/admin-roles` still offer this
71
+ * code when its owner is switched off?"* — a question only the merge this
72
+ * class performs can answer. A second implementation over
73
+ * `PERMISSION_CATALOGUE` plus the manifests would be right on the day it was
74
+ * written and would drift the first time the merge learns a rule: a shared
75
+ * code with two owners, or a catalogue row a manifest re-declares, is exactly
76
+ * where the two would disagree, and exactly the shape the sweep exists for.
77
+ */
78
+ listOwnersByCode() {
79
+ return new Map(this.#merge().map((row) => [row.entry.code, row.owners]));
80
+ }
81
+ /**
82
+ * The codes `moduleId` contributes, whether or not it is present.
83
+ *
84
+ * Exists for the off-state harness: proving a module's codes leave the
85
+ * grantable set needs to know which codes are its, and deriving that from the
86
+ * `module` display field would silently measure nothing for `_lifecycle`.
87
+ */
88
+ listOwnedCodes(moduleId) {
89
+ return this.#merge()
90
+ .filter((row) => row.owners.has(moduleId))
91
+ .map((row) => row.entry.code);
92
+ }
93
+ #merge() {
94
+ const byCode = new Map();
95
+ // Core first: a core row wins the label and the display module on a code
96
+ // collision, which is the long-standing behaviour and what the dedupe test
97
+ // pins. Its `module` is a real module id — `audit_log:read` says so in its
98
+ // own comment (feature 072, T017).
99
+ for (const row of PERMISSION_CATALOGUE) {
100
+ byCode.set(row.code, {
101
+ code: row.code,
102
+ module: row.module,
103
+ label: row.label,
104
+ owners: new Set([row.module]),
105
+ requires: new Set(),
106
+ });
107
+ }
108
+ for (const { manifest } of this.options.registryEntries) {
109
+ if (!manifest.permissions?.length)
110
+ continue;
111
+ for (const decl of manifest.permissions) {
112
+ const existing = byCode.get(decl.code);
113
+ if (existing) {
114
+ // A second declarer of an already-known code contributes an owner and
115
+ // its requirements — the row keeps the first label it was given.
116
+ //
117
+ // `requires` is the one field a later declarer adds to rather than
118
+ // loses (D-175). A shared code opens more than one surface and each
119
+ // has its own needs, so the union is the only answer that is right for
120
+ // every one of them; it is advisory, so advising a code an operator
121
+ // already holds costs nothing while dropping one leaves a screen
122
+ // half-working with no explanation.
123
+ existing.owners.add(manifest.id);
124
+ for (const code of decl.requires ?? [])
125
+ existing.requires.add(code);
126
+ continue;
127
+ }
128
+ byCode.set(decl.code, {
129
+ code: decl.code,
130
+ module: decl.module ?? manifest.id,
131
+ label: decl.label,
132
+ owners: new Set([manifest.id]),
133
+ requires: new Set(decl.requires ?? []),
134
+ });
135
+ }
136
+ }
137
+ return [...byCode.values()]
138
+ .map((row) => ({
139
+ entry: {
140
+ code: row.code,
141
+ module: row.module,
142
+ label: row.label,
143
+ owners: [...row.owners].sort(),
144
+ ...(row.requires.size > 0 ? { requires: [...row.requires].sort() } : {}),
145
+ },
146
+ owners: row.owners,
147
+ }))
148
+ .sort((a, b) => a.entry.module.localeCompare(b.entry.module) ||
149
+ a.entry.code.localeCompare(b.entry.code));
150
+ }
151
+ /**
152
+ * Every declared requirement, as `code -> the codes a role holding it needs`
153
+ * (D-175, feature 080 T057).
154
+ *
155
+ * The **vocabulary's** view, not the grantable set's: the advisory has to
156
+ * survive its target's owner being switched off, or it would go quiet at
157
+ * exactly the moment an operator most needs to know why a screen came back
158
+ * incomplete. Empty requirement sets are omitted, so `size` is the number of
159
+ * codes that declare one.
160
+ */
161
+ listRequirementsByCode() {
162
+ return new Map(this.#merge()
163
+ .filter((row) => (row.entry.requires?.length ?? 0) > 0)
164
+ .map((row) => [row.entry.code, row.entry.requires]));
165
+ }
166
+ }
167
+ /** Test / CI helper — build assignable codes without wiring composition. */
168
+ export function listAssignablePermissionCodes(registryEntries, presentModuleIds) {
169
+ const present = presentModuleIds === undefined ? null : new Set(presentModuleIds);
170
+ return new PermissionCatalogueService({
171
+ registryEntries,
172
+ ...(present ? { isModulePresent: (id) => present.has(id) } : {}),
173
+ }).listAssignableCodes();
174
+ }
175
+ //# sourceMappingURL=permission-catalogue.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-catalogue.service.js","sourceRoot":"","sources":["../../../src/backend/services/permission-catalogue.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AdminUserReadPort } from '@endora-commerce/contracts';
3
+ /**
4
+ * PermissionService (T187). Looks up an AdminUser's Role and checks if a
5
+ * specific permission string is present in the Role's permissions array.
6
+ *
7
+ * The wildcard `*` in a Role's permissions grants every permission — used for
8
+ * the bootstrap "platform admin" Role.
9
+ *
10
+ * The admin identity comes from `adminUserReadPort` (feature 075, Phase C)
11
+ * rather than from `em.findOne(AdminUser, …)`: the role assignment is a column
12
+ * on a table `admin_users` owns, and this module owns only what the role says.
13
+ * `activeOnly` is the `deletedAt: null` half of the query it replaced; the
14
+ * `status` test below is the other half and stays here, because "may this admin
15
+ * act" is this module's question.
16
+ */
17
+ export declare class PermissionService {
18
+ private readonly emFactory;
19
+ private readonly adminUsers;
20
+ constructor(emFactory: () => EntityManager, adminUsers: AdminUserReadPort);
21
+ hasPermission(adminUserId: string, permission: string): Promise<boolean>;
22
+ listPermissions(adminUserId: string): Promise<string[]>;
23
+ }
24
+ //# sourceMappingURL=permission-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/permission-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE;;;;;;;;;;;;;GAaG;AACH,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,SAAS,EAAE,MAAM,aAAa,EAC9B,UAAU,EAAE,iBAAiB;IAG1C,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQxE,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAM9D"}
@@ -0,0 +1,40 @@
1
+ import { AdminRole } from '../entities/admin-role.entity.js';
2
+ /**
3
+ * PermissionService (T187). Looks up an AdminUser's Role and checks if a
4
+ * specific permission string is present in the Role's permissions array.
5
+ *
6
+ * The wildcard `*` in a Role's permissions grants every permission — used for
7
+ * the bootstrap "platform admin" Role.
8
+ *
9
+ * The admin identity comes from `adminUserReadPort` (feature 075, Phase C)
10
+ * rather than from `em.findOne(AdminUser, …)`: the role assignment is a column
11
+ * on a table `admin_users` owns, and this module owns only what the role says.
12
+ * `activeOnly` is the `deletedAt: null` half of the query it replaced; the
13
+ * `status` test below is the other half and stays here, because "may this admin
14
+ * act" is this module's question.
15
+ */
16
+ export class PermissionService {
17
+ emFactory;
18
+ adminUsers;
19
+ constructor(emFactory, adminUsers) {
20
+ this.emFactory = emFactory;
21
+ this.adminUsers = adminUsers;
22
+ }
23
+ async hasPermission(adminUserId, permission) {
24
+ const admin = await this.adminUsers.findById(adminUserId, { activeOnly: true });
25
+ if (!admin || admin.status !== 'active' || !admin.adminRoleId)
26
+ return false;
27
+ const role = await this.emFactory().findOne(AdminRole, { id: admin.adminRoleId });
28
+ if (!role)
29
+ return false;
30
+ return role.permissions.includes('*') || role.permissions.includes(permission);
31
+ }
32
+ async listPermissions(adminUserId) {
33
+ const admin = await this.adminUsers.findById(adminUserId, { activeOnly: true });
34
+ if (!admin?.adminRoleId)
35
+ return [];
36
+ const role = await this.emFactory().findOne(AdminRole, { id: admin.adminRoleId });
37
+ return role?.permissions ?? [];
38
+ }
39
+ }
40
+ //# sourceMappingURL=permission-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-service.js","sourceRoot":"","sources":["../../../src/backend/services/permission-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,iBAAiB;IAET;IACA;IAFnB,YACmB,SAA8B,EAC9B,UAA6B;QAD7B,cAAS,GAAT,SAAS,CAAqB;QAC9B,eAAU,GAAV,UAAU,CAAmB;IAC7C,CAAC;IAEJ,KAAK,CAAC,aAAa,CAAC,WAAmB,EAAE,UAAkB;QACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QAC5E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,WAAmB;QACvC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,IAAI,CAAC,KAAK,EAAE,WAAW;YAAE,OAAO,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QAClF,OAAO,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Admin Roles module — manifest backfill (Module Lifecycle, feature 018).
3
+ *
4
+ * Predates the lifecycle system; this manifest is the static record
5
+ * required so the module participates in the registry. No install /
6
+ * uninstall hook today — the module's schema is owned by earlier
7
+ * platform-wide migrations.
8
+ */
9
+ export declare const manifest: {
10
+ id: string;
11
+ name: string;
12
+ version: string;
13
+ dependencies: string[];
14
+ description?: string | undefined;
15
+ acknowledgedDependencies?: {
16
+ moduleId: string;
17
+ port: string;
18
+ reason: string;
19
+ }[] | undefined;
20
+ nonBindingDependencies?: {
21
+ moduleId: string;
22
+ name: string;
23
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
24
+ reason: string;
25
+ whenAbsent?: string | undefined;
26
+ }[] | undefined;
27
+ activation?: {
28
+ settingCode: string;
29
+ default: boolean;
30
+ } | {
31
+ nonDeactivatable: true;
32
+ reason: string;
33
+ } | undefined;
34
+ settings?: {
35
+ moduleCode: string;
36
+ groups: {
37
+ code: string;
38
+ name: string;
39
+ salesChannelCodes?: string[] | undefined;
40
+ isSystemProtected?: boolean | undefined;
41
+ }[];
42
+ settings: {
43
+ code: string;
44
+ name: string;
45
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
46
+ defaultValue: unknown;
47
+ description?: string | undefined;
48
+ groupCode?: string | undefined;
49
+ previousDefaultValues?: unknown[] | undefined;
50
+ salesChannelCodes?: string[] | undefined;
51
+ enumOptions?: string[] | undefined;
52
+ configurationType?: string | undefined;
53
+ hidden?: boolean | undefined;
54
+ }[];
55
+ } | undefined;
56
+ i18n?: {
57
+ bundlesDir: string;
58
+ } | undefined;
59
+ docs?: false | {
60
+ dir: string;
61
+ } | undefined;
62
+ demo?: false | {
63
+ summary: string;
64
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
65
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
66
+ after?: readonly string[] | undefined;
67
+ package?: string | undefined;
68
+ } | undefined;
69
+ actions?: {
70
+ id: string;
71
+ labelKey: string;
72
+ icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
73
+ targetRoute: string;
74
+ keywords: string[];
75
+ weight: number;
76
+ descriptionKey?: string | undefined;
77
+ requiredPermission?: string | undefined;
78
+ }[] | undefined;
79
+ permissions?: {
80
+ code: string;
81
+ label: string;
82
+ module?: string | undefined;
83
+ description?: string | undefined;
84
+ requires?: string[] | undefined;
85
+ }[] | undefined;
86
+ transactionalEmails?: {
87
+ code: string;
88
+ name: string;
89
+ variables: {
90
+ key: string;
91
+ label: string;
92
+ sampleValue?: string | undefined;
93
+ description?: string | undefined;
94
+ }[];
95
+ description?: string | undefined;
96
+ group?: string | undefined;
97
+ }[] | undefined;
98
+ capabilities?: string[] | undefined;
99
+ exclusiveCapabilities?: {
100
+ key: string;
101
+ errorCode: string;
102
+ }[] | undefined;
103
+ errorCodes?: {
104
+ code: string;
105
+ tokens?: string[] | undefined;
106
+ }[] | undefined;
107
+ blocks?: {
108
+ name: string;
109
+ labelKey: string;
110
+ category: string;
111
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
112
+ fields: Record<string, {
113
+ type: "number" | "object" | "array" | "uuid" | "text" | "textarea" | "select" | "radio" | "external" | "richtext";
114
+ label?: string | undefined;
115
+ required?: boolean | undefined;
116
+ options?: {
117
+ label: string;
118
+ value: string | number;
119
+ }[] | undefined;
120
+ refKind?: string | undefined;
121
+ }>;
122
+ descriptionKey?: string | undefined;
123
+ defaultProps?: Record<string, unknown> | undefined;
124
+ responsiveFields?: string[] | undefined;
125
+ previewIcon?: string | undefined;
126
+ weight?: number | undefined;
127
+ }[] | undefined;
128
+ blockCategories?: {
129
+ key: string;
130
+ titleKey: string;
131
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
132
+ weight?: number | undefined;
133
+ visible?: boolean | undefined;
134
+ }[] | undefined;
135
+ env?: {
136
+ name: string;
137
+ describes: {
138
+ en: string;
139
+ pl: string;
140
+ };
141
+ requirement: {
142
+ kind: "required";
143
+ } | {
144
+ kind: "requiredWhen";
145
+ input: string;
146
+ equals: string;
147
+ } | {
148
+ kind: "optional";
149
+ without: {
150
+ en: string;
151
+ pl: string;
152
+ };
153
+ };
154
+ secret: boolean;
155
+ generable: boolean;
156
+ owner: {
157
+ kind: "platform";
158
+ } | {
159
+ kind: "application";
160
+ application: "admin" | "backend" | "storefront";
161
+ } | {
162
+ kind: "module";
163
+ moduleId: string;
164
+ };
165
+ consumers: ("admin" | "backend" | "storefront")[];
166
+ addressOf: "admin" | "backend" | "storefront" | null;
167
+ }[] | undefined;
168
+ };
169
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AA4BA;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgKnB,CAAC"}
@@ -0,0 +1,192 @@
1
+ import { defineModuleManifest } from '@endora-commerce/contracts';
2
+ /**
3
+ * The demo data this module owns (feature 113, T222 — contract §1.3).
4
+ *
5
+ * A typed `const` rather than an inline object literal: declared inline the
6
+ * parameter infers from the schema and is `never`, so the author loses
7
+ * `context.ctx: ModuleContext`.
8
+ *
9
+ * Both bodies are reached by a **relative `await import()`** (§1.4), in
10
+ * `cliCommands.run`'s shape and for `cliCommands`' reason: a manifest is loaded
11
+ * by every process that composes the platform and by the check scripts that
12
+ * import the generated index, so a demo body imported at the top of this file
13
+ * would be a service graph pulled into all of them. It needs no `exports`
14
+ * subpath and no `files` entry (§1.5).
15
+ *
16
+ * **Which administrator holds which role is not here.** An `admin_users` row
17
+ * carrying an `admin_roles` id is two modules' rows in one statement, so the
18
+ * assignment is a composition step (§5.1) and belongs to whoever owns the
19
+ * instance.
20
+ */
21
+ const demo = {
22
+ summary: 'The platform-administrator and sales-representative roles the demo signs in with.',
23
+ seed: async (context) => (await import('./backend/demo/seed.js')).seedDemo(context),
24
+ reset: async (context) => (await import('./backend/demo/reset.js')).resetDemo(context),
25
+ };
26
+ /**
27
+ * Admin Roles module — manifest backfill (Module Lifecycle, feature 018).
28
+ *
29
+ * Predates the lifecycle system; this manifest is the static record
30
+ * required so the module participates in the registry. No install /
31
+ * uninstall hook today — the module's schema is owned by earlier
32
+ * platform-wide migrations.
33
+ */
34
+ export const manifest = defineModuleManifest({
35
+ id: 'admin_roles',
36
+ name: 'Admin Roles',
37
+ description: 'Admin RBAC — roles, permissions, and policy enforcement for admin sessions.',
38
+ version: '1.0.0',
39
+ dependencies: [],
40
+ /**
41
+ * Feature 075, Phase C — the two reads of the `AdminUser` row became
42
+ * `adminUserReadPort`, and `admin_users` declares this module, so declaring
43
+ * it back in `dependencies` closes a cycle that `module-graph.test.ts` and the
44
+ * composer generator both refuse. The mutual shape
45
+ * `acknowledgedDependencies` exists for, and the same one `auth` records
46
+ * against `customer_accounts`.
47
+ *
48
+ * With `admin_users` absent the seam fails closed: the port is gated, so the
49
+ * permission check refuses rather than answering from an admin row nobody
50
+ * read. That is the same answer every `requireAdmin` route on such a
51
+ * deployment gives, and it is why the edge is safe to withhold from
52
+ * `dependencies` — not a claim about which flips the orchestrator refuses,
53
+ * which is derived from the manifests on every run and copied here by nobody
54
+ * (D-100).
55
+ */
56
+ acknowledgedDependencies: [
57
+ {
58
+ moduleId: 'admin_users',
59
+ port: 'adminUserReadPort',
60
+ reason: 'Answering "may this admin do this?" starts from the admin row — its role assignment ' +
61
+ 'and whether it is still live — and that row belongs to admin_users, which declares ' +
62
+ 'this module for the catalogue it reads back. Declaring it here closes a cycle. With ' +
63
+ 'admin_users absent the seam fails closed: the port is gated, so the permission check ' +
64
+ 'refuses rather than answering from an admin row nobody read.',
65
+ },
66
+ ],
67
+ // Feature 072/073 (Constitution XVII) — `admin_roles` answers "may this admin
68
+ // do this?" for every guarded route in the platform. Switched off, the
69
+ // question has no answer and `requireAdmin` has nothing to check against, so
70
+ // the correct behaviour would be to refuse every admin request — which is not
71
+ // a deployment anyone wants and not a state the orchestrator will produce.
72
+ demo,
73
+ activation: {
74
+ nonDeactivatable: true,
75
+ reason: 'Answers the permission check behind every guarded admin route; switched off, no admin ' +
76
+ 'request could be authorised at all.',
77
+ },
78
+ /**
79
+ * This module's first i18n bundle — D-129's remaining sweep, MR 6.
80
+ *
81
+ * It exists because a **declaring** module that contributes no bundle file is
82
+ * `check:error-translations` exit 2 for the whole tree rather than a finding
83
+ * (`specs/090-module-owned-error-codes/contracts/error-translation-population.md`
84
+ * §2.1, §2.3; `d129-sweep.md` §3.4). The bundle lives at the **package
85
+ * root**, not under `dist`: `manifest-locations.ts` resolves a packaged
86
+ * module's `manifestPath` to its `package.json`, so `dirname` is the package
87
+ * directory. The file is a **flat** `{"a.b.c": "text"}` map, because a nested
88
+ * object fails `TranslationBundleEntriesSchema` and the boot reconciler logs
89
+ * and skips it — silently.
90
+ */
91
+ i18n: { bundlesDir: 'i18n' },
92
+ docs: { dir: 'docs' },
93
+ /**
94
+ * This module's first command-palette action — feature 091, Phase 4, batch
95
+ * four — and it points at a route **`admin_users` declares**.
96
+ *
97
+ * That is the same split `src/admin/index.ts` makes for the sidebar entry and
98
+ * it is made for the same reason: the roles editor is served by
99
+ * `GET /api/v1/admin/admin-roles` in `admin_users`, so that module owns the
100
+ * route, while the advertisement of the roles capability is this module's.
101
+ * `check:action-route-permissions` resolves a `targetRoute` by path and not
102
+ * by owner, so it holds this declaration to the gate `/admin-roles` really
103
+ * enforces — `admin_users:manage`, which is why the code below is not an
104
+ * `admin_roles:*` one. This module registers no admin route and declares no
105
+ * permission of its own.
106
+ *
107
+ * It pays one of the fifteen entries `specs/deferred-defects.md` still holds
108
+ * under *"Sixteen modules with an admin screen declare no command-palette
109
+ * action"*, and the two labels live in this module's own bundle above.
110
+ */
111
+ actions: [
112
+ {
113
+ id: 'open-admin-roles',
114
+ labelKey: 'actions.openAdminRoles.label',
115
+ descriptionKey: 'actions.openAdminRoles.description',
116
+ icon: 'ShieldCheck',
117
+ targetRoute: '/admin-roles',
118
+ requiredPermission: 'admin_users:manage',
119
+ keywords: ['roles', 'role', 'rbac', 'permissions', 'uprawnienia', 'access'],
120
+ weight: 300,
121
+ },
122
+ ],
123
+ /**
124
+ * The three `ADMIN_ROLE_*` codes — D-129's remaining sweep, **Tier C**
125
+ * (`specs/090-module-owned-error-codes/d129-sweep.md` §5.2, Appendix A; MR 6).
126
+ *
127
+ * They were declared by `_i18n` until this merge request, not because anybody
128
+ * judged them the platform's but because the deleted prefix chain had no rule
129
+ * for them and its last line was `return 'core'`. **D-121 T1 puts all three
130
+ * here**: the noun is an admin role, this module's own `admin_roles` row, and
131
+ * every one of the four raise sites is in this module's
132
+ * `services/admin-role-service.ts`. Noun and thrower agree, which is why the
133
+ * sweep classes this batch by its *content* rather than by its judgement —
134
+ * Tier C is the two modules where prose moves.
135
+ *
136
+ * **`ADMIN_ROLE_IN_USE` is the sweep's only multi-token sentence**, and the
137
+ * one code in it where a wrong key is a wrong sentence rather than a missing
138
+ * one. `tokens` is derived from the raise sites and not from the bundle
139
+ * (runbook §5): both raises pass a `details.code` — `assigned` when live
140
+ * admin accounts still hold the role, `assigned_to_deleted` when only
141
+ * soft-deleted ones do — measured by balanced-paren extraction of each call's
142
+ * own argument list, resolving `ERROR_CODES.<CODE>` and the bare literal
143
+ * alike. The two populations are counted apart because the remedy differs,
144
+ * which is issue #168; `test/unit/admin_roles/role-in-use-sentence.test.ts`
145
+ * renders a real refusal against these keys.
146
+ *
147
+ * **The base key `errors.ADMIN_ROLE_IN_USE` is unreachable, and it is carried
148
+ * anyway.** `localizeErrorEnvelope` composes `` `${target.key}.${token}` ``
149
+ * whenever `details.code` is present and has **no** fall-back to the base key
150
+ * (`packages/platform/src/http/error-envelope.ts`), so with both raise sites
151
+ * tokened nothing in the tree renders that sentence. Deleting it is not free,
152
+ * and the measurement is the reason it is still here: P1 asks its question at
153
+ * `errors.<CODE>` and at no other key, so the bundle without the base pair
154
+ * reports `ADMIN_ROLE_IN_USE → admin_roles (missing in: en, pl)` and exits 1
155
+ * — for the best-translated code in the sweep. The two ways out are both
156
+ * worse than one unreachable string. `UNTRANSLATED_ERROR_CODES` would be a
157
+ * false entry: its own header defines an entry as a code with **no sentence
158
+ * anywhere**, this code has four, and nothing could ever drain it. Widening
159
+ * P1 to accept "every declared token answered" is the real repair, and it is
160
+ * a change to a tree-wide instrument made by the merge request that benefits
161
+ * from it — reported for the defect register instead, with the second half of
162
+ * the same finding: three places in this repository document a fall-back to
163
+ * `errors.<CODE>` that the envelope does not implement
164
+ * (`packages/contracts/src/modules.ts`'s `errorCodeTokenRe` note,
165
+ * `check-error-translations.ts`' `ERROR_KEY` note, and the test above).
166
+ *
167
+ * **The other two arrive with prose.** Both carried a placeholder in `_i18n`'s
168
+ * bundle — `"Admin Role Code Taken."` / `"Błąd: admin role code taken."` and
169
+ * the same for `ADMIN_ROLE_PROTECTED` — which D-186 §2 deletes rather than
170
+ * carries, because in this module's own bundle it would read as this module's
171
+ * answer. §5.4 keeps writing the prose available and is not a re-opening of
172
+ * that ruling, and both are the case for taking it: the reader is an operator
173
+ * on `/admin-roles`, saving a role or pressing Delete on a row, and each
174
+ * refusal has one remedy to offer.
175
+ *
176
+ * `ADMIN_ROLE_PROTECTED` needed one more thing first. Its raise interpolates
177
+ * the role's own code into the English message and passed no `details`, so a
178
+ * translated sentence with no placeholder would have rendered cleanly and
179
+ * lost the value — the shape !1181 measured at 85 codes tree-wide. The raise
180
+ * now carries `details: { role: role.code }` and both sentences name it as
181
+ * `{role}`. The member is `role` and not `code` deliberately: `details.code`
182
+ * is the refusal **token**, so the obvious spelling would have sent the
183
+ * envelope looking for `errors.ADMIN_ROLE_PROTECTED.<role code>` and rendered
184
+ * nothing at all.
185
+ */
186
+ errorCodes: [
187
+ { code: 'ADMIN_ROLE_CODE_TAKEN' },
188
+ { code: 'ADMIN_ROLE_IN_USE', tokens: ['assigned', 'assigned_to_deleted'] },
189
+ { code: 'ADMIN_ROLE_PROTECTED' },
190
+ ],
191
+ });
192
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAA2B,MAAM,4BAA4B,CAAC;AAG3F;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,IAAI,GAAsC;IAC9C,OAAO,EAAE,mFAAmF;IAC5F,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;IACnF,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;CACvF,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;IAC3C,EAAE,EAAE,aAAa;IACjB,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,6EAA6E;IAC/E,OAAO,EAAE,OAAO;IAChB,YAAY,EAAE,EAAE;IAChB;;;;;;;;;;;;;;;OAeG;IACH,wBAAwB,EAAE;QACxB;YACE,QAAQ,EAAE,aAAa;YACvB,IAAI,EAAE,mBAAmB;YACzB,MAAM,EACJ,sFAAsF;gBACtF,qFAAqF;gBACrF,sFAAsF;gBACtF,uFAAuF;gBACvF,8DAA8D;SACjE;KACF;IACD,8EAA8E;IAC9E,uEAAuE;IACvE,6EAA6E;IAC7E,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI;IACJ,UAAU,EAAE;QACV,gBAAgB,EAAE,IAAI;QACtB,MAAM,EACJ,wFAAwF;YACxF,qCAAqC;KACxC;IACD;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;IAC5B,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;IACrB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,EAAE;QACP;YACE,EAAE,EAAE,kBAAkB;YACtB,QAAQ,EAAE,8BAA8B;YACxC,cAAc,EAAE,oCAAoC;YACpD,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,cAAc;YAC3B,kBAAkB,EAAE,oBAAoB;YACxC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,CAAC;YAC3E,MAAM,EAAE,GAAG;SACZ;KACF;IACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8DG;IACH,UAAU,EAAE;QACV,EAAE,IAAI,EAAE,uBAAuB,EAAE;QACjC,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAAC,UAAU,EAAE,qBAAqB,CAAC,EAAE;QAC1E,EAAE,IAAI,EAAE,sBAAsB,EAAE;KACjC;CACF,CAAC,CAAC"}