forked from votrongdao/FlowX
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInfrastructure.cs
More file actions
397 lines (343 loc) · 12.4 KB
/
Copy pathInfrastructure.cs
File metadata and controls
397 lines (343 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
using System.Text.Json.Serialization;
using FlowX;
namespace Workflow;
/// <summary>Source-generated serialisation for the contracts that cross a boundary.</summary>
/// <remarks>
/// <para>
/// <strong><see cref="EmployeeOnboarded"/> is in here for a reason the other two are not.</strong>
/// An <c>Emit</c> step's body is written through a source-generated
/// <c>JsonSerializerContext</c> and never by reflection, so membership here is a build
/// requirement rather than a convention: an event contract that no generated context declares
/// is <c>FLOWX1024</c>, which is exactly how a durable flow's outbox stays trim- and
/// NativeAOT-safe.
/// </para>
/// <para>
/// <strong>The contracts below <see cref="EmployeeOnboarded"/> are here for a third reason:
/// <c>FLOWX1006</c>.</strong> Both flows in this sample are <c>Durable</c>, so each step's
/// result and the state bag after it are journaled, and every one of those needs the same
/// generated metadata for the same reason the event body does. Before WP-59 the journal
/// recorded no payloads at all — truthful about which steps had run, silent about what they
/// produced — so a resumed instance re-entered with an empty bag and re-ran everything after
/// the frontier. The list is not maintained by hand: the compiler names the missing contract.
/// </para>
/// <para>
/// The camelCase policy is not decoration. Without it the wire names are the C# ones, and a
/// client sending the conventional <c>"candidateId"</c> gets a <c>CandidateId</c> of null —
/// a missing member deserialises to <c>default</c>. Here <c>offer.validate</c> rejects it,
/// which is the point of validating at the first step.
/// </para>
/// </remarks>
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(OnboardEmployee))]
[JsonSerializable(typeof(OnboardingResult))]
[JsonSerializable(typeof(EmployeeOnboarded))]
[JsonSerializable(typeof(ValidatedOffer))]
[JsonSerializable(typeof(PayrollRecord))]
[JsonSerializable(typeof(SupplierAgreement))]
[JsonSerializable(typeof(Identity))]
[JsonSerializable(typeof(LaptopOrder))]
[JsonSerializable(typeof(AccessGrant))]
[JsonSerializable(typeof(ApprovedEquipment))]
[JsonSerializable(typeof(EquipmentAssignment))]
[JsonSerializable(typeof(BackgroundCheck))]
[JsonSerializable(typeof(CheckWaiver))]
[JsonSerializable(typeof(WelcomePack))]
[JsonSerializable(typeof(InductionBooking))]
[JsonSerializable(typeof(ProvisionWorkspace))]
[JsonSerializable(typeof(DeskAllocation))]
[JsonSerializable(typeof(BuildingPass))]
// offer.accept's contracts. OfferCountersigned is here for a reason none of the others is:
// it is a *signal* contract, and a delivered signal is put into the state bag under it and
// journaled by the commit that records the suspension point. So the flow's own wait depends
// on this line — without it the value is in memory for one invocation and lost to the next
// crash, which FLOWX1006 reports rather than allows.
[JsonSerializable(typeof(OfferToAccept))]
[JsonSerializable(typeof(OfferSent))]
[JsonSerializable(typeof(OfferCountersigned))]
[JsonSerializable(typeof(OnboardingStarted))]
[JsonSerializable(typeof(AcceptedOffer))]
[JsonSerializable(typeof(OfferPending))]
// offer.window.close's two. ScheduledFire is here for the reason OfferToAccept is: it is the
// flow's INPUT, and FlowHost journals an input on flow_instance.input through the generated
// dispatcher's DescribeInput — which needs a JsonTypeInfo<ScheduledFire> and can only get one
// from a context that declares it. Without this line the row that records which occurrence
// fired would be NULL, and the occurrence is the only thing distinguishing one night's
// instance from the next.
[JsonSerializable(typeof(ScheduledFire))]
[JsonSerializable(typeof(OfferWindowClosed))]
internal sealed partial class WorkflowJsonContext : JsonSerializerContext;
/// <summary>People records, in memory.</summary>
/// <remarks>
/// The capabilities depend on <see cref="IPeopleDirectory"/>, not on this. Every write is
/// keyed on the idempotency key, which is what the capabilities' <c>Idempotent = true</c>
/// promises and what makes a replayed step safe.
/// </remarks>
internal sealed class InMemoryPeopleDirectory : IPeopleDirectory
{
private readonly Dictionary<string, string> _payroll = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _agreements = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _accounts = new(StringComparer.Ordinal);
private readonly Lock _sync = new();
/// <summary>Accounts that exist right now. Read by the tests to check an unwind landed.</summary>
public int OpenAccounts
{
get
{
lock (_sync)
{
return _accounts.Count;
}
}
}
/// <summary>Payroll records that exist right now.</summary>
public int OpenPayrollRecords
{
get
{
lock (_sync)
{
return _payroll.Count;
}
}
}
public ValueTask<string> OpenPayrollAsync(string candidateId, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
if (!_payroll.TryGetValue(idempotencyKey, out var id))
{
id = "pay-" + candidateId;
_payroll[idempotencyKey] = id;
}
return ValueTask.FromResult(id);
}
}
public ValueTask ClosePayrollAsync(string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_payroll.Remove(idempotencyKey);
}
return ValueTask.CompletedTask;
}
public ValueTask<string> SignAgreementAsync(string candidateId, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
if (!_agreements.TryGetValue(idempotencyKey, out var id))
{
id = "agr-" + candidateId;
_agreements[idempotencyKey] = id;
}
return ValueTask.FromResult(id);
}
}
public ValueTask VoidAgreementAsync(string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_agreements.Remove(idempotencyKey);
}
return ValueTask.CompletedTask;
}
public ValueTask<string?> CreateAccountAsync(string candidateId, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
if (!_accounts.TryGetValue(idempotencyKey, out var upn))
{
upn = candidateId + "@example.test";
_accounts[idempotencyKey] = upn;
}
return ValueTask.FromResult<string?>(upn);
}
}
public ValueTask DisableAccountAsync(string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_accounts.Remove(idempotencyKey);
}
return ValueTask.CompletedTask;
}
}
/// <summary>Assets, in memory.</summary>
internal sealed class InMemoryAssetRegistry : IAssetRegistry
{
private readonly HashSet<string> _orders = new(StringComparer.Ordinal);
private readonly List<string> _assigned = [];
private readonly List<string> _approved = [];
private readonly Lock _sync = new();
/// <summary>Items still in someone's hands, in the order they were assigned.</summary>
public IReadOnlyList<string> Assigned
{
get
{
lock (_sync)
{
return [.. _assigned];
}
}
}
/// <summary>Items a human signed for.</summary>
public IReadOnlyList<string> Approved
{
get
{
lock (_sync)
{
return [.. _approved];
}
}
}
/// <summary>Orders still outstanding.</summary>
public int OutstandingOrders
{
get
{
lock (_sync)
{
return _orders.Count;
}
}
}
public ValueTask<string> OrderAsync(string item, string employeeId, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_orders.Add(item + ":" + idempotencyKey);
}
return ValueTask.FromResult(item + "-order");
}
public ValueTask CancelAsync(string item, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_orders.Remove(item + ":" + idempotencyKey);
}
return ValueTask.CompletedTask;
}
public ValueTask RecordApprovalAsync(string item, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_approved.Add(item);
}
return ValueTask.CompletedTask;
}
public ValueTask<string> AssignAsync(string item, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_assigned.Add(item);
}
return ValueTask.FromResult("tag-" + item);
}
public ValueTask ReturnAsync(string item, CancellationToken ct)
{
lock (_sync)
{
_assigned.Remove(item);
}
return ValueTask.CompletedTask;
}
}
/// <summary>Access, calendar and screening, in memory.</summary>
internal sealed class InMemoryAccessControl : IAccessControl
{
private readonly HashSet<string> _grants = new(StringComparer.Ordinal);
private readonly Lock _sync = new();
/// <summary>Grants still standing.</summary>
public int OutstandingGrants
{
get
{
lock (_sync)
{
return _grants.Count;
}
}
}
public ValueTask<string> GrantAsync(string upn, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_grants.Add(idempotencyKey);
}
return ValueTask.FromResult("grant-" + upn);
}
public ValueTask RevokeAsync(string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_grants.Remove(idempotencyKey);
}
return ValueTask.CompletedTask;
}
public ValueTask<string> BookInductionAsync(string employeeId, string idempotencyKey, CancellationToken ct)
=> ValueTask.FromResult("induction-" + employeeId);
public ValueTask<string> StartScreeningAsync(string employeeId, string idempotencyKey, CancellationToken ct)
=> ValueTask.FromResult("check-" + employeeId);
public ValueTask WaiveScreeningAsync(string employeeId, CancellationToken ct) => ValueTask.CompletedTask;
public ValueTask<string> PostWelcomePackAsync(string employeeId, string idempotencyKey, CancellationToken ct)
=> ValueTask.FromResult("post-" + employeeId);
}
/// <summary>Desks and passes, in memory.</summary>
internal sealed class InMemoryFacilities : IFacilities
{
private readonly Dictionary<string, string> _desks = new(StringComparer.Ordinal);
private readonly Dictionary<string, string> _passes = new(StringComparer.Ordinal);
private readonly Lock _sync = new();
private int _next;
/// <summary>Desks still held.</summary>
public int HeldDesks
{
get
{
lock (_sync)
{
return _desks.Count;
}
}
}
public ValueTask<string?> AllocateDeskAsync(string site, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
if (!_desks.TryGetValue(idempotencyKey, out var desk))
{
desk = string.Concat(site, "-desk-", (++_next).ToString(System.Globalization.CultureInfo.InvariantCulture));
_desks[idempotencyKey] = desk;
}
return ValueTask.FromResult<string?>(desk);
}
}
public ValueTask ReleaseDeskAsync(string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_desks.Remove(idempotencyKey);
}
return ValueTask.CompletedTask;
}
public ValueTask<string?> IssuePassAsync(string deskId, string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
if (!_passes.TryGetValue(idempotencyKey, out var pass))
{
pass = "pass-" + deskId;
_passes[idempotencyKey] = pass;
}
return ValueTask.FromResult<string?>(pass);
}
}
public ValueTask CancelPassAsync(string idempotencyKey, CancellationToken ct)
{
lock (_sync)
{
_passes.Remove(idempotencyKey);
}
return ValueTask.CompletedTask;
}
}