@astram/identity 0.13.0 → 0.14.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 (88) hide show
  1. package/dist/features/authz/grant-admin.service.d.ts +1 -1
  2. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  3. package/dist/features/authz/grant-admin.service.js +8 -0
  4. package/dist/features/authz/grant-ceiling.d.ts +11 -3
  5. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  6. package/dist/features/authz/grant-ceiling.js +20 -3
  7. package/dist/features/authz/record-authorization.d.ts +1 -1
  8. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  9. package/dist/features/authz/record-authorization.js +3 -2
  10. package/dist/features/company/company-hierarchy.service.d.ts +6 -0
  11. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  12. package/dist/features/company/company-hierarchy.service.js +19 -0
  13. package/dist/features/identity-feature.module.d.ts.map +1 -1
  14. package/dist/features/identity-feature.module.js +13 -0
  15. package/dist/features/membership/membership.service.d.ts +9 -0
  16. package/dist/features/membership/membership.service.d.ts.map +1 -1
  17. package/dist/features/membership/membership.service.js +40 -0
  18. package/dist/features/membership/posting.service.d.ts +18 -0
  19. package/dist/features/membership/posting.service.d.ts.map +1 -1
  20. package/dist/features/membership/posting.service.js +85 -27
  21. package/dist/features/org-bootstrap/org-bootstrap-error.d.ts +20 -0
  22. package/dist/features/org-bootstrap/org-bootstrap-error.d.ts.map +1 -0
  23. package/dist/features/org-bootstrap/org-bootstrap-error.js +24 -0
  24. package/dist/features/org-bootstrap/org-bootstrap-file.d.ts +58 -0
  25. package/dist/features/org-bootstrap/org-bootstrap-file.d.ts.map +1 -0
  26. package/dist/features/org-bootstrap/org-bootstrap-file.js +323 -0
  27. package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts +79 -0
  28. package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts.map +1 -0
  29. package/dist/features/org-bootstrap/org-bootstrap-plan.js +288 -0
  30. package/dist/features/org-bootstrap/org-bootstrap-runner.harness.d.ts +259 -0
  31. package/dist/features/org-bootstrap/org-bootstrap-runner.harness.d.ts.map +1 -0
  32. package/dist/features/org-bootstrap/org-bootstrap-runner.harness.js +604 -0
  33. package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.d.ts +24 -0
  34. package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.d.ts.map +1 -0
  35. package/dist/features/org-bootstrap/org-bootstrap-runner.mocks.js +39 -0
  36. package/dist/features/org-bootstrap/org-bootstrap.manifest.d.ts +19 -0
  37. package/dist/features/org-bootstrap/org-bootstrap.manifest.d.ts.map +1 -0
  38. package/dist/features/org-bootstrap/org-bootstrap.manifest.js +30 -0
  39. package/dist/features/org-bootstrap/org-bootstrap.tokens.d.ts +8 -0
  40. package/dist/features/org-bootstrap/org-bootstrap.tokens.d.ts.map +1 -0
  41. package/dist/features/org-bootstrap/org-bootstrap.tokens.js +10 -0
  42. package/dist/features/org-bootstrap/org-names.d.ts +12 -0
  43. package/dist/features/org-bootstrap/org-names.d.ts.map +1 -0
  44. package/dist/features/org-bootstrap/org-names.js +26 -0
  45. package/dist/features/org-bootstrap/org-state.reader.d.ts +15 -0
  46. package/dist/features/org-bootstrap/org-state.reader.d.ts.map +1 -0
  47. package/dist/features/org-bootstrap/org-state.reader.js +225 -0
  48. package/dist/features/org-bootstrap/root-password-source.d.ts +9 -0
  49. package/dist/features/org-bootstrap/root-password-source.d.ts.map +1 -0
  50. package/dist/features/org-bootstrap/root-password-source.js +57 -0
  51. package/dist/features/org-bootstrap/run-org-bootstrap.d.ts +42 -0
  52. package/dist/features/org-bootstrap/run-org-bootstrap.d.ts.map +1 -0
  53. package/dist/features/org-bootstrap/run-org-bootstrap.js +688 -0
  54. package/dist/features/org-bootstrap/set-password-links.d.ts +30 -0
  55. package/dist/features/org-bootstrap/set-password-links.d.ts.map +1 -0
  56. package/dist/features/org-bootstrap/set-password-links.js +55 -0
  57. package/dist/features/org-structure/org-catalog.service.d.ts +13 -0
  58. package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
  59. package/dist/features/org-structure/org-catalog.service.js +40 -0
  60. package/dist/features/role/role-owner-transition.d.ts +17 -0
  61. package/dist/features/role/role-owner-transition.d.ts.map +1 -0
  62. package/dist/features/role/role-owner-transition.js +23 -0
  63. package/dist/features/role/role.controller.d.ts +1 -1
  64. package/dist/features/role/role.controller.d.ts.map +1 -1
  65. package/dist/features/role/role.controller.js +6 -4
  66. package/dist/features/role/role.service.d.ts +8 -2
  67. package/dist/features/role/role.service.d.ts.map +1 -1
  68. package/dist/features/role/role.service.js +13 -3
  69. package/dist/features/user/credential-provisioner.token.d.ts +8 -0
  70. package/dist/features/user/credential-provisioner.token.d.ts.map +1 -0
  71. package/dist/features/user/credential-provisioner.token.js +10 -0
  72. package/dist/features/user/user.controller.d.ts +1 -3
  73. package/dist/features/user/user.controller.d.ts.map +1 -1
  74. package/dist/features/user/user.controller.js +2 -2
  75. package/dist/features/user/user.service.d.ts +28 -5
  76. package/dist/features/user/user.service.d.ts.map +1 -1
  77. package/dist/features/user/user.service.js +45 -12
  78. package/dist/index.d.ts +6 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +9 -1
  81. package/dist/internal.d.ts +2 -0
  82. package/dist/internal.d.ts.map +1 -1
  83. package/dist/internal.js +3 -1
  84. package/dist/role.object.d.ts +1 -0
  85. package/dist/role.object.d.ts.map +1 -1
  86. package/dist/role.object.js +8 -0
  87. package/migrations/1710000000101-AddRoleOwnerCompany.cjs +98 -0
  88. package/package.json +3 -3
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLAN_KIND_ORDER = void 0;
4
+ exports.planOrgBootstrap = planOrgBootstrap;
5
+ const org_names_js_1 = require("./org-names.js");
6
+ exports.PLAN_KIND_ORDER = [
7
+ 'company',
8
+ 'department',
9
+ 'branch',
10
+ 'company_department',
11
+ 'company_branch',
12
+ 'role',
13
+ 'role_claim',
14
+ 'grant',
15
+ 'user',
16
+ 'membership',
17
+ 'posting',
18
+ 'credential',
19
+ 'root_flag',
20
+ ];
21
+ function planOrgBootstrap(file, state, options) {
22
+ const out = [];
23
+ const add = (kind, path, status, detail) => {
24
+ out.push(detail === undefined ? { kind, path, status } : { kind, path, status, detail });
25
+ };
26
+ // ---- flatten the file (depth first, parent before children) ----
27
+ const companies = [];
28
+ const walk = (c, parent, tenant) => {
29
+ const path = parent === undefined ? c.name : `${parent}/${c.name}`;
30
+ companies.push({ path, tenant, node: c });
31
+ for (const child of c.children ?? [])
32
+ walk(child, path, tenant);
33
+ };
34
+ for (const t of file.tenants)
35
+ walk(t, undefined, t.name);
36
+ const tenantKeys = new Set(file.tenants.map((t) => (0, org_names_js_1.nameKey)(t.name)));
37
+ const inFileTenant = (path) => tenantKeys.has((0, org_names_js_1.nameKey)(path.split('/')[0] ?? ''));
38
+ // ---- companies, org units ----
39
+ const stateCompany = new Map(state.companies.map((c) => [(0, org_names_js_1.pathKey)(c.path), c]));
40
+ const stateDepts = new Set(state.departments.map(org_names_js_1.nameKey));
41
+ const stateBranches = new Set(state.branches.map(org_names_js_1.nameKey));
42
+ const declaredCompanyKeys = new Set(companies.map((c) => (0, org_names_js_1.pathKey)(c.path)));
43
+ const ambiguous = new Set(state.ambiguousCompanies.map(org_names_js_1.pathKey));
44
+ for (const c of companies) {
45
+ if (ambiguous.has((0, org_names_js_1.pathKey)(c.path))) {
46
+ add('company', c.path, 'conflict', `company "${c.path}" matches more than one existing company; the bootstrap never guesses`);
47
+ continue;
48
+ }
49
+ add('company', c.path, stateCompany.has((0, org_names_js_1.pathKey)(c.path)) ? 'unchanged' : 'create');
50
+ }
51
+ for (const c of state.companies) {
52
+ if (inFileTenant(c.path) && !declaredCompanyKeys.has((0, org_names_js_1.pathKey)(c.path))) {
53
+ add('company', c.path, 'not-in-file');
54
+ }
55
+ }
56
+ const seenDept = new Set();
57
+ const seenBranch = new Set();
58
+ for (const c of companies) {
59
+ for (const d of c.node.departments ?? []) {
60
+ if (seenDept.has((0, org_names_js_1.nameKey)(d)))
61
+ continue;
62
+ seenDept.add((0, org_names_js_1.nameKey)(d));
63
+ add('department', `dept:${d}`, stateDepts.has((0, org_names_js_1.nameKey)(d)) ? 'unchanged' : 'create');
64
+ }
65
+ for (const b of c.node.branches ?? []) {
66
+ if (seenBranch.has((0, org_names_js_1.nameKey)(b)))
67
+ continue;
68
+ seenBranch.add((0, org_names_js_1.nameKey)(b));
69
+ add('branch', `branch:${b}`, stateBranches.has((0, org_names_js_1.nameKey)(b)) ? 'unchanged' : 'create');
70
+ }
71
+ }
72
+ for (const c of companies) {
73
+ const sc = stateCompany.get((0, org_names_js_1.pathKey)(c.path));
74
+ const haveD = new Set((sc?.departments ?? []).map(org_names_js_1.nameKey));
75
+ const haveB = new Set((sc?.branches ?? []).map(org_names_js_1.nameKey));
76
+ for (const d of c.node.departments ?? []) {
77
+ add('company_department', `${c.path}#dept:${d}`, haveD.has((0, org_names_js_1.nameKey)(d)) ? 'unchanged' : 'create');
78
+ }
79
+ for (const b of c.node.branches ?? []) {
80
+ add('company_branch', `${c.path}#branch:${b}`, haveB.has((0, org_names_js_1.nameKey)(b)) ? 'unchanged' : 'create');
81
+ }
82
+ }
83
+ // ---- roles and grants ----
84
+ const stateRole = new Map(state.roles.map((r) => [(0, org_names_js_1.nameKey)(r.name), r]));
85
+ const declaredRoles = new Set();
86
+ for (const c of companies) {
87
+ for (const r of c.node.roles ?? []) {
88
+ const key = (0, org_names_js_1.nameKey)(r.name);
89
+ declaredRoles.add(key);
90
+ const path = `role:${r.name}`;
91
+ const existing = stateRole.get(key);
92
+ if (!existing) {
93
+ add('role', path, 'create');
94
+ for (const k of r.permissions)
95
+ add('grant', `${path}#${k}`, 'create');
96
+ continue;
97
+ }
98
+ if (existing.ownerTenant !== null && !tenantKeys.has((0, org_names_js_1.nameKey)(existing.ownerTenant))) {
99
+ add('role', path, 'conflict', `role "${r.name}" is owned by another tenant`);
100
+ continue;
101
+ }
102
+ if (existing.ownerTenant !== null && (0, org_names_js_1.nameKey)(existing.ownerTenant) !== (0, org_names_js_1.nameKey)(c.tenant)) {
103
+ add('role', path, 'conflict', `role "${r.name}" is owned by tenant "${existing.ownerTenant}", not by "${c.tenant}" which declares it`);
104
+ continue;
105
+ }
106
+ if (existing.ownerTenant === null) {
107
+ if (existing.heldOutside) {
108
+ add('role', path, 'conflict', `role "${r.name}" has no owner and is held outside the file's tenants`);
109
+ continue;
110
+ }
111
+ if (!options.claimUnownedRoles) {
112
+ add('role', path, 'conflict', `role "${r.name}" has no owner; pass --claim-unowned-roles to claim it`);
113
+ continue;
114
+ }
115
+ add('role', path, 'unchanged');
116
+ add('role_claim', path, 'create', `claim "${r.name}" for tenant ${c.tenant}`);
117
+ }
118
+ else {
119
+ add('role', path, 'unchanged');
120
+ }
121
+ const have = new Set(existing.permissions);
122
+ for (const k of r.permissions)
123
+ add('grant', `${path}#${k}`, have.has(k) ? 'unchanged' : 'create');
124
+ const want = new Set(r.permissions);
125
+ for (const k of existing.permissions)
126
+ if (!want.has(k))
127
+ add('grant', `${path}#${k}`, 'not-in-file');
128
+ }
129
+ }
130
+ for (const r of state.roles) {
131
+ if (r.ownerTenant !== null &&
132
+ tenantKeys.has((0, org_names_js_1.nameKey)(r.ownerTenant)) &&
133
+ !declaredRoles.has((0, org_names_js_1.nameKey)(r.name))) {
134
+ add('role', `role:${r.name}`, 'not-in-file');
135
+ }
136
+ }
137
+ // ---- users, memberships, postings, credentials ----
138
+ const stateUser = new Map(state.users.map((u) => [(0, org_names_js_1.nameKey)(u.username), u]));
139
+ const emailHolder = new Map(state.emailHolders.map((h) => [(0, org_names_js_1.nameKey)(h.email), h.username]));
140
+ const declaredUsers = new Set();
141
+ const postingKey = (p) => [(0, org_names_js_1.nameKey)(p.role), (0, org_names_js_1.nameKey)(p.department ?? ''), (0, org_names_js_1.nameKey)(p.branch ?? '')].join('|');
142
+ const postingPath = (user, company, p) => `user:${user}@${company} role:${p.role}${p.department ? ` dept:${p.department}` : ''}${p.branch ? ` branch:${p.branch}` : ''}`;
143
+ const planUser = (username, email, company, postings, isRoot) => {
144
+ declaredUsers.add((0, org_names_js_1.nameKey)(username));
145
+ const path = `user:${username}`;
146
+ const existing = stateUser.get((0, org_names_js_1.nameKey)(username));
147
+ if (!existing) {
148
+ const holder = emailHolder.get((0, org_names_js_1.nameKey)(email));
149
+ if (holder !== undefined && (0, org_names_js_1.nameKey)(holder) !== (0, org_names_js_1.nameKey)(username)) {
150
+ add('user', path, 'conflict', `email of "${username}" is already held by another user`);
151
+ return;
152
+ }
153
+ add('user', path, 'create');
154
+ if (isRoot)
155
+ add('credential', path, 'create', 'password credential from the operator-supplied root password');
156
+ for (const p of postings)
157
+ add('posting', postingPath(username, company, p), 'create');
158
+ if (isRoot)
159
+ add('root_flag', path, 'create');
160
+ return;
161
+ }
162
+ // R1-1: usernames are global, so a user the file names must provably be this file's user before
163
+ // anything is written for it. A non-root that holds a membership inside the file's tenants is; its
164
+ // differing email is only reported (OD-5). A root (which holds no membership) or a user with no
165
+ // membership in the file's tenants is tied to the file by its email alone, so a differing email
166
+ // means another account and the run refuses. A root holding any membership, or a non-root that
167
+ // belongs only to other tenants, is refused too.
168
+ const emailDiffers = (0, org_names_js_1.nameKey)(existing.email) !== (0, org_names_js_1.nameKey)(email);
169
+ const memberHere = existing.memberships.length > 0;
170
+ if (emailDiffers && (isRoot || !memberHere)) {
171
+ add('user', path, 'conflict', `user "${username}" already exists with a different email; the bootstrap never adopts or changes it`);
172
+ return;
173
+ }
174
+ const holdsAny = memberHere || existing.membershipsOutside;
175
+ if (isRoot && holdsAny) {
176
+ add('user', path, 'conflict', `user "${username}" already exists and holds memberships; a root holds none, so it cannot be made a root`);
177
+ return;
178
+ }
179
+ if (!isRoot && existing.memberships.length === 0 && existing.membershipsOutside) {
180
+ add('user', path, 'conflict', `user "${username}" already exists as a member of another tenant only; the bootstrap never adopts it`);
181
+ return;
182
+ }
183
+ if (!isRoot && !holdsAny) {
184
+ // N3: an account with no membership anywhere cannot be read by the anchored people principal
185
+ // (the user read wall), so adding it would fail mid-apply. Report it and plan nothing for it.
186
+ add('user', path, 'drift', 'user exists with no membership; add it from the admin console, the bootstrap plans nothing for it');
187
+ return;
188
+ }
189
+ add('user', path, emailDiffers ? 'drift' : 'unchanged', emailDiffers ? 'email differs from the file; not overwritten' : undefined);
190
+ // A root holds no membership: root is barred from the data plane, and its home company only
191
+ // anchors the credential (`ic_tenant_id`). Planning one would be denied and never converge.
192
+ const declaredKey = (0, org_names_js_1.pathKey)(company);
193
+ const membership = existing.memberships.find((m) => (0, org_names_js_1.pathKey)(m.companyPath) === declaredKey);
194
+ const mpath = `${path}@${company}`;
195
+ if (isRoot) {
196
+ // nothing to plan for memberships or postings
197
+ }
198
+ else if (!membership) {
199
+ add('membership', mpath, 'create');
200
+ for (const p of postings)
201
+ add('posting', postingPath(username, company, p), 'create');
202
+ }
203
+ else if (!membership.active) {
204
+ add('membership', mpath, 'drift', 'inactive membership; the bootstrap never activates it');
205
+ const have = new Set(membership.postings.map(postingKey));
206
+ for (const p of postings) {
207
+ add('posting', postingPath(username, company, p), have.has(postingKey(p)) ? 'unchanged' : 'drift', have.has(postingKey(p)) ? undefined : 'inactive membership; posting not added');
208
+ }
209
+ }
210
+ else {
211
+ add('membership', mpath, 'unchanged');
212
+ const have = new Set(membership.postings.map(postingKey));
213
+ for (const p of postings) {
214
+ add('posting', postingPath(username, company, p), have.has(postingKey(p)) ? 'unchanged' : 'create');
215
+ }
216
+ }
217
+ if (isRoot) {
218
+ if (existing.credential === 'none') {
219
+ add('credential', path, 'create', 'password credential from the operator-supplied root password');
220
+ }
221
+ else if (existing.credential === 'locked') {
222
+ add('credential', path, 'drift', 'credential is locked, not a password; the bootstrap does not replace it. Reissue a set-password link');
223
+ }
224
+ else {
225
+ add('credential', path, 'unchanged');
226
+ }
227
+ add('root_flag', path, existing.isPlatformSuperuser ? 'unchanged' : 'create');
228
+ }
229
+ else if (existing.credential === 'none' && existing.membershipsOutside) {
230
+ // N1: the account also reaches another tenant; this file's operator must not get its link.
231
+ add('credential', path, 'drift', 'no credential and the user also belongs to another tenant; none created (no link issued)');
232
+ }
233
+ else if (existing.credential === 'none' && emailDiffers) {
234
+ // The login hint would be the file's email, which the bootstrap does not adopt (R1-6).
235
+ add('credential', path, 'drift', 'no credential and the email differs from the file; none created (set the email first)');
236
+ }
237
+ else if (existing.credential === 'none') {
238
+ add('credential', path, 'create', 'locked credential; a set-password link is issued on apply');
239
+ }
240
+ // Memberships and postings in the file's companies that the file does not declare.
241
+ const declaredPostings = new Set(postings.map(postingKey));
242
+ for (const m of existing.memberships) {
243
+ if (!inFileTenant(m.companyPath))
244
+ continue;
245
+ if ((0, org_names_js_1.pathKey)(m.companyPath) === declaredKey) {
246
+ for (const p of m.postings) {
247
+ if (!declaredPostings.has(postingKey(p))) {
248
+ add('posting', postingPath(username, company, p), 'not-in-file');
249
+ }
250
+ }
251
+ }
252
+ else {
253
+ add('membership', `${path}@${m.companyPath}`, 'not-in-file');
254
+ for (const p of m.postings)
255
+ add('posting', postingPath(username, m.companyPath, p), 'not-in-file');
256
+ }
257
+ }
258
+ };
259
+ const tenantPath = (tenantName, rel) => rel === undefined ? tenantName : `${tenantName}/${rel}`;
260
+ for (const r of file.roots)
261
+ planUser(r.username, r.email, r.homeCompany, [], true);
262
+ for (const c of companies) {
263
+ for (const u of c.node.users ?? []) {
264
+ const company = u.company === undefined ? c.path : tenantPath(c.tenant, u.company);
265
+ planUser(u.username, u.email, company, u.postings ?? [], false);
266
+ }
267
+ }
268
+ for (const u of state.users) {
269
+ if (declaredUsers.has((0, org_names_js_1.nameKey)(u.username)))
270
+ continue;
271
+ const inside = u.memberships.filter((m) => inFileTenant(m.companyPath));
272
+ if (inside.length === 0)
273
+ continue;
274
+ const path = `user:${u.username}`;
275
+ add('user', path, 'not-in-file');
276
+ for (const m of inside) {
277
+ for (const p of m.postings) {
278
+ add('posting', postingPath(u.username, m.companyPath, p), 'not-in-file');
279
+ }
280
+ }
281
+ }
282
+ // R10 order: stable by kind.
283
+ const entries = out
284
+ .map((e, i) => ({ e, i }))
285
+ .sort((a, b) => exports.PLAN_KIND_ORDER.indexOf(a.e.kind) - exports.PLAN_KIND_ORDER.indexOf(b.e.kind) || a.i - b.i)
286
+ .map((x) => x.e);
287
+ return { entries, conflicts: entries.filter((e) => e.status === 'conflict') };
288
+ }
@@ -0,0 +1,259 @@
1
+ import type { Principal } from '@astram/core';
2
+ import { current } from './org-bootstrap-runner.mocks.js';
3
+ import type { OrgState } from './org-bootstrap-plan.js';
4
+ export declare const SCHEMAS: {
5
+ readonly namespace: "zzns";
6
+ readonly app: "zzns_app";
7
+ readonly identity: "zzns_identity";
8
+ readonly authz: "zzns_authz";
9
+ readonly audit: "zzns_audit";
10
+ readonly isDefault: false;
11
+ };
12
+ export declare const PW = "Zq-root-pass-1";
13
+ export declare const OPERATOR = "op.person@example.test";
14
+ export declare const BASE = "https://app.example.test/reset-password";
15
+ export declare const DEFAULT_DEV_BASE = "http://localhost:5173/reset-password";
16
+ export declare const LOCK_NAME = "astram_org_bootstrap:zzns_app";
17
+ export declare const ROOT_PW_VAR = "ASTRAM_BOOTSTRAP_ROOT_PASSWORD_ZROOT";
18
+ /** Env of a development/test run that would pass every refusal. */
19
+ export declare const TEST_ENV: Record<string, string>;
20
+ /** Env of a production run; the root password must be secret-ref resolved (the spec adds ROOT_PW_VAR to its hoisted secretRefs set). */
21
+ export declare const PROD_ENV: Record<string, string>;
22
+ /** Fresh-database file: 1 root, 2 companies, 1 dept, 1 branch, 3 roles, 3 non-root users. */
23
+ export declare const FILE_MAIN: {
24
+ version: number;
25
+ roots: {
26
+ username: string;
27
+ email: string;
28
+ homeCompany: string;
29
+ }[];
30
+ tenants: {
31
+ name: string;
32
+ departments: string[];
33
+ branches: string[];
34
+ roles: {
35
+ name: string;
36
+ permissions: string[];
37
+ }[];
38
+ users: ({
39
+ username: string;
40
+ email: string;
41
+ postings: {
42
+ role: string;
43
+ department: string;
44
+ branch: string;
45
+ }[];
46
+ } | {
47
+ username: string;
48
+ email: string;
49
+ postings?: never;
50
+ })[];
51
+ children: {
52
+ name: string;
53
+ roles: {
54
+ name: string;
55
+ permissions: never[];
56
+ }[];
57
+ users: {
58
+ username: string;
59
+ email: string;
60
+ postings: {
61
+ role: string;
62
+ }[];
63
+ }[];
64
+ }[];
65
+ }[];
66
+ };
67
+ /** Two tenants: used to prove `operationRoleIds` never reach across tenants. */
68
+ export declare const FILE_TWO_TENANTS: {
69
+ version: number;
70
+ roots: {
71
+ username: string;
72
+ email: string;
73
+ homeCompany: string;
74
+ }[];
75
+ tenants: {
76
+ name: string;
77
+ roles: {
78
+ name: string;
79
+ permissions: never[];
80
+ }[];
81
+ users: {
82
+ username: string;
83
+ email: string;
84
+ postings: {
85
+ role: string;
86
+ }[];
87
+ }[];
88
+ }[];
89
+ };
90
+ /** One tenant already holding everything: the state below makes every entry `unchanged`. */
91
+ export declare const FILE_MINI: {
92
+ version: number;
93
+ roots: {
94
+ username: string;
95
+ email: string;
96
+ homeCompany: string;
97
+ }[];
98
+ tenants: {
99
+ name: string;
100
+ roles: {
101
+ name: string;
102
+ permissions: string[];
103
+ }[];
104
+ users: {
105
+ username: string;
106
+ email: string;
107
+ postings: {
108
+ role: string;
109
+ }[];
110
+ }[];
111
+ }[];
112
+ };
113
+ /** A tenant with one user whose membership does not exist yet (for the "existing user" cases). */
114
+ export declare const FILE_DAVE: {
115
+ version: number;
116
+ roots: {
117
+ username: string;
118
+ email: string;
119
+ homeCompany: string;
120
+ }[];
121
+ tenants: {
122
+ name: string;
123
+ roles: {
124
+ name: string;
125
+ permissions: never[];
126
+ }[];
127
+ users: {
128
+ username: string;
129
+ email: string;
130
+ postings: {
131
+ role: string;
132
+ }[];
133
+ }[];
134
+ }[];
135
+ };
136
+ export declare const emptyState: () => OrgState;
137
+ type Cred = 'none' | 'locked' | 'usable';
138
+ /** The state that makes FILE_MINI entirely `unchanged`, alice's credential as given. */
139
+ export declare function stateMini(aliceCredential?: Cred): OrgState;
140
+ export interface Call {
141
+ seq: number;
142
+ service: string;
143
+ method: string;
144
+ args: unknown[];
145
+ /** The principal registered on the resolve context (request-scoped services), if any. */
146
+ principal: Principal | undefined;
147
+ /** The bootstrap principal passed as an explicit argument, if any. */
148
+ actor: Principal | undefined;
149
+ }
150
+ export interface Query {
151
+ conn: string;
152
+ sql: string;
153
+ params: unknown[];
154
+ }
155
+ export type Row = Record<string, unknown>;
156
+ export interface World {
157
+ app: FakeApp;
158
+ state: OrgState;
159
+ out: string[];
160
+ outFn: (line: string) => void;
161
+ events: string[];
162
+ calls: Call[];
163
+ queries: Query[];
164
+ transitions: unknown[][];
165
+ issued: {
166
+ principalId: string;
167
+ hash: string;
168
+ expiresAtMs: number;
169
+ }[];
170
+ invalidated: string[];
171
+ provisionedLocked: {
172
+ principalId: string;
173
+ hint: string;
174
+ tenantId: string;
175
+ }[];
176
+ provisionedPassword: {
177
+ principalId: string;
178
+ hint: string;
179
+ tenantId: string;
180
+ password: string;
181
+ }[];
182
+ conns: FakeConn[];
183
+ readerCalls: number;
184
+ lockMode: 'acquired' | 'busy';
185
+ failOn: ((service: string, method: string, args: unknown[]) => Error | undefined) | undefined;
186
+ failTransition: Error | undefined;
187
+ /** Throws for a matching statement (e.g. the audit INSERT), on whichever connection runs it. */
188
+ failQuery: ((connId: string, sql: string, params: unknown[]) => Error | undefined) | undefined;
189
+ /** Whether a posting outside the file's tenants holds the role being claimed (R1-8 re-check). */
190
+ roleHeldOutside: boolean;
191
+ id: (kind: string, name: string) => string;
192
+ tables: Record<string, Row[]>;
193
+ }
194
+ export interface FakeConn {
195
+ id: string;
196
+ released: boolean;
197
+ log: string[];
198
+ query: (sql: string, params?: unknown[]) => Promise<unknown>;
199
+ beginTransaction: () => Promise<void>;
200
+ commit: () => Promise<void>;
201
+ rollback: () => Promise<void>;
202
+ release: () => Promise<void>;
203
+ end: () => Promise<void>;
204
+ }
205
+ export { current };
206
+ export declare class FakeApp {
207
+ private readonly singletons;
208
+ private readonly scoped;
209
+ initCalls: number;
210
+ readonly requests: Map<object, {
211
+ principal?: Principal;
212
+ }>;
213
+ readonly resolved: {
214
+ token: unknown;
215
+ principal: Principal | undefined;
216
+ }[];
217
+ constructor(singletons: Map<unknown, unknown>, scoped: Map<unknown, (principal: Principal | undefined) => object>);
218
+ init(): Promise<this>;
219
+ get(token: unknown, _options?: unknown): unknown;
220
+ registerRequestByContextId(request: {
221
+ principal?: Principal;
222
+ }, contextId: object): void;
223
+ resolve(token: unknown, contextId: object, _options?: unknown): Promise<unknown>;
224
+ }
225
+ export interface WorldOptions {
226
+ /** `IDENTITY_AUTHZ_POOL`, imported from `./org-bootstrap.tokens.js` by the spec. */
227
+ poolToken: unknown;
228
+ /** false = IdentityFeatureModule is not wired: `app.get` / `app.resolve` find nothing. */
229
+ wired?: boolean;
230
+ state?: OrgState;
231
+ tables?: Record<string, Row[]>;
232
+ lockMode?: 'acquired' | 'busy';
233
+ memberships?: {
234
+ id: string;
235
+ userUuid: string;
236
+ companyUuid: string;
237
+ }[];
238
+ failOn?: World['failOn'];
239
+ }
240
+ export declare function makeWorld(options: WorldOptions): World;
241
+ export declare const sha256: (s: string) => string;
242
+ export declare const isWriteCall: (c: Call) => boolean;
243
+ export declare const writeCalls: (w: World) => Call[];
244
+ export declare const callsOf: (w: World, service: string, method: string) => Call[];
245
+ /** Every non-audit write the runner could make is absent. Lock and SELECT traffic is allowed. */
246
+ export declare function noWritesAtAll(w: World): string[];
247
+ /** Lock discipline: every GET_LOCK ran on a dedicated connection and was released on that one. */
248
+ export declare function lockProblems(w: World): string[];
249
+ /** Everything the run said or stored that must never carry a secret. Provisioner args excluded. */
250
+ export declare function visibleText(w: World, extra?: unknown[]): string;
251
+ /** The raw token of the link printed for `username` (a line with the name and `<base>?token=<hex>`). */
252
+ export declare function linkFor(w: World, username: string): {
253
+ raw: string;
254
+ line: string;
255
+ } | undefined;
256
+ export declare const linkLines: (w: World) => string[];
257
+ /** The audit rows (INSERTs into audit_log) naming `action`, from any connection. */
258
+ export declare const auditRowsFor: (w: World, action: string) => Query[];
259
+ //# sourceMappingURL=org-bootstrap-runner.harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-bootstrap-runner.harness.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/org-bootstrap-runner.harness.ts"],"names":[],"mappings":"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"}