@astram/identity 0.16.0 → 0.17.1
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.
- package/dist/branch.object.d.ts +9 -1
- package/dist/branch.object.d.ts.map +1 -1
- package/dist/branch.object.js +14 -1
- package/dist/company-branch.object.d.ts +5 -4
- package/dist/company-branch.object.d.ts.map +1 -1
- package/dist/company-branch.object.js +6 -4
- package/dist/company-department.object.d.ts +6 -6
- package/dist/company-department.object.d.ts.map +1 -1
- package/dist/company-department.object.js +7 -6
- package/dist/department.object.d.ts +9 -1
- package/dist/department.object.d.ts.map +1 -1
- package/dist/department.object.js +14 -1
- package/dist/features/access/access-admin.access.d.ts.map +1 -1
- package/dist/features/access/access-admin.access.js +8 -1
- package/dist/features/access/index.d.ts +1 -0
- package/dist/features/access/index.d.ts.map +1 -1
- package/dist/features/access/index.js +10 -2
- package/dist/features/access/org-structure.access.d.ts +3 -0
- package/dist/features/access/org-structure.access.d.ts.map +1 -0
- package/dist/features/access/org-structure.access.js +41 -0
- package/dist/features/access/user-management.access.d.ts.map +1 -1
- package/dist/features/access/user-management.access.js +5 -0
- package/dist/features/audit/audit-read.service.d.ts +32 -1
- package/dist/features/audit/audit-read.service.d.ts.map +1 -1
- package/dist/features/audit/audit-read.service.js +67 -14
- package/dist/features/audit/auth-audit-sink.d.ts +19 -0
- package/dist/features/audit/auth-audit-sink.d.ts.map +1 -0
- package/dist/features/audit/auth-audit-sink.js +24 -0
- package/dist/features/audit/directory-label-resolver.d.ts +10 -1
- package/dist/features/audit/directory-label-resolver.d.ts.map +1 -1
- package/dist/features/audit/directory-label-resolver.js +28 -1
- package/dist/features/authz/grant-admin.controller.d.ts +4 -4
- package/dist/features/authz/grant-admin.controller.js +4 -4
- package/dist/features/authz/grant-admin.service.d.ts +9 -8
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +20 -24
- package/dist/features/authz/grant-ceiling.d.ts +35 -16
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +44 -29
- package/dist/features/authz/role-binding.d.ts +25 -24
- package/dist/features/authz/role-binding.d.ts.map +1 -1
- package/dist/features/authz/role-binding.js +31 -44
- package/dist/features/identity-feature.module.d.ts +0 -11
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +90 -1
- package/dist/features/membership/membership.controller.d.ts.map +1 -1
- package/dist/features/membership/membership.controller.js +20 -6
- package/dist/features/membership/membership.service.d.ts +16 -10
- package/dist/features/membership/membership.service.d.ts.map +1 -1
- package/dist/features/membership/membership.service.js +61 -21
- package/dist/features/membership/posting.service.d.ts +25 -6
- package/dist/features/membership/posting.service.d.ts.map +1 -1
- package/dist/features/membership/posting.service.js +63 -16
- package/dist/features/org-bootstrap/org-bootstrap-file.d.ts +6 -2
- package/dist/features/org-bootstrap/org-bootstrap-file.d.ts.map +1 -1
- package/dist/features/org-bootstrap/org-bootstrap-file.js +54 -11
- package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts +30 -11
- package/dist/features/org-bootstrap/org-bootstrap-plan.d.ts.map +1 -1
- package/dist/features/org-bootstrap/org-bootstrap-plan.js +172 -68
- package/dist/features/org-bootstrap/org-state.reader.d.ts.map +1 -1
- package/dist/features/org-bootstrap/org-state.reader.js +56 -49
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts +6 -1
- package/dist/features/org-bootstrap/run-org-bootstrap.d.ts.map +1 -1
- package/dist/features/org-bootstrap/run-org-bootstrap.js +81 -92
- package/dist/features/org-structure/org-catalog.controller.d.ts +29 -6
- package/dist/features/org-structure/org-catalog.controller.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.controller.js +105 -17
- package/dist/features/org-structure/org-catalog.service.d.ts +99 -18
- package/dist/features/org-structure/org-catalog.service.d.ts.map +1 -1
- package/dist/features/org-structure/org-catalog.service.js +327 -51
- package/dist/features/org-structure/org-unit-name.d.ts +31 -0
- package/dist/features/org-structure/org-unit-name.d.ts.map +1 -0
- package/dist/features/org-structure/org-unit-name.js +50 -0
- package/dist/features/role/role.controller.d.ts +28 -13
- package/dist/features/role/role.controller.d.ts.map +1 -1
- package/dist/features/role/role.controller.js +54 -14
- package/dist/features/role/role.service.d.ts +76 -21
- package/dist/features/role/role.service.d.ts.map +1 -1
- package/dist/features/role/role.service.js +204 -27
- package/dist/features/security-policy/security-policy-version-bump.d.ts +41 -0
- package/dist/features/security-policy/security-policy-version-bump.d.ts.map +1 -0
- package/dist/features/security-policy/security-policy-version-bump.js +65 -0
- package/dist/features/security-policy/security-policy.controller.d.ts +51 -0
- package/dist/features/security-policy/security-policy.controller.d.ts.map +1 -0
- package/dist/features/security-policy/security-policy.controller.js +121 -0
- package/dist/features/security-policy/security-policy.service.d.ts +94 -0
- package/dist/features/security-policy/security-policy.service.d.ts.map +1 -0
- package/dist/features/security-policy/security-policy.service.js +429 -0
- package/dist/features/service-account/service-account.service.d.ts +13 -3
- package/dist/features/service-account/service-account.service.d.ts.map +1 -1
- package/dist/features/service-account/service-account.service.js +48 -10
- package/dist/features/service-account/service-account.store.d.ts +11 -1
- package/dist/features/service-account/service-account.store.d.ts.map +1 -1
- package/dist/features/service-account/service-account.store.js +20 -1
- package/dist/features/user/signin-admin.tokens.d.ts +11 -0
- package/dist/features/user/signin-admin.tokens.d.ts.map +1 -0
- package/dist/features/user/signin-admin.tokens.js +13 -0
- package/dist/features/user/user-mfa-status.d.ts +38 -0
- package/dist/features/user/user-mfa-status.d.ts.map +1 -0
- package/dist/features/user/user-mfa-status.js +47 -0
- package/dist/features/user/user-signin-admin.service.d.ts +48 -0
- package/dist/features/user/user-signin-admin.service.d.ts.map +1 -0
- package/dist/features/user/user-signin-admin.service.js +232 -0
- package/dist/features/user/user.controller.d.ts +29 -2
- package/dist/features/user/user.controller.d.ts.map +1 -1
- package/dist/features/user/user.controller.js +71 -3
- package/dist/features/user/user.service.d.ts +40 -14
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +143 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +2 -0
- package/dist/role.object.d.ts +1 -1
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +6 -15
- package/dist/security-policy.object.d.ts +43 -0
- package/dist/security-policy.object.d.ts.map +1 -0
- package/dist/security-policy.object.js +107 -0
- package/dist/user.object.d.ts +6 -0
- package/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +13 -1
- package/migrations/1710000000103-AddUserLoginName.cjs +87 -0
- package/migrations/1710000000104-CreateSecurityPolicy.cjs +90 -0
- package/migrations/1710000000105-OwnDepartmentsAndBranches.cjs +327 -0
- package/migrations/1710000000106-OwnRolesPerCompany.cjs +622 -0
- package/package.json +4 -4
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PLAN_KIND_ORDER = void 0;
|
|
3
|
+
exports.rolePath = exports.PLAN_KIND_ORDER = exports.orgUnitDriftNote = void 0;
|
|
4
|
+
exports.orgUnitKey = orgUnitKey;
|
|
4
5
|
exports.planOrgBootstrap = planOrgBootstrap;
|
|
5
6
|
const org_names_js_1 = require("./org-names.js");
|
|
7
|
+
const org_unit_name_js_1 = require("../org-structure/org-unit-name.js");
|
|
8
|
+
const role_service_js_1 = require("../role/role.service.js");
|
|
9
|
+
/**
|
|
10
|
+
* G-012 (spec §5.A/§5.D, NAES review C): the comparison key of a department/branch name — the
|
|
11
|
+
* shared name rule (`normaliseOrgUnitName`: NFC, trim, collapse whitespace) then `nameKey` (case,
|
|
12
|
+
* accents). "Quality Assurance" in a file therefore matches "Quality Assurance" in the database.
|
|
13
|
+
* A name the rule refuses falls back to `nameKey` alone (the planner reports it as a conflict).
|
|
14
|
+
*/
|
|
15
|
+
function orgUnitKey(name) {
|
|
16
|
+
const n = (0, org_unit_name_js_1.normaliseOrgUnitName)(name);
|
|
17
|
+
return (0, org_names_js_1.nameKey)(n.ok ? n.value : name);
|
|
18
|
+
}
|
|
19
|
+
/** The drift note of a stored department/branch the file does not list (spec §5.D). */
|
|
20
|
+
const orgUnitDriftNote = (name) => `in the database, not in the file: ${name} — renamed or added outside the file?`;
|
|
21
|
+
exports.orgUnitDriftNote = orgUnitDriftNote;
|
|
6
22
|
exports.PLAN_KIND_ORDER = [
|
|
7
23
|
'company',
|
|
8
24
|
'department',
|
|
@@ -10,7 +26,6 @@ exports.PLAN_KIND_ORDER = [
|
|
|
10
26
|
'company_department',
|
|
11
27
|
'company_branch',
|
|
12
28
|
'role',
|
|
13
|
-
'role_claim',
|
|
14
29
|
'grant',
|
|
15
30
|
'user',
|
|
16
31
|
'membership',
|
|
@@ -18,7 +33,10 @@ exports.PLAN_KIND_ORDER = [
|
|
|
18
33
|
'credential',
|
|
19
34
|
'root_flag',
|
|
20
35
|
];
|
|
21
|
-
|
|
36
|
+
/** The plan path of a company's own role (G-013, spec §5.E): `<company path>#role:<name>`. */
|
|
37
|
+
const rolePath = (companyPath, name) => `${companyPath}#role:${name}`;
|
|
38
|
+
exports.rolePath = rolePath;
|
|
39
|
+
function planOrgBootstrap(file, state) {
|
|
22
40
|
const out = [];
|
|
23
41
|
const add = (kind, path, status, detail) => {
|
|
24
42
|
out.push(detail === undefined ? { kind, path, status } : { kind, path, status, detail });
|
|
@@ -37,8 +55,6 @@ function planOrgBootstrap(file, state, options) {
|
|
|
37
55
|
const inFileTenant = (path) => tenantKeys.has((0, org_names_js_1.nameKey)(path.split('/')[0] ?? ''));
|
|
38
56
|
// ---- companies, org units ----
|
|
39
57
|
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
58
|
const declaredCompanyKeys = new Set(companies.map((c) => (0, org_names_js_1.pathKey)(c.path)));
|
|
43
59
|
const ambiguous = new Set(state.ambiguousCompanies.map(org_names_js_1.pathKey));
|
|
44
60
|
for (const c of companies) {
|
|
@@ -53,71 +69,105 @@ function planOrgBootstrap(file, state, options) {
|
|
|
53
69
|
add('company', c.path, 'not-in-file');
|
|
54
70
|
}
|
|
55
71
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
// G-012: departments and branches are owned per company (no shared row), keyed
|
|
73
|
+
// `<company path>#dept:<name>` / `#branch:<name>`. A created row comes with its one junction row
|
|
74
|
+
// (`OrgCatalogService.ensure*`), so the junction entry mirrors the row's status.
|
|
75
|
+
const UNIT_KINDS = [
|
|
76
|
+
{
|
|
77
|
+
kind: 'department',
|
|
78
|
+
junction: 'company_department',
|
|
79
|
+
tag: 'dept',
|
|
80
|
+
declared: (c) => c.departments ?? [],
|
|
81
|
+
stored: (c) => c?.departments ?? [],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
kind: 'branch',
|
|
85
|
+
junction: 'company_branch',
|
|
86
|
+
tag: 'branch',
|
|
87
|
+
declared: (c) => c.branches ?? [],
|
|
88
|
+
stored: (c) => c?.branches ?? [],
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
/** Per company path key: the unit keys the file declares or the database holds. */
|
|
92
|
+
const unitsHere = new Map();
|
|
93
|
+
const unitsOf = (path) => {
|
|
94
|
+
let u = unitsHere.get((0, org_names_js_1.pathKey)(path));
|
|
95
|
+
if (u === undefined) {
|
|
96
|
+
u = { department: new Set(), branch: new Set() };
|
|
97
|
+
unitsHere.set((0, org_names_js_1.pathKey)(path), u);
|
|
70
98
|
}
|
|
71
|
-
|
|
99
|
+
return u;
|
|
100
|
+
};
|
|
72
101
|
for (const c of companies) {
|
|
73
102
|
const sc = stateCompany.get((0, org_names_js_1.pathKey)(c.path));
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
for (const k of UNIT_KINDS) {
|
|
104
|
+
const have = new Set(k.stored(sc).map(orgUnitKey));
|
|
105
|
+
for (const key of have)
|
|
106
|
+
unitsOf(c.path)[k.kind].add(key);
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
for (const name of k.declared(c.node)) {
|
|
109
|
+
const path = `${c.path}#${k.tag}:${name}`;
|
|
110
|
+
const n = (0, org_unit_name_js_1.normaliseOrgUnitName)(name);
|
|
111
|
+
if (!n.ok) {
|
|
112
|
+
add(k.kind, path, 'conflict', `${k.kind} ${JSON.stringify(name)}: ${(0, org_unit_name_js_1.orgUnitNameMessage)(k.kind, n.reason)}`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const key = (0, org_names_js_1.nameKey)(n.value);
|
|
116
|
+
if (seen.has(key))
|
|
117
|
+
continue;
|
|
118
|
+
seen.add(key);
|
|
119
|
+
unitsOf(c.path)[k.kind].add(key);
|
|
120
|
+
const status = have.has(key) ? 'unchanged' : 'create';
|
|
121
|
+
add(k.kind, path, status);
|
|
122
|
+
add(k.junction, path, status);
|
|
123
|
+
}
|
|
78
124
|
}
|
|
79
|
-
|
|
80
|
-
|
|
125
|
+
}
|
|
126
|
+
// Drift note (spec §5.D, NAES review #2): per company of the file's tenants, the stored units the
|
|
127
|
+
// file does not list. Informational only: the bootstrap is additive and never renames or deletes.
|
|
128
|
+
const declaredCompany = new Map(companies.map((c) => [(0, org_names_js_1.pathKey)(c.path), c.node]));
|
|
129
|
+
for (const sc of state.companies) {
|
|
130
|
+
if (!inFileTenant(sc.path))
|
|
131
|
+
continue;
|
|
132
|
+
const node = declaredCompany.get((0, org_names_js_1.pathKey)(sc.path));
|
|
133
|
+
for (const k of UNIT_KINDS) {
|
|
134
|
+
const declared = new Set((node ? k.declared(node) : []).map(orgUnitKey));
|
|
135
|
+
for (const name of k.stored(sc)) {
|
|
136
|
+
if (declared.has(orgUnitKey(name)))
|
|
137
|
+
continue;
|
|
138
|
+
add(k.kind, `${sc.path}#${k.tag}:${name}`, 'not-in-file', (0, exports.orgUnitDriftNote)(name));
|
|
139
|
+
}
|
|
81
140
|
}
|
|
82
141
|
}
|
|
83
142
|
// ---- roles and grants ----
|
|
84
|
-
|
|
85
|
-
|
|
143
|
+
// G-013 (spec §5.E): every role is OWNED by the company that declares it, keyed
|
|
144
|
+
// `<company path>#role:<name>` (name rule + `nameKey`, as the per-company unique index compares).
|
|
145
|
+
// A name another company uses is simply that company's own role: no cross-company conflict.
|
|
146
|
+
const stateRoles = new Map();
|
|
147
|
+
for (const r of state.roles)
|
|
148
|
+
stateRoles.set(`${(0, org_names_js_1.pathKey)(r.companyPath)}\u0000${orgUnitKey(r.name)}`, r);
|
|
149
|
+
const roleOf = (companyPath, name) => stateRoles.get(`${(0, org_names_js_1.pathKey)(companyPath)}\u0000${orgUnitKey(name)}`);
|
|
86
150
|
for (const c of companies) {
|
|
151
|
+
const seen = new Set();
|
|
87
152
|
for (const r of c.node.roles ?? []) {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
153
|
+
const path = (0, exports.rolePath)(c.path, r.name);
|
|
154
|
+
const n = (0, org_unit_name_js_1.normaliseOrgUnitName)(r.name);
|
|
155
|
+
if (!n.ok) {
|
|
156
|
+
add('role', path, 'conflict', `role ${JSON.stringify(r.name)}: ${(0, role_service_js_1.roleNameMessage)(n.reason)}`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const key = (0, org_names_js_1.nameKey)(n.value);
|
|
160
|
+
if (seen.has(key))
|
|
161
|
+
continue; // the parser refuses a duplicate; never planned twice
|
|
162
|
+
seen.add(key);
|
|
163
|
+
const existing = roleOf(c.path, r.name);
|
|
92
164
|
if (!existing) {
|
|
93
165
|
add('role', path, 'create');
|
|
94
166
|
for (const k of r.permissions)
|
|
95
167
|
add('grant', `${path}#${k}`, 'create');
|
|
96
168
|
continue;
|
|
97
169
|
}
|
|
98
|
-
|
|
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
|
-
}
|
|
170
|
+
add('role', path, 'unchanged');
|
|
121
171
|
const have = new Set(existing.permissions);
|
|
122
172
|
for (const k of r.permissions)
|
|
123
173
|
add('grant', `${path}#${k}`, have.has(k) ? 'unchanged' : 'create');
|
|
@@ -127,20 +177,50 @@ function planOrgBootstrap(file, state, options) {
|
|
|
127
177
|
add('grant', `${path}#${k}`, 'not-in-file');
|
|
128
178
|
}
|
|
129
179
|
}
|
|
180
|
+
// Drift note (as G-012's units): per company of the file's tenants, the stored roles the file
|
|
181
|
+
// does not declare on it. Informational only: the bootstrap is additive and never renames or
|
|
182
|
+
// deletes a role.
|
|
130
183
|
for (const r of state.roles) {
|
|
131
|
-
if (r.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
184
|
+
if (!inFileTenant(r.companyPath))
|
|
185
|
+
continue;
|
|
186
|
+
const node = declaredCompany.get((0, org_names_js_1.pathKey)(r.companyPath));
|
|
187
|
+
const declared = new Set((node?.roles ?? []).map((x) => orgUnitKey(x.name)));
|
|
188
|
+
if (declared.has(orgUnitKey(r.name)))
|
|
189
|
+
continue;
|
|
190
|
+
add('role', (0, exports.rolePath)(r.companyPath, r.name), 'not-in-file', (0, exports.orgUnitDriftNote)(r.name));
|
|
136
191
|
}
|
|
137
192
|
// ---- users, memberships, postings, credentials ----
|
|
138
193
|
const stateUser = new Map(state.users.map((u) => [(0, org_names_js_1.nameKey)(u.username), u]));
|
|
139
194
|
const emailHolder = new Map(state.emailHolders.map((h) => [(0, org_names_js_1.nameKey)(h.email), h.username]));
|
|
195
|
+
const loginNameHolder = new Map((state.loginNameHolders ?? []).map((h) => [h.loginName.toLowerCase(), h.username]));
|
|
140
196
|
const declaredUsers = new Set();
|
|
141
|
-
const postingKey = (p) => [(
|
|
197
|
+
const postingKey = (p) => [orgUnitKey(p.role), orgUnitKey(p.department ?? ''), orgUnitKey(p.branch ?? '')].join('|');
|
|
142
198
|
const postingPath = (user, company, p) => `user:${user}@${company} role:${p.role}${p.department ? ` dept:${p.department}` : ''}${p.branch ? ` branch:${p.branch}` : ''}`;
|
|
143
|
-
|
|
199
|
+
/**
|
|
200
|
+
* G-012: a posting resolves its department/branch within its OWN company (the posting check
|
|
201
|
+
* reads that company's junction rows). One declared only on an ancestor in the file, and not
|
|
202
|
+
* held by the company in the database, could never be posted: refuse it at plan time.
|
|
203
|
+
*/
|
|
204
|
+
const postingUnitProblem = (company, p) => {
|
|
205
|
+
const units = unitsOf(company);
|
|
206
|
+
for (const [kind, name] of [
|
|
207
|
+
['department', p.department],
|
|
208
|
+
['branch', p.branch],
|
|
209
|
+
]) {
|
|
210
|
+
if (name !== undefined && !units[kind].has(orgUnitKey(name))) {
|
|
211
|
+
return `${kind} "${name}" is not one of company "${company}"'s own ${kind === 'department' ? 'departments' : 'branches'}; declare it on that company`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
};
|
|
216
|
+
const addPostingCreate = (path, company, p) => {
|
|
217
|
+
const problem = postingUnitProblem(company, p);
|
|
218
|
+
if (problem === undefined)
|
|
219
|
+
add('posting', path, 'create');
|
|
220
|
+
else
|
|
221
|
+
add('posting', path, 'conflict', problem);
|
|
222
|
+
};
|
|
223
|
+
const planUser = (username, email, company, postings, isRoot, loginName) => {
|
|
144
224
|
declaredUsers.add((0, org_names_js_1.nameKey)(username));
|
|
145
225
|
const path = `user:${username}`;
|
|
146
226
|
const existing = stateUser.get((0, org_names_js_1.nameKey)(username));
|
|
@@ -150,11 +230,17 @@ function planOrgBootstrap(file, state, options) {
|
|
|
150
230
|
add('user', path, 'conflict', `email of "${username}" is already held by another user`);
|
|
151
231
|
return;
|
|
152
232
|
}
|
|
233
|
+
// Auth capabilities R1.10: login names are global; a new user cannot take a held one.
|
|
234
|
+
const nameHolder = loginName === undefined ? undefined : loginNameHolder.get(loginName);
|
|
235
|
+
if (nameHolder !== undefined && (0, org_names_js_1.nameKey)(nameHolder) !== (0, org_names_js_1.nameKey)(username)) {
|
|
236
|
+
add('user', path, 'conflict', `login name of "${username}" is already held by another user`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
153
239
|
add('user', path, 'create');
|
|
154
240
|
if (isRoot)
|
|
155
241
|
add('credential', path, 'create', 'password credential from the operator-supplied root password');
|
|
156
242
|
for (const p of postings)
|
|
157
|
-
|
|
243
|
+
addPostingCreate(postingPath(username, company, p), company, p);
|
|
158
244
|
if (isRoot)
|
|
159
245
|
add('root_flag', path, 'create');
|
|
160
246
|
return;
|
|
@@ -186,7 +272,21 @@ function planOrgBootstrap(file, state, options) {
|
|
|
186
272
|
add('user', path, 'drift', 'user exists with no membership; add it from the admin console, the bootstrap plans nothing for it');
|
|
187
273
|
return;
|
|
188
274
|
}
|
|
189
|
-
|
|
275
|
+
// Auth capabilities R1.7: an existing user's login name is never set or changed here (as its
|
|
276
|
+
// email): a stored one that differs from the file's, or none stored, is reported only.
|
|
277
|
+
const storedLoginName = typeof existing.loginName === 'string' && existing.loginName !== ''
|
|
278
|
+
? existing.loginName.toLowerCase()
|
|
279
|
+
: null;
|
|
280
|
+
const loginNameDiffers = loginName !== undefined && storedLoginName !== loginName;
|
|
281
|
+
const drifts = [
|
|
282
|
+
...(emailDiffers ? ['email differs from the file; not overwritten'] : []),
|
|
283
|
+
...(loginNameDiffers
|
|
284
|
+
? [
|
|
285
|
+
'login name differs from the file; not set or overwritten (set it from the admin console)',
|
|
286
|
+
]
|
|
287
|
+
: []),
|
|
288
|
+
];
|
|
289
|
+
add('user', path, drifts.length > 0 ? 'drift' : 'unchanged', drifts.length > 0 ? drifts.join('; ') : undefined);
|
|
190
290
|
// A root holds no membership: root is barred from the data plane, and its home company only
|
|
191
291
|
// anchors the credential (`ic_tenant_id`). Planning one would be denied and never converge.
|
|
192
292
|
const declaredKey = (0, org_names_js_1.pathKey)(company);
|
|
@@ -198,7 +298,7 @@ function planOrgBootstrap(file, state, options) {
|
|
|
198
298
|
else if (!membership) {
|
|
199
299
|
add('membership', mpath, 'create');
|
|
200
300
|
for (const p of postings)
|
|
201
|
-
|
|
301
|
+
addPostingCreate(postingPath(username, company, p), company, p);
|
|
202
302
|
}
|
|
203
303
|
else if (!membership.active) {
|
|
204
304
|
add('membership', mpath, 'drift', 'inactive membership; the bootstrap never activates it');
|
|
@@ -211,7 +311,11 @@ function planOrgBootstrap(file, state, options) {
|
|
|
211
311
|
add('membership', mpath, 'unchanged');
|
|
212
312
|
const have = new Set(membership.postings.map(postingKey));
|
|
213
313
|
for (const p of postings) {
|
|
214
|
-
|
|
314
|
+
const path = postingPath(username, company, p);
|
|
315
|
+
if (have.has(postingKey(p)))
|
|
316
|
+
add('posting', path, 'unchanged');
|
|
317
|
+
else
|
|
318
|
+
addPostingCreate(path, company, p);
|
|
215
319
|
}
|
|
216
320
|
}
|
|
217
321
|
if (isRoot) {
|
|
@@ -258,11 +362,11 @@ function planOrgBootstrap(file, state, options) {
|
|
|
258
362
|
};
|
|
259
363
|
const tenantPath = (tenantName, rel) => rel === undefined ? tenantName : `${tenantName}/${rel}`;
|
|
260
364
|
for (const r of file.roots)
|
|
261
|
-
planUser(r.username, r.email, r.homeCompany, [], true);
|
|
365
|
+
planUser(r.username, r.email, r.homeCompany, [], true, r.loginName);
|
|
262
366
|
for (const c of companies) {
|
|
263
367
|
for (const u of c.node.users ?? []) {
|
|
264
368
|
const company = u.company === undefined ? c.path : tenantPath(c.tenant, u.company);
|
|
265
|
-
planUser(u.username, u.email, company, u.postings ?? [], false);
|
|
369
|
+
planUser(u.username, u.email, company, u.postings ?? [], false, u.loginName);
|
|
266
370
|
}
|
|
267
371
|
}
|
|
268
372
|
for (const u of state.users) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"org-state.reader.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/org-state.reader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"org-state.reader.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/org-state.reader.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAuB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAErF,OAAO,KAAK,EAEV,QAAQ,EAMT,MAAM,yBAAyB,CAAC;AAEjC,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3D,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,UAAU,GAAG,OAAO,CAAC,CAAC;CAC/D;AAuCD,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,qBAAqB;IAElD,IAAI,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;CA2OtD"}
|
|
@@ -6,8 +6,9 @@ exports.OrgStateReader = void 0;
|
|
|
6
6
|
*
|
|
7
7
|
* Issues SELECTs only, every name bound as a parameter, database names taken from the injected
|
|
8
8
|
* `schemas` (never a literal). Scope: the file's tenants' subtrees (top-level companies matched by name,
|
|
9
|
-
* then `scope_closure`), plus the global lookups the planner's conflicts need (
|
|
10
|
-
*
|
|
9
|
+
* then `scope_closure`), plus the global lookups the planner's conflicts need (usernames, emails,
|
|
10
|
+
* credential state). Roles, like departments and branches, are read per owner company inside the
|
|
11
|
+
* subtrees (G-013: names are unique per company only). The row-to-state mapping is proven live.
|
|
11
12
|
* A query failure propagates: a partial state is never returned.
|
|
12
13
|
*/
|
|
13
14
|
const guard_1 = require("@astram/guard");
|
|
@@ -15,26 +16,19 @@ const org_names_js_1 = require("./org-names.js");
|
|
|
15
16
|
/** `?, ?, ?`; an empty list reads as `NULL` so the clause stays valid SQL and matches nothing. */
|
|
16
17
|
const marks = (n) => n === 0 ? 'NULL' : Array.from({ length: n }, () => '?').join(', ');
|
|
17
18
|
const str = (v) => (v === null || v === undefined ? '' : String(v));
|
|
18
|
-
const strOrNull = (v) => (v === null || v === undefined ? null : String(v));
|
|
19
19
|
function collect(file) {
|
|
20
20
|
const names = {
|
|
21
21
|
tenants: [],
|
|
22
|
-
roles: new Set(),
|
|
23
|
-
departments: new Set(),
|
|
24
|
-
branches: new Set(),
|
|
25
22
|
usernames: new Set(),
|
|
26
23
|
emails: new Set(),
|
|
24
|
+
loginNames: new Set(),
|
|
27
25
|
};
|
|
28
26
|
const walk = (c) => {
|
|
29
|
-
for (const d of c.departments ?? [])
|
|
30
|
-
names.departments.add(d);
|
|
31
|
-
for (const b of c.branches ?? [])
|
|
32
|
-
names.branches.add(b);
|
|
33
|
-
for (const r of c.roles ?? [])
|
|
34
|
-
names.roles.add(r.name);
|
|
35
27
|
for (const u of c.users ?? []) {
|
|
36
28
|
names.usernames.add(u.username);
|
|
37
29
|
names.emails.add(u.email);
|
|
30
|
+
if (u.loginName !== undefined)
|
|
31
|
+
names.loginNames.add(u.loginName);
|
|
38
32
|
}
|
|
39
33
|
for (const ch of c.children ?? [])
|
|
40
34
|
walk(ch);
|
|
@@ -46,14 +40,14 @@ function collect(file) {
|
|
|
46
40
|
for (const r of file.roots) {
|
|
47
41
|
names.usernames.add(r.username);
|
|
48
42
|
names.emails.add(r.email);
|
|
43
|
+
if (r.loginName !== undefined)
|
|
44
|
+
names.loginNames.add(r.loginName);
|
|
49
45
|
}
|
|
50
46
|
return {
|
|
51
47
|
tenants: names.tenants,
|
|
52
|
-
roles: [...names.roles],
|
|
53
|
-
departments: [...names.departments],
|
|
54
|
-
branches: [...names.branches],
|
|
55
48
|
usernames: [...names.usernames],
|
|
56
49
|
emails: [...names.emails],
|
|
50
|
+
loginNames: [...names.loginNames],
|
|
57
51
|
};
|
|
58
52
|
}
|
|
59
53
|
class OrgStateReader {
|
|
@@ -100,30 +94,22 @@ class OrgStateReader {
|
|
|
100
94
|
};
|
|
101
95
|
markAmbiguous(tenantRows, (r) => (0, org_names_js_1.nameKey)(str(r.c_name)), (r) => str(r.c_name));
|
|
102
96
|
markAmbiguous(subtreeRows.filter((r) => r.c_parent_uuid !== null && r.c_parent_uuid !== undefined), (r) => `${str(r.c_parent_uuid)}\u0000${(0, org_names_js_1.nameKey)(str(r.c_name))}`, (r) => pathOf(str(r.c_uuid)));
|
|
103
|
-
// 3.
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
`JOIN ${app}.departments d ON d.d_uuid = cd.cd_department_uuid WHERE cd.cd_company_uuid IN (${marks(subtreeUuids.length)})`, subtreeUuids);
|
|
108
|
-
const branchJunction = await query(`SELECT cb.cb_company_uuid, b.b_name FROM ${app}.company_branches cb ` +
|
|
109
|
-
`JOIN ${app}.branches b ON b.b_uuid = cb.cb_branch_uuid WHERE cb.cb_company_uuid IN (${marks(subtreeUuids.length)})`, subtreeUuids);
|
|
97
|
+
// 3. each company's OWN departments and branches (G-012: the owner column, migration 105;
|
|
98
|
+
// no row is shared between companies, so there is no global catalog to read)
|
|
99
|
+
const deptRows = await query(`SELECT d_company_uuid, d_name FROM ${app}.departments WHERE d_company_uuid IN (${marks(subtreeUuids.length)})`, subtreeUuids);
|
|
100
|
+
const branchRows = await query(`SELECT b_company_uuid, b_name FROM ${app}.branches WHERE b_company_uuid IN (${marks(subtreeUuids.length)})`, subtreeUuids);
|
|
110
101
|
const companies = new Map();
|
|
111
102
|
for (const uuid of subtreeUuids) {
|
|
112
103
|
companies.set(uuid, { path: pathOf(uuid), departments: [], branches: [] });
|
|
113
104
|
}
|
|
114
|
-
for (const r of
|
|
115
|
-
companies.get(str(r.
|
|
116
|
-
for (const r of
|
|
117
|
-
companies.get(str(r.
|
|
118
|
-
// 4.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`WHERE
|
|
122
|
-
`SELECT sc2.descendant_uuid FROM ${authz}.scope_closure sc2 WHERE sc2.ancestor_uuid IN (${marks(tenantUuids.length)}))) AS held_outside ` +
|
|
123
|
-
`FROM ${app}.roles r ` +
|
|
124
|
-
`LEFT JOIN ${authz}.scope_closure oc ON oc.descendant_uuid = r.r_owner_company_uuid ` +
|
|
125
|
-
`LEFT JOIN ${app}.companies t ON t.c_uuid = oc.ancestor_uuid AND t.c_parent_uuid IS NULL ` +
|
|
126
|
-
`WHERE r.r_name IN (${marks(names.roles.length)}) OR r.r_owner_company_uuid IN (${marks(subtreeUuids.length)})`, [...tenantUuids, ...names.roles, ...subtreeUuids]);
|
|
105
|
+
for (const r of deptRows)
|
|
106
|
+
companies.get(str(r.d_company_uuid))?.departments.push(str(r.d_name));
|
|
107
|
+
for (const r of branchRows)
|
|
108
|
+
companies.get(str(r.b_company_uuid))?.branches.push(str(r.b_name));
|
|
109
|
+
// 4. each company's OWN roles (G-013: the owner column; a name is unique per company only, so
|
|
110
|
+
// roles are read by (owner, name) and never merged by name across companies)
|
|
111
|
+
const roleRows = await query(`SELECT r_uuid, r_name, r_owner_company_uuid FROM ${app}.roles ` +
|
|
112
|
+
`WHERE r_owner_company_uuid IN (${marks(subtreeUuids.length)})`, subtreeUuids);
|
|
127
113
|
const roleUuids = roleRows.map((r) => str(r.r_uuid));
|
|
128
114
|
const grantRows = await query(`SELECT rp_role_uuid, rp_perm_key FROM ${authz}.role_permission WHERE rp_role_uuid IN (${marks(roleUuids.length)})`, roleUuids);
|
|
129
115
|
const grants = new Map();
|
|
@@ -132,19 +118,13 @@ class OrgStateReader {
|
|
|
132
118
|
list.push(str(g.rp_perm_key));
|
|
133
119
|
grants.set(str(g.rp_role_uuid), list);
|
|
134
120
|
}
|
|
135
|
-
const roles =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
name: str(r.r_name),
|
|
141
|
-
ownerTenant: strOrNull(r.owner_tenant),
|
|
142
|
-
heldOutside: Number(r.held_outside) === 1,
|
|
143
|
-
permissions: grants.get(str(r.r_uuid)) ?? [],
|
|
144
|
-
});
|
|
145
|
-
}
|
|
121
|
+
const roles = roleRows.map((r) => ({
|
|
122
|
+
companyPath: pathOf(str(r.r_owner_company_uuid)),
|
|
123
|
+
name: str(r.r_name),
|
|
124
|
+
permissions: grants.get(str(r.r_uuid)) ?? [],
|
|
125
|
+
}));
|
|
146
126
|
// 5. users (by username, or a member of the subtrees) with their credential
|
|
147
|
-
const userRows = await query(`SELECT u.u_uuid, u.u_username, u.u_email, u.u_is_platform_superuser, ic.ic_password_hash ` +
|
|
127
|
+
const userRows = await query(`SELECT u.u_uuid, u.u_username, u.u_email, u.u_login_name, u.u_is_platform_superuser, ic.ic_password_hash ` +
|
|
148
128
|
`FROM ${app}.users u LEFT JOIN ${identity}.identity_credential ic ON ic.ic_principal_id = u.u_uuid ` +
|
|
149
129
|
`WHERE u.u_username IN (${marks(names.usernames.length)}) ` +
|
|
150
130
|
`OR u.u_uuid IN (SELECT cm_user_uuid FROM ${app}.company_membership WHERE cm_company_uuid IN (${marks(subtreeUuids.length)}))`, [...names.usernames, ...subtreeUuids]);
|
|
@@ -156,6 +136,18 @@ class OrgStateReader {
|
|
|
156
136
|
`SELECT sc3.descendant_uuid FROM ${authz}.scope_closure sc3 WHERE sc3.ancestor_uuid IN (${marks(tenantUuids.length)}))`, [...userUuids, ...tenantUuids]);
|
|
157
137
|
const outsideUsers = new Set(outsideRows.map((r) => str(r.cm_user_uuid)));
|
|
158
138
|
const emailRows = await query(`SELECT u_email, u_username FROM ${app}.users WHERE u_email IN (${marks(names.emails.length)})`, names.emails);
|
|
139
|
+
// Auth capabilities R1.10: who holds the file's login names (read only when it declares any).
|
|
140
|
+
const loginNameRows = names.loginNames.length === 0
|
|
141
|
+
? null
|
|
142
|
+
: await query(`SELECT u_login_name, u_username FROM ${app}.users WHERE u_login_name IN (${marks(names.loginNames.length)})`, names.loginNames);
|
|
143
|
+
// Review round 1, item F: a legacy (non-email) sign-in hint equal to a file login name holds it
|
|
144
|
+
// too (the sign-in lookup would match two accounts). Compared under the column's collation; an
|
|
145
|
+
// orphan credential (no user row) is a holder with no username.
|
|
146
|
+
const hintRows = names.loginNames.length === 0
|
|
147
|
+
? []
|
|
148
|
+
: await query(`SELECT ic.ic_identity_hint AS hint, u.u_username FROM ${identity}.identity_credential ic ` +
|
|
149
|
+
`LEFT JOIN ${app}.users u ON u.u_uuid = ic.ic_principal_id ` +
|
|
150
|
+
`WHERE ic.ic_identity_hint IN (${marks(names.loginNames.length)})`, names.loginNames);
|
|
159
151
|
// 6. memberships and postings inside the subtrees
|
|
160
152
|
const memberRows = await query(`SELECT cm_uuid, cm_user_uuid, cm_company_uuid, cm_is_active FROM ${app}.company_membership ` +
|
|
161
153
|
`WHERE cm_company_uuid IN (${marks(subtreeUuids.length)})`, subtreeUuids);
|
|
@@ -205,6 +197,9 @@ class OrgStateReader {
|
|
|
205
197
|
users.set(id, {
|
|
206
198
|
username: str(u.u_username),
|
|
207
199
|
email: str(u.u_email),
|
|
200
|
+
...(u.u_login_name !== null && u.u_login_name !== undefined
|
|
201
|
+
? { loginName: str(u.u_login_name) }
|
|
202
|
+
: {}),
|
|
208
203
|
credential,
|
|
209
204
|
isPlatformSuperuser: Number(u.u_is_platform_superuser) === 1,
|
|
210
205
|
memberships: memberships.get(id) ?? [],
|
|
@@ -213,11 +208,23 @@ class OrgStateReader {
|
|
|
213
208
|
}
|
|
214
209
|
return {
|
|
215
210
|
companies: [...companies.values()],
|
|
216
|
-
departments: deptCatalog.map((r) => str(r.d_name)),
|
|
217
|
-
branches: branchCatalog.map((r) => str(r.b_name)),
|
|
218
211
|
roles,
|
|
219
212
|
users: [...users.values()],
|
|
220
213
|
emailHolders: emailRows.map((r) => ({ email: str(r.u_email), username: str(r.u_username) })),
|
|
214
|
+
...(loginNameRows === null
|
|
215
|
+
? {}
|
|
216
|
+
: {
|
|
217
|
+
loginNameHolders: [
|
|
218
|
+
...loginNameRows.map((r) => ({
|
|
219
|
+
loginName: str(r.u_login_name),
|
|
220
|
+
username: str(r.u_username),
|
|
221
|
+
})),
|
|
222
|
+
...hintRows.map((r) => ({
|
|
223
|
+
loginName: str(r.hint),
|
|
224
|
+
username: str(r.u_username),
|
|
225
|
+
})),
|
|
226
|
+
],
|
|
227
|
+
}),
|
|
221
228
|
ambiguousCompanies,
|
|
222
229
|
};
|
|
223
230
|
}
|
|
@@ -11,7 +11,10 @@ export interface RunOrgBootstrapOptions {
|
|
|
11
11
|
/** The human operator (audit `onBehalfOf`); else `ASTRAM_BOOTSTRAP_OPERATOR`. Required with `apply`. */
|
|
12
12
|
operator?: string;
|
|
13
13
|
env?: NodeJS.ProcessEnv;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* No longer needed (G-013): roles belong to the company that declares them, so there is nothing
|
|
16
|
+
* to claim. Accepted for compatibility; it changes nothing and prints {@link CLAIM_UNOWNED_NOTICE}.
|
|
17
|
+
*/
|
|
15
18
|
claimUnownedRoles?: boolean;
|
|
16
19
|
/** Issue a fresh set-password link to users whose credential is still locked. */
|
|
17
20
|
reissueLinks?: boolean;
|
|
@@ -57,5 +60,7 @@ export interface OrgBootstrapReport {
|
|
|
57
60
|
/** Uuid of every root in the file that exists after the run. */
|
|
58
61
|
rootIds: string[];
|
|
59
62
|
}
|
|
63
|
+
/** Printed when `claimUnownedRoles` is passed (G-013, spec §5.E): the flag now does nothing. */
|
|
64
|
+
export declare const CLAIM_UNOWNED_NOTICE = "--claim-unowned-roles is no longer needed: roles belong to the company that declares them.";
|
|
60
65
|
export declare function runOrgBootstrap(app: INestApplicationContext, opts: RunOrgBootstrapOptions): Promise<OrgBootstrapReport>;
|
|
61
66
|
//# sourceMappingURL=run-org-bootstrap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-org-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/run-org-bootstrap.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,uBAAuB,EAAQ,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"run-org-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/features/org-bootstrap/run-org-bootstrap.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,uBAAuB,EAAQ,MAAM,gBAAgB,CAAC;AA8BpE,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACvF,OAAO,EAIL,KAAK,OAAO,EAIb,MAAM,yBAAyB,CAAC;AAkBjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,MAAM,WAAW,sBAAsB;IACrC,yDAAyD;IACzD,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,qFAAqF;IACrF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wGAAwG;IACxG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,8FAA8F;IAC9F,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D;AAED,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,kDAAkD;IAClD,OAAO,EAAE,mBAAmB,CAAC;IAC7B,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAwCD,gGAAgG;AAChG,eAAO,MAAM,oBAAoB,+FAC6D,CAAC;AA8D/F,wBAAsB,eAAe,CACnC,GAAG,EAAE,uBAAuB,EAC5B,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAmqB7B"}
|