@forestadmin/workflow-executor 1.33.1 → 1.34.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/adapters/agent-client-agent-port.js +6 -8
- package/dist/adapters/agent-client-segment-reader.d.ts +24 -0
- package/dist/adapters/agent-client-segment-reader.js +182 -0
- package/dist/adapters/agent-errors.d.ts +11 -0
- package/dist/adapters/agent-errors.js +81 -0
- package/dist/adapters/forest-server-automation-port.d.ts +22 -0
- package/dist/adapters/forest-server-automation-port.js +174 -0
- package/dist/adapters/forest-server-workflow-port.d.ts +3 -1
- package/dist/adapters/forest-server-workflow-port.js +47 -21
- package/dist/adapters/forestadmin-client-activity-log-port.js +3 -3
- package/dist/adapters/project-timezone.d.ts +2 -0
- package/dist/adapters/project-timezone.js +11 -0
- package/dist/adapters/run-to-available-step-mapper.js +7 -16
- package/dist/adapters/server-types.d.ts +119 -1
- package/dist/adapters/server-types.js +104 -2
- package/dist/adapters/step-user.d.ts +23 -0
- package/dist/adapters/step-user.js +33 -0
- package/dist/automation/automation-poller.d.ts +45 -0
- package/dist/automation/automation-poller.js +201 -0
- package/dist/automation/inbox-poll.d.ts +47 -0
- package/dist/automation/inbox-poll.js +357 -0
- package/dist/automation/lease-keeper.d.ts +12 -0
- package/dist/automation/lease-keeper.js +30 -0
- package/dist/automation/read-failure.d.ts +5 -0
- package/dist/automation/read-failure.js +40 -0
- package/dist/automation/reconciliation.d.ts +13 -0
- package/dist/automation/reconciliation.js +88 -0
- package/dist/build-workflow-executor.d.ts +2 -0
- package/dist/build-workflow-executor.js +62 -4
- package/dist/cli-core.js +7 -1
- package/dist/defaults.d.ts +2 -0
- package/dist/defaults.js +10 -2
- package/dist/errors.d.ts +30 -2
- package/dist/errors.js +56 -26
- package/dist/http/pending-data-validators.js +3 -3
- package/dist/http/step-serializer.js +5 -5
- package/dist/ports/automation-port.d.ts +19 -0
- package/dist/ports/automation-port.js +3 -0
- package/dist/ports/segment-reader-port.d.ts +43 -0
- package/dist/ports/segment-reader-port.js +3 -0
- package/dist/ports/workflow-port.d.ts +3 -1
- package/dist/{adapters/record-id-serializer.d.ts → record-id.d.ts} +2 -2
- package/dist/record-id.js +20 -0
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +59 -12
- package/dist/types/automation.d.ts +64 -0
- package/dist/types/automation.js +3 -0
- package/dist/types/validated/execution.d.ts +2 -1
- package/dist/types/validated/execution.js +2 -1
- package/package.json +6 -4
- package/dist/adapters/record-id-serializer.js +0 -20
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Contains both step-level types (workflow step variants) and the run envelope
|
|
6
6
|
* (HydratedWorkflowRun + user profile + step history).
|
|
7
7
|
*/
|
|
8
|
+
import { z } from 'zod';
|
|
8
9
|
export interface ServerWorkflowTransition {
|
|
9
10
|
stepId: string;
|
|
10
11
|
buttonText: string | null;
|
|
@@ -172,7 +173,8 @@ export type ServerWorkflowRunState = 'started' | 'pending' | 'loading' | 'aborte
|
|
|
172
173
|
export declare enum ServerWorkflowTriggerType {
|
|
173
174
|
manual = "manual",
|
|
174
175
|
webhook = "webhook",
|
|
175
|
-
mcp = "mcp"
|
|
176
|
+
mcp = "mcp",
|
|
177
|
+
dataChange = "dataChange"
|
|
176
178
|
}
|
|
177
179
|
export interface ServerHydratedWorkflowRun {
|
|
178
180
|
id: number;
|
|
@@ -222,5 +224,121 @@ export interface ServerUpdateStepRequest {
|
|
|
222
224
|
stepUpdate: ServerStepUpdate;
|
|
223
225
|
executionStatus: ServerExecutionStatus;
|
|
224
226
|
}
|
|
227
|
+
export interface ServerReleaseRunRequest {
|
|
228
|
+
runId: number;
|
|
229
|
+
lockedAt: string;
|
|
230
|
+
}
|
|
231
|
+
export type ServerPlainConditionTree = {
|
|
232
|
+
field: string;
|
|
233
|
+
operator: string;
|
|
234
|
+
value?: unknown;
|
|
235
|
+
} | {
|
|
236
|
+
aggregator: string;
|
|
237
|
+
conditions: ServerPlainConditionTree[];
|
|
238
|
+
};
|
|
239
|
+
export declare const ServerAutomatedSegmentDescriptorSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
240
|
+
kind: z.ZodLiteral<"smart">;
|
|
241
|
+
name: z.ZodString;
|
|
242
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
243
|
+
kind: z.ZodLiteral<"sql">;
|
|
244
|
+
query: z.ZodString;
|
|
245
|
+
connectionName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
246
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
247
|
+
kind: z.ZodLiteral<"filter">;
|
|
248
|
+
conditionTree: z.ZodType<ServerPlainConditionTree, unknown, z.core.$ZodTypeInternals<ServerPlainConditionTree, unknown>>;
|
|
249
|
+
}, z.core.$strip>], "kind">;
|
|
250
|
+
export type ServerAutomatedSegmentDescriptor = z.infer<typeof ServerAutomatedSegmentDescriptorSchema>;
|
|
251
|
+
export declare const ServerAutomatedInboxServiceAccountProfileSchema: z.ZodObject<{
|
|
252
|
+
id: z.ZodNumber;
|
|
253
|
+
email: z.ZodString;
|
|
254
|
+
firstName: z.ZodNullable<z.ZodString>;
|
|
255
|
+
lastName: z.ZodNullable<z.ZodString>;
|
|
256
|
+
team: z.ZodNullable<z.ZodString>;
|
|
257
|
+
renderingId: z.ZodNumber;
|
|
258
|
+
role: z.ZodNullable<z.ZodString>;
|
|
259
|
+
permissionLevel: z.ZodNullable<z.ZodString>;
|
|
260
|
+
tags: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
261
|
+
}, z.core.$strip>;
|
|
262
|
+
export type ServerAutomatedInboxServiceAccountProfile = z.infer<typeof ServerAutomatedInboxServiceAccountProfileSchema>;
|
|
263
|
+
export declare const ServerAutomatedInboxConfigSchema: z.ZodObject<{
|
|
264
|
+
inboxId: z.ZodString;
|
|
265
|
+
renderingId: z.ZodOptional<z.ZodNumber>;
|
|
266
|
+
workflowId: z.ZodOptional<z.ZodString>;
|
|
267
|
+
collectionName: z.ZodString;
|
|
268
|
+
primaryKeys: z.ZodArray<z.ZodString>;
|
|
269
|
+
maxConcurrentRuns: z.ZodNumber;
|
|
270
|
+
timezone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
271
|
+
liana: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
272
|
+
lianaVersion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
273
|
+
segment: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
274
|
+
kind: z.ZodLiteral<"smart">;
|
|
275
|
+
name: z.ZodString;
|
|
276
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
277
|
+
kind: z.ZodLiteral<"sql">;
|
|
278
|
+
query: z.ZodString;
|
|
279
|
+
connectionName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
280
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
281
|
+
kind: z.ZodLiteral<"filter">;
|
|
282
|
+
conditionTree: z.ZodType<ServerPlainConditionTree, unknown, z.core.$ZodTypeInternals<ServerPlainConditionTree, unknown>>;
|
|
283
|
+
}, z.core.$strip>], "kind">;
|
|
284
|
+
sort: z.ZodCatch<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
285
|
+
field: z.ZodString;
|
|
286
|
+
ascending: z.ZodBoolean;
|
|
287
|
+
}, z.core.$strip>>>>;
|
|
288
|
+
serviceAccountProfile: z.ZodObject<{
|
|
289
|
+
id: z.ZodNumber;
|
|
290
|
+
email: z.ZodString;
|
|
291
|
+
firstName: z.ZodNullable<z.ZodString>;
|
|
292
|
+
lastName: z.ZodNullable<z.ZodString>;
|
|
293
|
+
team: z.ZodNullable<z.ZodString>;
|
|
294
|
+
renderingId: z.ZodNumber;
|
|
295
|
+
role: z.ZodNullable<z.ZodString>;
|
|
296
|
+
permissionLevel: z.ZodNullable<z.ZodString>;
|
|
297
|
+
tags: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
298
|
+
}, z.core.$strip>;
|
|
299
|
+
}, z.core.$strip>;
|
|
300
|
+
export type ServerAutomatedInboxConfig = z.infer<typeof ServerAutomatedInboxConfigSchema>;
|
|
301
|
+
export declare const ServerAutomatedInboxesResponseSchema: z.ZodObject<{
|
|
302
|
+
inboxes: z.ZodArray<z.ZodUnknown>;
|
|
303
|
+
}, z.core.$strip>;
|
|
304
|
+
export declare const ServerAutomationLeaseResponseSchema: z.ZodObject<{
|
|
305
|
+
held: z.ZodBoolean;
|
|
306
|
+
}, z.core.$strip>;
|
|
307
|
+
export declare const ServerAutomatedInboxAssignmentSchema: z.ZodObject<{
|
|
308
|
+
recordId: z.ZodString;
|
|
309
|
+
state: z.ZodString;
|
|
310
|
+
workflowRunId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
311
|
+
runState: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
312
|
+
}, z.core.$strip>;
|
|
313
|
+
export type ServerAutomatedInboxAssignment = z.infer<typeof ServerAutomatedInboxAssignmentSchema>;
|
|
314
|
+
export declare const ServerAutomatedInboxAssignmentsResponseSchema: z.ZodObject<{
|
|
315
|
+
assignments: z.ZodArray<z.ZodObject<{
|
|
316
|
+
recordId: z.ZodString;
|
|
317
|
+
state: z.ZodString;
|
|
318
|
+
workflowRunId: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
319
|
+
runState: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
320
|
+
}, z.core.$strip>>;
|
|
321
|
+
}, z.core.$strip>;
|
|
322
|
+
export declare const SERVER_AUTOMATED_INBOX_READ_FAILURE_REASONS: readonly ["agent-forbidden", "agent-unreachable", "segment-read-failed"];
|
|
323
|
+
export type ServerAutomatedInboxReadFailureReason = (typeof SERVER_AUTOMATED_INBOX_READ_FAILURE_REASONS)[number];
|
|
324
|
+
export interface ServerAutomatedInboxReadFailure {
|
|
325
|
+
reason: ServerAutomatedInboxReadFailureReason;
|
|
326
|
+
httpStatus?: number;
|
|
327
|
+
}
|
|
328
|
+
export interface ServerAutomatedInboxSyncRequest {
|
|
329
|
+
closed: {
|
|
330
|
+
recordId: string;
|
|
331
|
+
stillInSegment: boolean;
|
|
332
|
+
}[];
|
|
333
|
+
candidates: string[];
|
|
334
|
+
readFailure?: ServerAutomatedInboxReadFailure;
|
|
335
|
+
}
|
|
336
|
+
export declare const ServerAutomatedInboxSyncResponseSchema: z.ZodObject<{
|
|
337
|
+
results: z.ZodArray<z.ZodObject<{
|
|
338
|
+
recordId: z.ZodString;
|
|
339
|
+
outcome: z.ZodString;
|
|
340
|
+
}, z.core.$strip>>;
|
|
341
|
+
}, z.core.$strip>;
|
|
342
|
+
export type ServerAutomatedInboxSyncResponse = z.infer<typeof ServerAutomatedInboxSyncResponseSchema>;
|
|
225
343
|
export {};
|
|
226
344
|
//# sourceMappingURL=server-types.d.ts.map
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* (HydratedWorkflowRun + user profile + step history).
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.ServerWorkflowTriggerType = exports.ServerStepExecutionTypeEnum = exports.ServerTaskTypeEnum = exports.ServerStepTypeEnum = void 0;
|
|
10
|
+
exports.ServerAutomatedInboxSyncResponseSchema = exports.SERVER_AUTOMATED_INBOX_READ_FAILURE_REASONS = exports.ServerAutomatedInboxAssignmentsResponseSchema = exports.ServerAutomatedInboxAssignmentSchema = exports.ServerAutomationLeaseResponseSchema = exports.ServerAutomatedInboxesResponseSchema = exports.ServerAutomatedInboxConfigSchema = exports.ServerAutomatedInboxServiceAccountProfileSchema = exports.ServerAutomatedSegmentDescriptorSchema = exports.ServerWorkflowTriggerType = exports.ServerStepExecutionTypeEnum = exports.ServerTaskTypeEnum = exports.ServerStepTypeEnum = void 0;
|
|
11
|
+
const zod_1 = require("zod");
|
|
11
12
|
var ServerStepTypeEnum;
|
|
12
13
|
(function (ServerStepTypeEnum) {
|
|
13
14
|
ServerStepTypeEnum["Task"] = "task";
|
|
@@ -37,5 +38,106 @@ var ServerWorkflowTriggerType;
|
|
|
37
38
|
ServerWorkflowTriggerType["manual"] = "manual";
|
|
38
39
|
ServerWorkflowTriggerType["webhook"] = "webhook";
|
|
39
40
|
ServerWorkflowTriggerType["mcp"] = "mcp";
|
|
41
|
+
ServerWorkflowTriggerType["dataChange"] = "dataChange";
|
|
40
42
|
})(ServerWorkflowTriggerType || (exports.ServerWorkflowTriggerType = ServerWorkflowTriggerType = {}));
|
|
41
|
-
|
|
43
|
+
// Branch first, deliberately: a node carrying both `conditions` and `field` is ambiguous, and the
|
|
44
|
+
// leaf schema would match it and strip the conditions, leaving a filter that reads a different
|
|
45
|
+
// segment than the one configured. agent-client's own `toWireFilter` resolves it the same way.
|
|
46
|
+
//
|
|
47
|
+
// Both discriminators are required and a branch may not be empty. The agent reads `And` over
|
|
48
|
+
// nothing as matching every record, so a tree this schema let through half-formed would widen the
|
|
49
|
+
// segment to the whole collection rather than fail — the one failure mode worth a rejected inbox.
|
|
50
|
+
const ServerPlainConditionTreeSchema = zod_1.z.lazy(() => zod_1.z.union([
|
|
51
|
+
zod_1.z.object({
|
|
52
|
+
aggregator: zod_1.z.string().min(1),
|
|
53
|
+
conditions: zod_1.z.array(ServerPlainConditionTreeSchema).min(1),
|
|
54
|
+
}),
|
|
55
|
+
// `value` is optional, not merely `unknown`: zod requires an `unknown` key to be present, and
|
|
56
|
+
// the operators the agent evaluates on its own — `present`, `blank`, `today`, every
|
|
57
|
+
// `previous_*` — are emitted as `{ field, operator }` with no value at all.
|
|
58
|
+
zod_1.z.object({
|
|
59
|
+
field: zod_1.z.string().min(1),
|
|
60
|
+
operator: zod_1.z.string().min(1),
|
|
61
|
+
value: zod_1.z.unknown().optional(),
|
|
62
|
+
}),
|
|
63
|
+
]));
|
|
64
|
+
exports.ServerAutomatedSegmentDescriptorSchema = zod_1.z.discriminatedUnion('kind', [
|
|
65
|
+
zod_1.z.object({ kind: zod_1.z.literal('smart'), name: zod_1.z.string().min(1) }),
|
|
66
|
+
zod_1.z.object({
|
|
67
|
+
kind: zod_1.z.literal('sql'),
|
|
68
|
+
query: zod_1.z.string().min(1),
|
|
69
|
+
// Null on the lianas that run a bare `segmentQuery` themselves (forest-rails,
|
|
70
|
+
// forest-express-sequelize). The server never emits a `sql` descriptor without one for a v2
|
|
71
|
+
// agent — it degrades the inbox instead.
|
|
72
|
+
connectionName: zod_1.z.string().nullish(),
|
|
73
|
+
}),
|
|
74
|
+
zod_1.z.object({ kind: zod_1.z.literal('filter'), conditionTree: ServerPlainConditionTreeSchema }),
|
|
75
|
+
]);
|
|
76
|
+
// `ServerUserProfile` without `serverToken`: there is no run yet, and the poller only reaches the
|
|
77
|
+
// SaaS with the environment secret. The executor mints its agent JWT from this profile, through the
|
|
78
|
+
// same `toStepUser` the run envelope goes through — which is what keeps the two shapes tied.
|
|
79
|
+
exports.ServerAutomatedInboxServiceAccountProfileSchema = zod_1.z.object({
|
|
80
|
+
id: zod_1.z.number(),
|
|
81
|
+
email: zod_1.z.string(),
|
|
82
|
+
firstName: zod_1.z.string().nullable(),
|
|
83
|
+
lastName: zod_1.z.string().nullable(),
|
|
84
|
+
team: zod_1.z.string().nullable(),
|
|
85
|
+
renderingId: zod_1.z.number().int().nonnegative(),
|
|
86
|
+
role: zod_1.z.string().nullable(),
|
|
87
|
+
permissionLevel: zod_1.z.string().nullable(),
|
|
88
|
+
tags: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
|
|
89
|
+
});
|
|
90
|
+
// Only what the poller reads is required. A field it merely logs must never be the reason an inbox
|
|
91
|
+
// is dropped from the sweep, and `.nullable()` alone would still reject an omitted key.
|
|
92
|
+
exports.ServerAutomatedInboxConfigSchema = zod_1.z.object({
|
|
93
|
+
inboxId: zod_1.z.string().min(1),
|
|
94
|
+
renderingId: zod_1.z.number().int().nonnegative().optional(),
|
|
95
|
+
workflowId: zod_1.z.string().min(1).optional(),
|
|
96
|
+
collectionName: zod_1.z.string().min(1),
|
|
97
|
+
primaryKeys: zod_1.z.array(zod_1.z.string().min(1)).min(1),
|
|
98
|
+
maxConcurrentRuns: zod_1.z.number().int().positive(),
|
|
99
|
+
timezone: zod_1.z.string().nullish(),
|
|
100
|
+
// Which agent answers the segment read. Absent on an orchestrator that predates the exclusion
|
|
101
|
+
// filter, which reads as unknown: the poller then pads its page, as it always did.
|
|
102
|
+
liana: zod_1.z.string().nullish(),
|
|
103
|
+
// Absent on an orchestrator that predates it, which reads as a version too old to sort on several
|
|
104
|
+
// fields.
|
|
105
|
+
lianaVersion: zod_1.z.string().nullish(),
|
|
106
|
+
segment: exports.ServerAutomatedSegmentDescriptorSchema,
|
|
107
|
+
// A sort the executor cannot read costs the inbox its order, never the inbox itself.
|
|
108
|
+
sort: zod_1.z
|
|
109
|
+
.array(zod_1.z.object({ field: zod_1.z.string().min(1), ascending: zod_1.z.boolean() }))
|
|
110
|
+
.min(1)
|
|
111
|
+
.optional()
|
|
112
|
+
.catch(undefined),
|
|
113
|
+
serviceAccountProfile: exports.ServerAutomatedInboxServiceAccountProfileSchema,
|
|
114
|
+
});
|
|
115
|
+
exports.ServerAutomatedInboxesResponseSchema = zod_1.z.object({
|
|
116
|
+
inboxes: zod_1.z.array(zod_1.z.unknown()),
|
|
117
|
+
});
|
|
118
|
+
exports.ServerAutomationLeaseResponseSchema = zod_1.z.object({ held: zod_1.z.boolean() });
|
|
119
|
+
// States are read as plain strings, not enums: the poller only ever tests set membership, and one
|
|
120
|
+
// assignment in a state a newer orchestrator introduced must not take the whole inbox down.
|
|
121
|
+
exports.ServerAutomatedInboxAssignmentSchema = zod_1.z.object({
|
|
122
|
+
recordId: zod_1.z.string(),
|
|
123
|
+
state: zod_1.z.string(),
|
|
124
|
+
workflowRunId: zod_1.z.number().nullish(),
|
|
125
|
+
runState: zod_1.z.string().nullish(),
|
|
126
|
+
});
|
|
127
|
+
exports.ServerAutomatedInboxAssignmentsResponseSchema = zod_1.z.object({
|
|
128
|
+
assignments: zod_1.z.array(exports.ServerAutomatedInboxAssignmentSchema),
|
|
129
|
+
});
|
|
130
|
+
exports.SERVER_AUTOMATED_INBOX_READ_FAILURE_REASONS = [
|
|
131
|
+
'agent-forbidden',
|
|
132
|
+
'agent-unreachable',
|
|
133
|
+
'segment-read-failed',
|
|
134
|
+
];
|
|
135
|
+
exports.ServerAutomatedInboxSyncResponseSchema = zod_1.z.object({
|
|
136
|
+
results: zod_1.z.array(zod_1.z.object({
|
|
137
|
+
recordId: zod_1.z.string(),
|
|
138
|
+
// Unknown outcomes are kept as-is: the poller only counts them for logs, and refusing the
|
|
139
|
+
// whole response would stop a sync the server already applied.
|
|
140
|
+
outcome: zod_1.z.string(),
|
|
141
|
+
})),
|
|
142
|
+
});
|
|
143
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmVyLXR5cGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FkYXB0ZXJzL3NlcnZlci10eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7OztHQU1HOzs7QUFFSCw2QkFBd0I7QUFTeEIsSUFBWSxrQkFPWDtBQVBELFdBQVksa0JBQWtCO0lBQzVCLG1DQUFhLENBQUE7SUFDYiw2Q0FBdUIsQ0FBQTtJQUN2QixpQ0FBVyxDQUFBO0lBQ1gsK0NBQXlCLENBQUE7SUFDekIsNkRBQXVDLENBQUE7SUFDdkMsNkRBQXVDLENBQUE7QUFDekMsQ0FBQyxFQVBXLGtCQUFrQixrQ0FBbEIsa0JBQWtCLFFBTzdCO0FBRUQsSUFBWSxrQkFPWDtBQVBELFdBQVksa0JBQWtCO0lBQzVCLDZDQUF1QixDQUFBO0lBQ3ZCLHNEQUFnQyxDQUFBO0lBQ2hDLDBDQUFvQixDQUFBO0lBQ3BCLGdEQUEwQixDQUFBO0lBQzFCLCtEQUF5QyxDQUFBO0lBQ3pDLDhDQUF3QixDQUFBO0FBQzFCLENBQUMsRUFQVyxrQkFBa0Isa0NBQWxCLGtCQUFrQixRQU83QjtBQUVELElBQVksMkJBSVg7QUFKRCxXQUFZLDJCQUEyQjtJQUNyQyxnREFBaUIsQ0FBQTtJQUNqQix3RkFBeUQsQ0FBQTtJQUN6RCxpRUFBa0MsQ0FBQTtBQUNwQyxDQUFDLEVBSlcsMkJBQTJCLDJDQUEzQiwyQkFBMkIsUUFJdEM7QUE4S0QsSUFBWSx5QkFLWDtBQUxELFdBQVkseUJBQXlCO0lBQ25DLDhDQUFpQixDQUFBO0lBQ2pCLGdEQUFtQixDQUFBO0lBQ25CLHdDQUFXLENBQUE7SUFDWCxzREFBeUIsQ0FBQTtBQUMzQixDQUFDLEVBTFcseUJBQXlCLHlDQUF6Qix5QkFBeUIsUUFLcEM7QUFtRUQsa0dBQWtHO0FBQ2xHLCtGQUErRjtBQUMvRiwrRkFBK0Y7QUFDL0YsRUFBRTtBQUNGLDZGQUE2RjtBQUM3RixrR0FBa0c7QUFDbEcsa0dBQWtHO0FBQ2xHLE1BQU0sOEJB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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StepUser } from '../types/execution-context';
|
|
2
|
+
/**
|
|
3
|
+
* What a run envelope's `userProfile` and an automated inbox's service-account profile have in
|
|
4
|
+
* common. The optional strings are what the server actually sends as null.
|
|
5
|
+
*/
|
|
6
|
+
export interface StepUserProfile {
|
|
7
|
+
id: number;
|
|
8
|
+
email: string;
|
|
9
|
+
firstName?: string | null;
|
|
10
|
+
lastName?: string | null;
|
|
11
|
+
team?: string | null;
|
|
12
|
+
renderingId: number;
|
|
13
|
+
role?: string | null;
|
|
14
|
+
permissionLevel?: string | null;
|
|
15
|
+
tags: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Shared so a run-driven step and an automation poll reach the agent as the exact same caller: the
|
|
19
|
+
* JWT they mint has to carry identical claims, or the same account would be two callers.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toStepUser(profile: StepUserProfile): StepUser;
|
|
22
|
+
export declare function mintStepToken(user: StepUser, authSecret: string): string;
|
|
23
|
+
//# sourceMappingURL=step-user.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.toStepUser = toStepUser;
|
|
7
|
+
exports.mintStepToken = mintStepToken;
|
|
8
|
+
const agent_client_1 = require("@forestadmin/agent-client");
|
|
9
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
10
|
+
const AGENT_TOKEN_TTL = '5m';
|
|
11
|
+
/**
|
|
12
|
+
* Shared so a run-driven step and an automation poll reach the agent as the exact same caller: the
|
|
13
|
+
* JWT they mint has to carry identical claims, or the same account would be two callers.
|
|
14
|
+
*/
|
|
15
|
+
function toStepUser(profile) {
|
|
16
|
+
return {
|
|
17
|
+
id: profile.id,
|
|
18
|
+
email: profile.email,
|
|
19
|
+
firstName: profile.firstName ?? '',
|
|
20
|
+
lastName: profile.lastName ?? '',
|
|
21
|
+
team: profile.team ?? '',
|
|
22
|
+
renderingId: profile.renderingId,
|
|
23
|
+
role: profile.role ?? '',
|
|
24
|
+
permissionLevel: profile.permissionLevel ?? '',
|
|
25
|
+
tags: profile.tags,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function mintStepToken(user, authSecret) {
|
|
29
|
+
return jsonwebtoken_1.default.sign({ ...(0, agent_client_1.toAgentTokenClaims)(user), scope: 'step-execution' }, authSecret, {
|
|
30
|
+
expiresIn: AGENT_TOKEN_TTL,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RlcC11c2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FkYXB0ZXJzL3N0ZXAtdXNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQTJCQSxnQ0FZQztBQUVELHNDQUlDO0FBM0NELDREQUErRDtBQUMvRCxnRUFBd0M7QUFFeEMsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDO0FBa0I3Qjs7O0dBR0c7QUFDSCxTQUFnQixVQUFVLENBQUMsT0FBd0I7SUFDakQsT0FBTztRQUNMLEVBQUUsRUFBRSxPQUFPLENBQUMsRUFBRTtRQUNkLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSztRQUNwQixTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVMsSUFBSSxFQUFFO1FBQ2xDLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUSxJQUFJLEVBQUU7UUFDaEMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLElBQUksRUFBRTtRQUN4QixXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVc7UUFDaEMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLElBQUksRUFBRTtRQUN4QixlQUFlLEVBQUUsT0FBTyxDQUFDLGVBQWUsSUFBSSxFQUFFO1FBQzlDLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSTtLQUNuQixDQUFDO0FBQ0osQ0FBQztBQUVELFNBQWdCLGFBQWEsQ0FBQyxJQUFjLEVBQUUsVUFBa0I7SUFDOUQsT0FBTyxzQkFBWSxDQUFDLElBQUksQ0FBQyxFQUFFLEdBQUcsSUFBQSxpQ0FBa0IsRUFBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsRUFBRSxVQUFVLEVBQUU7UUFDN0YsU0FBUyxFQUFFLGVBQWU7S0FDM0IsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyJ9
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AutomationPort } from '../ports/automation-port';
|
|
2
|
+
import type { Logger } from '../ports/logger-port';
|
|
3
|
+
import type { SegmentReaderPort } from '../ports/segment-reader-port';
|
|
4
|
+
export type AutomationPollerState = 'idle' | 'running' | 'draining' | 'stopped';
|
|
5
|
+
export interface AutomationPollerConfig {
|
|
6
|
+
automationPort: AutomationPort;
|
|
7
|
+
segmentReaderPort: SegmentReaderPort;
|
|
8
|
+
pollingIntervalS: number;
|
|
9
|
+
/**
|
|
10
|
+
* Identifies this process to the orchestrator's single-poller election. Stable for the process's
|
|
11
|
+
* lifetime, distinct across instances.
|
|
12
|
+
*/
|
|
13
|
+
instanceId: string;
|
|
14
|
+
logger?: Logger;
|
|
15
|
+
stopTimeoutS?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Drives the automated inboxes of one environment: asks the orchestrator which ones to poll, reads
|
|
19
|
+
* their segment on the client's agent, and reports back. It never decides what runs — the
|
|
20
|
+
* orchestrator owns the concurrency cap, the active-run check and the run start.
|
|
21
|
+
*
|
|
22
|
+
* Kept apart from `Runner`: they share nothing but a polling shape, and a failure on one side must
|
|
23
|
+
* not stop the other.
|
|
24
|
+
*/
|
|
25
|
+
export default class AutomationPoller {
|
|
26
|
+
private readonly config;
|
|
27
|
+
private readonly logger;
|
|
28
|
+
private readonly inFlightInboxes;
|
|
29
|
+
private tickTimer;
|
|
30
|
+
private currentTick;
|
|
31
|
+
private currentCycle;
|
|
32
|
+
private readonly lease;
|
|
33
|
+
private readonly inboxPoll;
|
|
34
|
+
private sweepCutShort;
|
|
35
|
+
private nextSweepAt;
|
|
36
|
+
private _state;
|
|
37
|
+
constructor(config: AutomationPollerConfig);
|
|
38
|
+
get state(): AutomationPollerState;
|
|
39
|
+
start(): void;
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
private scheduleTick;
|
|
42
|
+
private tick;
|
|
43
|
+
private runPollCycle;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=automation-poller.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const inbox_poll_1 = __importDefault(require("./inbox-poll"));
|
|
7
|
+
const lease_keeper_1 = __importDefault(require("./lease-keeper"));
|
|
8
|
+
const console_logger_1 = __importDefault(require("../adapters/console-logger"));
|
|
9
|
+
const defaults_1 = require("../defaults");
|
|
10
|
+
const errors_1 = require("../errors");
|
|
11
|
+
const in_flight_run_registry_1 = __importDefault(require("../in-flight-run-registry"));
|
|
12
|
+
// Every inbox reads the customer's agent several times. Sweeping them all at once piles those reads
|
|
13
|
+
// onto the customer's database, and agent-client's ten-second timeout turns the pile-up into inboxes
|
|
14
|
+
// whose reads time out.
|
|
15
|
+
const MAX_CONCURRENT_INBOX_POLLS = 5;
|
|
16
|
+
// The orchestrator's poller lease lives three of these, so a dead holder is replaced within a
|
|
17
|
+
// minute. Kept apart from the sweep interval: a sweep can last longer than the lease.
|
|
18
|
+
const LEASE_HEARTBEAT_INTERVAL_S = 15;
|
|
19
|
+
/**
|
|
20
|
+
* Drives the automated inboxes of one environment: asks the orchestrator which ones to poll, reads
|
|
21
|
+
* their segment on the client's agent, and reports back. It never decides what runs — the
|
|
22
|
+
* orchestrator owns the concurrency cap, the active-run check and the run start.
|
|
23
|
+
*
|
|
24
|
+
* Kept apart from `Runner`: they share nothing but a polling shape, and a failure on one side must
|
|
25
|
+
* not stop the other.
|
|
26
|
+
*/
|
|
27
|
+
class AutomationPoller {
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.inFlightInboxes = new in_flight_run_registry_1.default();
|
|
30
|
+
this.tickTimer = null;
|
|
31
|
+
this.currentTick = null;
|
|
32
|
+
// The cycle itself, not just the inbox polls it spawns: a cycle still waiting on the config route
|
|
33
|
+
// has registered nothing, and draining only the registry would let it read and write after the
|
|
34
|
+
// host was told the poller had stopped.
|
|
35
|
+
this.currentCycle = null;
|
|
36
|
+
this.lease = new lease_keeper_1.default();
|
|
37
|
+
this.sweepCutShort = false;
|
|
38
|
+
this.nextSweepAt = 0;
|
|
39
|
+
this._state = 'idle';
|
|
40
|
+
this.config = config;
|
|
41
|
+
this.logger = config.logger ?? (0, console_logger_1.default)();
|
|
42
|
+
this.inboxPoll = new inbox_poll_1.default({
|
|
43
|
+
automationPort: config.automationPort,
|
|
44
|
+
segmentReaderPort: config.segmentReaderPort,
|
|
45
|
+
logger: this.logger,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
get state() {
|
|
49
|
+
return this._state;
|
|
50
|
+
}
|
|
51
|
+
start() {
|
|
52
|
+
if (this._state === 'stopped' || this._state === 'draining') {
|
|
53
|
+
throw new Error('AutomationPoller has been stopped and cannot be restarted');
|
|
54
|
+
}
|
|
55
|
+
if (this._state === 'running')
|
|
56
|
+
return;
|
|
57
|
+
// `AUTOMATION_POLL_INTERVAL_S=0` turns the sweep off. The poller stays constructed and `stop()`
|
|
58
|
+
// still answers, so nothing downstream has to know an executor is running without it.
|
|
59
|
+
if (this.config.pollingIntervalS === 0) {
|
|
60
|
+
this.logger('Info', 'Automation poller disabled by configuration', {
|
|
61
|
+
instanceId: this.config.instanceId,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this._state = 'running';
|
|
66
|
+
this.logger('Info', 'Automation poller started', {
|
|
67
|
+
instanceId: this.config.instanceId,
|
|
68
|
+
pollingIntervalS: this.config.pollingIntervalS,
|
|
69
|
+
});
|
|
70
|
+
this.scheduleTick(0);
|
|
71
|
+
}
|
|
72
|
+
async stop() {
|
|
73
|
+
if (this._state !== 'running')
|
|
74
|
+
return;
|
|
75
|
+
this._state = 'draining';
|
|
76
|
+
if (this.tickTimer !== null) {
|
|
77
|
+
clearTimeout(this.tickTimer);
|
|
78
|
+
this.tickTimer = null;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const timeoutS = this.config.stopTimeoutS ?? defaults_1.DEFAULT_STOP_TIMEOUT_S;
|
|
82
|
+
let drainTimer;
|
|
83
|
+
const outcome = await Promise.race([
|
|
84
|
+
Promise.allSettled([
|
|
85
|
+
this.currentTick,
|
|
86
|
+
this.currentCycle,
|
|
87
|
+
this.inFlightInboxes.drain(),
|
|
88
|
+
]).then(() => {
|
|
89
|
+
if (drainTimer)
|
|
90
|
+
clearTimeout(drainTimer);
|
|
91
|
+
return 'drained';
|
|
92
|
+
}),
|
|
93
|
+
new Promise(resolve => {
|
|
94
|
+
drainTimer = setTimeout(() => resolve('timeout'), timeoutS * 1000);
|
|
95
|
+
// The bound on a shutdown wait must not itself become a reason to stay up.
|
|
96
|
+
drainTimer.unref?.();
|
|
97
|
+
}),
|
|
98
|
+
]);
|
|
99
|
+
if (outcome === 'timeout') {
|
|
100
|
+
this.logger('Error', 'Automation poller drain timeout', {
|
|
101
|
+
remainingInboxes: this.inFlightInboxes.keys(),
|
|
102
|
+
timeoutS,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
this._state = 'stopped';
|
|
108
|
+
this.logger('Info', 'Automation poller stopped', {});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
scheduleTick(delayMs) {
|
|
112
|
+
if (this._state !== 'running')
|
|
113
|
+
return;
|
|
114
|
+
this.tickTimer = setTimeout(() => {
|
|
115
|
+
this.currentTick = this.tick();
|
|
116
|
+
}, delayMs);
|
|
117
|
+
// A background heartbeat must not be the reason a process stays alive: the host owns that.
|
|
118
|
+
this.tickTimer.unref?.();
|
|
119
|
+
}
|
|
120
|
+
async tick() {
|
|
121
|
+
try {
|
|
122
|
+
const held = await this.config.automationPort.holdLease(this.config.instanceId);
|
|
123
|
+
if (this._state !== 'running')
|
|
124
|
+
return;
|
|
125
|
+
if (this.lease.recordBeat(held, Date.now()).roleChanged) {
|
|
126
|
+
this.logger('Info', held
|
|
127
|
+
? 'Holding the automation poller lease, this instance sweeps the automated inboxes'
|
|
128
|
+
: 'Standing by, another instance sweeps the automated inboxes', { instanceId: this.config.instanceId });
|
|
129
|
+
}
|
|
130
|
+
if (!held) {
|
|
131
|
+
this.nextSweepAt = 0;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (this.currentCycle === null && Date.now() >= this.nextSweepAt) {
|
|
135
|
+
this.sweepCutShort = false;
|
|
136
|
+
// Not awaited: the heartbeat has to keep the lease through a sweep that outlasts it.
|
|
137
|
+
this.currentCycle = this.runPollCycle().finally(() => {
|
|
138
|
+
this.currentCycle = null;
|
|
139
|
+
// The inboxes a lost lease left undispatched are owed as soon as the lease is back.
|
|
140
|
+
this.nextSweepAt = this.sweepCutShort
|
|
141
|
+
? 0
|
|
142
|
+
: Date.now() + this.config.pollingIntervalS * 1000;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.logger('Error', 'Automation poller lease heartbeat failed', {
|
|
148
|
+
instanceId: this.config.instanceId,
|
|
149
|
+
error: (0, errors_1.extractErrorMessage)(error),
|
|
150
|
+
});
|
|
151
|
+
if (this.lease.recordFailedBeat(Date.now()).trustLost) {
|
|
152
|
+
this.logger('Warn', 'No heartbeat landed for too long, standing by until one does', {
|
|
153
|
+
instanceId: this.config.instanceId,
|
|
154
|
+
});
|
|
155
|
+
this.nextSweepAt = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
this.scheduleTick(LEASE_HEARTBEAT_INTERVAL_S * 1000);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async runPollCycle() {
|
|
163
|
+
try {
|
|
164
|
+
const inboxes = await this.config.automationPort.listAutomatedInboxes(this.config.instanceId);
|
|
165
|
+
// `stop()` may have run while that call was out. Dispatching now would read the customer's
|
|
166
|
+
// agent and start runs during a shutdown that is only waiting on this cycle to end.
|
|
167
|
+
if (this._state !== 'running')
|
|
168
|
+
return;
|
|
169
|
+
if (inboxes.length === 0) {
|
|
170
|
+
this.logger('Debug', 'No automated inbox to poll', { instanceId: this.config.instanceId });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.logger('Debug', 'Automation poll cycle started', { fetched: inboxes.length });
|
|
174
|
+
// Awaited, so no other cycle starts before this one is done: a slow segment read delays the
|
|
175
|
+
// sweep instead of stacking a second one on the customer's database. The registry is what
|
|
176
|
+
// `stop()` drains.
|
|
177
|
+
const queue = [...inboxes];
|
|
178
|
+
const sweepQueue = async () => {
|
|
179
|
+
let inbox = queue.shift();
|
|
180
|
+
// A lost lease means another instance may already be sweeping these inboxes.
|
|
181
|
+
while (inbox && this._state === 'running' && this.lease.isTrusted(Date.now())) {
|
|
182
|
+
// eslint-disable-next-line no-await-in-loop
|
|
183
|
+
await this.inFlightInboxes.track(inbox.inboxId, this.inboxPoll.poll(inbox));
|
|
184
|
+
inbox = queue.shift();
|
|
185
|
+
}
|
|
186
|
+
if (inbox && this._state === 'running')
|
|
187
|
+
this.sweepCutShort = true;
|
|
188
|
+
};
|
|
189
|
+
await Promise.all(Array.from({ length: Math.min(MAX_CONCURRENT_INBOX_POLLS, queue.length) }, sweepQueue));
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logger('Error', 'Automation poll cycle failed', {
|
|
193
|
+
instanceId: this.config.instanceId,
|
|
194
|
+
error: (0, errors_1.extractErrorMessage)(error),
|
|
195
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.default = AutomationPoller;
|
|
201
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0b21hdGlvbi1wb2xsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXV0b21hdGlvbi9hdXRvbWF0aW9uLXBvbGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUlBLDhEQUFxQztBQUNyQyxrRUFBeUM7QUFDekMsZ0ZBQTZEO0FBQzdELDBDQUFxRDtBQUNyRCxzQ0FBZ0Q7QUFDaEQsdUZBQTREO0FBRTVELG9HQUFvRztBQUNwRyxxR0FBcUc7QUFDckcsd0JBQXdCO0FBQ3hCLE1BQU0sMEJBQTBCLEdBQUcsQ0FBQyxDQUFDO0FBRXJDLDhGQUE4RjtBQUM5RixzRkFBc0Y7QUFDdEYsTUFBTSwwQkFBMEIsR0FBRyxFQUFFLENBQUM7QUFpQnRDOzs7Ozs7O0dBT0c7QUFDSCxNQUFxQixnQkFBZ0I7SUFnQm5DLFlBQVksTUFBOEI7UUFiekIsb0JBQWUsR0FBRyxJQUFJLGdDQUFtQixFQUFFLENBQUM7UUFDckQsY0FBUyxHQUEwQixJQUFJLENBQUM7UUFDeEMsZ0JBQVcsR0FBeUIsSUFBSSxDQUFDO1FBQ2pELGtHQUFrRztRQUNsRywrRkFBK0Y7UUFDL0Ysd0NBQXdDO1FBQ2hDLGlCQUFZLEdBQXlCLElBQUksQ0FBQztRQUNqQyxVQUFLLEdBQUcsSUFBSSxzQkFBVyxFQUFFLENBQUM7UUFFbkMsa0JBQWEsR0FBRyxLQUFLLENBQUM7UUFDdEIsZ0JBQVcsR0FBRyxDQUFDLENBQUM7UUFDaEIsV0FBTSxHQUEwQixNQUFNLENBQUM7UUFHN0MsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsTUFBTSxJQUFJLElBQUEsd0JBQW1CLEdBQUUsQ0FBQztRQUNyRCxJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksb0JBQVMsQ0FBQztZQUM3QixjQUFjLEVBQUUsTUFBTSxDQUFDLGNBQWM7WUFDckMsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLGlCQUFpQjtZQUMzQyxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07U0FDcEIsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELElBQUksS0FBSztRQUNQLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUNyQixDQUFDO0lBRUQsS0FBSztRQUNILElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxVQUFVLEVBQUUsQ0FBQztZQUM1RCxNQUFNLElBQUksS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7UUFDL0UsQ0FBQztRQUVELElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO1lBQUUsT0FBTztRQUV0QyxnR0FBZ0c7UUFDaEcsc0ZBQXNGO1FBQ3RGLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN2QyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSw2Q0FBNkMsRUFBRTtnQkFDakUsVUFBVSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVTthQUNuQyxDQUFDLENBQUM7WUFFSCxPQUFPO1FBQ1QsQ0FBQztRQUVELElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO1FBQ3hCLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLDJCQUEyQixFQUFFO1lBQy9DLFVBQVUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVU7WUFDbEMsZ0JBQWdCLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0I7U0FDL0MsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN2QixDQUFDO0lBRUQsS0FBSyxDQUFDLElBQUk7UUFDUixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssU0FBUztZQUFFLE9BQU87UUFFdEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxVQUFVLENBQUM7UUFFekIsSUFBSSxJQUFJLENBQUMsU0FBUyxLQUFLLElBQUksRUFBRSxDQUFDO1lBQzVCLFlBQVksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDN0IsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUM7UUFDeEIsQ0FBQztRQUVELElBQUksQ0FBQztZQUNILE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsWUFBWSxJQUFJLGlDQUFzQixDQUFDO1lBQ3BFLElBQUksVUFBc0MsQ0FBQztZQUUzQyxNQUFNLE9BQU8sR0FBRyxNQUFNLE9BQU8sQ0FBQyxJQUFJLENBQUM7Z0JBQ2pDLE9BQU8sQ0FBQyxVQUFVLENBQUM7b0JBQ2pCLElBQUksQ0FBQyxXQUFXO29CQUNoQixJQUFJLENBQUMsWUFBWTtvQkFDakIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLEVBQUU7aUJBQzdCLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO29CQUNYLElBQUksVUFBVTt3QkFBRSxZQUFZLENBQUMsVUFBVSxDQUFDLENBQUM7b0JBRXpDLE9BQU8sU0FBa0IsQ0FBQztnQkFDNUIsQ0FBQyxDQUFDO2dCQUNGLElBQUksT0FBTyxDQUFZLE9BQU8sQ0FBQyxFQUFFO29CQUMvQixVQUFVLEdBQUcsVUFBVSxDQUFDLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsRUFBRSxRQUFRLEdBQUcsSUFBSSxDQUFDLENBQUM7b0JBQ25FLDJFQUEyRTtvQkFDM0UsVUFBVSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUM7Z0JBQ3ZCLENBQUMsQ0FBQzthQUNILENBQUMsQ0FBQztZQUVILElBQUksT0FBTyxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUMxQixJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxpQ0FBaUMsRUFBRTtvQkFDdEQsZ0JBQWdCLEVBQUUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUU7b0JBQzdDLFFBQVE7aUJBQ1QsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztRQUNILENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO1lBQ3hCLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLDJCQUEyQixFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZELENBQUM7SUFDSCxDQUFDO0lBRU8sWUFBWSxDQUFDLE9BQWU7UUFDbEMsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFNBQVM7WUFBRSxPQUFPO1FBQ3RDLElBQUksQ0FBQyxTQUFTLEdBQUcsVUFBVSxDQUFDLEdBQUcsRUFBRTtZQUMvQixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNqQyxDQUFDLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDWiwyRkFBMkY7UUFDM0YsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFTyxLQUFLLENBQUMsSUFBSTtRQUNoQixJQUFJLENBQUM7WUFDSCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBRWhGLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO2dCQUFFLE9BQU87WUFFdEMsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7Z0JBQ3hELElBQUksQ0FBQyxNQUFNLENBQ1QsTUFBTSxFQUNOLElBQUk7b0JBQ0YsQ0FBQyxDQUFDLGlGQUFpRjtvQkFDbkYsQ0FBQyxDQUFDLDREQUE0RCxFQUNoRSxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUN2QyxDQUFDO1lBQ0osQ0FBQztZQUVELElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztnQkFDVixJQUFJLENBQUMsV0FBVyxHQUFHLENBQUMsQ0FBQztnQkFFckIsT0FBTztZQUNULENBQUM7WUFFRCxJQUFJLElBQUksQ0FBQyxZQUFZLEtBQUssSUFBSSxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7Z0JBQ2pFLElBQUksQ0FBQyxhQUFhLEdBQUcsS0FBSyxDQUFDO2dCQUMzQixxRkFBcUY7Z0JBQ3JGLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUU7b0JBQ25ELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDO29CQUN6QixvRkFBb0Y7b0JBQ3BGLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLGFBQWE7d0JBQ25DLENBQUMsQ0FBQyxDQUFDO3dCQUNILENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsR0FBRyxJQUFJLENBQUM7Z0JBQ3ZELENBQUMsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsMENBQTBDLEVBQUU7Z0JBQy9ELFVBQVUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVU7Z0JBQ2xDLEtBQUssRUFBRSxJQUFBLDRCQUFtQixFQUFDLEtBQUssQ0FBQzthQUNsQyxDQUFDLENBQUM7WUFFSCxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ3RELElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLDhEQUE4RCxFQUFFO29CQUNsRixVQUFVLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVO2lCQUNuQyxDQUFDLENBQUM7Z0JBQ0gsSUFBSSxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUM7WUFDdkIsQ0FBQztRQUNILENBQUM7Z0JBQVMsQ0FBQztZQUNULElBQUksQ0FBQyxZQUFZLENBQUMsMEJBQTBCLEdBQUcsSUFBSSxDQUFDLENBQUM7UUFDdkQsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsWUFBWTtRQUN4QixJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLG9CQUFvQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7WUFFOUYsMkZBQTJGO1lBQzNGLG9GQUFvRjtZQUNwRixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssU0FBUztnQkFBRSxPQUFPO1lBRXRDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDekIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO2dCQUUzRixPQUFPO1lBQ1QsQ0FBQztZQUVELElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLCtCQUErQixFQUFFLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1lBRW5GLDRGQUE0RjtZQUM1RiwwRkFBMEY7WUFDMUYsbUJBQW1CO1lBQ25CLE1BQU0sS0FBSyxHQUFHLENBQUMsR0FBRyxPQUFPLENBQUMsQ0FBQztZQUUzQixNQUFNLFVBQVUsR0FBRyxLQUFLLElBQW1CLEVBQUU7Z0JBQzNDLElBQUksS0FBSyxHQUFHLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFFMUIsNkVBQTZFO2dCQUM3RSxPQUFPLEtBQUssSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFNBQVMsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsRUFBRSxDQUFDO29CQUM5RSw0Q0FBNEM7b0JBQzVDLE1BQU0sSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO29CQUM1RSxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUN4QixDQUFDO2dCQUVELElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssU0FBUztvQkFBRSxJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQztZQUNwRSxDQUFDLENBQUM7WUFFRixNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQ2YsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLDBCQUEwQixFQUFFLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLFVBQVUsQ0FBQyxDQUN2RixDQUFDO1FBQ0osQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSw4QkFBOEIsRUFBRTtnQkFDbkQsVUFBVSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVTtnQkFDbEMsS0FBSyxFQUFFLElBQUEsNEJBQW1CLEVBQUMsS0FBSyxDQUFDO2dCQUNqQyxLQUFLLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUzthQUN4RCxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBMU1ELG1DQTBNQyJ9
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AutomationPort } from '../ports/automation-port';
|
|
2
|
+
import type { Logger } from '../ports/logger-port';
|
|
3
|
+
import type { SegmentReaderPort } from '../ports/segment-reader-port';
|
|
4
|
+
import type { AutomatedInbox } from '../types/automation';
|
|
5
|
+
/**
|
|
6
|
+
* One sweep of one automated inbox: reconciles the records whose run is over, reads new candidates,
|
|
7
|
+
* and reports both to the orchestrator in a single sync.
|
|
8
|
+
*/
|
|
9
|
+
export default class InboxPoll {
|
|
10
|
+
private readonly automationPort;
|
|
11
|
+
private readonly segmentReaderPort;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
constructor(params: {
|
|
14
|
+
automationPort: AutomationPort;
|
|
15
|
+
segmentReaderPort: SegmentReaderPort;
|
|
16
|
+
logger: Logger;
|
|
17
|
+
});
|
|
18
|
+
poll(inbox: AutomatedInbox): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* A record whose run is over is only treated once it has left the segment. Reading the assignment
|
|
21
|
+
* state alone is not enough: a record reported still in the segment while its run is going would
|
|
22
|
+
* be judged untreated before the run had a chance to take it out.
|
|
23
|
+
*/
|
|
24
|
+
private reconcileClosed;
|
|
25
|
+
/**
|
|
26
|
+
* Runs one of the two segment reads. A failure yields an empty list rather than ending the poll,
|
|
27
|
+
* so the other read and the sync still happen — but it is reported as a failure, because "read
|
|
28
|
+
* nothing" and "could not read" must not look alike to the caller.
|
|
29
|
+
*/
|
|
30
|
+
private tryRead;
|
|
31
|
+
/**
|
|
32
|
+
* Asks the agent for records the orchestrator has no assignment for. Excluding them in the query
|
|
33
|
+
* is what keeps the page at the run cap: a record already treated but still in the segment stays
|
|
34
|
+
* there for good, and left in the page it would take a slot on every poll from then on.
|
|
35
|
+
*/
|
|
36
|
+
private readCandidates;
|
|
37
|
+
private readPaddedCandidates;
|
|
38
|
+
/**
|
|
39
|
+
* A sort field renamed or out of the service account's reach must not stop the inbox from
|
|
40
|
+
* dispatching. Used on the not_in read and the first padded page only: a refused sort is refused
|
|
41
|
+
* on the first page already. The order is dropped for the rest of the candidate read only once the
|
|
42
|
+
* read without it succeeds, since a read failing both ways says nothing against the sort.
|
|
43
|
+
*/
|
|
44
|
+
private readWithInboxSort;
|
|
45
|
+
private paddedPageReason;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=inbox-poll.d.ts.map
|