@i4e/invest4edu-access-core 0.32.0 → 0.33.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.
@@ -1,93 +1,93 @@
1
- /**
2
- * Role capabilities — @i4e/invest4edu-access-core (Track 3 C1 · G7).
3
- *
4
- * Turns role classification from hardcoded per-repo Sets into DATA. A `roleCapabilities`
5
- * collection (one row per role) overrides these defaults; with no rows, `resolveCapabilities`
6
- * reproduces the exact legacy behaviour, so wiring this in is behaviour-neutral until config
7
- * is populated.
8
- *
9
- * Flags:
10
- * isFullAccess — sees all data within the account (BROKER_ADMIN-style super-role)
11
- * isEmployee — eligible for the reportee-tree / 5-field visibility scope
12
- * canGovernDelegation — may grant / edit / reactivate delegations
13
- * canCorrectE1 — may run the audited E1 correction
14
- * canCrossAccount — audited sysadmin cross-account path (default: none)
15
- * productScoped — pages + records restricted to the user's mapped products
16
- * bypassAllGates — system-admin break-glass (D27): opens EVERY gate WITHIN the caller's
17
- * own account (RBAC, nav, entitlement, product scope, grid visibleWhen,
18
- * kill switches). Deliberately NOT cross-tenant — crossing accounts is
19
- * the separate, stricter `canCrossAccount` path (D3/BRI-582: config data
20
- * only, header-driven, logged before bypass). bypassAllGates ≠
21
- * canCrossAccount. Exists so a broken access config can never lock
22
- * everyone out of the screens needed to repair it.
23
- */
24
-
25
- export const CAPABILITY_FLAGS = Object.freeze([
26
- "isFullAccess",
27
- "isEmployee",
28
- "canGovernDelegation",
29
- "canCorrectE1",
30
- "canCrossAccount",
31
- "productScoped",
32
- "bypassAllGates",
33
- // Edit the GLOBAL feature registry (modules/screens/actions + their flags). Deliberately
34
- // narrower than canGovernDelegation: that includes per-account BROKER_ADMINs, and the registry
35
- // is cross-account — one edit changes what every tenant sees.
36
- "canManageRegistry",
37
- ]);
38
-
39
- /**
40
- * Legacy-parity defaults. MUST mirror the hardcoded Sets they replace:
41
- * FULL_ACCESS_ROLES = SUPER_ADMIN, SYSTEM_ADMIN, BROKER_ADMIN, BACKOFFICE_USER, HR_USER
42
- * EMPLOYEE_ROLES = SALES_USER, PAT_SALES
43
- * GRANT_ROLES (deleg)= SUPER_ADMIN, SYSTEM_ADMIN, BROKER_ADMIN
44
- * ALLOWED_ROLES (e1) = SUPER_ADMIN, SYSTEM_ADMIN, BROKER_ADMIN, BACKOFFICE_USER
45
- */
46
- export const DEFAULT_ROLE_CAPABILITIES = Object.freeze({
47
- // bypassAllGates (D27) defaults to the two platform-admin roles. Inert until the access
48
- // engine reads it, and revocable from the `rolecapabilities` collection with no deploy.
49
- SUPER_ADMIN: { isFullAccess: true, canGovernDelegation: true, canCorrectE1: true, bypassAllGates: true, canManageRegistry: true },
50
- SYSTEM_ADMIN: { isFullAccess: true, canGovernDelegation: true, canCorrectE1: true, bypassAllGates: true, canManageRegistry: true },
51
- BROKER_ADMIN: { isFullAccess: true, canGovernDelegation: true, canCorrectE1: true },
52
- BACKOFFICE_USER: { isFullAccess: true, canCorrectE1: true },
53
- HR_USER: { isFullAccess: true },
54
- SALES_USER: { isEmployee: true },
55
- PAT_SALES: { isEmployee: true },
56
- });
57
-
58
- function pickFlags(row) {
59
- const out = {};
60
- for (const f of CAPABILITY_FLAGS) if (row[f] != null) out[f] = !!row[f];
61
- return out;
62
- }
63
-
64
- /**
65
- * Merge DB rows over the parity defaults → a role→capabilities map.
66
- * @param {Array<{role:string} & Record<string, boolean>>} dbRows
67
- */
68
- export function buildCapabilityMap(dbRows = []) {
69
- const map = {};
70
- for (const [role, caps] of Object.entries(DEFAULT_ROLE_CAPABILITIES)) map[role] = { ...caps };
71
- for (const row of dbRows) {
72
- if (!row || !row.role) continue;
73
- map[row.role] = { ...(map[row.role] || {}), ...pickFlags(row) };
74
- }
75
- return map;
76
- }
77
-
78
- /**
79
- * OR-combine the capability flags across all of a caller's roles (multi-role = union).
80
- * @param {string[]} roles
81
- * @param {Record<string, Record<string, boolean>>} [capabilityMap] defaults if omitted
82
- * @returns {Record<string, boolean>} every CAPABILITY_FLAG resolved to a boolean
83
- */
84
- export function resolveCapabilities(roles, capabilityMap = DEFAULT_ROLE_CAPABILITIES) {
85
- const out = {};
86
- for (const f of CAPABILITY_FLAGS) out[f] = false;
87
- for (const role of Array.isArray(roles) ? roles : [roles].filter(Boolean)) {
88
- const caps = capabilityMap[role];
89
- if (!caps) continue;
90
- for (const f of CAPABILITY_FLAGS) if (caps[f]) out[f] = true;
91
- }
92
- return out;
93
- }
1
+ /**
2
+ * Role capabilities — @i4e/invest4edu-access-core (Track 3 C1 · G7).
3
+ *
4
+ * Turns role classification from hardcoded per-repo Sets into DATA. A `roleCapabilities`
5
+ * collection (one row per role) overrides these defaults; with no rows, `resolveCapabilities`
6
+ * reproduces the exact legacy behaviour, so wiring this in is behaviour-neutral until config
7
+ * is populated.
8
+ *
9
+ * Flags:
10
+ * isFullAccess — sees all data within the account (BROKER_ADMIN-style super-role)
11
+ * isEmployee — eligible for the reportee-tree / 5-field visibility scope
12
+ * canGovernDelegation — may grant / edit / reactivate delegations
13
+ * canCorrectE1 — may run the audited E1 correction
14
+ * canCrossAccount — audited sysadmin cross-account path (default: none)
15
+ * productScoped — pages + records restricted to the user's mapped products
16
+ * bypassAllGates — system-admin break-glass (D27): opens EVERY gate WITHIN the caller's
17
+ * own account (RBAC, nav, entitlement, product scope, grid visibleWhen,
18
+ * kill switches). Deliberately NOT cross-tenant — crossing accounts is
19
+ * the separate, stricter `canCrossAccount` path (D3/BRI-582: config data
20
+ * only, header-driven, logged before bypass). bypassAllGates ≠
21
+ * canCrossAccount. Exists so a broken access config can never lock
22
+ * everyone out of the screens needed to repair it.
23
+ */
24
+
25
+ export const CAPABILITY_FLAGS = Object.freeze([
26
+ "isFullAccess",
27
+ "isEmployee",
28
+ "canGovernDelegation",
29
+ "canCorrectE1",
30
+ "canCrossAccount",
31
+ "productScoped",
32
+ "bypassAllGates",
33
+ // Edit the GLOBAL feature registry (modules/screens/actions + their flags). Deliberately
34
+ // narrower than canGovernDelegation: that includes per-account BROKER_ADMINs, and the registry
35
+ // is cross-account — one edit changes what every tenant sees.
36
+ "canManageRegistry",
37
+ ]);
38
+
39
+ /**
40
+ * Legacy-parity defaults. MUST mirror the hardcoded Sets they replace:
41
+ * FULL_ACCESS_ROLES = SUPER_ADMIN, SYSTEM_ADMIN, BROKER_ADMIN, BACKOFFICE_USER, HR_USER
42
+ * EMPLOYEE_ROLES = SALES_USER, PAT_SALES
43
+ * GRANT_ROLES (deleg)= SUPER_ADMIN, SYSTEM_ADMIN, BROKER_ADMIN
44
+ * ALLOWED_ROLES (e1) = SUPER_ADMIN, SYSTEM_ADMIN, BROKER_ADMIN, BACKOFFICE_USER
45
+ */
46
+ export const DEFAULT_ROLE_CAPABILITIES = Object.freeze({
47
+ // bypassAllGates (D27) defaults to the two platform-admin roles. Inert until the access
48
+ // engine reads it, and revocable from the `rolecapabilities` collection with no deploy.
49
+ SUPER_ADMIN: { isFullAccess: true, canGovernDelegation: true, canCorrectE1: true, bypassAllGates: true, canManageRegistry: true },
50
+ SYSTEM_ADMIN: { isFullAccess: true, canGovernDelegation: true, canCorrectE1: true, bypassAllGates: true, canManageRegistry: true },
51
+ BROKER_ADMIN: { isFullAccess: true, canGovernDelegation: true, canCorrectE1: true },
52
+ BACKOFFICE_USER: { isFullAccess: true, canCorrectE1: true },
53
+ HR_USER: { isFullAccess: true },
54
+ SALES_USER: { isEmployee: true },
55
+ PAT_SALES: { isEmployee: true },
56
+ });
57
+
58
+ function pickFlags(row) {
59
+ const out = {};
60
+ for (const f of CAPABILITY_FLAGS) if (row[f] != null) out[f] = !!row[f];
61
+ return out;
62
+ }
63
+
64
+ /**
65
+ * Merge DB rows over the parity defaults → a role→capabilities map.
66
+ * @param {Array<{role:string} & Record<string, boolean>>} dbRows
67
+ */
68
+ export function buildCapabilityMap(dbRows = []) {
69
+ const map = {};
70
+ for (const [role, caps] of Object.entries(DEFAULT_ROLE_CAPABILITIES)) map[role] = { ...caps };
71
+ for (const row of dbRows) {
72
+ if (!row || !row.role) continue;
73
+ map[row.role] = { ...(map[row.role] || {}), ...pickFlags(row) };
74
+ }
75
+ return map;
76
+ }
77
+
78
+ /**
79
+ * OR-combine the capability flags across all of a caller's roles (multi-role = union).
80
+ * @param {string[]} roles
81
+ * @param {Record<string, Record<string, boolean>>} [capabilityMap] defaults if omitted
82
+ * @returns {Record<string, boolean>} every CAPABILITY_FLAG resolved to a boolean
83
+ */
84
+ export function resolveCapabilities(roles, capabilityMap = DEFAULT_ROLE_CAPABILITIES) {
85
+ const out = {};
86
+ for (const f of CAPABILITY_FLAGS) out[f] = false;
87
+ for (const role of Array.isArray(roles) ? roles : [roles].filter(Boolean)) {
88
+ const caps = capabilityMap[role];
89
+ if (!caps) continue;
90
+ for (const f of CAPABILITY_FLAGS) if (caps[f]) out[f] = true;
91
+ }
92
+ return out;
93
+ }