@astram/identity 0.20.0 → 0.22.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 (22) hide show
  1. package/dist/features/authz/access-change-notifier.d.ts +109 -0
  2. package/dist/features/authz/access-change-notifier.d.ts.map +1 -0
  3. package/dist/features/authz/access-change-notifier.js +421 -0
  4. package/dist/features/authz/grant-admin.service.d.ts +27 -1
  5. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  6. package/dist/features/authz/grant-admin.service.js +52 -2
  7. package/dist/features/identity-feature.module.d.ts.map +1 -1
  8. package/dist/features/identity-feature.module.js +26 -1
  9. package/dist/features/membership/membership.service.d.ts +19 -2
  10. package/dist/features/membership/membership.service.d.ts.map +1 -1
  11. package/dist/features/membership/membership.service.js +57 -5
  12. package/dist/features/membership/posting.service.d.ts +12 -1
  13. package/dist/features/membership/posting.service.d.ts.map +1 -1
  14. package/dist/features/membership/posting.service.js +39 -2
  15. package/dist/features/permission-announcement/permission-announcement.controller.d.ts +22 -0
  16. package/dist/features/permission-announcement/permission-announcement.controller.d.ts.map +1 -0
  17. package/dist/features/permission-announcement/permission-announcement.controller.js +130 -0
  18. package/dist/features/permission-announcement/permission-announcement.service.d.ts +143 -0
  19. package/dist/features/permission-announcement/permission-announcement.service.d.ts.map +1 -0
  20. package/dist/features/permission-announcement/permission-announcement.service.js +389 -0
  21. package/migrations/1710000000109-CreatePermissionAnnouncementDismissal.cjs +46 -0
  22. package/package.json +4 -4
@@ -0,0 +1,109 @@
1
+ import type { ModuleRegistry, Principal } from '@astram/core';
2
+ import { type NotificationQuery, type NotificationPostInput, type NotificationPostResult } from '@astram/guard';
3
+ import type { IdentitySchemaNames } from '../../schema.js';
4
+ /** DI token: the module's one notifier (attached to `SHIELD_NOTIFICATIONS` when guard has it). */
5
+ export declare const ACCESS_CHANGE_NOTIFIER: unique symbol;
6
+ export declare const ACCESS_CHANGED_KIND = "shield.access_changed";
7
+ export declare const ACCESS_CHANGED_TITLE = "Your access changed";
8
+ export declare const ACCESS_NOTICE_SAVEPOINT = "shield_access_notice";
9
+ /** SPEC §10 limits (mirror guard's `NOTIFICATION_BODY_MAX` / `NOTIFICATION_RECIPIENTS_MAX`). */
10
+ export declare const ACCESS_NOTICE_LABELS_PER_LINE = 10;
11
+ export declare const ACCESS_NOTICE_BODY_MAX = 1000;
12
+ export declare const ACCESS_NOTICE_RECIPIENTS_MAX = 1000;
13
+ /** The part of guard's `NotificationService` the notifier uses. */
14
+ export interface AccessNoticePoster {
15
+ post(input: NotificationPostInput, opts?: {
16
+ query?: NotificationQuery;
17
+ }): Promise<NotificationPostResult>;
18
+ }
19
+ /** One user in one tenant (server-derived; never request input). */
20
+ export interface AccessPair {
21
+ readonly userId: string;
22
+ readonly tenantId: string;
23
+ }
24
+ /** A pair's effective exact keys at snapshot time. */
25
+ export interface AccessEntry extends AccessPair {
26
+ readonly keys: ReadonlySet<string>;
27
+ }
28
+ /** A successful snapshot, keyed by `pairKey(pair)`. */
29
+ export interface AccessSnapshot {
30
+ readonly ok: true;
31
+ readonly entries: ReadonlyMap<string, AccessEntry>;
32
+ }
33
+ /** A holder or snapshot read failed: carries counts and the error class only. */
34
+ export interface AccessReadFailure {
35
+ readonly ok: false;
36
+ /** Pairs the read was for (`null` when unknown, e.g. the holder read itself failed). */
37
+ readonly pairCount: number | null;
38
+ readonly errorClass: string;
39
+ }
40
+ export interface AccessNoticeResult {
41
+ readonly created: number;
42
+ readonly dropped: number;
43
+ }
44
+ export interface AccessChangeNotifierLogger {
45
+ warn(message: string): void;
46
+ }
47
+ /**
48
+ * The notice failed and its savepoint could not be rolled back: the caller's transaction is gone,
49
+ * so the caller fails and rolls back like any database error (SPEC v3 §5A). The message holds no
50
+ * value; the original error rides on `cause`.
51
+ */
52
+ export declare class AccessNoticeTransactionLostError extends Error {
53
+ constructor(cause: unknown);
54
+ }
55
+ /** Locale-independent code-point order (JS `<` compares UTF-16 code units). */
56
+ export declare function compareCodePoints(a: string, b: string): number;
57
+ /**
58
+ * SPEC §10 body: sorted labels, ≤ 10 per line, then labels folded into the `and N more` tail
59
+ * (from the line showing more, ties from the longer line) while the body exceeds 1000 characters.
60
+ * `null` when nothing was gained or lost.
61
+ */
62
+ export declare function accessNoticeBody(gained: readonly string[], lost: readonly string[]): string | null;
63
+ /**
64
+ * The step-1 generated label of a key with no catalog label (G-027 step 1 SPEC §10), computed as
65
+ * guard's catalog reconciler does (`catalog-reconciler.ts` `generatedText`, not exported): engine
66
+ * `permissionTextOf` over a one-action declaration, type label from the registry descriptor when
67
+ * it differs from the typeKey. An unresolvable key gets `"<Type>: <action>"`, never the raw key.
68
+ */
69
+ export declare function generatedPermissionLabel(registry: ModuleRegistry, key: string): string;
70
+ export declare class AccessChangeNotifier {
71
+ private readonly registry;
72
+ /** Injectable clock (epoch-ms) for the posting window, bound as `?` integers. */
73
+ private readonly now;
74
+ private service;
75
+ private readonly logger;
76
+ private readonly app;
77
+ private readonly authz;
78
+ constructor(registry: ModuleRegistry, schemas: IdentitySchemaNames, logger?: AccessChangeNotifierLogger,
79
+ /** Injectable clock (epoch-ms) for the posting window, bound as `?` integers. */
80
+ now?: () => number);
81
+ /** Bind guard's `NotificationService` (`null` ⇒ every method is a no-op). */
82
+ attach(service: AccessNoticePoster | null): void;
83
+ /** Whether a change by `actor` would be notified (service attached, not the org bootstrap). */
84
+ enabledFor(actor: Principal): boolean;
85
+ /**
86
+ * Users holding `roleUuid` through an in-effect posting of their active membership, with
87
+ * `u_status = 'active'` (the `readRoleIdsForTenant` predicates), per tenant. Never throws.
88
+ */
89
+ holdersOfRole(query: NotificationQuery, roleUuid: string): Promise<readonly AccessPair[] | AccessReadFailure>;
90
+ /**
91
+ * Effective keys per pair on the caller's open transaction (sees its uncommitted rows): one
92
+ * roles query for all pairs (chunked at 500), one `role_permission` query for all roles, then
93
+ * `expandGrantKeys`. A pair with no roles has an empty set. Never throws.
94
+ */
95
+ snapshot(query: NotificationQuery, pairs: readonly AccessPair[] | AccessReadFailure): Promise<AccessSnapshot | AccessReadFailure>;
96
+ /**
97
+ * Diff `before` and `after` per pair and post one notice to each changed user, under the
98
+ * `shield_access_notice` savepoint on `query`. Never throws. `dropped` = the pairs that would
99
+ * have been notified (on a snapshot failure: the pairs the failed read was for, when known).
100
+ */
101
+ notify(query: NotificationQuery, actor: Principal, before: AccessSnapshot | AccessReadFailure, after: AccessSnapshot | AccessReadFailure): Promise<AccessNoticeResult>;
102
+ /** One parameterized catalog read; a missing, blank or raw-key label ⇒ the generated label. */
103
+ private labelsOf;
104
+ private isBootstrap;
105
+ private warnDropped;
106
+ private rowsOf;
107
+ private pairsOf;
108
+ }
109
+ //# sourceMappingURL=access-change-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-change-notifier.d.ts","sourceRoot":"","sources":["../../../src/features/authz/access-change-notifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,421 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AccessChangeNotifier = exports.AccessNoticeTransactionLostError = exports.ACCESS_NOTICE_RECIPIENTS_MAX = exports.ACCESS_NOTICE_BODY_MAX = exports.ACCESS_NOTICE_LABELS_PER_LINE = exports.ACCESS_NOTICE_SAVEPOINT = exports.ACCESS_CHANGED_TITLE = exports.ACCESS_CHANGED_KIND = exports.ACCESS_CHANGE_NOTIFIER = void 0;
4
+ exports.compareCodePoints = compareCodePoints;
5
+ exports.accessNoticeBody = accessNoticeBody;
6
+ exports.generatedPermissionLabel = generatedPermissionLabel;
7
+ /**
8
+ * `AccessChangeNotifier` — G-027 step 3 (notifying-user SPEC §5A, §10, §11): one personal
9
+ * `shield.access_changed` notice to each user whose EFFECTIVE permission keys changed, posted on
10
+ * the change's own transaction `query` so a rolled-back change sends nothing.
11
+ *
12
+ * Effective keys follow `readRoleIdsForTenant` exactly (active membership `cm_is_active = 1`,
13
+ * `u_status = 'active'`, in-effect posting window bound as epoch-ms integers) → `role_permission`
14
+ * → `expandGrantKeys` against the registry's `listActions()`: a notice never claims access
15
+ * `decide()` would not give. Holders come from memberships only, so service accounts never appear.
16
+ *
17
+ * Failure model (SPEC v3 §5A): a notice failure never fails the change, with ONE exception.
18
+ * - `holdersOfRole` / `snapshot` never throw: they return an {@link AccessReadFailure} on a read
19
+ * error; `snapshot` passes a failure through. `notify` given a failure on either side logs and drops.
20
+ * - `notify` runs under `SAVEPOINT shield_access_notice` (label read + every `post`): any error ⇒
21
+ * `ROLLBACK TO SAVEPOINT`, one `[notifications] access-change notices dropped` warning with
22
+ * counts and the error class only (never SQL, values or labels), `{ created: 0, dropped }`.
23
+ * The caller's change continues and commits.
24
+ * - Exception: when that `ROLLBACK TO SAVEPOINT` itself fails, the database already ended the
25
+ * caller's transaction (e.g. a deadlock on the notice insert), so `notify` throws
26
+ * {@link AccessNoticeTransactionLostError} and the caller must fail and roll back: never catch it
27
+ * to continue, or the audit row and later writes would autocommit without the change.
28
+ * - Without an attached `NotificationService` (guard `auth: false`) every method is a no-op with
29
+ * no SQL; an actor bound to `shield:org-bootstrap` gets `notify` ⇒ `{ 0, 0 }` with no SQL
30
+ * (`enabledFor(actor)` lets a caller skip its snapshots too).
31
+ *
32
+ * Text (SPEC §10): title `Your access changed`; `You can now: a, b.` / `You can no longer: c.`
33
+ * joined by `\n`; labels sorted by code point, at most 10 per line then `, and N more.`, folded
34
+ * further while the body would exceed 1000 characters. No actor, other user, role, company, raw
35
+ * key or uuid. Users of one tenant with the same text share one `post` (≤ 1000 recipients).
36
+ */
37
+ const common_1 = require("@nestjs/common");
38
+ const engine_1 = require("@astram/engine");
39
+ const guard_1 = require("@astram/guard");
40
+ const org_bootstrap_manifest_js_1 = require("../org-bootstrap/org-bootstrap.manifest.js");
41
+ const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
42
+ /** DI token: the module's one notifier (attached to `SHIELD_NOTIFICATIONS` when guard has it). */
43
+ exports.ACCESS_CHANGE_NOTIFIER = Symbol('IDENTITY_ACCESS_CHANGE_NOTIFIER');
44
+ exports.ACCESS_CHANGED_KIND = 'shield.access_changed';
45
+ exports.ACCESS_CHANGED_TITLE = 'Your access changed';
46
+ exports.ACCESS_NOTICE_SAVEPOINT = 'shield_access_notice';
47
+ /** SPEC §10 limits (mirror guard's `NOTIFICATION_BODY_MAX` / `NOTIFICATION_RECIPIENTS_MAX`). */
48
+ exports.ACCESS_NOTICE_LABELS_PER_LINE = 10;
49
+ exports.ACCESS_NOTICE_BODY_MAX = 1000;
50
+ exports.ACCESS_NOTICE_RECIPIENTS_MAX = 1000;
51
+ /** Pairs per batched roles query (2 placeholders each). */
52
+ const PAIR_CHUNK = 500;
53
+ /** Values per `IN (…)` list (roles, permission keys); review F-2. */
54
+ const IN_CHUNK = 1000;
55
+ /** `items` in slices of at most `size`. */
56
+ function chunksOf(items, size) {
57
+ const out = [];
58
+ for (let i = 0; i < items.length; i += size)
59
+ out.push(items.slice(i, i + size));
60
+ return out;
61
+ }
62
+ /**
63
+ * The notice failed and its savepoint could not be rolled back: the caller's transaction is gone,
64
+ * so the caller fails and rolls back like any database error (SPEC v3 §5A). The message holds no
65
+ * value; the original error rides on `cause`.
66
+ */
67
+ class AccessNoticeTransactionLostError extends Error {
68
+ constructor(cause) {
69
+ super('access-change notice: transaction lost', { cause });
70
+ this.name = 'AccessNoticeTransactionLostError';
71
+ }
72
+ }
73
+ exports.AccessNoticeTransactionLostError = AccessNoticeTransactionLostError;
74
+ /** The class name of an error, never its message (core 50 #10). */
75
+ function errorClassOf(err) {
76
+ if (err instanceof Error)
77
+ return err.constructor?.name || 'Error';
78
+ return typeof err;
79
+ }
80
+ function pairKey(p) {
81
+ return `${p.tenantId}|${p.userId}`;
82
+ }
83
+ /** Locale-independent code-point order (JS `<` compares UTF-16 code units). */
84
+ function compareCodePoints(a, b) {
85
+ const x = Array.from(a);
86
+ const y = Array.from(b);
87
+ const n = Math.min(x.length, y.length);
88
+ for (let i = 0; i < n; i++) {
89
+ const d = x[i].codePointAt(0) - y[i].codePointAt(0);
90
+ if (d !== 0)
91
+ return d;
92
+ }
93
+ return x.length - y.length;
94
+ }
95
+ function codePointLength(s) {
96
+ return Array.from(s).length;
97
+ }
98
+ // C0, DEL, C1 and the Unicode line/paragraph separators (review F-4): a label is one line of text.
99
+ // Built from a string so no raw separator character sits in the source.
100
+ const CONTROL_CHARS = new RegExp(`[\u0000-\u001f\u007f-\u009f${String.fromCharCode(0x2028, 0x2029)}]`, 'g');
101
+ function line(prefix, labels, shown) {
102
+ const rest = labels.length - shown;
103
+ const head = labels.slice(0, shown).join(', ');
104
+ if (rest <= 0)
105
+ return `${prefix}: ${head}.`;
106
+ return shown === 0 ? `${prefix}: ${rest} more.` : `${prefix}: ${head}, and ${rest} more.`;
107
+ }
108
+ /**
109
+ * SPEC §10 body: sorted labels, ≤ 10 per line, then labels folded into the `and N more` tail
110
+ * (from the line showing more, ties from the longer line) while the body exceeds 1000 characters.
111
+ * `null` when nothing was gained or lost.
112
+ */
113
+ function accessNoticeBody(gained, lost) {
114
+ const g = [...gained].sort(compareCodePoints);
115
+ const l = [...lost].sort(compareCodePoints);
116
+ if (g.length === 0 && l.length === 0)
117
+ return null;
118
+ let sg = Math.min(g.length, exports.ACCESS_NOTICE_LABELS_PER_LINE);
119
+ let sl = Math.min(l.length, exports.ACCESS_NOTICE_LABELS_PER_LINE);
120
+ const render = () => [
121
+ ...(g.length > 0 ? [line('You can now', g, sg)] : []),
122
+ ...(l.length > 0 ? [line('You can no longer', l, sl)] : []),
123
+ ].join('\n');
124
+ let body = render();
125
+ while (codePointLength(body) > exports.ACCESS_NOTICE_BODY_MAX && sg + sl > 0) {
126
+ const gl = g.length > 0 ? codePointLength(line('You can now', g, sg)) : 0;
127
+ const ll = l.length > 0 ? codePointLength(line('You can no longer', l, sl)) : 0;
128
+ if (sg > sl || (sg === sl && gl >= ll))
129
+ sg -= 1;
130
+ else
131
+ sl -= 1;
132
+ body = render();
133
+ }
134
+ return body;
135
+ }
136
+ function humanize(s) {
137
+ const spaced = s.replace(/_/g, ' ');
138
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
139
+ }
140
+ /**
141
+ * The step-1 generated label of a key with no catalog label (G-027 step 1 SPEC §10), computed as
142
+ * guard's catalog reconciler does (`catalog-reconciler.ts` `generatedText`, not exported): engine
143
+ * `permissionTextOf` over a one-action declaration, type label from the registry descriptor when
144
+ * it differs from the typeKey. An unresolvable key gets `"<Type>: <action>"`, never the raw key.
145
+ */
146
+ function generatedPermissionLabel(registry, key) {
147
+ const registered = registry.resolveByPermissionKey(key);
148
+ const parts = key.split(':');
149
+ const typeKey = registered?.action.typeKey ?? parts[1] ?? 'permission';
150
+ const actionKey = registered?.action.actionKey ?? parts[2] ?? 'use';
151
+ if (registered !== undefined) {
152
+ const descriptorLabel = registry.resolveObjectType(typeKey)?.label;
153
+ const label = descriptorLabel && descriptorLabel !== typeKey ? descriptorLabel : undefined;
154
+ const isCrud = guard_1.IDENTITY_ACTIONS.includes(actionKey);
155
+ try {
156
+ const [t] = (0, engine_1.permissionTextOf)({
157
+ typeKey,
158
+ ...(label !== undefined ? { label } : {}),
159
+ ...(isCrud ? {} : { crudActions: false, actions: [actionKey] }),
160
+ }).filter((p) => p.action === actionKey);
161
+ if (t)
162
+ return t.label;
163
+ }
164
+ catch (err) {
165
+ if (!(err instanceof engine_1.RegistrationError))
166
+ throw err;
167
+ }
168
+ }
169
+ return `${humanize(typeKey)}: ${actionKey.replace(/_/g, ' ')}`;
170
+ }
171
+ class AccessChangeNotifier {
172
+ registry;
173
+ now;
174
+ service = null;
175
+ logger;
176
+ app;
177
+ authz;
178
+ constructor(registry, schemas, logger,
179
+ /** Injectable clock (epoch-ms) for the posting window, bound as `?` integers. */
180
+ now = Date.now) {
181
+ this.registry = registry;
182
+ this.now = now;
183
+ this.logger = logger ?? new common_1.Logger('IdentityAccessChangeNotifier');
184
+ this.app = `\`${schemas.app}\``;
185
+ this.authz = `\`${schemas.authz}\``;
186
+ }
187
+ /** Bind guard's `NotificationService` (`null` ⇒ every method is a no-op). */
188
+ attach(service) {
189
+ this.service = service;
190
+ }
191
+ /** Whether a change by `actor` would be notified (service attached, not the org bootstrap). */
192
+ enabledFor(actor) {
193
+ return this.service !== null && !this.isBootstrap(actor);
194
+ }
195
+ /**
196
+ * Users holding `roleUuid` through an in-effect posting of their active membership, with
197
+ * `u_status = 'active'` (the `readRoleIdsForTenant` predicates), per tenant. Never throws.
198
+ */
199
+ async holdersOfRole(query, roleUuid) {
200
+ if (this.service === null)
201
+ return [];
202
+ try {
203
+ const nowMs = this.now();
204
+ const rows = await query(`SELECT DISTINCT cm.cm_user_uuid AS user_id, cm.cm_company_uuid AS tenant_id
205
+ FROM ${this.app}.\`company_membership\` cm
206
+ JOIN ${this.app}.\`users\` u ON u.u_uuid = cm.cm_user_uuid AND u.u_status = 'active'
207
+ JOIN ${this.app}.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
208
+ WHERE mp.mp_role_uuid = ? AND cm.cm_is_active = 1
209
+ AND (mp.mp_effective_from_ms IS NULL OR mp.mp_effective_from_ms <= ?)
210
+ AND (mp.mp_effective_until_ms IS NULL OR mp.mp_effective_until_ms > ?)`, [roleUuid, nowMs, nowMs]);
211
+ return this.pairsOf(rows);
212
+ }
213
+ catch (err) {
214
+ return { ok: false, pairCount: null, errorClass: errorClassOf(err) };
215
+ }
216
+ }
217
+ /**
218
+ * Effective keys per pair on the caller's open transaction (sees its uncommitted rows): one
219
+ * roles query for all pairs (chunked at 500), one `role_permission` query for all roles, then
220
+ * `expandGrantKeys`. A pair with no roles has an empty set. Never throws.
221
+ */
222
+ async snapshot(query, pairs) {
223
+ if (!Array.isArray(pairs))
224
+ return pairs;
225
+ const entries = new Map();
226
+ if (this.service === null)
227
+ return { ok: true, entries };
228
+ const wanted = new Map();
229
+ for (const p of pairs) {
230
+ const userId = (0, engine_1.canonicalUuid)(p.userId);
231
+ const tenantId = (0, engine_1.canonicalUuid)(p.tenantId);
232
+ if (userId === null || tenantId === null)
233
+ continue;
234
+ const pair = { userId, tenantId };
235
+ wanted.set(pairKey(pair), pair);
236
+ }
237
+ if (wanted.size === 0)
238
+ return { ok: true, entries };
239
+ try {
240
+ const nowMs = this.now();
241
+ const rolesByPair = new Map();
242
+ const all = [...wanted.values()];
243
+ for (let i = 0; i < all.length; i += PAIR_CHUNK) {
244
+ const chunk = all.slice(i, i + PAIR_CHUNK);
245
+ const where = chunk
246
+ .map(() => '(cm.cm_user_uuid = ? AND cm.cm_company_uuid = ?)')
247
+ .join(' OR ');
248
+ const rows = await query(`SELECT DISTINCT cm.cm_user_uuid AS user_id, cm.cm_company_uuid AS tenant_id,
249
+ mp.mp_role_uuid AS role_uuid
250
+ FROM ${this.app}.\`company_membership\` cm
251
+ JOIN ${this.app}.\`users\` u ON u.u_uuid = cm.cm_user_uuid AND u.u_status = 'active'
252
+ JOIN ${this.app}.\`membership_posting\` mp ON mp.mp_membership_uuid = cm.cm_uuid
253
+ WHERE (${where}) AND cm.cm_is_active = 1
254
+ AND (mp.mp_effective_from_ms IS NULL OR mp.mp_effective_from_ms <= ?)
255
+ AND (mp.mp_effective_until_ms IS NULL OR mp.mp_effective_until_ms > ?)`, [...chunk.flatMap((p) => [p.userId, p.tenantId]), nowMs, nowMs]);
256
+ for (const r of this.rowsOf(rows)) {
257
+ const userId = (0, engine_1.canonicalUuid)(r.user_id);
258
+ const tenantId = (0, engine_1.canonicalUuid)(r.tenant_id);
259
+ const role = (0, engine_1.canonicalUuid)(r.role_uuid);
260
+ if (userId === null || tenantId === null || role === null)
261
+ continue;
262
+ const k = pairKey({ userId, tenantId });
263
+ if (!wanted.has(k))
264
+ continue;
265
+ const set = rolesByPair.get(k) ?? new Set();
266
+ set.add(role);
267
+ rolesByPair.set(k, set);
268
+ }
269
+ }
270
+ const roles = [...new Set([...rolesByPair.values()].flatMap((s) => [...s]))];
271
+ const keysByRole = new Map();
272
+ for (const slice of chunksOf(roles, IN_CHUNK)) {
273
+ const rows = await query(`SELECT rp_role_uuid AS role_uuid, rp_perm_key FROM ${this.authz}.\`role_permission\`
274
+ WHERE rp_role_uuid IN (${slice.map(() => '?').join(', ')})`, slice);
275
+ for (const r of this.rowsOf(rows)) {
276
+ const role = (0, engine_1.canonicalUuid)(r.role_uuid);
277
+ if (role === null || typeof r.rp_perm_key !== 'string')
278
+ continue;
279
+ const list = keysByRole.get(role) ?? [];
280
+ list.push(r.rp_perm_key);
281
+ keysByRole.set(role, list);
282
+ }
283
+ }
284
+ const allKeys = this.registry.listActions().map((a) => a.permissionKey);
285
+ for (const [k, pair] of wanted) {
286
+ const raw = [...(rolesByPair.get(k) ?? [])].flatMap((role) => keysByRole.get(role) ?? []);
287
+ entries.set(k, { ...pair, keys: new Set((0, db_permission_facts_adapter_js_1.expandGrantKeys)(raw, allKeys)) });
288
+ }
289
+ return { ok: true, entries };
290
+ }
291
+ catch (err) {
292
+ return { ok: false, pairCount: wanted.size, errorClass: errorClassOf(err) };
293
+ }
294
+ }
295
+ /**
296
+ * Diff `before` and `after` per pair and post one notice to each changed user, under the
297
+ * `shield_access_notice` savepoint on `query`. Never throws. `dropped` = the pairs that would
298
+ * have been notified (on a snapshot failure: the pairs the failed read was for, when known).
299
+ */
300
+ async notify(query, actor, before, after) {
301
+ const service = this.service;
302
+ if (service === null || this.isBootstrap(actor))
303
+ return { created: 0, dropped: 0 };
304
+ if (!before.ok || !after.ok) {
305
+ const failure = (!before.ok ? before : after);
306
+ const known = [before, after].map((s) => (s.ok ? s.entries.size : (s.pairCount ?? 0)));
307
+ const dropped = Math.max(...known);
308
+ this.warnDropped(dropped, failure.errorClass);
309
+ return { created: 0, dropped };
310
+ }
311
+ const changes = [];
312
+ const keys = new Set([...before.entries.keys(), ...after.entries.keys()]);
313
+ for (const k of keys) {
314
+ const b = before.entries.get(k);
315
+ const a = after.entries.get(k);
316
+ const pair = (a ?? b);
317
+ const was = b?.keys ?? new Set();
318
+ const now = a?.keys ?? new Set();
319
+ const gained = [...now].filter((x) => !was.has(x));
320
+ const lost = [...was].filter((x) => !now.has(x));
321
+ if (gained.length > 0 || lost.length > 0) {
322
+ changes.push({ pair: { userId: pair.userId, tenantId: pair.tenantId }, gained, lost });
323
+ }
324
+ }
325
+ if (changes.length === 0)
326
+ return { created: 0, dropped: 0 };
327
+ try {
328
+ await query(`SAVEPOINT ${exports.ACCESS_NOTICE_SAVEPOINT}`);
329
+ }
330
+ catch (err) {
331
+ this.warnDropped(changes.length, errorClassOf(err));
332
+ return { created: 0, dropped: changes.length };
333
+ }
334
+ try {
335
+ const labels = await this.labelsOf(query, [
336
+ ...new Set(changes.flatMap((c) => [...c.gained, ...c.lost])),
337
+ ]);
338
+ // tenant + body ⇒ recipients (same text in the same tenant shares one post).
339
+ const groups = new Map();
340
+ for (const c of changes) {
341
+ const body = accessNoticeBody(c.gained.map((k) => labels.get(k)), c.lost.map((k) => labels.get(k)));
342
+ if (body === null)
343
+ continue;
344
+ const g = `${c.pair.tenantId}\n${body}`;
345
+ const group = groups.get(g) ?? { tenantId: c.pair.tenantId, body, recipients: [] };
346
+ group.recipients.push(c.pair.userId);
347
+ groups.set(g, group);
348
+ }
349
+ let created = 0;
350
+ for (const group of groups.values()) {
351
+ for (let i = 0; i < group.recipients.length; i += exports.ACCESS_NOTICE_RECIPIENTS_MAX) {
352
+ const res = await service.post({
353
+ tenantId: group.tenantId,
354
+ recipients: group.recipients.slice(i, i + exports.ACCESS_NOTICE_RECIPIENTS_MAX),
355
+ kind: exports.ACCESS_CHANGED_KIND,
356
+ title: exports.ACCESS_CHANGED_TITLE,
357
+ body: group.body,
358
+ }, { query });
359
+ created += res.created;
360
+ }
361
+ }
362
+ await query(`RELEASE SAVEPOINT ${exports.ACCESS_NOTICE_SAVEPOINT}`);
363
+ return { created, dropped: 0 };
364
+ }
365
+ catch (err) {
366
+ try {
367
+ await query(`ROLLBACK TO SAVEPOINT ${exports.ACCESS_NOTICE_SAVEPOINT}`);
368
+ }
369
+ catch {
370
+ // The database already ended the caller's transaction (e.g. a deadlock rolled it back,
371
+ // savepoint included): its later writes would autocommit, so the change must fail (SPEC v3 §5A).
372
+ throw new AccessNoticeTransactionLostError(err);
373
+ }
374
+ this.warnDropped(changes.length, errorClassOf(err));
375
+ return { created: 0, dropped: changes.length };
376
+ }
377
+ }
378
+ /** One parameterized catalog read; a missing, blank or raw-key label ⇒ the generated label. */
379
+ async labelsOf(query, keys) {
380
+ const stored = new Map();
381
+ for (const slice of chunksOf(keys, IN_CHUNK)) {
382
+ const rows = await query(`SELECT perm_key, perm_label FROM ${this.authz}.\`permission\`
383
+ WHERE perm_key IN (${slice.map(() => '?').join(', ')})`, slice);
384
+ for (const r of this.rowsOf(rows)) {
385
+ if (typeof r.perm_key === 'string' && typeof r.perm_label === 'string') {
386
+ stored.set(r.perm_key, r.perm_label);
387
+ }
388
+ }
389
+ }
390
+ const out = new Map();
391
+ for (const key of keys) {
392
+ const label = (stored.get(key) ?? '').replace(CONTROL_CHARS, ' ').trim();
393
+ out.set(key, label !== '' && label !== key ? label : generatedPermissionLabel(this.registry, key));
394
+ }
395
+ return out;
396
+ }
397
+ isBootstrap(actor) {
398
+ return (0, engine_1.operationOf)(actor)?.operationKey === org_bootstrap_manifest_js_1.ORG_BOOTSTRAP_OPERATION;
399
+ }
400
+ warnDropped(pairs, errorClass) {
401
+ this.logger.warn(`[notifications] access-change notices dropped: pairs=${pairs} cause=${errorClass}`);
402
+ }
403
+ rowsOf(rows) {
404
+ if (!Array.isArray(rows))
405
+ throw new TypeError('unexpected result');
406
+ return rows;
407
+ }
408
+ pairsOf(rows) {
409
+ const out = new Map();
410
+ for (const r of this.rowsOf(rows)) {
411
+ const userId = (0, engine_1.canonicalUuid)(r.user_id);
412
+ const tenantId = (0, engine_1.canonicalUuid)(r.tenant_id);
413
+ if (userId === null || tenantId === null)
414
+ continue;
415
+ const pair = { userId, tenantId };
416
+ out.set(pairKey(pair), pair);
417
+ }
418
+ return [...out.values()];
419
+ }
420
+ }
421
+ exports.AccessChangeNotifier = AccessChangeNotifier;
@@ -4,6 +4,7 @@ import { type AuditPort } from '@astram/engine';
4
4
  import { PermissionVersionBumper } from './permission-version.bumper.js';
5
5
  import { AccessBundleStore } from './access-bundle-store.js';
6
6
  import { GrantCeiling } from './grant-ceiling.js';
7
+ import type { AccessChangeNotifier } from './access-change-notifier.js';
7
8
  import { type IdentitySchemaNames } from '../../schema.js';
8
9
  /**
9
10
  * The caller's reachable subtree, threaded from `req.principal` (Task 4.2). It scopes the
@@ -65,6 +66,12 @@ export declare class GrantAdminService {
65
66
  private readonly ceiling?;
66
67
  private readonly audit?;
67
68
  private readonly schemas;
69
+ /**
70
+ * G-027 step 3 (notifying-user SPEC §5B, §5C): personal access-change notices on the grant's
71
+ * own transaction. OPTIONAL (absent ⇒ no notices, no extra reads); the identity module
72
+ * always wires it.
73
+ */
74
+ private readonly accessNotifier?;
68
75
  private readonly bundles;
69
76
  /**
70
77
  * @param ceiling The ADR-0029 self-escalation ceiling ("only grant what you hold") plus the
@@ -76,7 +83,13 @@ export declare class GrantAdminService {
76
83
  * connection, fail-closed. The identity module ALWAYS wires the engine `MariaDbAuditSink`.
77
84
  * @param schemas Resolved framework names (absent ⇒ resolved from the namespace, once, here).
78
85
  */
79
- constructor(pool: Pool, bumper: PermissionVersionBumper, bundleStore?: AccessBundleStore, ceiling?: GrantCeiling | undefined, audit?: AuditPort | undefined, schemas?: IdentitySchemaNames);
86
+ constructor(pool: Pool, bumper: PermissionVersionBumper, bundleStore?: AccessBundleStore, ceiling?: GrantCeiling | undefined, audit?: AuditPort | undefined, schemas?: IdentitySchemaNames,
87
+ /**
88
+ * G-027 step 3 (notifying-user SPEC §5B, §5C): personal access-change notices on the grant's
89
+ * own transaction. OPTIONAL (absent ⇒ no notices, no extra reads); the identity module
90
+ * always wires it.
91
+ */
92
+ accessNotifier?: AccessChangeNotifier | undefined);
80
93
  /** All grantable exact keys (from the catalog) + the computed wildcard set. */
81
94
  listCatalog(): Promise<{
82
95
  catalog: CatalogRow[];
@@ -108,6 +121,19 @@ export declare class GrantAdminService {
108
121
  * to AuthorizationUnavailableError (503).
109
122
  */
110
123
  private writeGrants;
124
+ /**
125
+ * G-027 step 3 (SPEC §5B, §5C): read the role's holders and their effective keys on the grant's
126
+ * own transaction `conn`, BEFORE the `role_permission` write. `null` (no reads at all) when no
127
+ * notifier is wired or it is disabled for this actor (guard `auth: false`, the org bootstrap).
128
+ * Never throws: a failed read is carried as an `AccessReadFailure`.
129
+ */
130
+ private beginAccessNotice;
131
+ /**
132
+ * After the write and the bump: the after-snapshot and the notices, on the same connection (a
133
+ * rollback removes them with the grant). Returns the audit detail addition: `noticesDropped: n`
134
+ * when n > 0, `'unknown'` when a holder or snapshot read failed, else nothing (byte-identical).
135
+ */
136
+ private finishAccessNotice;
111
137
  /**
112
138
  * Write the grant-administration audit row(s) on the transaction connection (D6). One row per
113
139
  * distinct tenant among the actor's tenant and every tenant the change bumped, so each affected
@@ -1 +1 @@
1
- {"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}