@substrat-run/engine-absence 0.4.7 → 0.5.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/index.d.ts +3 -100
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -46
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +443 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +167 -0
- package/dist/operations.js.map +1 -0
- package/dist/schemas.d.ts +333 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +154 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +9 -5
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-absence' declared operation surface (#707/#865/#891/#896).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this file exists now
|
|
5
|
+
*
|
|
6
|
+
* Six of this engine's checks narrow to an entity, and until #896 they were
|
|
7
|
+
* **undeclarable rather than merely undeclared** — the two halves of that being
|
|
8
|
+
* separate problems:
|
|
9
|
+
*
|
|
10
|
+
* 1. There was no declaration to put them in. `index.ts` carried a map of
|
|
11
|
+
* handlers, so `ctx.check(PERM.read, ref)` and `ctx.check(PERM.read)` looked
|
|
12
|
+
* the same to every tool in the repo, and `entityCheckConformanceSuite`
|
|
13
|
+
* derives its behavioural pair from `permission` (#891).
|
|
14
|
+
* 2. The format could not hold them. A narrowed check named `entity: '<a type
|
|
15
|
+
* from a declared registry>'`, and this engine narrows to a subject whose type
|
|
16
|
+
* is the VERTICAL's — Meridian's `employee`, which appears in no registry
|
|
17
|
+
* absence can see. #890's `entityFrom` did not reach it either: it changes
|
|
18
|
+
* where the type name comes from, not that it has to be a name someone
|
|
19
|
+
* declared. `refFrom` names the field carrying the whole ref, so the type
|
|
20
|
+
* travels with it and needs no name here at all (#896).
|
|
21
|
+
*
|
|
22
|
+
* What is deliberately NOT here is `http`: an engine owns no URL shape. Meridian
|
|
23
|
+
* composes this engine by CALL — importing `requestAbsence`, `balanceAsOf` and
|
|
24
|
+
* the rest into its own operations — so these bindings are the engine's default
|
|
25
|
+
* surface rather than the path any vertical serves.
|
|
26
|
+
*
|
|
27
|
+
* ## Four narrow, two say node, and the two are the interesting ones
|
|
28
|
+
*
|
|
29
|
+
* `request`, `balance`, `availability` and `list-entries` declare `refFrom` and
|
|
30
|
+
* the kit drives all four. The other two checks are real and are **not** declared
|
|
31
|
+
* as narrowed, each for a reason worth stating rather than leaving to be
|
|
32
|
+
* inferred:
|
|
33
|
+
*
|
|
34
|
+
* - **`cancel` has two authorities and the narrowed one is resolved.** An
|
|
35
|
+
* approver cancels anything cancellable (node); a subject may withdraw their
|
|
36
|
+
* own still-`requested` row through the same grant they requested with — but
|
|
37
|
+
* that ref is read off the stored request, not the input, so there is no field
|
|
38
|
+
* to name. Declaring the node key alone would say a subject needs
|
|
39
|
+
* `absence:approve` to withdraw, which is false. It declares the gate it opens
|
|
40
|
+
* with and states the second path here.
|
|
41
|
+
* - **`list-requests` narrows only when the caller supplies a subject.** An
|
|
42
|
+
* `optional` field behind `refFrom` would claim a narrowing that a caller
|
|
43
|
+
* omitting it never gets — the unsafe direction for a review artifact to be
|
|
44
|
+
* wrong in, and the same call Meridian's `hr/list-leave-types` and Shop's
|
|
45
|
+
* `catalog` made (#892).
|
|
46
|
+
*
|
|
47
|
+
* ## Paging, and why no `over`
|
|
48
|
+
*
|
|
49
|
+
* Three reads answer lists, and #811 refuses a bare array. None is
|
|
50
|
+
* kernel-composed: the ledger and the request book are ROWS this engine owns, not
|
|
51
|
+
* registry entities — `absenceEntities` declares exactly one, `leave-type` — so
|
|
52
|
+
* `paged.over` has nothing to name for two of the three, and the third answers a
|
|
53
|
+
* projection (`active` as a boolean) rather than the stored row. So all three
|
|
54
|
+
* page over their own fold, and **this change provisions no list index and adds
|
|
55
|
+
* no migration.**
|
|
56
|
+
*/
|
|
57
|
+
export declare const ABSENCE_PERMISSIONS: readonly ['absence:read', 'absence:request', 'absence:approve', 'absence:configure'];
|
|
58
|
+
export declare const absenceOperations: {
|
|
59
|
+
readonly 'absence/configure-leave-type': {
|
|
60
|
+
readonly summary: "Create or update a leave type and its balance floor";
|
|
61
|
+
readonly permission: "absence:configure";
|
|
62
|
+
readonly input: import("zod").ZodObject<{
|
|
63
|
+
key: import("zod").ZodString;
|
|
64
|
+
floor: import("zod").ZodOptional<import("zod").ZodString>;
|
|
65
|
+
active: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
66
|
+
}, import("zod/v4/core").$strip>;
|
|
67
|
+
readonly output: import("zod").ZodObject<{
|
|
68
|
+
key: import("zod").ZodString;
|
|
69
|
+
floor: import("zod").ZodString;
|
|
70
|
+
active: import("zod").ZodBoolean;
|
|
71
|
+
createdAt: import("zod").ZodString;
|
|
72
|
+
}, import("zod/v4/core").$strip>;
|
|
73
|
+
};
|
|
74
|
+
readonly 'absence/list-leave-types': {
|
|
75
|
+
readonly summary: "The configured leave types, by key";
|
|
76
|
+
readonly permission: "absence:read";
|
|
77
|
+
readonly output: import("zod").ZodObject<{
|
|
78
|
+
key: import("zod").ZodString;
|
|
79
|
+
floor: import("zod").ZodString;
|
|
80
|
+
active: import("zod").ZodBoolean;
|
|
81
|
+
createdAt: import("zod").ZodString;
|
|
82
|
+
}, import("zod/v4/core").$strip>;
|
|
83
|
+
readonly paged: {
|
|
84
|
+
readonly sortKey: "key";
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
readonly 'absence/record-entry': {
|
|
88
|
+
readonly summary: "Write an accrual, correction or carryover to the ledger";
|
|
89
|
+
readonly permission: "absence:configure";
|
|
90
|
+
readonly input: import("zod").ZodObject<{
|
|
91
|
+
subject: import("zod").ZodObject<{
|
|
92
|
+
ref: import("zod").ZodObject<{
|
|
93
|
+
entityType: import("zod").ZodString;
|
|
94
|
+
entityId: import("zod").ZodString;
|
|
95
|
+
}, import("zod/v4/core").$strip>;
|
|
96
|
+
dataSubjectId: import("zod/v4/core").$ZodBranded<import("zod").ZodString, "DataSubjectId", "out">;
|
|
97
|
+
}, import("zod/v4/core").$strip>;
|
|
98
|
+
leaveTypeKey: import("zod").ZodString;
|
|
99
|
+
entryKind: import("zod").ZodEnum<{
|
|
100
|
+
accrual: "accrual";
|
|
101
|
+
carryover: "carryover";
|
|
102
|
+
correction: "correction";
|
|
103
|
+
}>;
|
|
104
|
+
delta: import("zod").ZodString;
|
|
105
|
+
effectiveDate: import("zod").ZodString;
|
|
106
|
+
note: import("zod").ZodOptional<import("zod").ZodString>;
|
|
107
|
+
}, import("zod/v4/core").$strip>;
|
|
108
|
+
readonly output: import("zod").ZodObject<{
|
|
109
|
+
id: import("zod").ZodString;
|
|
110
|
+
subject: import("zod").ZodObject<{
|
|
111
|
+
entityType: import("zod").ZodString;
|
|
112
|
+
entityId: import("zod").ZodString;
|
|
113
|
+
}, import("zod/v4/core").$strip>;
|
|
114
|
+
leaveTypeKey: import("zod").ZodString;
|
|
115
|
+
entryKind: import("zod").ZodEnum<{
|
|
116
|
+
accrual: "accrual";
|
|
117
|
+
booking: "booking";
|
|
118
|
+
carryover: "carryover";
|
|
119
|
+
correction: "correction";
|
|
120
|
+
reversal: "reversal";
|
|
121
|
+
}>;
|
|
122
|
+
delta: import("zod").ZodString;
|
|
123
|
+
effectiveDate: import("zod").ZodString;
|
|
124
|
+
requestId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
125
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
126
|
+
createdBy: import("zod").ZodString;
|
|
127
|
+
createdAt: import("zod").ZodString;
|
|
128
|
+
}, import("zod/v4/core").$strip>;
|
|
129
|
+
};
|
|
130
|
+
readonly 'absence/request': {
|
|
131
|
+
readonly summary: "Request absence for a subject";
|
|
132
|
+
/**
|
|
133
|
+
* The shape #896 exists for. A subject requests for THEMSELVES through a
|
|
134
|
+
* grant on their own ref while holding no role at all, so a node check here
|
|
135
|
+
* would let anyone holding `absence:request` anywhere in the scope file
|
|
136
|
+
* absence against anyone — with every test still green, because the engine's
|
|
137
|
+
* own suite grants scope-wide.
|
|
138
|
+
*
|
|
139
|
+
* `subject.ref` reaches one level in: the erasure key travels beside the ref
|
|
140
|
+
* (`{ ref, dataSubjectId }`) and only the ref is checked.
|
|
141
|
+
*/
|
|
142
|
+
readonly permission: {
|
|
143
|
+
readonly key: "absence:request";
|
|
144
|
+
readonly refFrom: "subject.ref";
|
|
145
|
+
};
|
|
146
|
+
readonly input: import("zod").ZodObject<{
|
|
147
|
+
subject: import("zod").ZodObject<{
|
|
148
|
+
ref: import("zod").ZodObject<{
|
|
149
|
+
entityType: import("zod").ZodString;
|
|
150
|
+
entityId: import("zod").ZodString;
|
|
151
|
+
}, import("zod/v4/core").$strip>;
|
|
152
|
+
dataSubjectId: import("zod/v4/core").$ZodBranded<import("zod").ZodString, "DataSubjectId", "out">;
|
|
153
|
+
}, import("zod/v4/core").$strip>;
|
|
154
|
+
leaveTypeKey: import("zod").ZodString;
|
|
155
|
+
startDate: import("zod").ZodString;
|
|
156
|
+
endDate: import("zod").ZodString;
|
|
157
|
+
days: import("zod").ZodString;
|
|
158
|
+
note: import("zod").ZodOptional<import("zod").ZodString>;
|
|
159
|
+
}, import("zod/v4/core").$strip>;
|
|
160
|
+
readonly output: import("zod").ZodObject<{
|
|
161
|
+
id: import("zod").ZodString;
|
|
162
|
+
subject: import("zod").ZodObject<{
|
|
163
|
+
entityType: import("zod").ZodString;
|
|
164
|
+
entityId: import("zod").ZodString;
|
|
165
|
+
}, import("zod/v4/core").$strip>;
|
|
166
|
+
leaveTypeKey: import("zod").ZodString;
|
|
167
|
+
startDate: import("zod").ZodString;
|
|
168
|
+
endDate: import("zod").ZodString;
|
|
169
|
+
days: import("zod").ZodString;
|
|
170
|
+
status: import("zod").ZodEnum<{
|
|
171
|
+
approved: "approved";
|
|
172
|
+
cancelled: "cancelled";
|
|
173
|
+
rejected: "rejected";
|
|
174
|
+
requested: "requested";
|
|
175
|
+
}>;
|
|
176
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
177
|
+
decidedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
178
|
+
decidedAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
179
|
+
createdBy: import("zod").ZodString;
|
|
180
|
+
createdAt: import("zod").ZodString;
|
|
181
|
+
}, import("zod/v4/core").$strip>;
|
|
182
|
+
};
|
|
183
|
+
readonly 'absence/decide': {
|
|
184
|
+
readonly summary: "Approve or reject a requested absence";
|
|
185
|
+
readonly permission: "absence:approve";
|
|
186
|
+
readonly input: import("zod").ZodObject<{
|
|
187
|
+
requestId: import("zod").ZodString;
|
|
188
|
+
decision: import("zod").ZodEnum<{
|
|
189
|
+
approve: "approve";
|
|
190
|
+
reject: "reject";
|
|
191
|
+
}>;
|
|
192
|
+
note: import("zod").ZodOptional<import("zod").ZodString>;
|
|
193
|
+
}, import("zod/v4/core").$strip>;
|
|
194
|
+
readonly output: import("zod").ZodObject<{
|
|
195
|
+
request: import("zod").ZodObject<{
|
|
196
|
+
id: import("zod").ZodString;
|
|
197
|
+
subject: import("zod").ZodObject<{
|
|
198
|
+
entityType: import("zod").ZodString;
|
|
199
|
+
entityId: import("zod").ZodString;
|
|
200
|
+
}, import("zod/v4/core").$strip>;
|
|
201
|
+
leaveTypeKey: import("zod").ZodString;
|
|
202
|
+
startDate: import("zod").ZodString;
|
|
203
|
+
endDate: import("zod").ZodString;
|
|
204
|
+
days: import("zod").ZodString;
|
|
205
|
+
status: import("zod").ZodEnum<{
|
|
206
|
+
approved: "approved";
|
|
207
|
+
cancelled: "cancelled";
|
|
208
|
+
rejected: "rejected";
|
|
209
|
+
requested: "requested";
|
|
210
|
+
}>;
|
|
211
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
212
|
+
decidedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
213
|
+
decidedAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
214
|
+
createdBy: import("zod").ZodString;
|
|
215
|
+
createdAt: import("zod").ZodString;
|
|
216
|
+
}, import("zod/v4/core").$strip>;
|
|
217
|
+
booking: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
218
|
+
id: import("zod").ZodString;
|
|
219
|
+
subject: import("zod").ZodObject<{
|
|
220
|
+
entityType: import("zod").ZodString;
|
|
221
|
+
entityId: import("zod").ZodString;
|
|
222
|
+
}, import("zod/v4/core").$strip>;
|
|
223
|
+
leaveTypeKey: import("zod").ZodString;
|
|
224
|
+
entryKind: import("zod").ZodEnum<{
|
|
225
|
+
accrual: "accrual";
|
|
226
|
+
booking: "booking";
|
|
227
|
+
carryover: "carryover";
|
|
228
|
+
correction: "correction";
|
|
229
|
+
reversal: "reversal";
|
|
230
|
+
}>;
|
|
231
|
+
delta: import("zod").ZodString;
|
|
232
|
+
effectiveDate: import("zod").ZodString;
|
|
233
|
+
requestId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
234
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
235
|
+
createdBy: import("zod").ZodString;
|
|
236
|
+
createdAt: import("zod").ZodString;
|
|
237
|
+
}, import("zod/v4/core").$strip>>;
|
|
238
|
+
}, import("zod/v4/core").$strip>;
|
|
239
|
+
};
|
|
240
|
+
readonly 'absence/cancel': {
|
|
241
|
+
readonly summary: "Cancel an approved absence, or withdraw a requested one";
|
|
242
|
+
readonly permission: "absence:approve";
|
|
243
|
+
readonly input: import("zod").ZodObject<{
|
|
244
|
+
requestId: import("zod").ZodString;
|
|
245
|
+
reason: import("zod").ZodOptional<import("zod").ZodString>;
|
|
246
|
+
}, import("zod/v4/core").$strip>;
|
|
247
|
+
readonly output: import("zod").ZodObject<{
|
|
248
|
+
request: import("zod").ZodObject<{
|
|
249
|
+
id: import("zod").ZodString;
|
|
250
|
+
subject: import("zod").ZodObject<{
|
|
251
|
+
entityType: import("zod").ZodString;
|
|
252
|
+
entityId: import("zod").ZodString;
|
|
253
|
+
}, import("zod/v4/core").$strip>;
|
|
254
|
+
leaveTypeKey: import("zod").ZodString;
|
|
255
|
+
startDate: import("zod").ZodString;
|
|
256
|
+
endDate: import("zod").ZodString;
|
|
257
|
+
days: import("zod").ZodString;
|
|
258
|
+
status: import("zod").ZodEnum<{
|
|
259
|
+
approved: "approved";
|
|
260
|
+
cancelled: "cancelled";
|
|
261
|
+
rejected: "rejected";
|
|
262
|
+
requested: "requested";
|
|
263
|
+
}>;
|
|
264
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
265
|
+
decidedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
266
|
+
decidedAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
267
|
+
createdBy: import("zod").ZodString;
|
|
268
|
+
createdAt: import("zod").ZodString;
|
|
269
|
+
}, import("zod/v4/core").$strip>;
|
|
270
|
+
reversal: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
271
|
+
id: import("zod").ZodString;
|
|
272
|
+
subject: import("zod").ZodObject<{
|
|
273
|
+
entityType: import("zod").ZodString;
|
|
274
|
+
entityId: import("zod").ZodString;
|
|
275
|
+
}, import("zod/v4/core").$strip>;
|
|
276
|
+
leaveTypeKey: import("zod").ZodString;
|
|
277
|
+
entryKind: import("zod").ZodEnum<{
|
|
278
|
+
accrual: "accrual";
|
|
279
|
+
booking: "booking";
|
|
280
|
+
carryover: "carryover";
|
|
281
|
+
correction: "correction";
|
|
282
|
+
reversal: "reversal";
|
|
283
|
+
}>;
|
|
284
|
+
delta: import("zod").ZodString;
|
|
285
|
+
effectiveDate: import("zod").ZodString;
|
|
286
|
+
requestId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
287
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
288
|
+
createdBy: import("zod").ZodString;
|
|
289
|
+
createdAt: import("zod").ZodString;
|
|
290
|
+
}, import("zod/v4/core").$strip>>;
|
|
291
|
+
}, import("zod/v4/core").$strip>;
|
|
292
|
+
};
|
|
293
|
+
readonly 'absence/expire-stale': {
|
|
294
|
+
readonly summary: "Expire requests left undecided past their window";
|
|
295
|
+
readonly permission: "absence:approve";
|
|
296
|
+
readonly output: import("zod").ZodObject<{
|
|
297
|
+
expired: import("zod").ZodNumber;
|
|
298
|
+
}, import("zod/v4/core").$strip>;
|
|
299
|
+
};
|
|
300
|
+
readonly 'absence/balance': {
|
|
301
|
+
readonly summary: "Balance for one subject and leave type, as of a date";
|
|
302
|
+
readonly permission: {
|
|
303
|
+
readonly key: "absence:read";
|
|
304
|
+
readonly refFrom: "subject";
|
|
305
|
+
};
|
|
306
|
+
readonly input: import("zod").ZodObject<{
|
|
307
|
+
subject: import("zod").ZodObject<{
|
|
308
|
+
entityType: import("zod").ZodString;
|
|
309
|
+
entityId: import("zod").ZodString;
|
|
310
|
+
}, import("zod/v4/core").$strip>;
|
|
311
|
+
leaveTypeKey: import("zod").ZodString;
|
|
312
|
+
asOf: import("zod").ZodOptional<import("zod").ZodString>;
|
|
313
|
+
}, import("zod/v4/core").$strip>;
|
|
314
|
+
readonly output: import("zod").ZodObject<{
|
|
315
|
+
balance: import("zod").ZodString;
|
|
316
|
+
}, import("zod/v4/core").$strip>;
|
|
317
|
+
};
|
|
318
|
+
readonly 'absence/availability': {
|
|
319
|
+
readonly summary: "Which calendar days an approved absence covers, for one subject";
|
|
320
|
+
readonly permission: {
|
|
321
|
+
readonly key: "absence:read";
|
|
322
|
+
readonly refFrom: "subject";
|
|
323
|
+
};
|
|
324
|
+
readonly input: import("zod").ZodObject<{
|
|
325
|
+
subject: import("zod").ZodObject<{
|
|
326
|
+
entityType: import("zod").ZodString;
|
|
327
|
+
entityId: import("zod").ZodString;
|
|
328
|
+
}, import("zod/v4/core").$strip>;
|
|
329
|
+
from: import("zod").ZodString;
|
|
330
|
+
to: import("zod").ZodString;
|
|
331
|
+
}, import("zod/v4/core").$strip>;
|
|
332
|
+
readonly output: import("zod").ZodObject<{
|
|
333
|
+
days: import("zod").ZodArray<import("zod").ZodObject<{
|
|
334
|
+
date: import("zod").ZodString;
|
|
335
|
+
leaveTypeKey: import("zod").ZodString;
|
|
336
|
+
requestId: import("zod").ZodString;
|
|
337
|
+
}, import("zod/v4/core").$strip>>;
|
|
338
|
+
requests: import("zod").ZodArray<import("zod").ZodObject<{
|
|
339
|
+
id: import("zod").ZodString;
|
|
340
|
+
subject: import("zod").ZodObject<{
|
|
341
|
+
entityType: import("zod").ZodString;
|
|
342
|
+
entityId: import("zod").ZodString;
|
|
343
|
+
}, import("zod/v4/core").$strip>;
|
|
344
|
+
leaveTypeKey: import("zod").ZodString;
|
|
345
|
+
startDate: import("zod").ZodString;
|
|
346
|
+
endDate: import("zod").ZodString;
|
|
347
|
+
days: import("zod").ZodString;
|
|
348
|
+
status: import("zod").ZodEnum<{
|
|
349
|
+
approved: "approved";
|
|
350
|
+
cancelled: "cancelled";
|
|
351
|
+
rejected: "rejected";
|
|
352
|
+
requested: "requested";
|
|
353
|
+
}>;
|
|
354
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
355
|
+
decidedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
356
|
+
decidedAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
357
|
+
createdBy: import("zod").ZodString;
|
|
358
|
+
createdAt: import("zod").ZodString;
|
|
359
|
+
}, import("zod/v4/core").$strip>>;
|
|
360
|
+
}, import("zod/v4/core").$strip>;
|
|
361
|
+
};
|
|
362
|
+
readonly 'absence/list-requests': {
|
|
363
|
+
readonly summary: "Requests, optionally for one subject or one status";
|
|
364
|
+
readonly permission: "absence:read";
|
|
365
|
+
readonly input: import("zod").ZodObject<{
|
|
366
|
+
subject: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
367
|
+
entityType: import("zod").ZodString;
|
|
368
|
+
entityId: import("zod").ZodString;
|
|
369
|
+
}, import("zod/v4/core").$strip>>;
|
|
370
|
+
status: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
371
|
+
approved: "approved";
|
|
372
|
+
cancelled: "cancelled";
|
|
373
|
+
rejected: "rejected";
|
|
374
|
+
requested: "requested";
|
|
375
|
+
}>>;
|
|
376
|
+
}, import("zod/v4/core").$strip>;
|
|
377
|
+
readonly inputOptional: true;
|
|
378
|
+
readonly output: import("zod").ZodObject<{
|
|
379
|
+
id: import("zod").ZodString;
|
|
380
|
+
subject: import("zod").ZodObject<{
|
|
381
|
+
entityType: import("zod").ZodString;
|
|
382
|
+
entityId: import("zod").ZodString;
|
|
383
|
+
}, import("zod/v4/core").$strip>;
|
|
384
|
+
leaveTypeKey: import("zod").ZodString;
|
|
385
|
+
startDate: import("zod").ZodString;
|
|
386
|
+
endDate: import("zod").ZodString;
|
|
387
|
+
days: import("zod").ZodString;
|
|
388
|
+
status: import("zod").ZodEnum<{
|
|
389
|
+
approved: "approved";
|
|
390
|
+
cancelled: "cancelled";
|
|
391
|
+
rejected: "rejected";
|
|
392
|
+
requested: "requested";
|
|
393
|
+
}>;
|
|
394
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
395
|
+
decidedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
396
|
+
decidedAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
397
|
+
createdBy: import("zod").ZodString;
|
|
398
|
+
createdAt: import("zod").ZodString;
|
|
399
|
+
}, import("zod/v4/core").$strip>;
|
|
400
|
+
readonly paged: {
|
|
401
|
+
readonly sortKey: "id";
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
readonly 'absence/list-entries': {
|
|
405
|
+
readonly summary: "One subject's ledger entries, oldest first";
|
|
406
|
+
readonly permission: {
|
|
407
|
+
readonly key: "absence:read";
|
|
408
|
+
readonly refFrom: "subject";
|
|
409
|
+
};
|
|
410
|
+
readonly input: import("zod").ZodObject<{
|
|
411
|
+
subject: import("zod").ZodObject<{
|
|
412
|
+
entityType: import("zod").ZodString;
|
|
413
|
+
entityId: import("zod").ZodString;
|
|
414
|
+
}, import("zod/v4/core").$strip>;
|
|
415
|
+
leaveTypeKey: import("zod").ZodOptional<import("zod").ZodString>;
|
|
416
|
+
}, import("zod/v4/core").$strip>;
|
|
417
|
+
readonly output: import("zod").ZodObject<{
|
|
418
|
+
id: import("zod").ZodString;
|
|
419
|
+
subject: import("zod").ZodObject<{
|
|
420
|
+
entityType: import("zod").ZodString;
|
|
421
|
+
entityId: import("zod").ZodString;
|
|
422
|
+
}, import("zod/v4/core").$strip>;
|
|
423
|
+
leaveTypeKey: import("zod").ZodString;
|
|
424
|
+
entryKind: import("zod").ZodEnum<{
|
|
425
|
+
accrual: "accrual";
|
|
426
|
+
booking: "booking";
|
|
427
|
+
carryover: "carryover";
|
|
428
|
+
correction: "correction";
|
|
429
|
+
reversal: "reversal";
|
|
430
|
+
}>;
|
|
431
|
+
delta: import("zod").ZodString;
|
|
432
|
+
effectiveDate: import("zod").ZodString;
|
|
433
|
+
requestId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
434
|
+
note: import("zod").ZodNullable<import("zod").ZodString>;
|
|
435
|
+
createdBy: import("zod").ZodString;
|
|
436
|
+
createdAt: import("zod").ZodString;
|
|
437
|
+
}, import("zod/v4/core").$strip>;
|
|
438
|
+
readonly paged: {
|
|
439
|
+
readonly sortKey: "effectiveDate";
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,mBAAmB,YAC9B,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,CACX,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA6B1B;;;;;;;;;WASG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwEL,CAAC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { defineOperations } from '@substrat-run/contracts';
|
|
2
|
+
import { absenceEntities } from './entities.js';
|
|
3
|
+
import { absenceEntry, absenceRequest, availabilityAnswer, availabilityInput, balanceAnswer, balanceInput, cancelAbsenceInput, cancelAnswer, configureLeaveTypeInput, decideAbsenceInput, decideAnswer, expiredAnswer, leaveType, listEntriesInput, listRequestsInput, recordEntryInput, requestAbsenceInput, } from './schemas.js';
|
|
4
|
+
/**
|
|
5
|
+
* engine-absence' declared operation surface (#707/#865/#891/#896).
|
|
6
|
+
*
|
|
7
|
+
* ## Why this file exists now
|
|
8
|
+
*
|
|
9
|
+
* Six of this engine's checks narrow to an entity, and until #896 they were
|
|
10
|
+
* **undeclarable rather than merely undeclared** — the two halves of that being
|
|
11
|
+
* separate problems:
|
|
12
|
+
*
|
|
13
|
+
* 1. There was no declaration to put them in. `index.ts` carried a map of
|
|
14
|
+
* handlers, so `ctx.check(PERM.read, ref)` and `ctx.check(PERM.read)` looked
|
|
15
|
+
* the same to every tool in the repo, and `entityCheckConformanceSuite`
|
|
16
|
+
* derives its behavioural pair from `permission` (#891).
|
|
17
|
+
* 2. The format could not hold them. A narrowed check named `entity: '<a type
|
|
18
|
+
* from a declared registry>'`, and this engine narrows to a subject whose type
|
|
19
|
+
* is the VERTICAL's — Meridian's `employee`, which appears in no registry
|
|
20
|
+
* absence can see. #890's `entityFrom` did not reach it either: it changes
|
|
21
|
+
* where the type name comes from, not that it has to be a name someone
|
|
22
|
+
* declared. `refFrom` names the field carrying the whole ref, so the type
|
|
23
|
+
* travels with it and needs no name here at all (#896).
|
|
24
|
+
*
|
|
25
|
+
* What is deliberately NOT here is `http`: an engine owns no URL shape. Meridian
|
|
26
|
+
* composes this engine by CALL — importing `requestAbsence`, `balanceAsOf` and
|
|
27
|
+
* the rest into its own operations — so these bindings are the engine's default
|
|
28
|
+
* surface rather than the path any vertical serves.
|
|
29
|
+
*
|
|
30
|
+
* ## Four narrow, two say node, and the two are the interesting ones
|
|
31
|
+
*
|
|
32
|
+
* `request`, `balance`, `availability` and `list-entries` declare `refFrom` and
|
|
33
|
+
* the kit drives all four. The other two checks are real and are **not** declared
|
|
34
|
+
* as narrowed, each for a reason worth stating rather than leaving to be
|
|
35
|
+
* inferred:
|
|
36
|
+
*
|
|
37
|
+
* - **`cancel` has two authorities and the narrowed one is resolved.** An
|
|
38
|
+
* approver cancels anything cancellable (node); a subject may withdraw their
|
|
39
|
+
* own still-`requested` row through the same grant they requested with — but
|
|
40
|
+
* that ref is read off the stored request, not the input, so there is no field
|
|
41
|
+
* to name. Declaring the node key alone would say a subject needs
|
|
42
|
+
* `absence:approve` to withdraw, which is false. It declares the gate it opens
|
|
43
|
+
* with and states the second path here.
|
|
44
|
+
* - **`list-requests` narrows only when the caller supplies a subject.** An
|
|
45
|
+
* `optional` field behind `refFrom` would claim a narrowing that a caller
|
|
46
|
+
* omitting it never gets — the unsafe direction for a review artifact to be
|
|
47
|
+
* wrong in, and the same call Meridian's `hr/list-leave-types` and Shop's
|
|
48
|
+
* `catalog` made (#892).
|
|
49
|
+
*
|
|
50
|
+
* ## Paging, and why no `over`
|
|
51
|
+
*
|
|
52
|
+
* Three reads answer lists, and #811 refuses a bare array. None is
|
|
53
|
+
* kernel-composed: the ledger and the request book are ROWS this engine owns, not
|
|
54
|
+
* registry entities — `absenceEntities` declares exactly one, `leave-type` — so
|
|
55
|
+
* `paged.over` has nothing to name for two of the three, and the third answers a
|
|
56
|
+
* projection (`active` as a boolean) rather than the stored row. So all three
|
|
57
|
+
* page over their own fold, and **this change provisions no list index and adds
|
|
58
|
+
* no migration.**
|
|
59
|
+
*/
|
|
60
|
+
export const ABSENCE_PERMISSIONS = [
|
|
61
|
+
'absence:read',
|
|
62
|
+
'absence:request',
|
|
63
|
+
'absence:approve',
|
|
64
|
+
'absence:configure',
|
|
65
|
+
];
|
|
66
|
+
export const absenceOperations = defineOperations(absenceEntities, ABSENCE_PERMISSIONS)({
|
|
67
|
+
'absence/configure-leave-type': {
|
|
68
|
+
summary: 'Create or update a leave type and its balance floor',
|
|
69
|
+
permission: 'absence:configure',
|
|
70
|
+
input: configureLeaveTypeInput,
|
|
71
|
+
output: leaveType,
|
|
72
|
+
},
|
|
73
|
+
'absence/list-leave-types': {
|
|
74
|
+
summary: 'The configured leave types, by key',
|
|
75
|
+
permission: 'absence:read',
|
|
76
|
+
output: leaveType,
|
|
77
|
+
// Handler-composed: what is answered is the PROJECTION of the row (`active`
|
|
78
|
+
// as a boolean), so the page is taken off the fold. `key` is the primary key,
|
|
79
|
+
// so it is both the order and a unique cursor.
|
|
80
|
+
paged: { sortKey: 'key' },
|
|
81
|
+
},
|
|
82
|
+
'absence/record-entry': {
|
|
83
|
+
summary: 'Write an accrual, correction or carryover to the ledger',
|
|
84
|
+
// Node, deliberately: the one write that bypasses the request flow is an
|
|
85
|
+
// administrator's, over whichever subject needs correcting.
|
|
86
|
+
permission: 'absence:configure',
|
|
87
|
+
input: recordEntryInput,
|
|
88
|
+
output: absenceEntry,
|
|
89
|
+
},
|
|
90
|
+
'absence/request': {
|
|
91
|
+
summary: 'Request absence for a subject',
|
|
92
|
+
/**
|
|
93
|
+
* The shape #896 exists for. A subject requests for THEMSELVES through a
|
|
94
|
+
* grant on their own ref while holding no role at all, so a node check here
|
|
95
|
+
* would let anyone holding `absence:request` anywhere in the scope file
|
|
96
|
+
* absence against anyone — with every test still green, because the engine's
|
|
97
|
+
* own suite grants scope-wide.
|
|
98
|
+
*
|
|
99
|
+
* `subject.ref` reaches one level in: the erasure key travels beside the ref
|
|
100
|
+
* (`{ ref, dataSubjectId }`) and only the ref is checked.
|
|
101
|
+
*/
|
|
102
|
+
permission: { key: 'absence:request', refFrom: 'subject.ref' },
|
|
103
|
+
input: requestAbsenceInput,
|
|
104
|
+
output: absenceRequest,
|
|
105
|
+
},
|
|
106
|
+
'absence/decide': {
|
|
107
|
+
summary: 'Approve or reject a requested absence',
|
|
108
|
+
// Node: deciding is the approver's authority over the whole scope. The
|
|
109
|
+
// 'booking' entry this mints is why approval is the only path into the
|
|
110
|
+
// ledger.
|
|
111
|
+
permission: 'absence:approve',
|
|
112
|
+
input: decideAbsenceInput,
|
|
113
|
+
output: decideAnswer,
|
|
114
|
+
},
|
|
115
|
+
'absence/cancel': {
|
|
116
|
+
summary: 'Cancel an approved absence, or withdraw a requested one',
|
|
117
|
+
// The gate it OPENS with. The second authority — the subject withdrawing
|
|
118
|
+
// their own still-requested row, narrowed to a ref read off the stored
|
|
119
|
+
// request — is undeclarable here and is described in the header.
|
|
120
|
+
permission: 'absence:approve',
|
|
121
|
+
input: cancelAbsenceInput,
|
|
122
|
+
output: cancelAnswer,
|
|
123
|
+
},
|
|
124
|
+
'absence/expire-stale': {
|
|
125
|
+
summary: 'Expire requests left undecided past their window',
|
|
126
|
+
// Node: a sweep acts on whatever has lapsed. Bound to the schedule this
|
|
127
|
+
// engine's own manifest declares (#383).
|
|
128
|
+
permission: 'absence:approve',
|
|
129
|
+
output: expiredAnswer,
|
|
130
|
+
},
|
|
131
|
+
'absence/balance': {
|
|
132
|
+
summary: 'Balance for one subject and leave type, as of a date',
|
|
133
|
+
permission: { key: 'absence:read', refFrom: 'subject' },
|
|
134
|
+
input: balanceInput,
|
|
135
|
+
output: balanceAnswer,
|
|
136
|
+
},
|
|
137
|
+
'absence/availability': {
|
|
138
|
+
summary: 'Which calendar days an approved absence covers, for one subject',
|
|
139
|
+
permission: { key: 'absence:read', refFrom: 'subject' },
|
|
140
|
+
input: availabilityInput,
|
|
141
|
+
output: availabilityAnswer,
|
|
142
|
+
},
|
|
143
|
+
'absence/list-requests': {
|
|
144
|
+
summary: 'Requests, optionally for one subject or one status',
|
|
145
|
+
// Node — see the header. This is the conditional narrow, and declaring it
|
|
146
|
+
// would claim something a caller omitting `subject` does not pass.
|
|
147
|
+
permission: 'absence:read',
|
|
148
|
+
input: listRequestsInput,
|
|
149
|
+
inputOptional: true,
|
|
150
|
+
output: absenceRequest,
|
|
151
|
+
// Newest first, which is the order this read shipped with. The cursor is the
|
|
152
|
+
// id: it is a ULID, so it is unique and it descends with `created_at`.
|
|
153
|
+
paged: { sortKey: 'id' },
|
|
154
|
+
},
|
|
155
|
+
'absence/list-entries': {
|
|
156
|
+
summary: "One subject's ledger entries, oldest first",
|
|
157
|
+
permission: { key: 'absence:read', refFrom: 'subject' },
|
|
158
|
+
input: listEntriesInput,
|
|
159
|
+
output: absenceEntry,
|
|
160
|
+
// `effectiveDate` is the order and is NOT unique — it is caller-supplied, so
|
|
161
|
+
// an accrual dated last year may be written today. The cursor is therefore
|
|
162
|
+
// the (effectiveDate, id) pair, the same pair the SQL orders by; `sortKey`
|
|
163
|
+
// names the field a reader sorts by, as Callout's timeline does over a rowid.
|
|
164
|
+
paged: { sortKey: 'effectiveDate' },
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"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"}
|