@forestadmin/workflow-executor 1.1.5 → 1.3.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/console-logger.d.ts +2 -6
- package/dist/adapters/console-logger.js +21 -12
- package/dist/adapters/forest-server-workflow-port.d.ts +3 -2
- package/dist/adapters/forest-server-workflow-port.js +4 -4
- package/dist/adapters/forestadmin-client-activity-log-port.js +4 -4
- package/dist/adapters/pretty-logger.d.ts +2 -8
- package/dist/adapters/pretty-logger.js +35 -28
- package/dist/adapters/with-retry.js +2 -2
- package/dist/build-workflow-executor.d.ts +2 -1
- package/dist/build-workflow-executor.js +8 -8
- package/dist/cli-core.d.ts +2 -2
- package/dist/cli-core.js +22 -9
- package/dist/defaults.d.ts +2 -0
- package/dist/defaults.js +3 -2
- package/dist/errors.d.ts +12 -7
- package/dist/errors.js +30 -13
- package/dist/executors/base-step-executor.js +8 -8
- package/dist/executors/load-related-record-step-executor.js +2 -2
- package/dist/executors/mcp-step-executor.js +3 -3
- package/dist/executors/step-executor-factory.js +2 -2
- package/dist/http/bearer-claims.d.ts +6 -0
- package/dist/http/bearer-claims.js +11 -0
- package/dist/http/executor-http-server.js +58 -68
- package/dist/http/http-errors.d.ts +39 -0
- package/dist/http/http-errors.js +82 -0
- package/dist/ports/logger-port.d.ts +2 -5
- package/dist/ports/workflow-port.d.ts +4 -2
- package/dist/remote-tool-fetcher.js +3 -3
- package/dist/runner.js +23 -23
- package/dist/stores/database-store.js +3 -3
- package/package.json +1 -1
|
@@ -8,6 +8,8 @@ const router_1 = __importDefault(require("@koa/router"));
|
|
|
8
8
|
const http_1 = __importDefault(require("http"));
|
|
9
9
|
const koa_1 = __importDefault(require("koa"));
|
|
10
10
|
const koa_jwt_1 = __importDefault(require("koa-jwt"));
|
|
11
|
+
const bearer_claims_1 = require("./bearer-claims");
|
|
12
|
+
const http_errors_1 = require("./http-errors");
|
|
11
13
|
const step_serializer_1 = __importDefault(require("./step-serializer"));
|
|
12
14
|
const console_logger_1 = __importDefault(require("../adapters/console-logger"));
|
|
13
15
|
const errors_1 = require("../errors");
|
|
@@ -15,28 +17,41 @@ class ExecutorHttpServer {
|
|
|
15
17
|
constructor(options) {
|
|
16
18
|
this.server = null;
|
|
17
19
|
this.options = options;
|
|
18
|
-
this.logger = options.logger ??
|
|
20
|
+
this.logger = options.logger ?? (0, console_logger_1.default)();
|
|
19
21
|
this.app = new koa_1.default();
|
|
20
|
-
// Error middleware —
|
|
22
|
+
// Error-translation middleware — the single place converting thrown errors (typed HTTP
|
|
23
|
+
// errors, domain errors via toHttpError, JWT 401) into HTTP responses. Handlers just throw.
|
|
21
24
|
this.app.use(async (ctx, next) => {
|
|
22
25
|
try {
|
|
23
26
|
await next();
|
|
24
27
|
}
|
|
25
28
|
catch (err) {
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const httpError = (0, http_errors_1.toHttpError)(err);
|
|
30
|
+
if (!httpError) {
|
|
31
|
+
this.logger('Error', 'Unhandled HTTP error', {
|
|
32
|
+
method: ctx.method,
|
|
33
|
+
path: ctx.path,
|
|
34
|
+
error: (0, errors_1.extractErrorMessage)(err),
|
|
35
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
36
|
+
});
|
|
37
|
+
ctx.status = 500;
|
|
38
|
+
ctx.body = { error: 'Internal server error' };
|
|
30
39
|
return;
|
|
31
40
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
if (httpError.log) {
|
|
42
|
+
this.logger('Error', 'HTTP request failed', {
|
|
43
|
+
method: ctx.method,
|
|
44
|
+
path: ctx.path,
|
|
45
|
+
status: httpError.status,
|
|
46
|
+
error: (0, errors_1.extractErrorMessage)(httpError.cause ?? httpError),
|
|
47
|
+
// Prefer the cause's stack (points at the real fault site); fall back to the HTTP
|
|
48
|
+
// error's own stack so a log:true error without a cause never logs an empty stack.
|
|
49
|
+
stack: (httpError.cause instanceof Error ? httpError.cause.stack : undefined) ??
|
|
50
|
+
httpError.stack,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
ctx.status = httpError.status;
|
|
54
|
+
ctx.body = { error: httpError.userMessage };
|
|
40
55
|
}
|
|
41
56
|
});
|
|
42
57
|
// Health endpoint — before JWT so it's publicly accessible (infra probes don't send tokens)
|
|
@@ -53,6 +68,24 @@ class ExecutorHttpServer {
|
|
|
53
68
|
// JWT middleware — validates Bearer token using authSecret
|
|
54
69
|
// tokenKey: 'rawToken' exposes the raw token string on ctx.state.rawToken for downstream use
|
|
55
70
|
this.app.use((0, koa_jwt_1.default)({ secret: options.authSecret, cookie: 'forest_session_token', tokenKey: 'rawToken' }));
|
|
71
|
+
// koa-jwt only validates the token's signature/expiry, not its payload shape. Validate the
|
|
72
|
+
// claims once, here, so every handler downstream gets a user with a guaranteed numeric id.
|
|
73
|
+
this.app.use(async (ctx, next) => {
|
|
74
|
+
const claims = bearer_claims_1.BearerClaimsSchema.safeParse(ctx.state.user);
|
|
75
|
+
if (!claims.success) {
|
|
76
|
+
// A token koa-jwt accepted (valid signature) but whose payload is malformed is rare and
|
|
77
|
+
// high-signal (token-issuance regression / version skew / forgery probe) — log it, unlike
|
|
78
|
+
// ordinary expired-token churn. Only the issue paths/codes, never the payload (PII).
|
|
79
|
+
this.logger('Warn', 'Bearer token has invalid claims', {
|
|
80
|
+
method: ctx.method,
|
|
81
|
+
path: ctx.path,
|
|
82
|
+
issues: claims.error.issues.map(issue => ({ path: issue.path, code: issue.code })),
|
|
83
|
+
});
|
|
84
|
+
throw new http_errors_1.UnauthorizedHttpError();
|
|
85
|
+
}
|
|
86
|
+
ctx.state.user = { ...ctx.state.user, ...claims.data };
|
|
87
|
+
await next();
|
|
88
|
+
});
|
|
56
89
|
const router = new router_1.default();
|
|
57
90
|
// hasRunAccess authorization — only on GET (read-only route).
|
|
58
91
|
// Trigger handles its own authz by comparing bearer user with step.user.
|
|
@@ -90,26 +123,23 @@ class ExecutorHttpServer {
|
|
|
90
123
|
}
|
|
91
124
|
async hasRunAccessMiddleware(ctx, next) {
|
|
92
125
|
const user = ctx.state.user;
|
|
126
|
+
let allowed;
|
|
93
127
|
try {
|
|
94
|
-
|
|
95
|
-
if (!allowed) {
|
|
96
|
-
ctx.status = 403;
|
|
97
|
-
ctx.body = { error: 'Forbidden' };
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
128
|
+
allowed = await this.options.workflowPort.hasRunAccess(ctx.params.runId, user);
|
|
100
129
|
}
|
|
101
130
|
catch (err) {
|
|
102
|
-
this.logger
|
|
131
|
+
this.logger('Error', 'Failed to check run access', {
|
|
103
132
|
runId: ctx.params.runId,
|
|
104
133
|
method: ctx.method,
|
|
105
134
|
path: ctx.path,
|
|
106
135
|
error: (0, errors_1.extractErrorMessage)(err),
|
|
107
136
|
stack: err instanceof Error ? err.stack : undefined,
|
|
108
137
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return;
|
|
138
|
+
// log:false — already logged above with the richer runId context.
|
|
139
|
+
throw new http_errors_1.ServiceUnavailableHttpError('Service unavailable', { cause: err });
|
|
112
140
|
}
|
|
141
|
+
if (!allowed)
|
|
142
|
+
throw new http_errors_1.ForbiddenHttpError();
|
|
113
143
|
await next();
|
|
114
144
|
}
|
|
115
145
|
async handleGetRun(ctx) {
|
|
@@ -118,53 +148,13 @@ class ExecutorHttpServer {
|
|
|
118
148
|
}
|
|
119
149
|
async handleTrigger(ctx) {
|
|
120
150
|
const { runId } = ctx.params;
|
|
121
|
-
|
|
122
|
-
const bearerUserId =
|
|
123
|
-
if (!Number.isFinite(bearerUserId)) {
|
|
124
|
-
ctx.status = 400;
|
|
125
|
-
ctx.body = { error: 'Missing or invalid user id in token' };
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
151
|
+
// Guaranteed a number by the bearer-claims middleware.
|
|
152
|
+
const bearerUserId = ctx.state.user.id;
|
|
128
153
|
const pendingData = ctx.request.body?.pendingData;
|
|
129
|
-
|
|
130
|
-
await this.options.runner.triggerPoll(runId, {
|
|
131
|
-
pendingData,
|
|
132
|
-
bearerUserId,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
if (err instanceof errors_1.RunNotFoundError) {
|
|
137
|
-
ctx.status = 404;
|
|
138
|
-
ctx.body = { error: 'Run not found or unavailable' };
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (err instanceof errors_1.RunAlreadyInFlightError) {
|
|
142
|
-
ctx.status = 400;
|
|
143
|
-
ctx.body = { error: err.message };
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (err instanceof errors_1.UserMismatchError) {
|
|
147
|
-
this.logger.error('User mismatch on trigger', { runId, bearerUserId });
|
|
148
|
-
ctx.status = 403;
|
|
149
|
-
ctx.body = { error: 'Forbidden' };
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (err instanceof errors_1.WorkflowExecutorError) {
|
|
153
|
-
this.logger.error('Malformed run on trigger', {
|
|
154
|
-
runId,
|
|
155
|
-
bearerUserId,
|
|
156
|
-
error: (0, errors_1.extractErrorMessage)(err),
|
|
157
|
-
stack: err.stack,
|
|
158
|
-
});
|
|
159
|
-
ctx.status = 400;
|
|
160
|
-
ctx.body = { error: err.userMessage };
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
throw err;
|
|
164
|
-
}
|
|
154
|
+
await this.options.runner.triggerPoll(runId, { pendingData, bearerUserId });
|
|
165
155
|
ctx.status = 200;
|
|
166
156
|
ctx.body = { triggered: true };
|
|
167
157
|
}
|
|
168
158
|
}
|
|
169
159
|
exports.default = ExecutorHttpServer;
|
|
170
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
160
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhlY3V0b3ItaHR0cC1zZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9leGVjdXRvci1odHRwLXNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUtBLGlFQUF5QztBQUN6Qyx5REFBaUM7QUFDakMsZ0RBQXdCO0FBQ3hCLDhDQUFzQjtBQUN0QixzREFBNkI7QUFFN0IsbURBQXdFO0FBQ3hFLCtDQUt1QjtBQUN2Qix3RUFBcUQ7QUFDckQsZ0ZBQTZEO0FBQzdELHNDQUFnRDtBQVVoRCxNQUFxQixrQkFBa0I7SUFNckMsWUFBWSxPQUFrQztRQUZ0QyxXQUFNLEdBQWtCLElBQUksQ0FBQztRQUduQyxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLElBQUksSUFBQSx3QkFBbUIsR0FBRSxDQUFDO1FBQ3RELElBQUksQ0FBQyxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQztRQUVyQix1RkFBdUY7UUFDdkYsNEZBQTRGO1FBQzVGLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7WUFDL0IsSUFBSSxDQUFDO2dCQUNILE1BQU0sSUFBSSxFQUFFLENBQUM7WUFDZixDQUFDO1lBQUMsT0FBTyxHQUFZLEVBQUUsQ0FBQztnQkFDdEIsTUFBTSxTQUFTLEdBQUcsSUFBQSx5QkFBVyxFQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUVuQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7b0JBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsc0JBQXNCLEVBQUU7d0JBQzNDLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTt3QkFDbEIsSUFBSSxFQUFFLEdBQUcsQ0FBQyxJQUFJO3dCQUNkLEtBQUssRUFBRSxJQUFBLDRCQUFtQixFQUFDLEdBQUcsQ0FBQzt3QkFDL0IsS0FBSyxFQUFFLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVM7cUJBQ3BELENBQUMsQ0FBQztvQkFDSCxHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztvQkFDakIsR0FBRyxDQUFDLElBQUksR0FBRyxFQUFFLEtBQUssRUFBRSx1QkFBdUIsRUFBRSxDQUFDO29CQUU5QyxPQUFPO2dCQUNULENBQUM7Z0JBRUQsSUFBSSxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUM7b0JBQ2xCLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLHFCQUFxQixFQUFFO3dCQUMxQyxNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07d0JBQ2xCLElBQUksRUFBRSxHQUFHLENBQUMsSUFBSTt3QkFDZCxNQUFNLEVBQUUsU0FBUyxDQUFDLE1BQU07d0JBQ3hCLEtBQUssRUFBRSxJQUFBLDRCQUFtQixFQUFDLFNBQVMsQ0FBQyxLQUFLLElBQUksU0FBUyxDQUFDO3dCQUN4RCxrRkFBa0Y7d0JBQ2xGLG1GQUFtRjt3QkFDbkYsS0FBSyxFQUNILENBQUMsU0FBUyxDQUFDLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7NEJBQ3RFLFNBQVMsQ0FBQyxLQUFLO3FCQUNsQixDQUFDLENBQUM7Z0JBQ0wsQ0FBQztnQkFFRCxHQUFHLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQUM7Z0JBQzlCLEdBQUcsQ0FBQyxJQUFJLEdBQUcsRUFBRSxLQUFLLEVBQUUsU0FBUyxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQzlDLENBQUM7UUFDSCxDQUFDLENBQUMsQ0FBQztRQUVILDRGQUE0RjtRQUM1RixJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFO1lBQy9CLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxLQUFLLElBQUksR0FBRyxDQUFDLElBQUksS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDbkQsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO2dCQUN0QyxHQUFHLENBQUMsTUFBTSxHQUFHLEtBQUssS0FBSyxTQUFTLElBQUksS0FBSyxLQUFLLFVBQVUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7Z0JBQ3JFLEdBQUcsQ0FBQyxJQUFJLEdBQUcsRUFBRSxLQUFLLEVBQUUsQ0FBQztnQkFFckIsT0FBTztZQUNULENBQUM7WUFFRCxNQUFNLElBQUksRUFBRSxDQUFDO1FBQ2YsQ0FBQyxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFBLG9CQUFVLEdBQUUsQ0FBQyxDQUFDO1FBRTNCLDJEQUEyRDtRQUMzRCw2RkFBNkY7UUFDN0YsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQ1YsSUFBQSxpQkFBTSxFQUFDLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxVQUFVLEVBQUUsTUFBTSxFQUFFLHNCQUFzQixFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUM3RixDQUFDO1FBRUYsMkZBQTJGO1FBQzNGLDJGQUEyRjtRQUMzRixJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxFQUFFO1lBQy9CLE1BQU0sTUFBTSxHQUFHLGtDQUFrQixDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRTVELElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUM7Z0JBQ3BCLHdGQUF3RjtnQkFDeEYsMEZBQTBGO2dCQUMxRixxRkFBcUY7Z0JBQ3JGLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLGlDQUFpQyxFQUFFO29CQUNyRCxNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07b0JBQ2xCLElBQUksRUFBRSxHQUFHLENBQUMsSUFBSTtvQkFDZCxNQUFNLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztpQkFDbkYsQ0FBQyxDQUFDO2dCQUVILE1BQU0sSUFBSSxtQ0FBcUIsRUFBRSxDQUFDO1lBQ3BDLENBQUM7WUFFRCxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxFQUFFLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsR0FBRyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7WUFFdkQsTUFBTSxJQUFJLEVBQUUsQ0FBQztRQUNmLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxNQUFNLEdBQUcsSUFBSSxnQkFBTSxFQUFFLENBQUM7UUFFNUIsOERBQThEO1FBQzlELHlFQUF5RTtRQUN6RSxNQUFNLENBQUMsR0FBRyxDQUNSLGNBQWMsRUFDZCxJQUFJLENBQUMsc0JBQXNCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUN0QyxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FDN0IsQ0FBQztRQUNGLE1BQU0sQ0FBQyxJQUFJLENBQUMsc0JBQXNCLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUVuRSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUM5QixJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUN4QyxDQUFDO0lBRUQsS0FBSyxDQUFDLEtBQUs7UUFDVCxPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsY0FBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDckQsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQ2xDLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ2pELENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELEtBQUssQ0FBQyxJQUFJO1FBQ1IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUNyQyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUNqQixPQUFPLEVBQUUsQ0FBQztnQkFFVixPQUFPO1lBQ1QsQ0FBQztZQUVELElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFO2dCQUN0QixJQUFJLEdBQUcsRUFBRSxDQUFDO29CQUNSLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDZCxDQUFDO3FCQUFNLENBQUM7b0JBQ04sSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUM7b0JBQ25CLE9BQU8sRUFBRSxDQUFDO2dCQUNaLENBQUM7WUFDSCxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELElBQUksUUFBUTtRQUNWLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUM3QixDQUFDO0lBRU8sS0FBSyxDQUFDLHNCQUFzQixDQUFDLEdBQWdCLEVBQUUsSUFBYztRQUNuRSxNQUFNLElBQUksR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQW9CLENBQUM7UUFDNUMsSUFBSSxPQUFnQixDQUFDO1FBRXJCLElBQUksQ0FBQztZQUNILE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNqRixDQUFDO1FBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztZQUNiLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLDRCQUE0QixFQUFFO2dCQUNqRCxLQUFLLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxLQUFLO2dCQUN2QixNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07Z0JBQ2xCLElBQUksRUFBRSxHQUFHLENBQUMsSUFBSTtnQkFDZCxLQUFLLEVBQUUsSUFBQSw0QkFBbUIsRUFBQyxHQUFHLENBQUM7Z0JBQy9CLEtBQUssRUFBRSxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTO2FBQ3BELENBQUMsQ0FBQztZQUVILGtFQUFrRTtZQUNsRSxNQUFNLElBQUkseUNBQTJCLENBQUMscUJBQXFCLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUMvRSxDQUFDO1FBRUQsSUFBSSxDQUFDLE9BQU87WUFBRSxNQUFNLElBQUksZ0NBQWtCLEVBQUUsQ0FBQztRQUU3QyxNQUFNLElBQUksRUFBRSxDQUFDO0lBQ2YsQ0FBQztJQUVPLEtBQUssQ0FBQyxZQUFZLENBQUMsR0FBZ0I7UUFDekMsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQy9FLEdBQUcsQ0FBQyxJQUFJLEdBQUcsRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQyx5QkFBb0IsQ0FBQyxFQUFFLENBQUM7SUFDeEQsQ0FBQztJQUVPLEtBQUssQ0FBQyxhQUFhLENBQUMsR0FBZ0I7UUFDMUMsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUM7UUFDN0IsdURBQXVEO1FBQ3ZELE1BQU0sWUFBWSxHQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBcUIsQ0FBQyxFQUFFLENBQUM7UUFFekQsTUFBTSxXQUFXLEdBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFrQyxFQUFFLFdBQVcsQ0FBQztRQUVqRixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxLQUFLLEVBQUUsRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLENBQUMsQ0FBQztRQUU1RSxHQUFHLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQztRQUNqQixHQUFHLENBQUMsSUFBSSxHQUFHLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDO0lBQ2pDLENBQUM7Q0FDRjtBQXRMRCxxQ0FzTEMifQ==
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare abstract class BaseHttpError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly userMessage: string;
|
|
4
|
+
readonly log: boolean;
|
|
5
|
+
cause?: unknown;
|
|
6
|
+
constructor(status: number, userMessage: string, options?: {
|
|
7
|
+
log?: boolean;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare class BadRequestHttpError extends BaseHttpError {
|
|
12
|
+
constructor(userMessage: string, options?: {
|
|
13
|
+
log?: boolean;
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export declare class UnauthorizedHttpError extends BaseHttpError {
|
|
18
|
+
constructor();
|
|
19
|
+
}
|
|
20
|
+
export declare class ForbiddenHttpError extends BaseHttpError {
|
|
21
|
+
constructor(options?: {
|
|
22
|
+
log?: boolean;
|
|
23
|
+
cause?: unknown;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export declare class NotFoundHttpError extends BaseHttpError {
|
|
27
|
+
constructor(userMessage: string, options?: {
|
|
28
|
+
log?: boolean;
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export declare class ServiceUnavailableHttpError extends BaseHttpError {
|
|
33
|
+
constructor(userMessage: string, options?: {
|
|
34
|
+
log?: boolean;
|
|
35
|
+
cause?: unknown;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export declare function toHttpError(err: unknown): BaseHttpError | null;
|
|
39
|
+
//# sourceMappingURL=http-errors.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServiceUnavailableHttpError = exports.NotFoundHttpError = exports.ForbiddenHttpError = exports.UnauthorizedHttpError = exports.BadRequestHttpError = exports.BaseHttpError = void 0;
|
|
4
|
+
exports.toHttpError = toHttpError;
|
|
5
|
+
/* eslint-disable max-classes-per-file */
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
// HTTP-typed errors: each carries its response semantics (status + user-facing body) so the
|
|
8
|
+
// error-translation middleware can render any thrown error without per-handler branching. One
|
|
9
|
+
// concrete class per status; handlers throw them directly, and toHttpError maps domain error
|
|
10
|
+
// categories onto them.
|
|
11
|
+
class BaseHttpError extends Error {
|
|
12
|
+
constructor(status, userMessage, options = {}) {
|
|
13
|
+
super(userMessage);
|
|
14
|
+
this.name = this.constructor.name;
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.userMessage = userMessage;
|
|
17
|
+
this.log = options.log ?? false;
|
|
18
|
+
if (options.cause !== undefined)
|
|
19
|
+
this.cause = options.cause;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.BaseHttpError = BaseHttpError;
|
|
23
|
+
class BadRequestHttpError extends BaseHttpError {
|
|
24
|
+
constructor(userMessage, options) {
|
|
25
|
+
super(400, userMessage, options);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.BadRequestHttpError = BadRequestHttpError;
|
|
29
|
+
class UnauthorizedHttpError extends BaseHttpError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super(401, 'Unauthorized');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.UnauthorizedHttpError = UnauthorizedHttpError;
|
|
35
|
+
class ForbiddenHttpError extends BaseHttpError {
|
|
36
|
+
// 403 bodies stay opaque ('Forbidden') on purpose — never echo why access was denied.
|
|
37
|
+
constructor(options) {
|
|
38
|
+
super(403, 'Forbidden', options);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.ForbiddenHttpError = ForbiddenHttpError;
|
|
42
|
+
class NotFoundHttpError extends BaseHttpError {
|
|
43
|
+
constructor(userMessage, options) {
|
|
44
|
+
super(404, userMessage, options);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.NotFoundHttpError = NotFoundHttpError;
|
|
48
|
+
class ServiceUnavailableHttpError extends BaseHttpError {
|
|
49
|
+
constructor(userMessage, options) {
|
|
50
|
+
super(503, userMessage, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.ServiceUnavailableHttpError = ServiceUnavailableHttpError;
|
|
54
|
+
// The single domain→HTTP mapping. Maps by domain category, not concrete class, so a new
|
|
55
|
+
// NotFoundError/ConflictError/… is translated correctly without touching this function. Returns
|
|
56
|
+
// null when the error has no HTTP translation — the middleware then responds 500 without leaking
|
|
57
|
+
// internals.
|
|
58
|
+
function toHttpError(err) {
|
|
59
|
+
if (err instanceof BaseHttpError)
|
|
60
|
+
return err;
|
|
61
|
+
// koa-jwt rejects with an error object carrying status 401.
|
|
62
|
+
if (err?.status === 401)
|
|
63
|
+
return new UnauthorizedHttpError();
|
|
64
|
+
// Category branches MUST precede the WorkflowExecutorError catch-all: every category extends it.
|
|
65
|
+
if (err instanceof errors_1.NotFoundError)
|
|
66
|
+
return new NotFoundHttpError(err.userMessage, { cause: err });
|
|
67
|
+
if (err instanceof errors_1.AccessDeniedError)
|
|
68
|
+
return new ForbiddenHttpError({ log: true, cause: err });
|
|
69
|
+
// Expected client churn (a double trigger): 400, not logged. Precedes the catch-all, which logs.
|
|
70
|
+
if (err instanceof errors_1.RunAlreadyInFlightError) {
|
|
71
|
+
return new BadRequestHttpError(err.userMessage, { cause: err });
|
|
72
|
+
}
|
|
73
|
+
if (err instanceof errors_1.UnavailableError) {
|
|
74
|
+
return new ServiceUnavailableHttpError(err.userMessage, { log: true, cause: err });
|
|
75
|
+
}
|
|
76
|
+
// Uncategorized domain error: 400 with its userMessage (safe default — never a silent 500).
|
|
77
|
+
if (err instanceof errors_1.WorkflowExecutorError) {
|
|
78
|
+
return new BadRequestHttpError(err.userMessage, { log: true, cause: err });
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1lcnJvcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9odHRwLWVycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFzRUEsa0NBeUJDO0FBL0ZELHlDQUF5QztBQUN6QyxzQ0FNbUI7QUFFbkIsNEZBQTRGO0FBQzVGLDhGQUE4RjtBQUM5Riw2RkFBNkY7QUFDN0Ysd0JBQXdCO0FBQ3hCLE1BQXNCLGFBQWMsU0FBUSxLQUFLO0lBUS9DLFlBQ0UsTUFBYyxFQUNkLFdBQW1CLEVBQ25CLFVBQThDLEVBQUU7UUFFaEQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQ25CLElBQUksQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUM7UUFDbEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUM7UUFDL0IsSUFBSSxDQUFDLEdBQUcsR0FBRyxPQUFPLENBQUMsR0FBRyxJQUFJLEtBQUssQ0FBQztRQUNoQyxJQUFJLE9BQU8sQ0FBQyxLQUFLLEtBQUssU0FBUztZQUFFLElBQUksQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUM5RCxDQUFDO0NBQ0Y7QUFwQkQsc0NBb0JDO0FBRUQsTUFBYSxtQkFBb0IsU0FBUSxhQUFhO0lBQ3BELFlBQVksV0FBbUIsRUFBRSxPQUE0QztRQUMzRSxLQUFLLENBQUMsR0FBRyxFQUFFLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNuQyxDQUFDO0NBQ0Y7QUFKRCxrREFJQztBQUVELE1BQWEscUJBQXNCLFNBQVEsYUFBYTtJQUN0RDtRQUNFLEtBQUssQ0FBQyxHQUFHLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFDN0IsQ0FBQztDQUNGO0FBSkQsc0RBSUM7QUFFRCxNQUFhLGtCQUFtQixTQUFRLGFBQWE7SUFDbkQsc0ZBQXNGO0lBQ3RGLFlBQVksT0FBNEM7UUFDdEQsS0FBSyxDQUFDLEdBQUcsRUFBRSxXQUFXLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDbkMsQ0FBQztDQUNGO0FBTEQsZ0RBS0M7QUFFRCxNQUFhLGlCQUFrQixTQUFRLGFBQWE7SUFDbEQsWUFBWSxXQUFtQixFQUFFLE9BQTRDO1FBQzNFLEtBQUssQ0FBQyxHQUFHLEVBQUUsV0FBVyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ25DLENBQUM7Q0FDRjtBQUpELDhDQUlDO0FBRUQsTUFBYSwyQkFBNEIsU0FBUSxhQUFhO0lBQzVELFlBQVksV0FBbUIsRUFBRSxPQUE0QztRQUMzRSxLQUFLLENBQUMsR0FBRyxFQUFFLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNuQyxDQUFDO0NBQ0Y7QUFKRCxrRUFJQztBQUVELHdGQUF3RjtBQUN4RixnR0FBZ0c7QUFDaEcsaUdBQWlHO0FBQ2pHLGFBQWE7QUFDYixTQUFnQixXQUFXLENBQUMsR0FBWTtJQUN0QyxJQUFJLEdBQUcsWUFBWSxhQUFhO1FBQUUsT0FBTyxHQUFHLENBQUM7SUFFN0MsNERBQTREO0lBQzVELElBQUssR0FBMkIsRUFBRSxNQUFNLEtBQUssR0FBRztRQUFFLE9BQU8sSUFBSSxxQkFBcUIsRUFBRSxDQUFDO0lBRXJGLGlHQUFpRztJQUNqRyxJQUFJLEdBQUcsWUFBWSxzQkFBYTtRQUFFLE9BQU8sSUFBSSxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDaEcsSUFBSSxHQUFHLFlBQVksMEJBQWlCO1FBQUUsT0FBTyxJQUFJLGtCQUFrQixDQUFDLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUUvRixpR0FBaUc7SUFDakcsSUFBSSxHQUFHLFlBQVksZ0NBQXVCLEVBQUUsQ0FBQztRQUMzQyxPQUFPLElBQUksbUJBQW1CLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBQ2xFLENBQUM7SUFFRCxJQUFJLEdBQUcsWUFBWSx5QkFBZ0IsRUFBRSxDQUFDO1FBQ3BDLE9BQU8sSUFBSSwyQkFBMkIsQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNyRixDQUFDO0lBRUQsNEZBQTRGO0lBQzVGLElBQUksR0FBRyxZQUFZLDhCQUFxQixFQUFFLENBQUM7UUFDekMsT0FBTyxJQUFJLG1CQUFtQixDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBQzdFLENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUMifQ==
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
warn(message: string, context: Record<string, unknown>): void;
|
|
4
|
-
info(message: string, context: Record<string, unknown>): void;
|
|
5
|
-
}
|
|
1
|
+
export type LoggerLevel = 'Debug' | 'Info' | 'Warn' | 'Error';
|
|
2
|
+
export type Logger = (level: LoggerLevel, message: string, context?: Record<string, unknown>) => void;
|
|
6
3
|
//# sourceMappingURL=logger-port.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AvailableStepExecution
|
|
1
|
+
import type { AvailableStepExecution } from '../types/execution-context';
|
|
2
2
|
import type { CollectionSchema } from '../types/validated/collection';
|
|
3
3
|
import type { StepOutcome } from '../types/validated/step-outcome';
|
|
4
4
|
import type { ToolConfig } from '@forestadmin/ai-proxy';
|
|
@@ -25,6 +25,8 @@ export interface WorkflowPort {
|
|
|
25
25
|
updateStepExecution(runId: string, stepOutcome: StepOutcome): Promise<AvailableRunDispatch | null>;
|
|
26
26
|
getCollectionSchema(collectionName: string, runId: string): Promise<CollectionSchema>;
|
|
27
27
|
getMcpServerConfigs(): Promise<Record<string, ToolConfig>>;
|
|
28
|
-
hasRunAccess(runId: string, user:
|
|
28
|
+
hasRunAccess(runId: string, user: {
|
|
29
|
+
id: number;
|
|
30
|
+
}): Promise<boolean>;
|
|
29
31
|
}
|
|
30
32
|
//# sourceMappingURL=workflow-port.d.ts.map
|
|
@@ -31,7 +31,7 @@ class RemoteToolFetcher {
|
|
|
31
31
|
const availableMcpServerIds = Object.values(configs)
|
|
32
32
|
.map(cfg => cfg.id)
|
|
33
33
|
.filter((id) => Boolean(id));
|
|
34
|
-
this.logger
|
|
34
|
+
this.logger('Warn', Object.keys(configs).length === 0
|
|
35
35
|
? 'MCP step targets a server but orchestrator returned no MCP configs'
|
|
36
36
|
: 'MCP step targets a server not advertised by the orchestrator', { requestedMcpServerId: mcpServerId, mcpServerName, availableMcpServerIds });
|
|
37
37
|
}
|
|
@@ -45,7 +45,7 @@ class RemoteToolFetcher {
|
|
|
45
45
|
.map(([name]) => name);
|
|
46
46
|
if (failedConfigNames.length === 0)
|
|
47
47
|
return;
|
|
48
|
-
this.logger
|
|
48
|
+
this.logger('Error', 'MCP servers failed to load tools', {
|
|
49
49
|
requestedMcpServerId: mcpServerId,
|
|
50
50
|
mcpServerName,
|
|
51
51
|
failedConfigNames,
|
|
@@ -53,4 +53,4 @@ class RemoteToolFetcher {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
exports.default = RemoteToolFetcher;
|
|
56
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVtb3RlLXRvb2wtZmV0Y2hlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9yZW1vdGUtdG9vbC1mZXRjaGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBTUEsb0RBS0M7QUFORCxrRkFBa0Y7QUFDbEYsU0FBZ0Isb0JBQW9CLENBQ2xDLE9BQW1DLEVBQ25DLFdBQW1CO0lBRW5CLE9BQU8sTUFBTSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsS0FBSyxXQUFXLENBQUMsQ0FBQyxDQUFDO0FBQ2pHLENBQUM7QUFPRCxNQUFxQixpQkFBaUI7SUFLcEMsWUFBWSxZQUEwQixFQUFFLFdBQXdCLEVBQUUsTUFBYztRQUM5RSxJQUFJLENBQUMsWUFBWSxHQUFHLFlBQVksQ0FBQztRQUNqQyxJQUFJLENBQUMsV0FBVyxHQUFHLFdBQVcsQ0FBQztRQUMvQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztJQUN2QixDQUFDO0lBRUQsS0FBSyxDQUFDLEtBQUssQ0FBQyxXQUFtQjtRQUM3QixNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUM5RCxNQUFNLE1BQU0sR0FBRyxvQkFBb0IsQ0FBQyxPQUFPLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDMUQsTUFBTSxDQUFDLGFBQWEsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFNUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBRTFFLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQztZQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLGFBQWEsRUFBRSxDQUFDO1FBRTFFLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFN0QsSUFBSSxDQUFDLHlCQUF5QixDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBRTFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsYUFBYSxFQUFFLENBQUM7SUFDbEMsQ0FBQztJQUVELDZGQUE2RjtJQUM3RiwyRkFBMkY7SUFDM0Ysb0JBQW9CO0lBQ1osdUJBQXVCLENBQzdCLE9BQW1DLEVBQ25DLE1BQWtDLEVBQ2xDLFdBQW1CLEVBQ25CLGFBQWlDO1FBRWpDLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQztZQUFFLE9BQU87UUFFM0MsTUFBTSxxQkFBcUIsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQzthQUNqRCxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2FBQ2xCLE1BQU0sQ0FBQyxDQUFDLEVBQUUsRUFBZ0IsRUFBRSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBRTdDLElBQUksQ0FBQyxNQUFNLENBQ1QsTUFBTSxFQUNOLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUM7WUFDL0IsQ0FBQyxDQUFDLG9FQUFvRTtZQUN0RSxDQUFDLENBQUMsOERBQThELEVBQ2xFLEVBQUUsb0JBQW9CLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxxQkFBcUIsRUFBRSxDQUM1RSxDQUFDO0lBQ0osQ0FBQztJQUVELDRGQUE0RjtJQUM1Riw0RkFBNEY7SUFDNUYseUZBQXlGO0lBQ2pGLHlCQUF5QixDQUMvQixNQUFrQyxFQUNsQyxLQUFtQixFQUNuQixXQUFtQixFQUNuQixhQUFpQztRQUVqQyxNQUFNLGtCQUFrQixHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztRQUNsRSxNQUFNLGlCQUFpQixHQUFHLE1BQU0sQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO2FBQzdDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO2FBQ3BELEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRXpCLElBQUksaUJBQWlCLENBQUMsTUFBTSxLQUFLLENBQUM7WUFBRSxPQUFPO1FBRTNDLElBQUksQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLGtDQUFrQyxFQUFFO1lBQ3ZELG9CQUFvQixFQUFFLFdBQVc7WUFDakMsYUFBYTtZQUNiLGlCQUFpQjtTQUNsQixDQUFDLENBQUM7SUFDTCxDQUFDO0NBQ0Y7QUF6RUQsb0NBeUVDIn0=
|