@kernhq/module-hr 0.16.0 → 0.17.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.
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/models.d.ts +1 -0
- package/dist/contract/models.d.ts.map +1 -1
- package/dist/contract/models.js +15 -0
- package/dist/contract/models.js.map +1 -1
- package/dist/contract/permissions.d.ts +8 -0
- package/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +20 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/privacy.d.ts +860 -0
- package/dist/contract/privacy.d.ts.map +1 -0
- package/dist/contract/privacy.js +412 -0
- package/dist/contract/privacy.js.map +1 -0
- package/dist/contract/router.d.ts +815 -0
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +105 -0
- package/dist/contract/router.js.map +1 -1
- package/dist/server/router.d.ts +876 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/router.js +312 -2
- package/dist/server/router.js.map +1 -1
- package/dist/server/schema.d.ts +397 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/schema.js +109 -0
- package/dist/server/schema.js.map +1 -1
- package/dist/server/services/audit.d.ts +249 -0
- package/dist/server/services/audit.d.ts.map +1 -0
- package/dist/server/services/audit.js +230 -0
- package/dist/server/services/audit.js.map +1 -0
- package/dist/server/services/people.d.ts +53 -1
- package/dist/server/services/people.d.ts.map +1 -1
- package/dist/server/services/people.js +74 -1
- package/dist/server/services/people.js.map +1 -1
- package/dist/server/services/privacy.d.ts +514 -0
- package/dist/server/services/privacy.d.ts.map +1 -0
- package/dist/server/services/privacy.js +972 -0
- package/dist/server/services/privacy.js.map +1 -0
- package/migrations/0011_privacy.sql +77 -0
- package/migrations/meta/0011_snapshot.json +4450 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/contract/index.ts +1 -0
- package/src/contract/models.ts +15 -0
- package/src/contract/permissions.ts +22 -0
- package/src/contract/privacy.ts +452 -0
- package/src/contract/router.ts +121 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/contract/privacy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiDvB;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;;;EAiBzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;;;;;;;;;iBAStB,CAAA;AACF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAA;AAErD,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;iBAY9B,CAAA;AACF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAErE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;iBAgB5B,CAAA;AACF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAMjE,0FAA0F;AAC1F,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;EAgBvB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD,yEAAyE;AACzE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;iBAM3B,CAAA;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,cAAc;;;;;;EAWzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAE3D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAO1B,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAE7D;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;EAqBxB,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAEzD;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsBxB,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAA;AAMzD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB;;;;EAAkC,CAAA;AACjE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAEnE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;iBAuB1B,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAM7D,qGAAqG;AACrG,eAAO,MAAM,kBAAkB;;;;;;;;iBAQ7B,CAAA;AACF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAEnE,gFAAgF;AAChF,eAAO,MAAM,eAAe;;;;;;;iBAO1B,CAAA;AACF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAE7D,gGAAgG;AAChG,eAAO,MAAM,gBAAgB;;;;iBAI3B,CAAA;AAEF,uDAAuD;AACvD,eAAO,MAAM,eAAe;;;;;;iBAW1B,CAAA;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;iBAShC,CAAA;AACF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAEzE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmD9B,CAAA;AACF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { Timestamp, WorkspaceId } from '@kernhq/contracts';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ApprovalDecision, ApprovalRequest, ApprovalStep, Delegation } from './approvals.js';
|
|
4
|
+
import { AttendanceDay, Punch, Regularization } from './attendance.js';
|
|
5
|
+
import { LeaveLedgerEntry, LeaveRequest, LeaveType } from './leave.js';
|
|
6
|
+
import { Employment, IsoDate, OfficeAssignment, Person, PersonDocument, PersonSensitive } from './models.js';
|
|
7
|
+
import { ResolvedPolicy } from './policies.js';
|
|
8
|
+
/**
|
|
9
|
+
* Subject access, erasure and retention — the three things a data-protection law asks a personnel
|
|
10
|
+
* system for, and the one part of HR whose whole job is to say what it did.
|
|
11
|
+
*
|
|
12
|
+
* Three positions hold this file together, and each of them is a decision the rest of the module
|
|
13
|
+
* already made somewhere else:
|
|
14
|
+
*
|
|
15
|
+
* - **Erasure is redaction, never deletion.** `leave_ledger` is append-only because a balance is the
|
|
16
|
+
* sum of it and nothing else; `punches` is append-only because an attendance record somebody can
|
|
17
|
+
* quietly rewrite is worth nothing in the dispute it exists for; `employments` is effective-dated
|
|
18
|
+
* so March is still answerable in June. Deleting an erased person's rows would break all three and
|
|
19
|
+
* would answer a payroll audit with "she left, so we deleted her file". So erasure clears the
|
|
20
|
+
* columns that identify a person and leaves every record that a wage, an entitlement or an
|
|
21
|
+
* authorisation was computed from. Afterwards the ledger still has somebody to attribute a day
|
|
22
|
+
* to, and nobody has a name.
|
|
23
|
+
* - **It says what it kept, per class, and why.** The `kept` half of the report is not a courtesy:
|
|
24
|
+
* an erasure that silently retains is the same failure as an export that silently omits, and the
|
|
25
|
+
* only difference is which of the two the subject finds out about later.
|
|
26
|
+
* - **Nothing here is a capability.** A workspace that could switch privacy off would be a workspace
|
|
27
|
+
* that stopped honouring subject requests, which fails the capability registry's own second rule —
|
|
28
|
+
* a switch must be reversible without destroying data, and this one would not even be a switch
|
|
29
|
+
* anybody should be offered. One permission key, `hr.privacy.manage`, gates all four procedures,
|
|
30
|
+
* and it ships in the same change as them.
|
|
31
|
+
*/
|
|
32
|
+
const ws = { workspaceId: WorkspaceId };
|
|
33
|
+
// =====================================================================================
|
|
34
|
+
// retention
|
|
35
|
+
// =====================================================================================
|
|
36
|
+
/**
|
|
37
|
+
* A horizon in days, or null.
|
|
38
|
+
*
|
|
39
|
+
* **Null is the shipped value for every class and it means "keep indefinitely".** "Seven years" is a
|
|
40
|
+
* fact about one country and one document class, not about the world, so no number here is a
|
|
41
|
+
* default: the suggested figures live in the help text on the settings screen, beside the sentence
|
|
42
|
+
* saying Kern gives no legal advice. The workspace owner sets them, and where their legal entities
|
|
43
|
+
* differ they set them per entity — which this shape cannot yet express, and says so below.
|
|
44
|
+
*/
|
|
45
|
+
const RetentionDays = z.number().int().min(1).max(36_500).nullable().default(null);
|
|
46
|
+
/**
|
|
47
|
+
* The classes retention is expressed in.
|
|
48
|
+
*
|
|
49
|
+
* They follow the redact/retain split rather than the table list, because that is the split a
|
|
50
|
+
* retention decision is actually made in: "how long do we keep the evidence behind a day sheet" is
|
|
51
|
+
* one question whatever tables it lands in.
|
|
52
|
+
*/
|
|
53
|
+
export const RetentionClass = z.enum([
|
|
54
|
+
/** Where a punch happened, on what device, and what was typed on it. */
|
|
55
|
+
'punchDetail',
|
|
56
|
+
/** The punch rows themselves — the evidence behind a day sheet. */
|
|
57
|
+
'punches',
|
|
58
|
+
/** The derived day sheet: hours, overtime, lateness. This is pay. */
|
|
59
|
+
'attendanceDays',
|
|
60
|
+
/** Ledger movements and the requests behind them. Also pay. */
|
|
61
|
+
'leave',
|
|
62
|
+
/** The audit trail. At the horizon the values go and the rows stay — see `privacy.erase`. */
|
|
63
|
+
'personHistory',
|
|
64
|
+
/** Contracts, payslips, identity scans. */
|
|
65
|
+
'personDocuments',
|
|
66
|
+
/** People who have left, and have not been erased. */
|
|
67
|
+
'terminatedPeople',
|
|
68
|
+
/** Who read somebody's identity, birth date or bank details. Itself personal data. */
|
|
69
|
+
'sensitiveAccessLog',
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* How long HR keeps each class, per workspace.
|
|
73
|
+
*
|
|
74
|
+
* A table rather than module settings, and the reason is mechanical: `core.settings.setModule`
|
|
75
|
+
* validates the merged blob against the module's declared zod schema and a zod object strips what it
|
|
76
|
+
* does not declare — so a retention key stored beside `country` and `employeeNumberPrefix` would be
|
|
77
|
+
* deleted by the next unrelated settings write, silently, from a screen that never mentions
|
|
78
|
+
* retention. Extending `HrSettings` instead is the other honest route and is a change to a shared
|
|
79
|
+
* shape; this one keeps the numbers where nothing can quietly drop them.
|
|
80
|
+
*
|
|
81
|
+
* Workspace-wide for now. The honest end state is per legal entity — a Dutch entity and a Turkish
|
|
82
|
+
* one have different obligations, which is exactly why `periods` is already per legal entity — and
|
|
83
|
+
* the row carries a jsonb config so that move is an added column rather than a new table.
|
|
84
|
+
*/
|
|
85
|
+
export const HrRetention = z.object({
|
|
86
|
+
punchDetail: RetentionDays,
|
|
87
|
+
punches: RetentionDays,
|
|
88
|
+
attendanceDays: RetentionDays,
|
|
89
|
+
leave: RetentionDays,
|
|
90
|
+
personHistory: RetentionDays,
|
|
91
|
+
personDocuments: RetentionDays,
|
|
92
|
+
terminatedPeople: RetentionDays,
|
|
93
|
+
sensitiveAccessLog: RetentionDays,
|
|
94
|
+
});
|
|
95
|
+
/** One class, its horizon, and what has already passed it. */
|
|
96
|
+
export const RetentionClassState = z.object({
|
|
97
|
+
class: RetentionClass,
|
|
98
|
+
/** Null means "kept indefinitely", which is what every class ships as. */
|
|
99
|
+
days: z.number().int().nullable(),
|
|
100
|
+
/**
|
|
101
|
+
* How many rows are already older than the horizon — the dry run, asked for with `withCounts`.
|
|
102
|
+
*
|
|
103
|
+
* Null when no horizon is set (nothing to be past) or when the caller did not ask for counts. A
|
|
104
|
+
* retention screen that cannot say what a sweep would touch before it runs is a data-loss button
|
|
105
|
+
* with no confirmation, so this is the number that belongs beside every horizon on it.
|
|
106
|
+
*/
|
|
107
|
+
dueNow: z.number().int().nullable(),
|
|
108
|
+
});
|
|
109
|
+
export const RetentionSettings = z.object({
|
|
110
|
+
...ws,
|
|
111
|
+
classes: z.array(RetentionClassState),
|
|
112
|
+
updatedAt: Timestamp.nullable(),
|
|
113
|
+
updatedBy: z.uuid().nullable(),
|
|
114
|
+
/**
|
|
115
|
+
* Whether anything in HR deletes on these horizons yet. **It is false, and saying so is the
|
|
116
|
+
* point.**
|
|
117
|
+
*
|
|
118
|
+
* The horizons are read in two places today: here, to count what has passed one, and by
|
|
119
|
+
* `privacy.erase`, to say under which horizon each surviving class was kept. No timed sweep acts
|
|
120
|
+
* on them. An unattended job that prunes personnel records is the one act in this module that
|
|
121
|
+
* cannot be undone by re-running anything, so it ships off, with a dry run and a per-run report
|
|
122
|
+
* naming every person it touched — and until it exists this field must not claim otherwise.
|
|
123
|
+
*/
|
|
124
|
+
sweepEnabled: z.literal(false),
|
|
125
|
+
});
|
|
126
|
+
// =====================================================================================
|
|
127
|
+
// erasure
|
|
128
|
+
// =====================================================================================
|
|
129
|
+
/** The units erasure reports in. One per group of columns that survive or go together. */
|
|
130
|
+
export const ErasureClass = z.enum([
|
|
131
|
+
'identity',
|
|
132
|
+
'sensitive',
|
|
133
|
+
'history',
|
|
134
|
+
'documents',
|
|
135
|
+
'punches',
|
|
136
|
+
'attendance',
|
|
137
|
+
'leaveRequests',
|
|
138
|
+
'leaveLedger',
|
|
139
|
+
'employment',
|
|
140
|
+
'officeAssignments',
|
|
141
|
+
'approvals',
|
|
142
|
+
'approvalDecisions',
|
|
143
|
+
'regularizations',
|
|
144
|
+
'delegations',
|
|
145
|
+
'headship',
|
|
146
|
+
]);
|
|
147
|
+
/** A class the erasure cleared, and exactly which columns it cleared. */
|
|
148
|
+
export const ErasureRedaction = z.object({
|
|
149
|
+
class: ErasureClass,
|
|
150
|
+
table: z.string(),
|
|
151
|
+
/** Rows that still had something to clear. Zero on a replay, which is what makes it replayable. */
|
|
152
|
+
rows: z.number().int(),
|
|
153
|
+
columns: z.array(z.string()),
|
|
154
|
+
});
|
|
155
|
+
/**
|
|
156
|
+
* Why a record survived an erasure.
|
|
157
|
+
*
|
|
158
|
+
* An enum rather than a sentence: a reason composed on the server is English for every reader, and
|
|
159
|
+
* this one is read by the person who asked to be forgotten. The client renders it.
|
|
160
|
+
*/
|
|
161
|
+
export const RetentionBasis = z.enum([
|
|
162
|
+
/** A wage, an entitlement or the hours behind one. Statutory wherever Kern ships a country pack. */
|
|
163
|
+
'payRecord',
|
|
164
|
+
/** What changed, when, and who authorised it. Erasing the trail and erasing the data differ. */
|
|
165
|
+
'auditTrail',
|
|
166
|
+
/** The workspace set a horizon for this class and it has not passed. */
|
|
167
|
+
'retentionHorizon',
|
|
168
|
+
/** HR cannot remove it: the bytes belong to another module. See `caveats`. */
|
|
169
|
+
'notRemovable',
|
|
170
|
+
/** It is somebody else's record. Erasing A is not authority to rewrite what A did to B's row. */
|
|
171
|
+
'anotherPersonsRecord',
|
|
172
|
+
]);
|
|
173
|
+
export const ErasureRetained = z.object({
|
|
174
|
+
class: ErasureClass,
|
|
175
|
+
table: z.string(),
|
|
176
|
+
rows: z.number().int(),
|
|
177
|
+
basis: RetentionBasis,
|
|
178
|
+
/** The horizon in force for the retention class covering this, when the workspace set one. */
|
|
179
|
+
retentionDays: z.number().int().nullable(),
|
|
180
|
+
});
|
|
181
|
+
/**
|
|
182
|
+
* Things the response has to state in words, as keys the client renders.
|
|
183
|
+
*
|
|
184
|
+
* Each one is a promise this module would otherwise be making and not keeping.
|
|
185
|
+
*/
|
|
186
|
+
export const ErasureCaveat = z.enum([
|
|
187
|
+
/**
|
|
188
|
+
* `person_documents` rows and their files both survive. `core.files.get` is the only file
|
|
189
|
+
* procedure a module can reach — there is no `files.delete` — so an erasure claiming to remove
|
|
190
|
+
* employee documents would null a row and leave the passport scan in the bucket. The rows stay
|
|
191
|
+
* because they are also the only remaining record of which files those are.
|
|
192
|
+
*/
|
|
193
|
+
'documentFilesRemain',
|
|
194
|
+
/** The profile photo pointer was cleared; the object behind it is in `filesRemaining`. */
|
|
195
|
+
'photoFileOrphaned',
|
|
196
|
+
/** Sick notes attached to leave requests, same reason and same list. */
|
|
197
|
+
'leaveDocumentFilesRemain',
|
|
198
|
+
/** The national identity number was kept, because the caller asked for it to be. */
|
|
199
|
+
'nationalIdKeptForAudit',
|
|
200
|
+
/**
|
|
201
|
+
* History rows where this person was the **actor** on somebody else's record keep their values.
|
|
202
|
+
* Erasing A is not authority to rewrite the trail of what A did to B.
|
|
203
|
+
*/
|
|
204
|
+
'actorHistoryKept',
|
|
205
|
+
/** At least one attendance day or punch sits in a locked period and was left exactly as it is. */
|
|
206
|
+
'lockedPeriodUntouched',
|
|
207
|
+
]);
|
|
208
|
+
/**
|
|
209
|
+
* What an erasure did, or would do.
|
|
210
|
+
*
|
|
211
|
+
* The same shape either way, because a preview computed differently from the thing it previews is a
|
|
212
|
+
* preview that eventually lies — the rule `accrual.preview` is written under.
|
|
213
|
+
*/
|
|
214
|
+
export const ErasureReport = z.object({
|
|
215
|
+
...ws,
|
|
216
|
+
personId: z.uuid(),
|
|
217
|
+
/** True when nothing was written. See `privacy.erase` for why this is what the default gives you. */
|
|
218
|
+
dryRun: z.boolean(),
|
|
219
|
+
/**
|
|
220
|
+
* When this person was erased. Null on a dry run of a person who has not been.
|
|
221
|
+
*
|
|
222
|
+
* On a replay it carries the **first** erasure's timestamp: the second run finds nothing left to
|
|
223
|
+
* clear, reports zero rows everywhere, and does not restamp the tombstone.
|
|
224
|
+
*/
|
|
225
|
+
erasedAt: Timestamp.nullable(),
|
|
226
|
+
/** The pseudonym the directory shows now. Never a name, never empty — `displayName` is not null. */
|
|
227
|
+
displayName: z.string(),
|
|
228
|
+
redacted: z.array(ErasureRedaction),
|
|
229
|
+
kept: z.array(ErasureRetained),
|
|
230
|
+
caveats: z.array(ErasureCaveat),
|
|
231
|
+
/**
|
|
232
|
+
* File objects in core's storage that this erasure could not delete, and which are recorded on the
|
|
233
|
+
* person row so a later release can finish the job rather than losing them.
|
|
234
|
+
*/
|
|
235
|
+
filesRemaining: z.array(z.uuid()),
|
|
236
|
+
});
|
|
237
|
+
// =====================================================================================
|
|
238
|
+
// the sensitive read log
|
|
239
|
+
// =====================================================================================
|
|
240
|
+
/**
|
|
241
|
+
* How a sensitive record was reached.
|
|
242
|
+
*
|
|
243
|
+
* Derived from the principal, never taken from the caller — a client-supplied value is one the
|
|
244
|
+
* reader controls, and this log exists to say what happened rather than what the reader claimed.
|
|
245
|
+
* `export` is the exception and is not reachable from a request: it is passed by the one server-side
|
|
246
|
+
* path that takes a bulk copy of a record, which is `privacy.subjectAccess`.
|
|
247
|
+
*
|
|
248
|
+
* There is no `mcp` and no `service`, and both omissions are the same rule rather than an oversight.
|
|
249
|
+
* Core's MCP proxy forwards a plain bearer token, so an assistant's read arrives as an ordinary
|
|
250
|
+
* `kind: 'user'` principal that nothing here can honestly tell from a browser; and a service
|
|
251
|
+
* principal carries no user id at all, so the read is refused before a row is built. A value nothing
|
|
252
|
+
* can ever write is the same lie as a permission key nothing asks about. See `services/audit.ts`.
|
|
253
|
+
*/
|
|
254
|
+
export const SensitiveAccessVia = z.enum(['ui', 'api', 'export']);
|
|
255
|
+
/**
|
|
256
|
+
* One read of somebody's identity, birth date or bank details.
|
|
257
|
+
*
|
|
258
|
+
* Written in the same transaction as the read, in HR's own schema. `mod_core.activity_events` also
|
|
259
|
+
* gets a best-effort copy so the workspace's single audit console shows that HR sensitive data was
|
|
260
|
+
* touched — but it cannot be the record, for two reasons: it is read behind `core.audit.view`,
|
|
261
|
+
* which owners and admins hold by default and `hr.person.view_sensitive` deliberately nobody does,
|
|
262
|
+
* so publishing the read there inverts the model the sensitive split exists to create; and the
|
|
263
|
+
* cross-service call is allowed to fail, which is right for a notification and wrong for evidence.
|
|
264
|
+
* A subject-access response that answers "who looked at my data" out of a log with silent holes is
|
|
265
|
+
* worse than one that says it cannot answer.
|
|
266
|
+
*/
|
|
267
|
+
export const SensitiveAccess = z.object({
|
|
268
|
+
id: z.uuid(),
|
|
269
|
+
/** Whose record was read. */
|
|
270
|
+
personId: z.uuid(),
|
|
271
|
+
/**
|
|
272
|
+
* The account the disclosure is recorded against. Never null: a reader nobody can be named for is
|
|
273
|
+
* refused before the record is loaded, because "somebody read your bank details" is not an answer
|
|
274
|
+
* to give a subject.
|
|
275
|
+
*/
|
|
276
|
+
actorUserId: z.uuid(),
|
|
277
|
+
/** The reader's own HR record, when they have one. Plenty of accounts are not employees. */
|
|
278
|
+
actorPersonId: z.uuid().nullable(),
|
|
279
|
+
/**
|
|
280
|
+
* Only the fields that actually came back with a value.
|
|
281
|
+
*
|
|
282
|
+
* Logging "read the record" when three of the four were empty overstates what happened, and this
|
|
283
|
+
* log is itself read by the subject.
|
|
284
|
+
*/
|
|
285
|
+
fields: z.array(z.string()),
|
|
286
|
+
/** Why, when the caller said. Shown to the subject in their own bundle — nowhere else. */
|
|
287
|
+
purpose: z.string().max(500).nullable(),
|
|
288
|
+
via: SensitiveAccessVia,
|
|
289
|
+
at: Timestamp,
|
|
290
|
+
});
|
|
291
|
+
// =====================================================================================
|
|
292
|
+
// the subject-access bundle
|
|
293
|
+
// =====================================================================================
|
|
294
|
+
/** `person_history`, with the values. Redacting it in a subject's own bundle defeats the purpose. */
|
|
295
|
+
export const PersonHistoryEntry = z.object({
|
|
296
|
+
id: z.uuid(),
|
|
297
|
+
field: z.string(),
|
|
298
|
+
from: z.unknown(),
|
|
299
|
+
to: z.unknown(),
|
|
300
|
+
at: Timestamp,
|
|
301
|
+
actorId: z.uuid().nullable(),
|
|
302
|
+
source: z.string(),
|
|
303
|
+
});
|
|
304
|
+
/** A request exploded into days — what the balance was actually charged for. */
|
|
305
|
+
export const LeaveRequestDay = z.object({
|
|
306
|
+
id: z.uuid(),
|
|
307
|
+
requestId: z.uuid(),
|
|
308
|
+
date: IsoDate,
|
|
309
|
+
fraction: z.number(),
|
|
310
|
+
counted: z.boolean(),
|
|
311
|
+
status: z.string(),
|
|
312
|
+
});
|
|
313
|
+
/** A section that hit the row cap. Named, with its numbers, so nothing is short by surprise. */
|
|
314
|
+
export const BundleTruncation = z.object({
|
|
315
|
+
section: z.string(),
|
|
316
|
+
returned: z.number().int(),
|
|
317
|
+
cap: z.number().int(),
|
|
318
|
+
});
|
|
319
|
+
/** Something this bundle does not contain, and why. */
|
|
320
|
+
export const BundleExclusion = z.object({
|
|
321
|
+
section: z.string(),
|
|
322
|
+
reason: z.enum([
|
|
323
|
+
/**
|
|
324
|
+
* The bytes of a file. HR holds the metadata and the id; the object lives in core's storage and
|
|
325
|
+
* a module can only ask for a download URL for one file at a time.
|
|
326
|
+
*/
|
|
327
|
+
'fileContentsNotExportable',
|
|
328
|
+
/** The caller asked for the fast path. */
|
|
329
|
+
'notRequested',
|
|
330
|
+
]),
|
|
331
|
+
});
|
|
332
|
+
export const SubjectAccessManifest = z.object({
|
|
333
|
+
...ws,
|
|
334
|
+
personId: z.uuid(),
|
|
335
|
+
generatedAt: Timestamp,
|
|
336
|
+
generatedBy: z.uuid().nullable(),
|
|
337
|
+
/** From `packageVersion(import.meta.url)`, never a literal — the literals drifted for months. */
|
|
338
|
+
moduleVersion: z.string(),
|
|
339
|
+
truncated: z.array(BundleTruncation),
|
|
340
|
+
excluded: z.array(BundleExclusion),
|
|
341
|
+
});
|
|
342
|
+
/**
|
|
343
|
+
* Everything HR holds about one person, including the parts they cannot reach through the product.
|
|
344
|
+
*
|
|
345
|
+
* Structured JSON rather than a zip of files, and one person at a time. Both are deliberate:
|
|
346
|
+
*
|
|
347
|
+
* - **The rows are not the problem; documents are.** Five years of four punches a day is about nine
|
|
348
|
+
* thousand rows and two to three megabytes — large for a response and bounded. A contract PDF,
|
|
349
|
+
* three payslips a year and an identity scan has no upper bound at all, which is why the file
|
|
350
|
+
* bytes are named in `manifest.excluded` instead of being streamed through a request handler.
|
|
351
|
+
* Every section is capped and every cap that bites is named in `manifest.truncated`.
|
|
352
|
+
* - **There is no workspace-wide variant, and there will not be.** Five hundred people is two and a
|
|
353
|
+
* half million rows with decrypted bank details in flight. `personId` is required and there is no
|
|
354
|
+
* filter.
|
|
355
|
+
*
|
|
356
|
+
* `sensitive` is decrypted here, which makes this the most dangerous object the module produces —
|
|
357
|
+
* so producing it writes a `sensitive_access_log` row with `via: 'export'` before it is returned. An
|
|
358
|
+
* export is a bulk read of that record, not a smaller one.
|
|
359
|
+
*/
|
|
360
|
+
export const SubjectAccessBundle = z.object({
|
|
361
|
+
manifest: SubjectAccessManifest,
|
|
362
|
+
/**
|
|
363
|
+
* The whole row, not the directory card.
|
|
364
|
+
*
|
|
365
|
+
* `forViewer` narrows `personalEmail`, `phone`, `hiredOn` and `terminatedOn` for a reader who may
|
|
366
|
+
* not see somebody's personnel record. Those four are the subject's own; they are not narrowed
|
|
367
|
+
* here.
|
|
368
|
+
*/
|
|
369
|
+
person: Person,
|
|
370
|
+
sensitive: PersonSensitive,
|
|
371
|
+
employment: z.array(Employment),
|
|
372
|
+
offices: z.array(OfficeAssignment),
|
|
373
|
+
history: z.array(PersonHistoryEntry),
|
|
374
|
+
/** Metadata and file ids. The bytes are named in `manifest.excluded`. */
|
|
375
|
+
documents: z.array(PersonDocument),
|
|
376
|
+
leave: z.object({
|
|
377
|
+
types: z.array(LeaveType),
|
|
378
|
+
requests: z.array(LeaveRequest),
|
|
379
|
+
days: z.array(LeaveRequestDay),
|
|
380
|
+
ledger: z.array(LeaveLedgerEntry),
|
|
381
|
+
/** The running balance down the ledger, so "why is my balance this number" is answered here. */
|
|
382
|
+
closingBalanceMinutes: z.number().int(),
|
|
383
|
+
}),
|
|
384
|
+
attendance: z.object({
|
|
385
|
+
punches: z.array(Punch),
|
|
386
|
+
days: z.array(AttendanceDay),
|
|
387
|
+
}),
|
|
388
|
+
regularizations: z.array(Regularization),
|
|
389
|
+
approvals: z.object({
|
|
390
|
+
/** Requests this person raised. */
|
|
391
|
+
raised: z.array(ApprovalRequest),
|
|
392
|
+
/** Steps they were named on as an approver, with the decisions filed against them. */
|
|
393
|
+
approverOn: z.array(ApprovalStep),
|
|
394
|
+
/** Their own decisions, wherever they were filed. */
|
|
395
|
+
decisions: z.array(ApprovalDecision),
|
|
396
|
+
}),
|
|
397
|
+
/** Both directions: what they handed over, and what was handed to them. */
|
|
398
|
+
delegations: z.object({
|
|
399
|
+
given: z.array(Delegation),
|
|
400
|
+
received: z.array(Delegation),
|
|
401
|
+
}),
|
|
402
|
+
/**
|
|
403
|
+
* The accrual, overtime and rounding policy resolved for this person today, with the rung that
|
|
404
|
+
* answered. Included whatever the `leave_accrual` capability is set to: it is the subject's own
|
|
405
|
+
* data, and "why does she accrue differently from her team" is the commonest follow-up a subject
|
|
406
|
+
* access request produces.
|
|
407
|
+
*/
|
|
408
|
+
policiesInForce: z.array(ResolvedPolicy),
|
|
409
|
+
/** Who read their identity, birth date or bank details. A bundle without it is incomplete. */
|
|
410
|
+
accessLog: z.array(SensitiveAccess),
|
|
411
|
+
});
|
|
412
|
+
//# sourceMappingURL=privacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/contract/privacy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC5F,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC5G,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE,CAAA;AAEvC,wFAAwF;AACxF,YAAY;AACZ,wFAAwF;AAExF;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAElF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC;IACnC,wEAAwE;IACxE,aAAa;IACb,mEAAmE;IACnE,SAAS;IACT,qEAAqE;IACrE,gBAAgB;IAChB,+DAA+D;IAC/D,OAAO;IACP,6FAA6F;IAC7F,eAAe;IACf,2CAA2C;IAC3C,iBAAiB;IACjB,sDAAsD;IACtD,kBAAkB;IAClB,sFAAsF;IACtF,oBAAoB;CACrB,CAAC,CAAA;AAGF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,WAAW,EAAE,aAAa;IAC1B,OAAO,EAAE,aAAa;IACtB,cAAc,EAAE,aAAa;IAC7B,KAAK,EAAE,aAAa;IACpB,aAAa,EAAE,aAAa;IAC5B,eAAe,EAAE,aAAa;IAC9B,gBAAgB,EAAE,aAAa;IAC/B,kBAAkB,EAAE,aAAa;CAClC,CAAC,CAAA;AAGF,8DAA8D;AAC9D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,cAAc;IACrB,0EAA0E;IAC1E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACpC,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,GAAG,EAAE;IACL,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;IACrC,SAAS,EAAE,SAAS,CAAC,QAAQ,EAAE;IAC/B,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC9B;;;;;;;;;OASG;IACH,YAAY,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;CAC/B,CAAC,CAAA;AAGF,wFAAwF;AACxF,UAAU;AACV,wFAAwF;AAExF,0FAA0F;AAC1F,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC;IACjC,UAAU;IACV,WAAW;IACX,SAAS;IACT,WAAW;IACX,SAAS;IACT,YAAY;IACZ,eAAe;IACf,aAAa;IACb,YAAY;IACZ,mBAAmB;IACnB,WAAW;IACX,mBAAmB;IACnB,iBAAiB;IACjB,aAAa;IACb,UAAU;CACX,CAAC,CAAA;AAGF,yEAAyE;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,mGAAmG;IACnG,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACtB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;CAC7B,CAAC,CAAA;AAGF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC;IACnC,oGAAoG;IACpG,WAAW;IACX,gGAAgG;IAChG,YAAY;IACZ,wEAAwE;IACxE,kBAAkB;IAClB,8EAA8E;IAC9E,cAAc;IACd,iGAAiG;IACjG,sBAAsB;CACvB,CAAC,CAAA;AAGF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACtB,KAAK,EAAE,cAAc;IACrB,8FAA8F;IAC9F,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CAC3C,CAAC,CAAA;AAGF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC;IAClC;;;;;OAKG;IACH,qBAAqB;IACrB,0FAA0F;IAC1F,mBAAmB;IACnB,wEAAwE;IACxE,0BAA0B;IAC1B,oFAAoF;IACpF,wBAAwB;IACxB;;;OAGG;IACH,kBAAkB;IAClB,kGAAkG;IAClG,uBAAuB;CACxB,CAAC,CAAA;AAGF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,GAAG,EAAE;IACL,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;IAClB,qGAAqG;IACrG,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;IACnB;;;;;OAKG;IACH,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE;IAC9B,oGAAoG;IACpG,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;IAC9B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC;IAC/B;;;OAGG;IACH,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;CAClC,CAAC,CAAA;AAGF,wFAAwF;AACxF,yBAAyB;AACzB,wFAAwF;AAExF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;AAGjE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;IACZ,6BAA6B;IAC7B,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;IAClB;;;;OAIG;IACH,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE;IACrB,4FAA4F;IAC5F,aAAa,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAClC;;;;;OAKG;IACH,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC3B,0FAA0F;IAC1F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IACvC,GAAG,EAAE,kBAAkB;IACvB,EAAE,EAAE,SAAS;CACd,CAAC,CAAA;AAGF,wFAAwF;AACxF,4BAA4B;AAC5B,wFAAwF;AAExF,qGAAqG;AACrG,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;IACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;IACjB,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE;IACf,EAAE,EAAE,SAAS;IACb,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAA;AAGF,gFAAgF;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE;IACZ,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE;IACnB,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAA;AAGF,gGAAgG;AAChG,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC1B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CACtB,CAAC,CAAA;AAEF,uDAAuD;AACvD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;QACb;;;WAGG;QACH,2BAA2B;QAC3B,0CAA0C;QAC1C,cAAc;KACf,CAAC;CACH,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,GAAG,EAAE;IACL,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE;IAClB,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAChC,iGAAiG;IACjG,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;IACpC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;CACnC,CAAC,CAAA;AAGF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,QAAQ,EAAE,qBAAqB;IAC/B;;;;;;OAMG;IACH,MAAM,EAAE,MAAM;IACd,SAAS,EAAE,eAAe;IAC1B,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;IAC/B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC;IACpC,yEAAyE;IACzE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC;QACzB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QAC/B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;QAC9B,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;QACjC,gGAAgG;QAChG,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;KACxC,CAAC;IACF,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;QACvB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC;KAC7B,CAAC;IACF,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;QAClB,mCAAmC;QACnC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;QAChC,sFAAsF;QACtF,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;QACjC,qDAAqD;QACrD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;KACrC,CAAC;IACF,2EAA2E;IAC3E,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;QAC1B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;KAC9B,CAAC;IACF;;;;;OAKG;IACH,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC;IACxC,8FAA8F;IAC9F,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;CACpC,CAAC,CAAA"}
|