@things-factory/auth-base 10.1.79 → 10.1.107

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 (71) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/dist-server/ai-entry/access-assistant.d.ts +50 -0
  3. package/dist-server/ai-entry/access-assistant.js +145 -0
  4. package/dist-server/ai-entry/access-assistant.js.map +1 -0
  5. package/dist-server/ai-entry/access-entry-targets.d.ts +32 -0
  6. package/dist-server/ai-entry/access-entry-targets.js +196 -0
  7. package/dist-server/ai-entry/access-entry-targets.js.map +1 -0
  8. package/dist-server/ai-entry/entry-target-shape.d.ts +82 -0
  9. package/dist-server/ai-entry/entry-target-shape.js +3 -0
  10. package/dist-server/ai-entry/entry-target-shape.js.map +1 -0
  11. package/dist-server/controllers/access-request.d.ts +57 -0
  12. package/dist-server/controllers/access-request.js +373 -0
  13. package/dist-server/controllers/access-request.js.map +1 -0
  14. package/dist-server/index.d.ts +3 -0
  15. package/dist-server/index.js +3 -0
  16. package/dist-server/index.js.map +1 -1
  17. package/dist-server/service/access-request/access-request-mutation.d.ts +17 -0
  18. package/dist-server/service/access-request/access-request-mutation.js +65 -0
  19. package/dist-server/service/access-request/access-request-mutation.js.map +1 -0
  20. package/dist-server/service/access-request/access-request-query.d.ts +16 -0
  21. package/dist-server/service/access-request/access-request-query.js +100 -0
  22. package/dist-server/service/access-request/access-request-query.js.map +1 -0
  23. package/dist-server/service/access-request/access-request-types.d.ts +32 -0
  24. package/dist-server/service/access-request/access-request-types.js +115 -0
  25. package/dist-server/service/access-request/access-request-types.js.map +1 -0
  26. package/dist-server/service/access-request/access-request.d.ts +33 -0
  27. package/dist-server/service/access-request/access-request.js +160 -0
  28. package/dist-server/service/access-request/access-request.js.map +1 -0
  29. package/dist-server/service/access-request/index.d.ts +5 -0
  30. package/dist-server/service/access-request/index.js +9 -0
  31. package/dist-server/service/access-request/index.js.map +1 -0
  32. package/dist-server/service/index.d.ts +3 -1
  33. package/dist-server/service/index.js +42 -37
  34. package/dist-server/service/index.js.map +1 -1
  35. package/dist-server/service/open-fields.js +5 -1
  36. package/dist-server/service/open-fields.js.map +1 -1
  37. package/dist-server/service/privilege/privilege-directive.js +6 -6
  38. package/dist-server/service/privilege/privilege-directive.js.map +1 -1
  39. package/dist-server/service/privilege/privilege-rejection.d.ts +10 -2
  40. package/dist-server/service/privilege/privilege-rejection.js +23 -10
  41. package/dist-server/service/privilege/privilege-rejection.js.map +1 -1
  42. package/dist-server/service/user/user-mutation.js +5 -4
  43. package/dist-server/service/user/user-mutation.js.map +1 -1
  44. package/dist-server/service/user/user-query.js +5 -17
  45. package/dist-server/service/user/user-query.js.map +1 -1
  46. package/dist-server/tsconfig.tsbuildinfo +1 -1
  47. package/dist-server/utils/access-request-review.d.ts +69 -0
  48. package/dist-server/utils/access-request-review.js +119 -0
  49. package/dist-server/utils/access-request-review.js.map +1 -0
  50. package/dist-server/utils/assignable-roles.d.ts +23 -0
  51. package/dist-server/utils/assignable-roles.js +89 -0
  52. package/dist-server/utils/assignable-roles.js.map +1 -0
  53. package/dist-server/utils/check-permission.d.ts +29 -2
  54. package/dist-server/utils/check-permission.js +48 -14
  55. package/dist-server/utils/check-permission.js.map +1 -1
  56. package/package.json +4 -4
  57. package/tests/access-assistant-db.test.ts +155 -0
  58. package/tests/access-entry-targets-db.test.ts +138 -0
  59. package/tests/access-request-db.test.ts +273 -0
  60. package/tests/access-request-pg.test.ts +72 -0
  61. package/tests/access-request-review.test.ts +155 -0
  62. package/tests/assignable-roles-db.test.ts +141 -0
  63. package/tests/permission-gate.test.ts +5 -7
  64. package/tests/privilege-directive.test.ts +39 -0
  65. package/tests/privilege-rejection.test.ts +32 -0
  66. package/tests/refusal-cause.test.ts +80 -0
  67. package/translations/en.json +28 -1
  68. package/translations/ja.json +28 -1
  69. package/translations/ko.json +28 -1
  70. package/translations/ms.json +28 -1
  71. package/translations/zh.json +28 -1
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AUTH_ACCESS_REQUEST = exports.AUTH_ACCESS_DECISION = void 0;
37
+ exports.requestLabel = requestLabel;
38
+ exports.requestsNamed = requestsNamed;
39
+ exports.rolesNamed = rolesNamed;
40
+ exports.decisionOf = decisionOf;
41
+ exports.issuesOf = issuesOf;
42
+ exports.accessDecisionEntryTarget = accessDecisionEntryTarget;
43
+ exports.accessRequestEntryTarget = accessRequestEntryTarget;
44
+ exports.accessEntryTargets = accessEntryTargets;
45
+ exports.AUTH_ACCESS_DECISION = 'auth.access-decision';
46
+ exports.AUTH_ACCESS_REQUEST = 'auth.access-request';
47
+ const PENDING_REQUESTS = 'query ($filters: [Filter!], $pagination: Pagination) { accessRequests(filters: $filters, pagination: $pagination) { items { id category privilege state requester { name email } } } }';
48
+ const ROLES_NAMED = 'query ($filters: [Filter!], $pagination: Pagination) { roles(filters: $filters, pagination: $pagination) { items { id name } } }';
49
+ /** "김○○ · warehouse:mutation" - how a request is named on the card. */
50
+ function requestLabel(request) {
51
+ const who = request?.requester?.name || request?.requester?.email || '';
52
+ return [who, request?.category && request?.privilege ? `${request.category}:${request.privilege}` : ''].filter(Boolean).join(' · ');
53
+ }
54
+ /** The pending requests the said text names - by the requester's name or e-mail, or the privilege. Several stay several. */
55
+ function requestsNamed(items, said) {
56
+ const text = (said || '').trim().toLowerCase();
57
+ if (!text)
58
+ return [];
59
+ return (items || [])
60
+ .filter(item => item?.state === 'PENDING')
61
+ .filter(item => {
62
+ if (item.id === said.trim())
63
+ return true;
64
+ const names = [item.requester?.name, item.requester?.email, `${item.category}:${item.privilege}`, requestLabel(item)];
65
+ return names.some(name => typeof name === 'string' && name.toLowerCase().includes(text));
66
+ })
67
+ .map(item => ({ id: item.id, label: requestLabel(item) }));
68
+ }
69
+ /** A role named exactly - a near name is not taken. */
70
+ function rolesNamed(items, said) {
71
+ const text = (said || '').trim().toLowerCase();
72
+ return (items || []).filter(item => typeof item?.name === 'string' && item.name.toLowerCase() === text).map(item => ({ id: item.id, label: item.name }));
73
+ }
74
+ const DECISION_FIELDS = [
75
+ {
76
+ name: 'request',
77
+ type: 'reference',
78
+ required: true,
79
+ labelKey: 'access-request.field.request',
80
+ description: "the pending access request, by the requester's name and the privilege asked for",
81
+ reference: {
82
+ query: PENDING_REQUESTS,
83
+ variables: () => ({ filters: [{ name: 'state', operator: 'eq', value: 'PENDING' }], pagination: { page: 1, limit: 500 } }),
84
+ recordsOf: (data, said) => requestsNamed(data?.accessRequests?.items ?? [], said)
85
+ }
86
+ },
87
+ {
88
+ name: 'decision',
89
+ type: 'enum',
90
+ required: true,
91
+ values: ['GRANT', 'DENY'],
92
+ labelKey: 'access-request.field.decision',
93
+ valueKeyPrefix: 'access-request.decision.',
94
+ description: 'GRANT puts the requester into a role; DENY refuses with a reason'
95
+ },
96
+ {
97
+ name: 'role',
98
+ type: 'reference',
99
+ labelKey: 'access-request.field.granted role',
100
+ description: 'the role to put the requester into, by its exact name - only for GRANT. Prefer the role with the fewest privileges that still holds the one asked for; the review names a smaller one when there is.',
101
+ reference: {
102
+ query: ROLES_NAMED,
103
+ variables: (said) => ({ filters: [{ name: 'name', operator: 'eq', value: said.trim() }], pagination: { page: 1, limit: 20 } }),
104
+ recordsOf: (data, said) => rolesNamed(data?.roles?.items ?? [], said)
105
+ }
106
+ },
107
+ {
108
+ name: 'reason',
109
+ type: 'text',
110
+ labelKey: 'access-request.field.decision reason',
111
+ description: 'why - required for DENY. Never written by the model: ask the person.'
112
+ }
113
+ ];
114
+ const idOf = (value) => (value && typeof value === 'object' && typeof value.id === 'string' ? value.id : undefined);
115
+ /** A row as the decision door takes it. */
116
+ function decisionOf(row) {
117
+ const decision = row.values.decision === 'DENY' ? 'DENY' : 'GRANT';
118
+ return {
119
+ requestId: idOf(row.values.request),
120
+ decision,
121
+ roleId: decision === 'GRANT' ? idOf(row.values.role) : undefined,
122
+ reason: typeof row.values.reason === 'string' ? row.values.reason : undefined
123
+ };
124
+ }
125
+ /** The review's findings as the card's issues - the words are the screen's (`access-request.issue.<code>`). */
126
+ function issuesOf(findings) {
127
+ return findings.map(finding => ({
128
+ ...(finding.field && finding.field !== 'decision' ? { field: finding.field } : {}),
129
+ code: finding.code,
130
+ messageKey: `access-request.issue.${finding.code}`,
131
+ params: finding.params,
132
+ blocking: finding.blocking
133
+ }));
134
+ }
135
+ function accessDecisionEntryTarget() {
136
+ return {
137
+ key: exports.AUTH_ACCESS_DECISION,
138
+ labelKey: 'access-request.title.decision draft',
139
+ description: 'one decision on a pending access request: which request, grant or deny, the role to grant with, and the reason to deny.',
140
+ mutation: 'mutation ($decision: AccessDecisionInput!) { decideAccessRequest(decision: $decision) { id state } }',
141
+ fields: DECISION_FIELDS,
142
+ maxRows: 50,
143
+ variables: row => ({ decision: decisionOf(row) }),
144
+ appliedOf: (data, row) => ({ id: data?.decideAccessRequest?.id, label: row.values.request?.label }),
145
+ check: async (rows, { state }) => {
146
+ const { reviewDecision } = await Promise.resolve().then(() => __importStar(require('../controllers/access-request.js')));
147
+ const out = [];
148
+ for (const row of rows) {
149
+ const decision = decisionOf(row);
150
+ out.push(decision.requestId ? issuesOf(await reviewDecision(decision, { state })) : []);
151
+ }
152
+ return out;
153
+ }
154
+ };
155
+ }
156
+ const REQUEST_FIELDS = [
157
+ { name: 'category', type: 'string', required: true, labelKey: 'access-request.field.category', description: 'the category of the privilege, as the refusal named it' },
158
+ { name: 'privilege', type: 'string', required: true, labelKey: 'access-request.field.privilege', description: 'the privilege, as the refusal named it - query, mutation, ...' },
159
+ { name: 'reason', type: 'text', required: true, labelKey: 'access-request.field.reason', description: "why the person needs it, in the person's words - ask them; never write it yourself" }
160
+ ];
161
+ function accessRequestEntryTarget() {
162
+ return {
163
+ key: exports.AUTH_ACCESS_REQUEST,
164
+ labelKey: 'access-request.title.request draft',
165
+ description: 'one request for a privilege the person was refused for want of a role.',
166
+ mutation: 'mutation ($request: NewAccessRequest!) { requestAccess(request: $request) { id count } }',
167
+ fields: REQUEST_FIELDS,
168
+ maxRows: 10,
169
+ variables: row => ({ request: { category: row.values.category, privilege: row.values.privilege, reason: row.values.reason } }),
170
+ appliedOf: data => ({ id: data?.requestAccess?.id }),
171
+ /* What the door would refuse - asked before the card, so the person is not offered a request that cannot stand. */
172
+ check: async (rows, { state }) => {
173
+ const { judgePermission } = await Promise.resolve().then(() => __importStar(require('../utils/check-permission.js')));
174
+ const out = [];
175
+ for (const row of rows) {
176
+ const category = String(row.values.category ?? '');
177
+ const privilege = String(row.values.privilege ?? '');
178
+ if (!category || !privilege) {
179
+ out.push([]);
180
+ continue;
181
+ }
182
+ const judgement = await judgePermission({ category, privilege }, state?.user, state?.domain, state?.unsafeIP, state?.prohibitedPrivileges);
183
+ out.push(judgement.permitted === true
184
+ ? [{ code: 'already-holds', messageKey: 'access-request.issue.already-holds', blocking: true }]
185
+ : judgement.because !== 'role'
186
+ ? [{ code: 'not-by-role', messageKey: 'access-request.issue.not-by-role', params: { because: judgement.because }, blocking: true }]
187
+ : []);
188
+ }
189
+ return out;
190
+ }
191
+ };
192
+ }
193
+ function accessEntryTargets() {
194
+ return [accessDecisionEntryTarget(), accessRequestEntryTarget()];
195
+ }
196
+ //# sourceMappingURL=access-entry-targets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-entry-targets.js","sourceRoot":"","sources":["../../server/ai-entry/access-entry-targets.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,oCAGC;AAGD,sCAWC;AAGD,gCAGC;AA+CD,gCAQC;AAGD,4BAQC;AAED,8DAsBC;AAQD,4DAkCC;AAED,gDAEC;AAzKY,QAAA,oBAAoB,GAAG,sBAAsB,CAAA;AAC7C,QAAA,mBAAmB,GAAG,qBAAqB,CAAA;AAExD,MAAM,gBAAgB,GACpB,wLAAwL,CAAA;AAE1L,MAAM,WAAW,GACf,kIAAkI,CAAA;AAEpI,uEAAuE;AACvE,SAAgB,YAAY,CAAC,OAAY;IACvC,MAAM,GAAG,GAAG,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,OAAO,EAAE,SAAS,EAAE,KAAK,IAAI,EAAE,CAAA;IACvE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACrI,CAAC;AAED,4HAA4H;AAC5H,SAAgB,aAAa,CAAC,KAAY,EAAE,IAAY;IACtD,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC9C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC;SACzC,MAAM,CAAC,IAAI,CAAC,EAAE;QACb,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,IAAI,CAAA;QACxC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;QACrH,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;IAC1F,CAAC,CAAC;SACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AAC9D,CAAC;AAED,uDAAuD;AACvD,SAAgB,UAAU,CAAC,KAAY,EAAE,IAAY;IACnD,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC9C,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;AAC1J,CAAC;AAED,MAAM,eAAe,GAAiB;IACpC;QACE,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,8BAA8B;QACxC,WAAW,EAAE,iFAAiF;QAC9F,SAAS,EAAE;YACT,KAAK,EAAE,gBAAgB;YACvB,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;YAC1H,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,EAAE,IAAI,CAAC;SAClF;KACF;IACD;QACE,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,EAAE,+BAA+B;QACzC,cAAc,EAAE,0BAA0B;QAC1C,WAAW,EAAE,kEAAkE;KAChF;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,mCAAmC;QAC7C,WAAW,EACT,sMAAsM;QACxM,SAAS,EAAE;YACT,KAAK,EAAE,WAAW;YAClB,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC;YACtI,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,IAAI,CAAC;SACtE;KACF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,sCAAsC;QAChD,WAAW,EAAE,sEAAsE;KACpF;CACF,CAAA;AAED,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAQ,KAAa,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAa,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAE9I,2CAA2C;AAC3C,SAAgB,UAAU,CAAC,GAAa;IACtC,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;IAClE,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;QACnC,QAAQ;QACR,MAAM,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAChE,MAAM,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;KAC9E,CAAA;AACH,CAAC;AAED,+GAA+G;AAC/G,SAAgB,QAAQ,CAAC,QAAiG;IACxH,OAAO,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9B,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClF,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,UAAU,EAAE,wBAAwB,OAAO,CAAC,IAAI,EAAE;QAClD,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC,CAAA;AACL,CAAC;AAED,SAAgB,yBAAyB;IACvC,OAAO;QACL,GAAG,EAAE,4BAAoB;QACzB,QAAQ,EAAE,qCAAqC;QAC/C,WAAW,EACT,yHAAyH;QAC3H,QAAQ,EAAE,sGAAsG;QAChH,MAAM,EAAE,eAAe;QACvB,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,EAAE,EAAE,KAAK,EAAG,GAAG,CAAC,MAAM,CAAC,OAAe,EAAE,KAAK,EAAE,CAAC;QAE5G,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YAC/B,MAAM,EAAE,cAAc,EAAE,GAAG,wDAAa,kCAAkC,GAAC,CAAA;YAC3E,MAAM,GAAG,GAAmB,EAAE,CAAA;YAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;gBAChC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,cAAc,CAAC,QAAe,EAAE,EAAE,KAAK,EAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YACvG,CAAC;YACD,OAAO,GAAG,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC;AAED,MAAM,cAAc,GAAiB;IACnC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,+BAA+B,EAAE,WAAW,EAAE,wDAAwD,EAAE;IACtK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,gCAAgC,EAAE,WAAW,EAAE,+DAA+D,EAAE;IAC/K,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,6BAA6B,EAAE,WAAW,EAAE,oFAAoF,EAAE;CAC7L,CAAA;AAED,SAAgB,wBAAwB;IACtC,OAAO;QACL,GAAG,EAAE,2BAAmB;QACxB,QAAQ,EAAE,oCAAoC;QAC9C,WAAW,EAAE,wEAAwE;QACrF,QAAQ,EAAE,0FAA0F;QACpG,MAAM,EAAE,cAAc;QACtB,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QAC9H,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;QAEpD,mHAAmH;QACnH,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YAC/B,MAAM,EAAE,eAAe,EAAE,GAAG,wDAAa,8BAA8B,GAAC,CAAA;YACxE,MAAM,GAAG,GAAmB,EAAE,CAAA;YAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAA;gBAClD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;gBACpD,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;oBAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;oBACZ,SAAQ;gBACV,CAAC;gBACD,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAA;gBAC1I,GAAG,CAAC,IAAI,CACN,SAAS,CAAC,SAAS,KAAK,IAAI;oBAC1B,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,oCAAoC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;oBAC/F,CAAC,CAAC,SAAS,CAAC,OAAO,KAAK,MAAM;wBAC5B,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,kCAAkC,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;wBACnI,CAAC,CAAC,EAAE,CACT,CAAA;YACH,CAAC;YACD,OAAO,GAAG,CAAA;QACZ,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAgB,kBAAkB;IAChC,OAAO,CAAC,yBAAyB,EAAE,EAAE,wBAAwB,EAAE,CAAC,CAAA;AAClE,CAAC","sourcesContent":["/*\n * AI 입력 targets of the access request (ADR-0103 decision 4, docs/design/access-request.md §6).\n *\n * Plain declarations: this package does not depend on @things-factory/ai-assistant. An application that runs\n * the assistant passes them to `registerEntryTarget` and names their keys in a mode.\n *\n * ── The model drafts; a person decides ───────────────────────────────────\n * The model is given a tool that proposes a card. It is never given `decideAccessRequest`. The card's button\n * is pressed by the person, and the assistant then runs the mutation with that person's request state - the\n * door's `@privilege` and the review inside it apply as they do on the list screen.\n *\n * ── One review ────────────────────────────────────────────────────────────\n * `check` is `reviewDecision` - the function the list screen asks (`reviewAccessDecision`) and the decision\n * door runs again. The card cannot say something the screen does not.\n *\n * ⚠ The rows carry colleagues' names and the reasons they wrote, and proposing sends them to an external model.\n * Which product opens these targets is that product's decision.\n */\nimport type { EntryField, EntryIssue, EntryRow, EntryTargetDeclaration } from './entry-target-shape'\n\nexport const AUTH_ACCESS_DECISION = 'auth.access-decision'\nexport const AUTH_ACCESS_REQUEST = 'auth.access-request'\n\nconst PENDING_REQUESTS =\n 'query ($filters: [Filter!], $pagination: Pagination) { accessRequests(filters: $filters, pagination: $pagination) { items { id category privilege state requester { name email } } } }'\n\nconst ROLES_NAMED =\n 'query ($filters: [Filter!], $pagination: Pagination) { roles(filters: $filters, pagination: $pagination) { items { id name } } }'\n\n/** \"김○○ · warehouse:mutation\" - how a request is named on the card. */\nexport function requestLabel(request: any): string {\n const who = request?.requester?.name || request?.requester?.email || ''\n return [who, request?.category && request?.privilege ? `${request.category}:${request.privilege}` : ''].filter(Boolean).join(' · ')\n}\n\n/** The pending requests the said text names - by the requester's name or e-mail, or the privilege. Several stay several. */\nexport function requestsNamed(items: any[], said: string): { id: string; label: string }[] {\n const text = (said || '').trim().toLowerCase()\n if (!text) return []\n return (items || [])\n .filter(item => item?.state === 'PENDING')\n .filter(item => {\n if (item.id === said.trim()) return true\n const names = [item.requester?.name, item.requester?.email, `${item.category}:${item.privilege}`, requestLabel(item)]\n return names.some(name => typeof name === 'string' && name.toLowerCase().includes(text))\n })\n .map(item => ({ id: item.id, label: requestLabel(item) }))\n}\n\n/** A role named exactly - a near name is not taken. */\nexport function rolesNamed(items: any[], said: string): { id: string; label: string }[] {\n const text = (said || '').trim().toLowerCase()\n return (items || []).filter(item => typeof item?.name === 'string' && item.name.toLowerCase() === text).map(item => ({ id: item.id, label: item.name }))\n}\n\nconst DECISION_FIELDS: EntryField[] = [\n {\n name: 'request',\n type: 'reference',\n required: true,\n labelKey: 'access-request.field.request',\n description: \"the pending access request, by the requester's name and the privilege asked for\",\n reference: {\n query: PENDING_REQUESTS,\n variables: () => ({ filters: [{ name: 'state', operator: 'eq', value: 'PENDING' }], pagination: { page: 1, limit: 500 } }),\n recordsOf: (data, said) => requestsNamed(data?.accessRequests?.items ?? [], said)\n }\n },\n {\n name: 'decision',\n type: 'enum',\n required: true,\n values: ['GRANT', 'DENY'],\n labelKey: 'access-request.field.decision',\n valueKeyPrefix: 'access-request.decision.',\n description: 'GRANT puts the requester into a role; DENY refuses with a reason'\n },\n {\n name: 'role',\n type: 'reference',\n labelKey: 'access-request.field.granted role',\n description:\n 'the role to put the requester into, by its exact name - only for GRANT. Prefer the role with the fewest privileges that still holds the one asked for; the review names a smaller one when there is.',\n reference: {\n query: ROLES_NAMED,\n variables: (said: string) => ({ filters: [{ name: 'name', operator: 'eq', value: said.trim() }], pagination: { page: 1, limit: 20 } }),\n recordsOf: (data, said) => rolesNamed(data?.roles?.items ?? [], said)\n }\n },\n {\n name: 'reason',\n type: 'text',\n labelKey: 'access-request.field.decision reason',\n description: 'why - required for DENY. Never written by the model: ask the person.'\n }\n]\n\nconst idOf = (value: unknown) => (value && typeof value === 'object' && typeof (value as any).id === 'string' ? (value as any).id : undefined)\n\n/** A row as the decision door takes it. */\nexport function decisionOf(row: EntryRow) {\n const decision = row.values.decision === 'DENY' ? 'DENY' : 'GRANT'\n return {\n requestId: idOf(row.values.request),\n decision,\n roleId: decision === 'GRANT' ? idOf(row.values.role) : undefined,\n reason: typeof row.values.reason === 'string' ? row.values.reason : undefined\n }\n}\n\n/** The review's findings as the card's issues - the words are the screen's (`access-request.issue.<code>`). */\nexport function issuesOf(findings: { code: string; blocking: boolean; field?: string; params?: Record<string, unknown> }[]): EntryIssue[] {\n return findings.map(finding => ({\n ...(finding.field && finding.field !== 'decision' ? { field: finding.field } : {}),\n code: finding.code,\n messageKey: `access-request.issue.${finding.code}`,\n params: finding.params,\n blocking: finding.blocking\n }))\n}\n\nexport function accessDecisionEntryTarget(): EntryTargetDeclaration {\n return {\n key: AUTH_ACCESS_DECISION,\n labelKey: 'access-request.title.decision draft',\n description:\n 'one decision on a pending access request: which request, grant or deny, the role to grant with, and the reason to deny.',\n mutation: 'mutation ($decision: AccessDecisionInput!) { decideAccessRequest(decision: $decision) { id state } }',\n fields: DECISION_FIELDS,\n maxRows: 50,\n variables: row => ({ decision: decisionOf(row) }),\n appliedOf: (data, row) => ({ id: data?.decideAccessRequest?.id, label: (row.values.request as any)?.label }),\n\n check: async (rows, { state }) => {\n const { reviewDecision } = await import('../controllers/access-request.js')\n const out: EntryIssue[][] = []\n for (const row of rows) {\n const decision = decisionOf(row)\n out.push(decision.requestId ? issuesOf(await reviewDecision(decision as any, { state } as any)) : [])\n }\n return out\n }\n }\n}\n\nconst REQUEST_FIELDS: EntryField[] = [\n { name: 'category', type: 'string', required: true, labelKey: 'access-request.field.category', description: 'the category of the privilege, as the refusal named it' },\n { name: 'privilege', type: 'string', required: true, labelKey: 'access-request.field.privilege', description: 'the privilege, as the refusal named it - query, mutation, ...' },\n { name: 'reason', type: 'text', required: true, labelKey: 'access-request.field.reason', description: \"why the person needs it, in the person's words - ask them; never write it yourself\" }\n]\n\nexport function accessRequestEntryTarget(): EntryTargetDeclaration {\n return {\n key: AUTH_ACCESS_REQUEST,\n labelKey: 'access-request.title.request draft',\n description: 'one request for a privilege the person was refused for want of a role.',\n mutation: 'mutation ($request: NewAccessRequest!) { requestAccess(request: $request) { id count } }',\n fields: REQUEST_FIELDS,\n maxRows: 10,\n variables: row => ({ request: { category: row.values.category, privilege: row.values.privilege, reason: row.values.reason } }),\n appliedOf: data => ({ id: data?.requestAccess?.id }),\n\n /* What the door would refuse - asked before the card, so the person is not offered a request that cannot stand. */\n check: async (rows, { state }) => {\n const { judgePermission } = await import('../utils/check-permission.js')\n const out: EntryIssue[][] = []\n for (const row of rows) {\n const category = String(row.values.category ?? '')\n const privilege = String(row.values.privilege ?? '')\n if (!category || !privilege) {\n out.push([])\n continue\n }\n const judgement = await judgePermission({ category, privilege }, state?.user, state?.domain, state?.unsafeIP, state?.prohibitedPrivileges)\n out.push(\n judgement.permitted === true\n ? [{ code: 'already-holds', messageKey: 'access-request.issue.already-holds', blocking: true }]\n : judgement.because !== 'role'\n ? [{ code: 'not-by-role', messageKey: 'access-request.issue.not-by-role', params: { because: judgement.because }, blocking: true }]\n : []\n )\n }\n return out\n }\n }\n}\n\nexport function accessEntryTargets(): EntryTargetDeclaration[] {\n return [accessDecisionEntryTarget(), accessRequestEntryTarget()]\n}\n"]}
@@ -0,0 +1,82 @@
1
+ export type EntryFieldType = 'string' | 'text' | 'number' | 'integer' | 'date' | 'datetime' | 'boolean' | 'enum' | 'reference';
2
+ export interface EntryReference {
3
+ query: string;
4
+ variables(text: string): Record<string, unknown>;
5
+ recordsOf(data: any, text: string): {
6
+ id: string;
7
+ label?: string;
8
+ }[];
9
+ }
10
+ export interface EntryField {
11
+ name: string;
12
+ type: EntryFieldType;
13
+ required?: boolean;
14
+ values?: string[];
15
+ description?: string;
16
+ labelKey?: string;
17
+ total?: boolean;
18
+ valueKeyPrefix?: string;
19
+ reference?: EntryReference;
20
+ }
21
+ export interface EntryIssue {
22
+ field?: string;
23
+ code: string;
24
+ messageKey?: string;
25
+ params?: Record<string, unknown>;
26
+ blocking: boolean;
27
+ }
28
+ export interface EntryRow {
29
+ index: number;
30
+ values: Record<string, unknown>;
31
+ sources: unknown[];
32
+ issues: EntryIssue[];
33
+ }
34
+ export type EntryRecord = {
35
+ id: string;
36
+ } & Record<string, unknown>;
37
+ export interface EntryUpdate {
38
+ read: {
39
+ query: string;
40
+ variables(ids: string[]): Record<string, unknown>;
41
+ recordsOf(data: any, ids: string[]): EntryRecord[];
42
+ };
43
+ find?: {
44
+ query: string;
45
+ variables(criteria: Record<string, unknown>): Record<string, unknown>;
46
+ recordsOf(data: any, criteria: Record<string, unknown>): EntryRecord[];
47
+ criteria: EntryField[];
48
+ };
49
+ mutation: string;
50
+ variables(change: {
51
+ id: string;
52
+ record: EntryRecord;
53
+ values: Record<string, unknown>;
54
+ }): Record<string, unknown>;
55
+ labelOf?(record: EntryRecord): string;
56
+ fields?: string[];
57
+ /** Read-only checks of the changes, asked when the card is made and again when it is pressed - one issue list per row. */
58
+ check?(rows: {
59
+ id: string;
60
+ record?: EntryRecord;
61
+ values: Record<string, unknown>;
62
+ }[], context: {
63
+ state: any;
64
+ }): EntryIssue[][] | Promise<EntryIssue[][]>;
65
+ }
66
+ export interface EntryTargetDeclaration {
67
+ key: string;
68
+ labelKey: string;
69
+ description: string;
70
+ fields: EntryField[];
71
+ mutation: string;
72
+ variables(row: EntryRow): Record<string, unknown>;
73
+ appliedOf?(data: any, row: EntryRow): {
74
+ id?: string;
75
+ label?: string;
76
+ } | undefined;
77
+ maxRows?: number;
78
+ check?(rows: EntryRow[], context: {
79
+ state: any;
80
+ }): EntryIssue[][] | Promise<EntryIssue[][]>;
81
+ update?: EntryUpdate;
82
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=entry-target-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-target-shape.js","sourceRoot":"","sources":["../../server/ai-entry/entry-target-shape.ts"],"names":[],"mappings":"","sourcesContent":["/*\n * The shape of an AI 입력 (data entry) target - the same shape as `EntryTarget` in\n * @things-factory/ai-assistant (server/service/data-entry/entry-target.ts).\n *\n * Kept here as a structural copy (the same one organization keeps) so that this package declares its targets as plain data and does not\n * depend on the assistant at run time. An application that runs the assistant passes these declarations\n * to `registerEntryTarget`; the assistant validates them at boot.\n */\nexport type EntryFieldType = 'string' | 'text' | 'number' | 'integer' | 'date' | 'datetime' | 'boolean' | 'enum' | 'reference'\n\nexport interface EntryReference {\n query: string\n variables(text: string): Record<string, unknown>\n recordsOf(data: any, text: string): { id: string; label?: string }[]\n}\n\nexport interface EntryField {\n name: string\n type: EntryFieldType\n required?: boolean\n values?: string[]\n description?: string\n labelKey?: string\n total?: boolean\n valueKeyPrefix?: string\n reference?: EntryReference\n}\n\nexport interface EntryIssue {\n field?: string\n code: string\n messageKey?: string\n params?: Record<string, unknown>\n blocking: boolean\n}\n\nexport interface EntryRow {\n index: number\n values: Record<string, unknown>\n sources: unknown[]\n issues: EntryIssue[]\n}\n\nexport type EntryRecord = { id: string } & Record<string, unknown>\n\nexport interface EntryUpdate {\n read: { query: string; variables(ids: string[]): Record<string, unknown>; recordsOf(data: any, ids: string[]): EntryRecord[] }\n find?: {\n query: string\n variables(criteria: Record<string, unknown>): Record<string, unknown>\n recordsOf(data: any, criteria: Record<string, unknown>): EntryRecord[]\n criteria: EntryField[]\n }\n mutation: string\n variables(change: { id: string; record: EntryRecord; values: Record<string, unknown> }): Record<string, unknown>\n labelOf?(record: EntryRecord): string\n fields?: string[]\n /** Read-only checks of the changes, asked when the card is made and again when it is pressed - one issue list per row. */\n check?(rows: { id: string; record?: EntryRecord; values: Record<string, unknown> }[], context: { state: any }): EntryIssue[][] | Promise<EntryIssue[][]>\n}\n\nexport interface EntryTargetDeclaration {\n key: string\n labelKey: string\n description: string\n fields: EntryField[]\n mutation: string\n variables(row: EntryRow): Record<string, unknown>\n appliedOf?(data: any, row: EntryRow): { id?: string; label?: string } | undefined\n maxRows?: number\n check?(rows: EntryRow[], context: { state: any }): EntryIssue[][] | Promise<EntryIssue[][]>\n update?: EntryUpdate\n}\n"]}
@@ -0,0 +1,57 @@
1
+ import { EntityManager } from 'typeorm';
2
+ import { Domain, Refusal } from '@things-factory/shell';
3
+ import { AccessRequest } from '../service/access-request/access-request.js';
4
+ import { Role } from '../service/role/role.js';
5
+ import { AccessDecisionKind, DecisionFacts, DecisionIssue } from '../utils/access-request-review.js';
6
+ export type NewAccessRequestInput = {
7
+ category: string;
8
+ privilege: string;
9
+ operationName?: string;
10
+ reason?: string;
11
+ };
12
+ export type AccessDecisionInput = {
13
+ requestId: string;
14
+ decision: AccessDecisionKind;
15
+ roleId?: string;
16
+ reason?: string;
17
+ };
18
+ /** Refuses a decision whose review blocks it - the first blocking issue names the refusal, all codes go along. */
19
+ export declare class AccessDecisionRefused extends Refusal {
20
+ readonly issues: DecisionIssue[];
21
+ constructor(issues: DecisionIssue[]);
22
+ }
23
+ /**
24
+ * The caller asks for `category:privilege` in this domain.
25
+ *
26
+ * The server judges again whether a role would open it - the `because` a screen got with its refusal is not
27
+ * taken on trust (ADR-0103 decision 2). Asking again while one is pending adds to that one.
28
+ */
29
+ export declare function requestAccess(input: NewAccessRequestInput, context: ResolverContext): Promise<AccessRequest>;
30
+ /** How long a denial is shown back to the person and holds against the same words (questions Q9 · 5). */
31
+ export declare const ACCESS_REQUEST_DENIAL_DAYS = 7;
32
+ /** The latest denial of this person and privilege within ACCESS_REQUEST_DENIAL_DAYS, if any. */
33
+ export declare function latestDenial(domain: Domain, requesterId: string, category: string, privilege: string, now: Date, tx?: EntityManager): Promise<AccessRequest>;
34
+ /** The requester takes back their own pending request. */
35
+ export declare function withdrawAccessRequest(id: string, context: ResolverContext): Promise<AccessRequest>;
36
+ /**
37
+ * The people who can decide: domain owners and holders of the role-granting privilege - the same pair the
38
+ * decision door declares (`@privilege(category: "user", privilege: "mutation", domainOwnerGranted: true)`).
39
+ * The requester is never one of them.
40
+ */
41
+ export declare function findDeciderIds(domain: Domain, requesterId: string, tx?: EntityManager): Promise<string[]>;
42
+ /** Every assignable role that grants `category:privilege`, smallest first. */
43
+ export declare function rolesGranting(category: string, privilege: string, domain: Domain, tx?: EntityManager): Promise<{
44
+ id: string;
45
+ name: string;
46
+ privilegeCount: number;
47
+ role: Role;
48
+ }[]>;
49
+ /** The facts `reviewAccessDecision` reads - collected as the person deciding, inside this domain. */
50
+ export declare function collectDecisionFacts(input: AccessDecisionInput, context: ResolverContext): Promise<DecisionFacts>;
51
+ export declare function reviewDecision(input: AccessDecisionInput, context: ResolverContext): Promise<DecisionIssue[]>;
52
+ /**
53
+ * Grants or denies. The review runs again here - the card and the screen were shown a moment ago, and another
54
+ * person may have decided since. The state moves PENDING → decided in one conditional update, so two people
55
+ * deciding at once cannot both win.
56
+ */
57
+ export declare function decideAccessRequest(input: AccessDecisionInput, context: ResolverContext): Promise<AccessRequest>;