@forestadmin/agent-bff 1.31.0 → 1.33.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/README.md +107 -1
- package/dist/action/action-routes-middleware.d.ts +3 -1
- package/dist/action/action-routes-middleware.js +48 -13
- package/dist/activity-log/activity-log-drainer.d.ts +24 -0
- package/dist/activity-log/activity-log-drainer.js +63 -0
- package/dist/activity-log/activity-log-writer.d.ts +26 -0
- package/dist/activity-log/activity-log-writer.js +23 -0
- package/dist/activity-log/activity-logs-creator.d.ts +42 -0
- package/dist/activity-log/activity-logs-creator.js +209 -0
- package/dist/activity-log/activity-logs-service.d.ts +18 -0
- package/dist/activity-log/activity-logs-service.js +19 -0
- package/dist/activity-log/with-activity-log.d.ts +25 -0
- package/dist/activity-log/with-activity-log.js +63 -0
- package/dist/api-key/api-key-authenticator.d.ts +11 -0
- package/dist/api-key/api-key-authenticator.js +12 -2
- package/dist/api-key/api-key-client.d.ts +6 -0
- package/dist/api-key/api-key-client.js +2 -1
- package/dist/api-key/api-key-middleware.d.ts +10 -1
- package/dist/api-key/api-key-middleware.js +18 -1
- package/dist/api-key/resolve-cache.d.ts +12 -0
- package/dist/api-key/resolve-cache.js +38 -6
- package/dist/auth/auth-mode.d.ts +1 -0
- package/dist/auth/auth-mode.js +10 -3
- package/dist/auth/forest-server-token-middleware.d.ts +25 -0
- package/dist/auth/forest-server-token-middleware.js +90 -0
- package/dist/build-bff.d.ts +8 -0
- package/dist/build-bff.js +29 -10
- package/dist/cli-core.d.ts +9 -0
- package/dist/cli-core.js +38 -3
- package/dist/data/agent-query.d.ts +12 -0
- package/dist/data/agent-query.js +19 -1
- package/dist/data/data-routes-middleware.d.ts +3 -1
- package/dist/data/data-routes-middleware.js +41 -4
- package/dist/http/bff-http-server.d.ts +22 -0
- package/dist/http/bff-http-server.js +50 -5
- package/dist/http/bff-local-errors.d.ts +15 -0
- package/dist/http/bff-local-errors.js +29 -3
- package/dist/openapi/openapi-document.js +3 -3
- package/dist/tracing-handle.d.ts +26 -0
- package/dist/tracing-handle.js +40 -0
- package/dist/tracing-preload.d.ts +2 -0
- package/dist/tracing-preload.js +27 -0
- package/dist/tracing.d.ts +79 -0
- package/dist/tracing.js +168 -0
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Logger } from './ports/logger-port';
|
|
2
|
+
/**
|
|
3
|
+
* The OpenTelemetry setup. `tracing-preload.ts` is what actually runs it, via `--require` before
|
|
4
|
+
* cli.js so the auto-instrumentation patches land before anything the app imports; keeping the two
|
|
5
|
+
* apart is what lets this module be imported by a test without arming an SDK.
|
|
6
|
+
*
|
|
7
|
+
* The SDK is initialised only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set, so an install that never
|
|
8
|
+
* opted into APM pays nothing. Neither ending the process nor flushing on the way out is this
|
|
9
|
+
* module's business: it hands the SDK back, the preload parks it in `tracing-handle`, and the
|
|
10
|
+
* shutdown path flushes through that. Arming a signal handler here would consume a signal the CLI
|
|
11
|
+
* has not armed its own handler for yet, and nothing would then terminate the process.
|
|
12
|
+
* All configuration goes through the standard OTel environment variables:
|
|
13
|
+
*
|
|
14
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT OTLP receiver (e.g. http://localhost:4318)
|
|
15
|
+
* OTEL_SERVICE_NAME default: forestadmin-agent-bff
|
|
16
|
+
* OTEL_SDK_DISABLED set to "true" to force-disable
|
|
17
|
+
* OTEL_RESOURCE_ATTRIBUTES e.g. deployment.environment=production
|
|
18
|
+
* OTEL_METRICS_EXPORTER default: none (the SDK's own default is otlp)
|
|
19
|
+
* OTEL_LOGS_EXPORTER default: none (the SDK's own default is otlp)
|
|
20
|
+
*
|
|
21
|
+
* The OTel packages are installed only into the Docker image's isolated deps (see
|
|
22
|
+
* `packages/agent-bff/docker/`), never shipped to npm consumers of the CLI — hence the dynamic
|
|
23
|
+
* require behind a seam, and the warning rather than a crash when they are absent.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_SERVICE_NAME = "forestadmin-agent-bff";
|
|
26
|
+
export interface OtelSdk {
|
|
27
|
+
start(): void;
|
|
28
|
+
shutdown(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface OtelModules {
|
|
31
|
+
NodeSDK: new (options: Record<string, unknown>) => OtelSdk;
|
|
32
|
+
getNodeAutoInstrumentations: () => unknown;
|
|
33
|
+
}
|
|
34
|
+
export interface TracingOptions {
|
|
35
|
+
/**
|
|
36
|
+
* The environment OUR decisions read: whether to arm at all, and what to name the service when
|
|
37
|
+
* nothing else does. It stops there — the SDK reads the real `process.env` for everything about
|
|
38
|
+
* the export itself, so a test injecting `env` cannot assert where spans go. Identical objects in
|
|
39
|
+
* production, so this is a limit on what the seam can test, not a behaviour.
|
|
40
|
+
*/
|
|
41
|
+
env?: NodeJS.ProcessEnv;
|
|
42
|
+
logger?: Logger;
|
|
43
|
+
/** The dynamic require, as a seam: the packages exist only in the Docker image. */
|
|
44
|
+
load?: () => OtelModules | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** The packages this image installs for APM, and the export it takes from each. */
|
|
47
|
+
export declare const OTEL_MODULE_IDS: {
|
|
48
|
+
readonly sdk: "@opentelemetry/sdk-node";
|
|
49
|
+
readonly instrumentations: "@opentelemetry/auto-instrumentations-node";
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The module resolution, as a seam. These packages are absent everywhere except the Docker image,
|
|
53
|
+
* so the real `require` can only ever take the failure branch here — passing a loader is what lets
|
|
54
|
+
* the success branch, and the package ids it asks for, be exercised at all. A typo in one of them
|
|
55
|
+
* would otherwise surface as an untraced image and nothing else.
|
|
56
|
+
*/
|
|
57
|
+
export type ModuleLoader = (id: string) => Record<string, unknown>;
|
|
58
|
+
export declare function loadOtelModules(load?: ModuleLoader): OtelModules | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* The service name the environment already carries, by the spec's precedence: `OTEL_SERVICE_NAME`
|
|
61
|
+
* wins, then a `service.name` entry in the comma-separated `OTEL_RESOURCE_ATTRIBUTES` list.
|
|
62
|
+
* Undefined when neither names one, which is the only case where our own default should apply.
|
|
63
|
+
*/
|
|
64
|
+
export declare function serviceNameFromEnv(env: NodeJS.ProcessEnv): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Strips the credentials out of the endpoint before it reaches the logs. `https://user:token@host`
|
|
67
|
+
* is a legitimate way to reach a collector behind basic auth, and container logs are the last place
|
|
68
|
+
* that token should end up — this package does not echo a secret anywhere else either.
|
|
69
|
+
*
|
|
70
|
+
* The query string goes with them, unread: collectors that take their key as `?api_key=` exist, and
|
|
71
|
+
* telling them apart from a harmless parameter means knowing every vendor's spelling. Nothing about
|
|
72
|
+
* the destination is lost — the host and path are what the line is for.
|
|
73
|
+
*
|
|
74
|
+
* An endpoint that does not parse is dropped from the log entirely rather than passed through: it
|
|
75
|
+
* cannot be redacted, so it cannot be shown. The SDK will fail on it soon enough on its own.
|
|
76
|
+
*/
|
|
77
|
+
export declare function redactEndpoint(endpoint: string): string | undefined;
|
|
78
|
+
export default function initTracing(options?: TracingOptions): OtelSdk | undefined;
|
|
79
|
+
//# sourceMappingURL=tracing.d.ts.map
|
package/dist/tracing.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
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.OTEL_MODULE_IDS = exports.DEFAULT_SERVICE_NAME = void 0;
|
|
7
|
+
exports.loadOtelModules = loadOtelModules;
|
|
8
|
+
exports.serviceNameFromEnv = serviceNameFromEnv;
|
|
9
|
+
exports.redactEndpoint = redactEndpoint;
|
|
10
|
+
exports.default = initTracing;
|
|
11
|
+
const console_logger_1 = __importDefault(require("./adapters/console-logger"));
|
|
12
|
+
/**
|
|
13
|
+
* The OpenTelemetry setup. `tracing-preload.ts` is what actually runs it, via `--require` before
|
|
14
|
+
* cli.js so the auto-instrumentation patches land before anything the app imports; keeping the two
|
|
15
|
+
* apart is what lets this module be imported by a test without arming an SDK.
|
|
16
|
+
*
|
|
17
|
+
* The SDK is initialised only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set, so an install that never
|
|
18
|
+
* opted into APM pays nothing. Neither ending the process nor flushing on the way out is this
|
|
19
|
+
* module's business: it hands the SDK back, the preload parks it in `tracing-handle`, and the
|
|
20
|
+
* shutdown path flushes through that. Arming a signal handler here would consume a signal the CLI
|
|
21
|
+
* has not armed its own handler for yet, and nothing would then terminate the process.
|
|
22
|
+
* All configuration goes through the standard OTel environment variables:
|
|
23
|
+
*
|
|
24
|
+
* OTEL_EXPORTER_OTLP_ENDPOINT OTLP receiver (e.g. http://localhost:4318)
|
|
25
|
+
* OTEL_SERVICE_NAME default: forestadmin-agent-bff
|
|
26
|
+
* OTEL_SDK_DISABLED set to "true" to force-disable
|
|
27
|
+
* OTEL_RESOURCE_ATTRIBUTES e.g. deployment.environment=production
|
|
28
|
+
* OTEL_METRICS_EXPORTER default: none (the SDK's own default is otlp)
|
|
29
|
+
* OTEL_LOGS_EXPORTER default: none (the SDK's own default is otlp)
|
|
30
|
+
*
|
|
31
|
+
* The OTel packages are installed only into the Docker image's isolated deps (see
|
|
32
|
+
* `packages/agent-bff/docker/`), never shipped to npm consumers of the CLI — hence the dynamic
|
|
33
|
+
* require behind a seam, and the warning rather than a crash when they are absent.
|
|
34
|
+
*/
|
|
35
|
+
exports.DEFAULT_SERVICE_NAME = 'forestadmin-agent-bff';
|
|
36
|
+
/** The packages this image installs for APM, and the export it takes from each. */
|
|
37
|
+
exports.OTEL_MODULE_IDS = {
|
|
38
|
+
sdk: '@opentelemetry/sdk-node',
|
|
39
|
+
instrumentations: '@opentelemetry/auto-instrumentations-node',
|
|
40
|
+
};
|
|
41
|
+
function loadOtelModules(load = require) {
|
|
42
|
+
try {
|
|
43
|
+
const modules = {
|
|
44
|
+
NodeSDK: load(exports.OTEL_MODULE_IDS.sdk).NodeSDK,
|
|
45
|
+
getNodeAutoInstrumentations: load(exports.OTEL_MODULE_IDS.instrumentations)
|
|
46
|
+
.getNodeAutoInstrumentations,
|
|
47
|
+
};
|
|
48
|
+
// Resolving is not the same as finding what we came for. A package that loads but no longer
|
|
49
|
+
// exports the name we read — a rename on a version bump — would otherwise hand back a truthy
|
|
50
|
+
// object of undefined members, skip the "packages not available" branch, and throw a
|
|
51
|
+
// TypeError inside a `--require`, before the entry point runs at all. That is a dead container
|
|
52
|
+
// for an APM layer that is supposed to degrade to a warning.
|
|
53
|
+
if (Object.values(modules).some(exported => typeof exported !== 'function'))
|
|
54
|
+
return undefined;
|
|
55
|
+
return modules;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The OTel specification defines its boolean environment variables as case-insensitive, and this
|
|
63
|
+
* one is the kill switch: reading `TRUE` as "not disabled" would leave tracing running for someone
|
|
64
|
+
* who just asked for it to stop.
|
|
65
|
+
*/
|
|
66
|
+
function isDisabled(raw) {
|
|
67
|
+
return raw?.trim().toLowerCase() === 'true';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The service name the environment already carries, by the spec's precedence: `OTEL_SERVICE_NAME`
|
|
71
|
+
* wins, then a `service.name` entry in the comma-separated `OTEL_RESOURCE_ATTRIBUTES` list.
|
|
72
|
+
* Undefined when neither names one, which is the only case where our own default should apply.
|
|
73
|
+
*/
|
|
74
|
+
function serviceNameFromEnv(env) {
|
|
75
|
+
const explicit = env.OTEL_SERVICE_NAME?.trim();
|
|
76
|
+
if (explicit)
|
|
77
|
+
return explicit;
|
|
78
|
+
return env.OTEL_RESOURCE_ATTRIBUTES?.split(',')
|
|
79
|
+
.map(entry => entry.split('='))
|
|
80
|
+
.filter(([key, value]) => key?.trim() === 'service.name' && value?.trim())
|
|
81
|
+
.map(([, value]) => value.trim())
|
|
82
|
+
.pop();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Strips the credentials out of the endpoint before it reaches the logs. `https://user:token@host`
|
|
86
|
+
* is a legitimate way to reach a collector behind basic auth, and container logs are the last place
|
|
87
|
+
* that token should end up — this package does not echo a secret anywhere else either.
|
|
88
|
+
*
|
|
89
|
+
* The query string goes with them, unread: collectors that take their key as `?api_key=` exist, and
|
|
90
|
+
* telling them apart from a harmless parameter means knowing every vendor's spelling. Nothing about
|
|
91
|
+
* the destination is lost — the host and path are what the line is for.
|
|
92
|
+
*
|
|
93
|
+
* An endpoint that does not parse is dropped from the log entirely rather than passed through: it
|
|
94
|
+
* cannot be redacted, so it cannot be shown. The SDK will fail on it soon enough on its own.
|
|
95
|
+
*/
|
|
96
|
+
function redactEndpoint(endpoint) {
|
|
97
|
+
try {
|
|
98
|
+
const url = new URL(endpoint);
|
|
99
|
+
if (!url.username && !url.password && !url.search)
|
|
100
|
+
return endpoint;
|
|
101
|
+
url.username = '';
|
|
102
|
+
url.password = '';
|
|
103
|
+
url.search = '';
|
|
104
|
+
return url.toString();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function initTracing(options = {}) {
|
|
111
|
+
const { env = process.env, logger = (0, console_logger_1.default)(), load = loadOtelModules } = options;
|
|
112
|
+
// Any of the three standard ways to say "export traces somewhere" turns tracing on. The per-signal
|
|
113
|
+
// endpoint counts as much as the generic one, and OTEL_TRACES_EXPORTER counts on its own —
|
|
114
|
+
// requiring an OTLP collector before `console` does anything would be nonsense for an exporter
|
|
115
|
+
// that writes to stdout, and it is the obvious first thing to reach for when debugging.
|
|
116
|
+
//
|
|
117
|
+
// A blank value counts as unset throughout, not as "export to the OTel default": `env_file` hands
|
|
118
|
+
// an empty string through for a variable left blank in a `.env`, and arming the SDK against
|
|
119
|
+
// localhost:4318 is not what leaving the line empty asks for.
|
|
120
|
+
const endpoint = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim() || env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
|
|
121
|
+
const requestedExporter = env.OTEL_TRACES_EXPORTER?.trim().toLowerCase();
|
|
122
|
+
if ((!endpoint && !requestedExporter) || isDisabled(env.OTEL_SDK_DISABLED))
|
|
123
|
+
return undefined;
|
|
124
|
+
const modules = load();
|
|
125
|
+
if (!modules) {
|
|
126
|
+
logger('Warn', 'OpenTelemetry packages not available, skipping APM initialisation');
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const { NodeSDK, getNodeAutoInstrumentations } = modules;
|
|
130
|
+
// Passing `serviceName` unconditionally would override OTEL_RESOURCE_ATTRIBUTES=service.name=...,
|
|
131
|
+
// which is a documented knob — someone setting only that would silently get our default. So we
|
|
132
|
+
// fill it in only when the environment names no service at all, and otherwise leave the SDK to
|
|
133
|
+
// apply the spec's own precedence (OTEL_SERVICE_NAME first, then the resource attribute).
|
|
134
|
+
const named = serviceNameFromEnv(env);
|
|
135
|
+
// NodeSDK reads OTEL_METRICS_EXPORTER and OTEL_LOGS_EXPORTER itself, and an unset one does not
|
|
136
|
+
// mean "off" — it means otlp. So arming traces alone also starts a metric reader and a log
|
|
137
|
+
// processor aimed at the OTLP default, http://localhost:4318. Under OTEL_TRACES_EXPORTER=console,
|
|
138
|
+
// which asks for no collector at all, that is a POST every minute to a port nobody named; where
|
|
139
|
+
// something else on the host does listen there, it is telemetry that install never opted into.
|
|
140
|
+
// This module arms tracing. The other two signals stay opt-in, through their own variable, which
|
|
141
|
+
// still works: we only fill in what the environment left blank.
|
|
142
|
+
env.OTEL_METRICS_EXPORTER = env.OTEL_METRICS_EXPORTER?.trim() || 'none';
|
|
143
|
+
env.OTEL_LOGS_EXPORTER = env.OTEL_LOGS_EXPORTER?.trim() || 'none';
|
|
144
|
+
// No `traceExporter`, deliberately. Passing one puts NodeSDK on its manual-configuration path,
|
|
145
|
+
// where it stops reading the environment — which is how OTEL_TRACES_EXPORTER, then
|
|
146
|
+
// OTEL_EXPORTER_OTLP_PROTOCOL, then the per-signal endpoint each turned out to be silently
|
|
147
|
+
// ignored, one review round after another. They were three symptoms of doing the SDK's job.
|
|
148
|
+
//
|
|
149
|
+
// sdk-node depends on every OTLP exporter and on the Zipkin one, so all of them are in the image
|
|
150
|
+
// already; leaving the choice to the SDK costs nothing and makes the whole standard surface work,
|
|
151
|
+
// protocol and per-signal overrides included. Our only decisions are whether to arm at all, and
|
|
152
|
+
// the service name when nothing else supplies one.
|
|
153
|
+
const sdk = new NodeSDK({
|
|
154
|
+
...(named ? {} : { serviceName: exports.DEFAULT_SERVICE_NAME }),
|
|
155
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
156
|
+
});
|
|
157
|
+
sdk.start();
|
|
158
|
+
// What was ASKED for, not what the SDK settled on — we no longer choose, so claiming a
|
|
159
|
+
// destination we did not pick would be inventing one. The endpoint is dropped when an exporter
|
|
160
|
+
// was named instead, since it would read as a promise this line cannot keep.
|
|
161
|
+
logger('Info', 'OpenTelemetry tracing enabled', {
|
|
162
|
+
serviceName: named ?? exports.DEFAULT_SERVICE_NAME,
|
|
163
|
+
...(requestedExporter ? { exporter: requestedExporter } : {}),
|
|
164
|
+
...(endpoint && !requestedExporter ? { endpoint: redactEndpoint(endpoint) } : {}),
|
|
165
|
+
});
|
|
166
|
+
return sdk;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhY2luZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy90cmFjaW5nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQW1FQSwwQ0FtQkM7QUFnQkQsZ0RBVUM7QUFjRCx3Q0FjQztBQUVELDhCQXFFQztBQWpORCwrRUFBNEQ7QUFFNUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FzQkc7QUFFVSxRQUFBLG9CQUFvQixHQUFHLHVCQUF1QixDQUFDO0FBeUI1RCxtRkFBbUY7QUFDdEUsUUFBQSxlQUFlLEdBQUc7SUFDN0IsR0FBRyxFQUFFLHlCQUF5QjtJQUM5QixnQkFBZ0IsRUFBRSwyQ0FBMkM7Q0FDckQsQ0FBQztBQVVYLFNBQWdCLGVBQWUsQ0FBQyxPQUFxQixPQUFPO0lBQzFELElBQUksQ0FBQztRQUNILE1BQU0sT0FBTyxHQUFHO1lBQ2QsT0FBTyxFQUFFLElBQUksQ0FBQyx1QkFBZSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU87WUFDMUMsMkJBQTJCLEVBQUUsSUFBSSxDQUFDLHVCQUFlLENBQUMsZ0JBQWdCLENBQUM7aUJBQ2hFLDJCQUEyQjtTQUMvQixDQUFDO1FBRUYsNEZBQTRGO1FBQzVGLDZGQUE2RjtRQUM3RixxRkFBcUY7UUFDckYsK0ZBQStGO1FBQy9GLDZEQUE2RDtRQUM3RCxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsT0FBTyxRQUFRLEtBQUssVUFBVSxDQUFDO1lBQUUsT0FBTyxTQUFTLENBQUM7UUFFOUYsT0FBTyxPQUFzQixDQUFDO0lBQ2hDLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUFTLFVBQVUsQ0FBQyxHQUF1QjtJQUN6QyxPQUFPLEdBQUcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsS0FBSyxNQUFNLENBQUM7QUFDOUMsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxTQUFnQixrQkFBa0IsQ0FBQyxHQUFzQjtJQUN2RCxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsaUJBQWlCLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFFL0MsSUFBSSxRQUFRO1FBQUUsT0FBTyxRQUFRLENBQUM7SUFFOUIsT0FBTyxHQUFHLENBQUMsd0JBQXdCLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQztTQUM1QyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQzlCLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxFQUFFLEVBQUUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEtBQUssY0FBYyxJQUFJLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQztTQUN6RSxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztTQUNoQyxHQUFHLEVBQUUsQ0FBQztBQUNYLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7R0FXRztBQUNILFNBQWdCLGNBQWMsQ0FBQyxRQUFnQjtJQUM3QyxJQUFJLENBQUM7UUFDSCxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUU5QixJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTTtZQUFFLE9BQU8sUUFBUSxDQUFDO1FBRW5FLEdBQUcsQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO1FBQ2xCLEdBQUcsQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDO1FBQ2xCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFDO1FBRWhCLE9BQU8sR0FBRyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ3hCLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQXdCLFdBQVcsQ0FBQyxVQUEwQixFQUFFO0lBQzlELE1BQU0sRUFBRSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsRUFBRSxNQUFNLEdBQUcsSUFBQSx3QkFBbUIsR0FBRSxFQUFFLElBQUksR0FBRyxlQUFlLEVBQUUsR0FBRyxPQUFPLENBQUM7SUFFOUYsbUdBQW1HO0lBQ25HLDJGQUEyRjtJQUMzRiwrRkFBK0Y7SUFDL0Ysd0ZBQXdGO0lBQ3hGLEVBQUU7SUFDRixrR0FBa0c7SUFDbEcsNEZBQTRGO0lBQzVGLDhEQUE4RDtJQUM5RCxNQUFNLFFBQVEsR0FDWixHQUFHLENBQUMsa0NBQWtDLEVBQUUsSUFBSSxFQUFFLElBQUksR0FBRyxDQUFDLDJCQUEyQixFQUFFLElBQUksRUFBRSxDQUFDO0lBQzVGLE1BQU0saUJBQWlCLEdBQUcsR0FBRyxDQUFDLG9CQUFvQixFQUFFLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBRXpFLElBQUksQ0FBQyxDQUFDLFFBQVEsSUFBSSxDQUFDLGlCQUFpQixDQUFDLElBQUksVUFBVSxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBRTdGLE1BQU0sT0FBTyxHQUFHLElBQUksRUFBRSxDQUFDO0lBRXZCLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxNQUFNLEVBQUUsbUVBQW1FLENBQUMsQ0FBQztRQUVwRixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsTUFBTSxFQUFFLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxHQUFHLE9BQU8sQ0FBQztJQUV6RCxrR0FBa0c7SUFDbEcsK0ZBQStGO0lBQy9GLCtGQUErRjtJQUMvRiwwRkFBMEY7SUFDMUYsTUFBTSxLQUFLLEdBQUcsa0JBQWtCLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFdEMsK0ZBQStGO0lBQy9GLDJGQUEyRjtJQUMzRixrR0FBa0c7SUFDbEcsZ0dBQWdHO0lBQ2hHLCtGQUErRjtJQUMvRixpR0FBaUc7SUFDakcsZ0VBQWdFO0lBQ2hFLEdBQUcsQ0FBQyxxQkFBcUIsR0FBRyxHQUFHLENBQUMscUJBQXFCLEVBQUUsSUFBSSxFQUFFLElBQUksTUFBTSxDQUFDO0lBQ3hFLEdBQUcsQ0FBQyxrQkFBa0IsR0FBRyxHQUFHLENBQUMsa0JBQWtCLEVBQUUsSUFBSSxFQUFFLElBQUksTUFBTSxDQUFDO0lBRWxFLCtGQUErRjtJQUMvRixtRkFBbUY7SUFDbkYsMkZBQTJGO0lBQzNGLDRGQUE0RjtJQUM1RixFQUFFO0lBQ0YsaUdBQWlHO0lBQ2pHLGtHQUFrRztJQUNsRyxnR0FBZ0c7SUFDaEcsbURBQW1EO0lBQ25ELE1BQU0sR0FBRyxHQUFHLElBQUksT0FBTyxDQUFDO1FBQ3RCLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxXQUFXLEVBQUUsNEJBQW9CLEVBQUUsQ0FBQztRQUN2RCxnQkFBZ0IsRUFBRSxDQUFDLDJCQUEyQixFQUFFLENBQUM7S0FDbEQsQ0FBQyxDQUFDO0lBRUgsR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBRVosdUZBQXVGO0lBQ3ZGLCtGQUErRjtJQUMvRiw2RUFBNkU7SUFDN0UsTUFBTSxDQUFDLE1BQU0sRUFBRSwrQkFBK0IsRUFBRTtRQUM5QyxXQUFXLEVBQUUsS0FBSyxJQUFJLDRCQUFvQjtRQUMxQyxHQUFHLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLGlCQUFpQixFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUM3RCxHQUFHLENBQUMsUUFBUSxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLGNBQWMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7S0FDbEYsQ0FBQyxDQUFDO0lBRUgsT0FBTyxHQUFHLENBQUM7QUFDYixDQUFDIn0=
|