@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,114 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { effectiveState } from '@endora-commerce/platform/kernel';
3
+ import { AdminRoleService } from './services/admin-role-service.js';
4
+ import { createAdminRolePort, createSystemRoleCodePort } from './services/admin-role-ports.js';
5
+ import { PermissionCatalogueService } from './services/permission-catalogue.service.js';
6
+ import { PermissionService } from './services/permission-service.js';
7
+ import { AdminRole } from './entities/admin-role.entity.js';
8
+ export function registerModule(ctx) {
9
+ /**
10
+ * Who holds a role, and who an admin id belongs to (feature 075, Phase C).
11
+ *
12
+ * Both services used to run `em.findOne(AdminUser, …)` against a table
13
+ * `admin_users` owns. Held as a lazy proxy rather than resolved here: the
14
+ * resolution happens per call, so a singleton service never captures the
15
+ * registration.
16
+ */
17
+ const adminUsers = lazyPort(ctx, 'adminUserReadPort');
18
+ ctx.di.register({
19
+ permissionCatalogueService: ctx
20
+ .asFunction(({ resolvedModuleRegistry }) => new PermissionCatalogueService({
21
+ registryEntries: [...resolvedModuleRegistry],
22
+ // Issue #213 — the presence predicate is the module's own to read,
23
+ // not a knob a root turns on its behalf. Both roots used to wire
24
+ // `registryCache.enabledIds()` here through a setter, which is
25
+ // composition-checklist item 6's "a knob a root resolves on the
26
+ // module's behalf is a knob that drifts between the two roots" —
27
+ // and it had drifted onto the wrong axis in both copies at once.
28
+ // `effectiveState` is the kernel's one place where the two axes are
29
+ // combined, and reading it directly is what the other twenty
30
+ // modules that need presence already do.
31
+ isModulePresent: (moduleId) => effectiveState.isPresent(moduleId),
32
+ }))
33
+ .singleton(),
34
+ adminRoleService: ctx
35
+ .asFunction(({ emFactory, permissionCatalogueService, auditLogService }) => new AdminRoleService(emFactory, permissionCatalogueService, adminUsers, auditLogService))
36
+ .singleton(),
37
+ });
38
+ // A port: `auth` resolves it from another module, so its availability is a
39
+ // cross-module question and answers on the effective state.
40
+ ctx.di.providePort('permissionService', ctx
41
+ .asFunction(({ emFactory }) => new PermissionService(emFactory, adminUsers))
42
+ .singleton());
43
+ // ---------------------------------------------------------------------------
44
+ // Feature 075, Phase P — the published surface.
45
+ //
46
+ // `adminRoleService` and `permissionCatalogueService` above are plain
47
+ // registrations that three other modules resolve; these three ports are what
48
+ // they rewire to, and `adminRolePort` is the one that stops handing the
49
+ // `AdminRole` entity across.
50
+ //
51
+ // `systemRoleCodePort` is a **contribution seam** and stays one: `blog`
52
+ // registers its seeded code from a boot hook, so a gate would throw during
53
+ // composition — and, worse, would let an operator delete a protected role by
54
+ // switching its owner off for a moment. It is `providePort` all the same
55
+ // because reading the list back is a call rather than a contribution, and
56
+ // the gate is unreachable in the direction that matters: nothing in the tree
57
+ // registers a code after boot.
58
+ // ---------------------------------------------------------------------------
59
+ ctx.di.providePort('adminRolePort', ctx
60
+ .asFunction(({ emFactory }) => createAdminRolePort(emFactory, () => ctx.cradle().adminRoleService))
61
+ .singleton());
62
+ ctx.di.providePort('permissionCataloguePort', ctx
63
+ .asFunction(({ permissionCatalogueService }) => permissionCatalogueService)
64
+ .singleton());
65
+ ctx.di.register({
66
+ systemRoleCodePort: ctx.asFunction(() => createSystemRoleCodePort()).singleton(),
67
+ });
68
+ }
69
+ /**
70
+ * The module's persisted entity classes, on the `./backend` subpath, as one
71
+ * array and **no named class export** (D-168).
72
+ *
73
+ * This is the shape the platform reads when the package is *installed*: the
74
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
75
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
76
+ * which is the third source of `check:module-boundary`'s `table->owner` map and
77
+ * the package pass of `check-entity-tenant-classification`. A missing array is
78
+ * answered with `[]` — zero entities registered, no error anywhere.
79
+ *
80
+ * The order is the one `db/entities-registry.generated.ts` declared before this
81
+ * module became a package, so the registered set is the same list in the same
82
+ * sequence.
83
+ */
84
+ export const entities = [
85
+ AdminRole,
86
+ ];
87
+ /**
88
+ * The permission catalogue and the inventory scanner, published **by name**.
89
+ *
90
+ * Three host programs read them — the acceptance instance probe,
91
+ * `check:action-route-permissions`, and the permission-inventory contract test
92
+ * — and each of those processes also holds this package's published artefact,
93
+ * so a filesystem path into this module's source would give them a second copy
94
+ * of everything on that file's graph (D-160.6.1). None of these is an entity,
95
+ * which is the one thing D-168 keeps off this door; what they are is the
96
+ * host-facing half of the permission estate, and it has to be the same half the
97
+ * platform composed or a scanner reports on a catalogue nobody enforces.
98
+ */
99
+ /**
100
+ * The permission list the demo's `sales_representative` role carries (feature
101
+ * 113, T222).
102
+ *
103
+ * It was `backend/src/seeds/seeded-role-permissions.ts`, and it is published
104
+ * here because six `test/contract/*\/permission-authority.test.ts` files assert
105
+ * what that role may reach and must read **the list the seed actually writes**,
106
+ * not a copy of it. Since T222 the seed that writes it is this module's own
107
+ * demo body, so this is where the list lives and this is the door those tests
108
+ * name. It is a `readonly string[]` of catalogue codes — no entity, so D-168
109
+ * does not bar it — and `demo/rows.ts` carries nothing else on its graph.
110
+ */
111
+ export { SALES_REPRESENTATIVE_PERMISSIONS } from './demo/rows.js';
112
+ export { PermissionCatalogueService, listAssignablePermissionCodes } from './services/permission-catalogue.service.js';
113
+ export { ConstantResolver, defaultScanRoots, scanEnforcedPermissionCodes, scanEnforcedPermissionGates } from './permission-inventory.js';
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ /** How a gate site resolves to a permission code. */
2
+ export type GateResolution =
3
+ /** The argument resolved to one or more literal permission codes. */
4
+ 'code'
5
+ /** `requireAdmin()` — any authenticated admin, no code to check. */
6
+ | 'authenticated-admin'
7
+ /**
8
+ * The argument is a value bound at runtime — a guard implementation consuming
9
+ * its own parameter, a delegating re-registration forwarding one, or a
10
+ * registry entry supplying a code its contributing module writes elsewhere.
11
+ * There is no literal to check here; the literal is checked where it is
12
+ * written, which is a site this scan also reads.
13
+ */
14
+ | 'runtime-value'
15
+ /** An argument the scanner could not resolve — reported, never dropped. */
16
+ | 'unresolved';
17
+ export interface EnforcedGateSite {
18
+ /** Path relative to `backend/src`, for a failure message that can be acted on. */
19
+ readonly file: string;
20
+ /** The module directory owning the file, or `null` outside a module tree. */
21
+ readonly moduleId: string | null;
22
+ /** The raw argument text, normalised to a single line. */
23
+ readonly expression: string;
24
+ readonly resolution: GateResolution;
25
+ /** Codes this site enforces; empty unless `resolution === 'code'`. */
26
+ readonly codes: readonly string[];
27
+ }
28
+ export interface PermissionScanResult {
29
+ readonly codes: Set<string>;
30
+ readonly sites: readonly EnforcedGateSite[];
31
+ readonly unresolved: readonly EnforcedGateSite[];
32
+ readonly runtimeValue: readonly EnforcedGateSite[];
33
+ readonly authenticatedAdminOnly: readonly EnforcedGateSite[];
34
+ }
35
+ /**
36
+ * One directory the scan walks.
37
+ *
38
+ * `moduleId` is set when the directory **is** one module's — a module that has
39
+ * become a workspace package (feature 080, T040b). Its internal layout is its
40
+ * own, so `src/backend/routes.admin.ts` carries no `modules/<id>/` segment for
41
+ * {@link moduleIdFor} to read, and the id comes off the declaration that put the
42
+ * root in the list (D-142) rather than off the path.
43
+ */
44
+ export interface PermissionScanRoot {
45
+ readonly dir: string;
46
+ readonly moduleId?: string;
47
+ }
48
+ /**
49
+ * The roots a bare-core build enforces gates in, plus the active deployment's
50
+ * overlay modules (feature 057 — an overlay module is an ordinary lifecycle
51
+ * participant, so its gates count for that deployment and for no other).
52
+ *
53
+ * `src/apps` is skipped during the walk rather than excluded by name: a
54
+ * deployment that is not the selected one is not part of this build, and its
55
+ * permissions are not assignable here.
56
+ *
57
+ * **A module that is a workspace package is not here, and is the caller's to
58
+ * supply.** This scanner has no runtime caller — its two callers are
59
+ * `test/contract/admin_users/permission-inventory.test.ts` and the scanner's own
60
+ * unit test — and the module layout is derived by `scripts/lib/module-roots.ts`,
61
+ * which a module may not import (`check:module-boundary`, and it is right to
62
+ * refuse: this file belongs to `admin_roles`). So the root list is an input, and
63
+ * the caller that already resolves the layout passes it. Leaving a packaged
64
+ * module out silently reports its codes as *"grantable but enforced by no
65
+ * gate"* — a checkbox `/admin-roles` says grants nothing while the gate is
66
+ * there — which is what the contract test caught the day `blog` moved.
67
+ *
68
+ * **The overlay root is an input for the same reason** (feature 080, T051). It
69
+ * used to be computed here, from `overlay/overlay-roots.js` — a file
70
+ * `contracts/host-package.md` §1.4l classifies **A**, because `overlay/*` is
71
+ * how the platform *discovers* overlays and a module reading it as an installed
72
+ * package would be an artefact enumerating its own siblings (O3's cycle). The
73
+ * answer is the same one this paragraph already gives for a packaged module:
74
+ * the caller resolves the layout, and there is no runtime caller to inconvenience.
75
+ * `null` means a bare-core build, which is what a deployment-less environment
76
+ * has always produced.
77
+ */
78
+ export declare function defaultScanRoots(applicationSourceRoot: string, overlayModulesRoot: string | null): string[];
79
+ /**
80
+ * Distinct permission codes referenced by an enforcement site under the scan
81
+ * roots. Kept for callers that only need the set (SC-001).
82
+ */
83
+ export declare function scanEnforcedPermissionCodes(roots: readonly (string | PermissionScanRoot)[]): Set<string>;
84
+ /**
85
+ * Every enforcement site under `roots`, classified.
86
+ *
87
+ * `roots` is required. It used to default to `defaultScanRoots()`, which
88
+ * resolved the active deployment for itself; that resolution is the caller's
89
+ * now (see {@link defaultScanRoots}), and a default of "bare core" would be a
90
+ * scan that silently covered less than it was asked for.
91
+ *
92
+ * Each site's `file` is reported relative to the **first bare-string root**,
93
+ * which is the application's own source root by construction of
94
+ * {@link defaultScanRoots} — a module package is passed as an object carrying
95
+ * its `moduleId`. It used to be relative to a `backend/src` this file computed
96
+ * by climbing out of its own directory, which answered a different directory
97
+ * the day `admin_roles` became a package (feature 080, T040b).
98
+ */
99
+ export declare function scanEnforcedPermissionGates(roots: readonly (string | PermissionScanRoot)[]): PermissionScanResult;
100
+ /**
101
+ * Resolves a constant argument to its literal, following at most a few hops:
102
+ * module-local, a relative import, or `@endora-commerce/contracts`. Deeper indirection has
103
+ * never appeared, and pretending to resolve it would hide the same gate the
104
+ * `unresolved` bucket is there to surface.
105
+ *
106
+ * Exported for `scripts/check-action-route-permissions.ts` (issue #232), which
107
+ * asks the third direction — is an action's `requiredPermission` the code
108
+ * enforced on **its own** target route — and needs the same argument shapes read
109
+ * the same way. A second resolver would answer the same question differently the
110
+ * first time either of them learned a shape.
111
+ */
112
+ export declare class ConstantResolver {
113
+ #private;
114
+ sourceOf(file: string): string | null;
115
+ lookup(file: string, name: string, property: string | null, depth?: number): string | null;
116
+ }
117
+ //# sourceMappingURL=permission-inventory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-inventory.d.ts","sourceRoot":"","sources":["../../src/backend/permission-inventory.ts"],"names":[],"mappings":"AA6CA,qDAAqD;AACrD,MAAM,MAAM,cAAc;AACxB,qEAAqE;AACnE,MAAM;AACR,oEAAoE;GAClE,qBAAqB;AACvB;;;;;;GAMG;GACD,eAAe;AACjB,2EAA2E;GACzE,YAAY,CAAC;AAEjB,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACjD,QAAQ,CAAC,YAAY,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnD,QAAQ,CAAC,sBAAsB,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9D;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,CAC9B,qBAAqB,EAAE,MAAM,EAC7B,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAChC,MAAM,EAAE,CAIV;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,GAC9C,GAAG,CAAC,MAAM,CAAC,CAEb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,GAC9C,oBAAoB,CAoBtB;AA8PD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAgB;;IAI3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIrC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,SAAI,GAAG,MAAM,GAAG,IAAI;CAmEtF"}