@atolis-hq/wake 0.3.77 → 0.3.79
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/src/bootstrap/composition-root.js +1 -0
- package/dist/src/bootstrap/surface-api-execution-applications.js +3 -3
- package/dist/src/bootstrap/surface-api-run-context.js +44 -0
- package/dist/src/bootstrap/surface-api-work-applications.js +2 -2
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/control-plane/application/advance-once-dispatch.js +109 -0
- package/dist/src/control-plane/application/advance-once.js +19 -74
- package/dist/src/control-plane/infrastructure/intake-host.js +2 -2
- package/dist/src/control-plane/infrastructure/tick-host.js +1 -1
- package/dist/src/integrations/delivery/application/delivery-projector.js +9 -3
- package/dist/src/surfaces/web-assets/assets/{index-CBE1yusZ.js → index-CRUtOkcs.js} +9 -9
- package/dist/src/surfaces/web-assets/index.html +1 -1
- package/package.json +1 -1
|
@@ -113,6 +113,7 @@ export async function createCompositionRoot(wakeRoot, options = {}) {
|
|
|
113
113
|
ids,
|
|
114
114
|
dispatchPolicy: new DispatchPolicy({ maxDispatches: config.controlPlane.maxDispatches }),
|
|
115
115
|
maxConcurrentRuns: config.controlPlane.maxConcurrentRuns,
|
|
116
|
+
maxDispatches: config.controlPlane.maxDispatches,
|
|
116
117
|
isDispatchPaused: isRuntimePaused,
|
|
117
118
|
workspaceRecovery: workspaces,
|
|
118
119
|
work,
|
|
@@ -4,7 +4,7 @@ import { correlationId, EventActorKind } from '../kernel/index.js';
|
|
|
4
4
|
import { ApiCommandStatus, presentRun } from '../surfaces/index.js';
|
|
5
5
|
import { projectionMeta, sampledMeta } from './surface-api-metadata.js';
|
|
6
6
|
import { projectionPage } from './surface-api-projection-pages.js';
|
|
7
|
-
import {
|
|
7
|
+
import { enrichRun } from './surface-api-run-context.js';
|
|
8
8
|
import { readWorkTranscript } from './surface-api-transcripts.js';
|
|
9
9
|
export function createExecutionApplications(root, now) {
|
|
10
10
|
return {
|
|
@@ -43,7 +43,7 @@ export function createExecutionApplications(root, now) {
|
|
|
43
43
|
});
|
|
44
44
|
return {
|
|
45
45
|
...page,
|
|
46
|
-
items: await Promise.all(page.items.map((item) =>
|
|
46
|
+
items: await Promise.all(page.items.map((item) => enrichRun(root, item))),
|
|
47
47
|
};
|
|
48
48
|
},
|
|
49
49
|
async get(runId) {
|
|
@@ -51,7 +51,7 @@ export function createExecutionApplications(root, now) {
|
|
|
51
51
|
if (stored?.value.view == null)
|
|
52
52
|
return undefined;
|
|
53
53
|
return {
|
|
54
|
-
data: await
|
|
54
|
+
data: await enrichRun(root, presentRun(stored.value.view)),
|
|
55
55
|
meta: await projectionMeta(root.journal, [stored], now()),
|
|
56
56
|
};
|
|
57
57
|
},
|
|
@@ -1,5 +1,49 @@
|
|
|
1
|
+
import { ActivityOutcomeKind } from '../activities/index.js';
|
|
2
|
+
import { DeliveryState, IntegrationStreamKind, } from '../integrations/index.js';
|
|
1
3
|
import { workflowInstanceId } from '../orchestration/index.js';
|
|
2
4
|
export async function withWorkflowContext(root, run) {
|
|
3
5
|
const instance = await root.orchestration.get(workflowInstanceId(run.workflowInstanceId));
|
|
4
6
|
return instance === null ? run : { ...run, workflowName: instance.workflowName };
|
|
5
7
|
}
|
|
8
|
+
const deliveryResolutionSentinels = {
|
|
9
|
+
[DeliveryState.Confirmed]: 'DONE',
|
|
10
|
+
[DeliveryState.Failed]: 'FAILED',
|
|
11
|
+
[DeliveryState.Ambiguous]: 'AMBIGUOUS',
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* A run whose own outcome was `waiting` on a delivery is frozen at that
|
|
15
|
+
* sentinel forever (the run itself never re-fires once terminal). Join it
|
|
16
|
+
* against the delivery intent it named to surface how that delivery has
|
|
17
|
+
* since resolved, without rewriting the run's own history.
|
|
18
|
+
*/
|
|
19
|
+
export async function withDeliveryResolution(root, run) {
|
|
20
|
+
const intentEventId = waitingDeliveryIntentEventId(run);
|
|
21
|
+
if (intentEventId === undefined)
|
|
22
|
+
return run;
|
|
23
|
+
const stored = await root.projections.read(IntegrationStreamKind.Delivery, intentEventId);
|
|
24
|
+
const delivery = stored?.value;
|
|
25
|
+
if (delivery === undefined || delivery.resolvedAt === undefined)
|
|
26
|
+
return run;
|
|
27
|
+
const sentinel = deliveryResolutionSentinels[delivery.state];
|
|
28
|
+
return sentinel === undefined
|
|
29
|
+
? run
|
|
30
|
+
: { ...run, resolution: { sentinel, resolvedAt: delivery.resolvedAt } };
|
|
31
|
+
}
|
|
32
|
+
/** Full read-time enrichment applied to every presented run. */
|
|
33
|
+
export async function enrichRun(root, run) {
|
|
34
|
+
return withDeliveryResolution(root, await withWorkflowContext(root, run));
|
|
35
|
+
}
|
|
36
|
+
function waitingDeliveryIntentEventId(run) {
|
|
37
|
+
if (typeof run.outcome !== 'object' || run.outcome === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (Reflect.get(run.outcome, 'kind') !== ActivityOutcomeKind.Waiting)
|
|
40
|
+
return undefined;
|
|
41
|
+
const data = Reflect.get(run.outcome, 'data');
|
|
42
|
+
if (typeof data !== 'object' || data === null)
|
|
43
|
+
return undefined;
|
|
44
|
+
const intentEventId = Reflect.get(data, 'intentEventId');
|
|
45
|
+
const signalKind = Reflect.get(data, 'signalKind');
|
|
46
|
+
return typeof intentEventId === 'string' && signalKind === 'delivery-result'
|
|
47
|
+
? intentEventId
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
@@ -8,7 +8,7 @@ import { workItemId, WorkStatus } from '../work/index.js';
|
|
|
8
8
|
import { primaryExternalRef } from './external-ref.js';
|
|
9
9
|
import { projectionMeta } from './surface-api-metadata.js';
|
|
10
10
|
import { projectionPage } from './surface-api-projection-pages.js';
|
|
11
|
-
import {
|
|
11
|
+
import { enrichRun } from './surface-api-run-context.js';
|
|
12
12
|
import { readWorkTranscript, transcriptGroups } from './surface-api-transcripts.js';
|
|
13
13
|
export function createSurfaceWorkApplications(root, now) {
|
|
14
14
|
return {
|
|
@@ -115,7 +115,7 @@ async function workDetail(root, key, now) {
|
|
|
115
115
|
.map(presentWorkflowInstance),
|
|
116
116
|
},
|
|
117
117
|
execution: {
|
|
118
|
-
runs: await Promise.all(runs.map((run) =>
|
|
118
|
+
runs: await Promise.all(runs.map((run) => enrichRun(root, presentRun(run)))),
|
|
119
119
|
transcriptGroups: await transcriptGroups(root.transcriptStore, id, runs),
|
|
120
120
|
},
|
|
121
121
|
activities: presentPullRequest(pullRequest?.value),
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ActivationClaimConflictError, RunStatus, WorkspaceMode } from '../../execution/index.js';
|
|
2
|
+
import { WorkflowStatus } from '../../orchestration/index.js';
|
|
3
|
+
import { isExecutionFailureTerminal } from './execution-reconciliation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fills open capacity within one Advancement call: dispatches ready
|
|
6
|
+
* activations one at a time, rechecking `maxConcurrentRuns` and reselecting
|
|
7
|
+
* fresh candidates after every dispatch (per #346's capacity-recheck
|
|
8
|
+
* principle), until capacity, the per-call `maxDispatches` burst cap, or
|
|
9
|
+
* eligible candidates are exhausted. Candidates dispatched earlier in the
|
|
10
|
+
* same call are excluded from later selection via `dispatchedIds`, since
|
|
11
|
+
* their `RunStarted` event is not guaranteed visible through
|
|
12
|
+
* `execution.list()` yet.
|
|
13
|
+
*/
|
|
14
|
+
export async function runDispatchLoop(pending, ctx) {
|
|
15
|
+
const dispatched = [];
|
|
16
|
+
const dispatchedIds = new Set();
|
|
17
|
+
let stopReason;
|
|
18
|
+
while (dispatched.length < ctx.maxDispatches) {
|
|
19
|
+
const allRuns = await ctx.execution.list();
|
|
20
|
+
if (allRuns.filter((run) => run.status === RunStatus.Started).length >= ctx.maxConcurrentRuns) {
|
|
21
|
+
stopReason = { kind: 'no-work' };
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
const selectedCandidate = ctx.dispatchPolicy.select(await Promise.all(pending.map(async (item, requestedPosition) => ({
|
|
25
|
+
workItemId: item.workflow.workItemId,
|
|
26
|
+
activationId: item.activation.activationId,
|
|
27
|
+
requestedPosition,
|
|
28
|
+
hasActiveRun: dispatchedIds.has(item.activation.activationId) ||
|
|
29
|
+
(await ctx.execution.list(item.activation.activationId)).some((run) => run.status === RunStatus.Started) ||
|
|
30
|
+
allRuns.some((run) => run.status === RunStatus.Started &&
|
|
31
|
+
run.workflowInstanceId === item.workflow.workflowInstanceId &&
|
|
32
|
+
run.workspace?.mode === WorkspaceMode.Branch),
|
|
33
|
+
cancelled: false,
|
|
34
|
+
}))))[0];
|
|
35
|
+
const selected = selectedCandidate === undefined
|
|
36
|
+
? undefined
|
|
37
|
+
: pending.find((item) => item.activation.activationId === selectedCandidate.activationId);
|
|
38
|
+
if (selected === undefined) {
|
|
39
|
+
const waiting = (await ctx.orchestration.listWaiting()).find((view) => view !== null);
|
|
40
|
+
stopReason =
|
|
41
|
+
waiting === undefined
|
|
42
|
+
? { kind: 'no-work' }
|
|
43
|
+
: { kind: WorkflowStatus.Waiting, workflowInstanceId: waiting.workflowInstanceId };
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
// Recheck at the dispatch boundary so maintenance cannot race a selected activation.
|
|
47
|
+
if (await ctx.isDispatchPaused()) {
|
|
48
|
+
stopReason = { kind: 'paused' };
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
if ((await ctx.orchestration.validateActivationDispatch?.(selected.workflow.workflowInstanceId, ctx.commandContext(selected.activation.activationId))) === false) {
|
|
52
|
+
stopReason = { kind: 'no-work' };
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
await ctx.orchestration.markActivationStarted(selected.workflow.workflowInstanceId, selected.activation.activationId, ctx.commandContext(selected.activation.activationId));
|
|
56
|
+
const correlated = await ctx.resources.correlationsForWork(selected.workflow.workItemId);
|
|
57
|
+
const resourceViews = (await Promise.all(correlated.map((entry) => ctx.resources.get(entry.resourceId)))).filter((resource) => resource !== null);
|
|
58
|
+
const ineligible = await ctx.runnerIneligibility();
|
|
59
|
+
let run;
|
|
60
|
+
try {
|
|
61
|
+
run = await ctx.execution.attempt(selected.activation, {
|
|
62
|
+
workItemId: selected.workflow.workItemId,
|
|
63
|
+
workflowInstanceId: selected.workflow.workflowInstanceId,
|
|
64
|
+
orchestrationGroupId: selected.workflow.orchestrationGroupId,
|
|
65
|
+
resources: resourceViews,
|
|
66
|
+
sessionPolicy: selected.workflow.parentWorkflowInstanceId === undefined ? 'resume-stage' : 'fresh',
|
|
67
|
+
awaitImmediateCompletion: true,
|
|
68
|
+
...(ineligible.size === 0 ? {} : { ineligibleRunners: ineligible }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof ActivationClaimConflictError) {
|
|
73
|
+
stopReason = { kind: 'no-work' };
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
if (run.status === RunStatus.Succeeded && run.outcome !== undefined) {
|
|
79
|
+
if (await ctx.isDispatchPaused()) {
|
|
80
|
+
stopReason = { kind: 'paused' };
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
await ctx.orchestration.acceptOutcome({
|
|
84
|
+
workflowInstanceId: selected.workflow.workflowInstanceId,
|
|
85
|
+
activationId: selected.activation.activationId,
|
|
86
|
+
outcome: run.outcome,
|
|
87
|
+
}, ctx.commandContext(run.runId));
|
|
88
|
+
}
|
|
89
|
+
if (isExecutionFailureTerminal(run.status))
|
|
90
|
+
await ctx.orchestration.resolveExecutionFailure?.(selected.workflow.workflowInstanceId, {
|
|
91
|
+
activationId: selected.activation.activationId,
|
|
92
|
+
runId: run.runId,
|
|
93
|
+
reason: run.failure?.message ?? 'execution failed',
|
|
94
|
+
}, ctx.commandContext(run.runId));
|
|
95
|
+
if (run.status !== RunStatus.Succeeded && run.status !== RunStatus.Started) {
|
|
96
|
+
stopReason = {
|
|
97
|
+
kind: WorkflowStatus.Blocked,
|
|
98
|
+
workflowInstanceId: selected.workflow.workflowInstanceId,
|
|
99
|
+
reason: run.failure?.message ?? 'execution failed',
|
|
100
|
+
};
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
dispatched.push({ activationId: selected.activation.activationId, runId: run.runId });
|
|
104
|
+
dispatchedIds.add(selected.activation.activationId);
|
|
105
|
+
}
|
|
106
|
+
return dispatched.length > 0
|
|
107
|
+
? { kind: 'progressed', dispatched }
|
|
108
|
+
: (stopReason ?? { kind: 'no-work' });
|
|
109
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable complexity, max-lines-per-function */
|
|
2
|
+
import { RunStatus } from '../../execution/index.js';
|
|
2
3
|
import { correlationId, EventActorKind } from '../../kernel/index.js';
|
|
3
4
|
import { isAmbiguityResolutionBlock, WorkflowStatus } from '../../orchestration/index.js';
|
|
4
5
|
import { WorkStatus } from '../../work/index.js';
|
|
5
6
|
import { ControlStreamKind } from '../contracts/streams.js';
|
|
6
7
|
import { DispatchPolicy } from '../domain/dispatch-policy.js';
|
|
7
|
-
import {
|
|
8
|
+
import { runDispatchLoop } from './advance-once-dispatch.js';
|
|
9
|
+
import { findUnresolvedSucceededTerminal, findUnresolvedTerminal, } from './execution-reconciliation.js';
|
|
8
10
|
export function createAdvanceOnce(orchestration, execution, resources, clock, dependencies) {
|
|
9
11
|
const runnerIneligibility = dependencies.runnerIneligibility ?? (async () => new Set());
|
|
10
12
|
const isDispatchPaused = dependencies.isDispatchPaused ?? (async () => false);
|
|
@@ -12,6 +14,7 @@ export function createAdvanceOnce(orchestration, execution, resources, clock, de
|
|
|
12
14
|
const transcriptRetention = dependencies.transcriptRetention;
|
|
13
15
|
const dispatchPolicy = dependencies.dispatchPolicy ?? new DispatchPolicy({ maxDispatches: 1 });
|
|
14
16
|
const maxConcurrentRuns = dependencies.maxConcurrentRuns ?? 1;
|
|
17
|
+
const maxDispatches = dependencies.maxDispatches ?? 1;
|
|
15
18
|
const context = (cause) => ({
|
|
16
19
|
commandId: dependencies.ids.next('command'),
|
|
17
20
|
correlationId: correlationId(cause),
|
|
@@ -100,8 +103,9 @@ export function createAdvanceOnce(orchestration, execution, resources, clock, de
|
|
|
100
103
|
}, context(recovery.run.runId));
|
|
101
104
|
return {
|
|
102
105
|
kind: 'progressed',
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
dispatched: [
|
|
107
|
+
{ activationId: recovery.item.activation.activationId, runId: recovery.run.runId },
|
|
108
|
+
],
|
|
105
109
|
};
|
|
106
110
|
}
|
|
107
111
|
await orchestration.resolveExecutionFailure?.(recovery.item.workflow.workflowInstanceId, {
|
|
@@ -115,76 +119,17 @@ export function createAdvanceOnce(orchestration, execution, resources, clock, de
|
|
|
115
119
|
reason: recovery.run.failure?.message ?? 'execution failed',
|
|
116
120
|
};
|
|
117
121
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
cancelled: false,
|
|
130
|
-
}))))[0];
|
|
131
|
-
const selected = selectedCandidate === undefined
|
|
132
|
-
? undefined
|
|
133
|
-
: pending.find((item) => item.activation.activationId === selectedCandidate.activationId);
|
|
134
|
-
if (selected === undefined) {
|
|
135
|
-
const waiting = (await orchestration.listWaiting()).find((view) => view !== null);
|
|
136
|
-
return waiting === undefined
|
|
137
|
-
? { kind: 'no-work' }
|
|
138
|
-
: { kind: WorkflowStatus.Waiting, workflowInstanceId: waiting.workflowInstanceId };
|
|
139
|
-
}
|
|
140
|
-
// Recheck at the dispatch boundary so maintenance cannot race a selected activation.
|
|
141
|
-
if (await isDispatchPaused())
|
|
142
|
-
return { kind: 'paused' };
|
|
143
|
-
if ((await orchestration.validateActivationDispatch?.(selected.workflow.workflowInstanceId, context(selected.activation.activationId))) === false)
|
|
144
|
-
return { kind: 'no-work' };
|
|
145
|
-
await orchestration.markActivationStarted(selected.workflow.workflowInstanceId, selected.activation.activationId, context(selected.activation.activationId));
|
|
146
|
-
const correlated = await resources.correlationsForWork(selected.workflow.workItemId);
|
|
147
|
-
const resourceViews = (await Promise.all(correlated.map((entry) => resources.get(entry.resourceId)))).filter((resource) => resource !== null);
|
|
148
|
-
const ineligible = await runnerIneligibility();
|
|
149
|
-
let run;
|
|
150
|
-
try {
|
|
151
|
-
run = await execution.attempt(selected.activation, {
|
|
152
|
-
workItemId: selected.workflow.workItemId,
|
|
153
|
-
workflowInstanceId: selected.workflow.workflowInstanceId,
|
|
154
|
-
orchestrationGroupId: selected.workflow.orchestrationGroupId,
|
|
155
|
-
resources: resourceViews,
|
|
156
|
-
sessionPolicy: selected.workflow.parentWorkflowInstanceId === undefined ? 'resume-stage' : 'fresh',
|
|
157
|
-
awaitImmediateCompletion: true,
|
|
158
|
-
...(ineligible.size === 0 ? {} : { ineligibleRunners: ineligible }),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
if (error instanceof ActivationClaimConflictError)
|
|
163
|
-
return { kind: 'no-work' };
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
if (run.status === RunStatus.Succeeded && run.outcome !== undefined) {
|
|
167
|
-
if (await isDispatchPaused())
|
|
168
|
-
return { kind: 'paused' };
|
|
169
|
-
await orchestration.acceptOutcome({
|
|
170
|
-
workflowInstanceId: selected.workflow.workflowInstanceId,
|
|
171
|
-
activationId: selected.activation.activationId,
|
|
172
|
-
outcome: run.outcome,
|
|
173
|
-
}, context(run.runId));
|
|
174
|
-
}
|
|
175
|
-
if (isExecutionFailureTerminal(run.status))
|
|
176
|
-
await orchestration.resolveExecutionFailure?.(selected.workflow.workflowInstanceId, {
|
|
177
|
-
activationId: selected.activation.activationId,
|
|
178
|
-
runId: run.runId,
|
|
179
|
-
reason: run.failure?.message ?? 'execution failed',
|
|
180
|
-
}, context(run.runId));
|
|
181
|
-
return run.status === RunStatus.Succeeded || run.status === RunStatus.Started
|
|
182
|
-
? { kind: 'progressed', activationId: selected.activation.activationId, runId: run.runId }
|
|
183
|
-
: {
|
|
184
|
-
kind: WorkflowStatus.Blocked,
|
|
185
|
-
workflowInstanceId: selected.workflow.workflowInstanceId,
|
|
186
|
-
reason: run.failure?.message ?? 'execution failed',
|
|
187
|
-
};
|
|
122
|
+
return runDispatchLoop(pending, {
|
|
123
|
+
orchestration,
|
|
124
|
+
execution,
|
|
125
|
+
resources,
|
|
126
|
+
dispatchPolicy,
|
|
127
|
+
maxConcurrentRuns,
|
|
128
|
+
maxDispatches,
|
|
129
|
+
runnerIneligibility,
|
|
130
|
+
isDispatchPaused,
|
|
131
|
+
commandContext: context,
|
|
132
|
+
});
|
|
188
133
|
};
|
|
189
134
|
// Tick, resident, and API callers share this advancement instance. Serialize the
|
|
190
135
|
// capacity check through Run creation so concurrent callers cannot over-dispatch.
|
|
@@ -2,8 +2,8 @@ import { HostStopReason } from '../contracts/commands.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Bounded host for IntakePipeline. Unlike TickHost, one cycle is always
|
|
4
4
|
* exactly one poll-and-translate pass — there's no Advancement to loop
|
|
5
|
-
* within a budget, and AdvanceResult's `progressed` variant requires
|
|
6
|
-
*
|
|
5
|
+
* within a budget, and AdvanceResult's `progressed` variant requires a
|
|
6
|
+
* dispatched batch that intake has no honest value for. ResidentHost
|
|
7
7
|
* only needs `advances > 0` to decide whether its next sleep resets to the
|
|
8
8
|
* fast end of backoff, which `processed` maps onto directly.
|
|
9
9
|
*/
|
|
@@ -161,20 +161,26 @@ function foldDeliveryFact(previous, delivery) {
|
|
|
161
161
|
case DeliveryEventType.AttemptStarted:
|
|
162
162
|
return { ...current, attempts: current.attempts + 1 };
|
|
163
163
|
case DeliveryEventType.Confirmed:
|
|
164
|
-
return { ...current, state: DeliveryState.Confirmed };
|
|
164
|
+
return { ...current, state: DeliveryState.Confirmed, resolvedAt: delivery.occurredAt };
|
|
165
165
|
case DeliveryEventType.Failed:
|
|
166
|
-
return { ...current, state: DeliveryState.Failed };
|
|
166
|
+
return { ...current, state: DeliveryState.Failed, resolvedAt: delivery.occurredAt };
|
|
167
167
|
case DeliveryEventType.Ambiguous:
|
|
168
168
|
return {
|
|
169
169
|
...current,
|
|
170
170
|
state: DeliveryState.Ambiguous,
|
|
171
|
+
resolvedAt: delivery.occurredAt,
|
|
171
172
|
reconciliationKey: delivery.payload.reconciliationKey,
|
|
172
173
|
};
|
|
173
174
|
case DeliveryEventType.Escalated:
|
|
174
175
|
return { ...current, escalation: { reason: delivery.payload.reason } };
|
|
175
176
|
case DeliveryEventType.Reconciled:
|
|
176
177
|
if (delivery.payload.result === DeliveryResultKind.Confirmed)
|
|
177
|
-
return {
|
|
178
|
+
return {
|
|
179
|
+
...current,
|
|
180
|
+
state: DeliveryState.Confirmed,
|
|
181
|
+
resolvedAt: delivery.occurredAt,
|
|
182
|
+
escalation: undefined,
|
|
183
|
+
};
|
|
178
184
|
return delivery.payload.result === DeliveryResultKind.Unknown
|
|
179
185
|
? { ...current, reconciliationAttempts: (current.reconciliationAttempts ?? 0) + 1 }
|
|
180
186
|
: current;
|