@pipeline-builder/api-core 5.2.2 → 5.2.4
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/lib/constants/ai-providers.d.ts +3 -22
- package/lib/constants/ai-providers.js +6 -56
- package/lib/errors/app-errors.d.ts +14 -0
- package/lib/errors/app-errors.js +21 -1
- package/lib/helpers/index.d.ts +1 -0
- package/lib/helpers/index.js +2 -1
- package/lib/helpers/sse-helpers.d.ts +33 -3
- package/lib/helpers/sse-helpers.js +91 -19
- package/lib/helpers/support-calendar.d.ts +69 -0
- package/lib/helpers/support-calendar.js +179 -0
- package/lib/middleware/auth.js +6 -3
- package/lib/middleware/deferred-quota-check.d.ts +5 -0
- package/lib/middleware/deferred-quota-check.js +33 -0
- package/lib/middleware/ecosystem-guard.d.ts +3 -3
- package/lib/middleware/ecosystem-guard.js +5 -5
- package/lib/middleware/index.d.ts +5 -2
- package/lib/middleware/index.js +6 -3
- package/lib/middleware/on-behalf.d.ts +37 -0
- package/lib/middleware/on-behalf.js +143 -0
- package/lib/middleware/permission-gates.d.ts +11 -0
- package/lib/middleware/permission-gates.js +7 -1
- package/lib/middleware/quota-exempt-routes.d.ts +36 -0
- package/lib/middleware/quota-exempt-routes.js +47 -0
- package/lib/middleware/route-table.d.ts +104 -0
- package/lib/middleware/route-table.js +72 -2
- package/lib/middleware/service-tokens.d.ts +8 -0
- package/lib/middleware/service-tokens.js +5 -3
- package/lib/middleware/step-up.js +11 -1
- package/lib/services/ai-call-weight.d.ts +24 -0
- package/lib/services/ai-call-weight.js +77 -0
- package/lib/services/alert-delivery.d.ts +99 -0
- package/lib/services/alert-delivery.js +132 -0
- package/lib/services/audit-context.d.ts +105 -0
- package/lib/services/audit-context.js +134 -0
- package/lib/services/index.d.ts +10 -2
- package/lib/services/index.js +10 -2
- package/lib/services/org-ai-client.d.ts +61 -0
- package/lib/services/org-ai-client.js +112 -0
- package/lib/services/pagerduty-inbound.d.ts +101 -0
- package/lib/services/pagerduty-inbound.js +195 -0
- package/lib/services/pagerduty.d.ts +109 -0
- package/lib/services/pagerduty.js +186 -0
- package/lib/services/quota-increment-batcher.d.ts +81 -0
- package/lib/services/quota-increment-batcher.js +166 -0
- package/lib/services/quota.d.ts +158 -14
- package/lib/services/quota.js +252 -22
- package/lib/services/remote-audit-client.d.ts +44 -1
- package/lib/services/remote-audit-client.js +106 -20
- package/lib/services/version-vuln-policy.d.ts +174 -0
- package/lib/services/version-vuln-policy.js +242 -0
- package/lib/types/ai-catalog.d.ts +63 -0
- package/lib/types/ai-catalog.js +82 -0
- package/lib/types/ask-proposals.js +6 -6
- package/lib/types/build-catalog.d.ts +154 -0
- package/lib/types/build-catalog.js +158 -0
- package/lib/types/common.d.ts +37 -3
- package/lib/types/common.js +20 -2
- package/lib/types/ecosystem-notifications.d.ts +5 -5
- package/lib/types/ecosystem-notifications.js +9 -3
- package/lib/types/error-codes.d.ts +13 -6
- package/lib/types/error-codes.js +18 -8
- package/lib/types/feature-flags.d.ts +71 -1
- package/lib/types/feature-flags.js +131 -11
- package/lib/types/index.d.ts +6 -4
- package/lib/types/index.js +12 -5
- package/lib/types/metadata-keys.d.ts +6 -0
- package/lib/types/metadata-keys.js +4 -1
- package/lib/types/permissions.d.ts +351 -3
- package/lib/types/permissions.js +610 -21
- package/lib/types/pipeline.d.ts +39 -0
- package/lib/types/pipeline.js +57 -1
- package/lib/types/plugin-health.d.ts +0 -2
- package/lib/types/plugin-health.js +1 -11
- package/lib/types/plugin-scan.d.ts +192 -42
- package/lib/types/plugin-scan.js +159 -43
- package/lib/types/quota-catalog.d.ts +247 -0
- package/lib/types/quota-catalog.js +122 -0
- package/lib/types/quota-tiers.d.ts +150 -0
- package/lib/types/quota-tiers.js +167 -2
- package/lib/types/remote-audit-actions.d.ts +1 -1
- package/lib/types/remote-audit-actions.js +217 -4
- package/lib/types/wire-vocabulary.js +8 -1
- package/lib/utils/index.d.ts +1 -1
- package/lib/utils/index.js +2 -2
- package/lib/utils/response.d.ts +35 -2
- package/lib/utils/response.js +42 -3
- package/lib/validation/ai-schemas.d.ts +27 -0
- package/lib/validation/ai-schemas.js +31 -5
- package/lib/validation/plugin-schemas.d.ts +15 -0
- package/lib/validation/plugin-schemas.js +28 -1
- package/package.json +31 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { OPENAI_COMPATIBLE_PROVIDER_ID } from '../constants/ai-providers.js';
|
|
4
|
+
import { createLogger } from '../utils/logger.js';
|
|
5
|
+
const logger = createLogger('ai-call-weight');
|
|
6
|
+
/** Largest weight one model may carry — one call can never reserve more than this many units. */
|
|
7
|
+
export const MAX_AI_CALL_WEIGHT = 100;
|
|
8
|
+
/**
|
|
9
|
+
* Built-in weights, under whatever `AI_MODEL_WEIGHTS` sets. The self-hosted
|
|
10
|
+
* OpenAI-compatible provider (the in-cluster `ask-model`) runs on hardware the
|
|
11
|
+
* operator already pays for, so a call on it spends NO `aiCalls` unit by default;
|
|
12
|
+
* set `{"openai-compatible": 1}` to meter it like any other model.
|
|
13
|
+
*/
|
|
14
|
+
const BUILT_IN_WEIGHTS = { [OPENAI_COMPATIBLE_PROVIDER_ID]: 0 };
|
|
15
|
+
/**
|
|
16
|
+
* Parse `AI_MODEL_WEIGHTS` — a JSON object of `key → integer weight` — into
|
|
17
|
+
* lower-cased entries. A malformed value is IGNORED (logged), never fatal: the
|
|
18
|
+
* weights are an optional pricing lever and every call still meters at 1.
|
|
19
|
+
*/
|
|
20
|
+
export function parseAiModelWeights(raw) {
|
|
21
|
+
const out = { ...BUILT_IN_WEIGHTS };
|
|
22
|
+
if (!raw || raw.trim() === '')
|
|
23
|
+
return out;
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
logger.warn('AI_MODEL_WEIGHTS is not valid JSON; every model weighs 1');
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
33
|
+
logger.warn('AI_MODEL_WEIGHTS must be a JSON object; every model weighs 1');
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
37
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > MAX_AI_CALL_WEIGHT) {
|
|
38
|
+
logger.warn('AI_MODEL_WEIGHTS entry ignored (weight must be an integer 0..100)', { key });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
out[key.trim().toLowerCase()] = value;
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
let cached;
|
|
46
|
+
function currentWeights() {
|
|
47
|
+
const raw = process.env.AI_MODEL_WEIGHTS;
|
|
48
|
+
if (!cached || cached.raw !== raw)
|
|
49
|
+
cached = { raw, weights: parseAiModelWeights(raw) };
|
|
50
|
+
return cached.weights;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* How many `aiCalls` units one call on `{ provider, modelId }` reserves.
|
|
54
|
+
*
|
|
55
|
+
* MODEL-CLASS WEIGHTING is OFF by default: with `AI_MODEL_WEIGHTS` unset every
|
|
56
|
+
* model weighs 1 — except the self-hosted provider, which weighs 0 (see
|
|
57
|
+
* {@link BUILT_IN_WEIGHTS}). Matching, first hit wins:
|
|
58
|
+
* 1. a key equal to the PROVIDER id (`"openai-compatible": 0`, `"bedrock": 2`);
|
|
59
|
+
* 2. the LONGEST key contained in the model id (`"opus": 3` matches
|
|
60
|
+
* `claude-opus-4-1`; `"sonnet": 1`, `"haiku": 1`).
|
|
61
|
+
* No match → 1. A weight of 0 means the call reserves nothing (it is still
|
|
62
|
+
* recorded in the usage ledger with its tokens).
|
|
63
|
+
*/
|
|
64
|
+
export function aiCallWeight(selection) {
|
|
65
|
+
const weights = currentWeights();
|
|
66
|
+
const provider = selection.provider.toLowerCase();
|
|
67
|
+
if (provider in weights)
|
|
68
|
+
return weights[provider];
|
|
69
|
+
const model = selection.modelId.toLowerCase();
|
|
70
|
+
let best;
|
|
71
|
+
for (const [key, weight] of Object.entries(weights)) {
|
|
72
|
+
if (key.length > 0 && model.includes(key) && (!best || key.length > best.key.length))
|
|
73
|
+
best = { key, weight };
|
|
74
|
+
}
|
|
75
|
+
return best ? best.weight : 1;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWktY2FsbC13ZWlnaHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvYWktY2FsbC13ZWlnaHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM3RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbEQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGdCQUFnQixDQUFDLENBQUM7QUFFOUMsaUdBQWlHO0FBQ2pHLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLEdBQUcsQ0FBQztBQUV0Qzs7Ozs7R0FLRztBQUNILE1BQU0sZ0JBQWdCLEdBQXFDLEVBQUUsQ0FBQyw2QkFBNkIsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDO0FBRWxHOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQUMsR0FBdUI7SUFDekQsTUFBTSxHQUFHLEdBQTJCLEVBQUUsR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO0lBQzVELElBQUksQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUU7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUMxQyxJQUFJLE1BQWUsQ0FBQztJQUNwQixJQUFJLENBQUM7UUFDSCxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsTUFBTSxDQUFDLElBQUksQ0FBQywwREFBMEQsQ0FBQyxDQUFDO1FBQ3hFLE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUNELElBQUksTUFBTSxLQUFLLElBQUksSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQzNFLE1BQU0sQ0FBQyxJQUFJLENBQUMsOERBQThELENBQUMsQ0FBQztRQUM1RSxPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7SUFDRCxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFpQyxDQUFDLEVBQUUsQ0FBQztRQUM3RSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsSUFBSSxLQUFLLEdBQUcsa0JBQWtCLEVBQUUsQ0FBQztZQUNyRyxNQUFNLENBQUMsSUFBSSxDQUFDLG1FQUFtRSxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztZQUMxRixTQUFTO1FBQ1gsQ0FBQztRQUNELEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDeEMsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVELElBQUksTUFBZ0YsQ0FBQztBQUVyRixTQUFTLGNBQWM7SUFDckIsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQztJQUN6QyxJQUFJLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQyxHQUFHLEtBQUssR0FBRztRQUFFLE1BQU0sR0FBRyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsbUJBQW1CLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztJQUN2RixPQUFPLE1BQU0sQ0FBQyxPQUFPLENBQUM7QUFDeEIsQ0FBQztBQUVEOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxVQUFVLFlBQVksQ0FBQyxTQUFnRDtJQUMzRSxNQUFNLE9BQU8sR0FBRyxjQUFjLEVBQUUsQ0FBQztJQUNqQyxNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ2xELElBQUksUUFBUSxJQUFJLE9BQU87UUFBRSxPQUFPLE9BQU8sQ0FBQyxRQUFRLENBQUUsQ0FBQztJQUNuRCxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQzlDLElBQUksSUFBaUQsQ0FBQztJQUN0RCxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ3BELElBQUksR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxJQUFJLEdBQUcsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUM7WUFBRSxJQUFJLEdBQUcsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFDL0csQ0FBQztJQUNELE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDaEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBPUEVOQUlfQ09NUEFUSUJMRV9QUk9WSURFUl9JRCB9IGZyb20gJy4uL2NvbnN0YW50cy9haS1wcm92aWRlcnMuanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdhaS1jYWxsLXdlaWdodCcpO1xuXG4vKiogTGFyZ2VzdCB3ZWlnaHQgb25lIG1vZGVsIG1heSBjYXJyeSDigJQgb25lIGNhbGwgY2FuIG5ldmVyIHJlc2VydmUgbW9yZSB0aGFuIHRoaXMgbWFueSB1bml0cy4gKi9cbmV4cG9ydCBjb25zdCBNQVhfQUlfQ0FMTF9XRUlHSFQgPSAxMDA7XG5cbi8qKlxuICogQnVpbHQtaW4gd2VpZ2h0cywgdW5kZXIgd2hhdGV2ZXIgYEFJX01PREVMX1dFSUdIVFNgIHNldHMuIFRoZSBzZWxmLWhvc3RlZFxuICogT3BlbkFJLWNvbXBhdGlibGUgcHJvdmlkZXIgKHRoZSBpbi1jbHVzdGVyIGBhc2stbW9kZWxgKSBydW5zIG9uIGhhcmR3YXJlIHRoZVxuICogb3BlcmF0b3IgYWxyZWFkeSBwYXlzIGZvciwgc28gYSBjYWxsIG9uIGl0IHNwZW5kcyBOTyBgYWlDYWxsc2AgdW5pdCBieSBkZWZhdWx0O1xuICogc2V0IGB7XCJvcGVuYWktY29tcGF0aWJsZVwiOiAxfWAgdG8gbWV0ZXIgaXQgbGlrZSBhbnkgb3RoZXIgbW9kZWwuXG4gKi9cbmNvbnN0IEJVSUxUX0lOX1dFSUdIVFM6IFJlYWRvbmx5PFJlY29yZDxzdHJpbmcsIG51bWJlcj4+ID0geyBbT1BFTkFJX0NPTVBBVElCTEVfUFJPVklERVJfSURdOiAwIH07XG5cbi8qKlxuICogUGFyc2UgYEFJX01PREVMX1dFSUdIVFNgIOKAlCBhIEpTT04gb2JqZWN0IG9mIGBrZXkg4oaSIGludGVnZXIgd2VpZ2h0YCDigJQgaW50b1xuICogbG93ZXItY2FzZWQgZW50cmllcy4gQSBtYWxmb3JtZWQgdmFsdWUgaXMgSUdOT1JFRCAobG9nZ2VkKSwgbmV2ZXIgZmF0YWw6IHRoZVxuICogd2VpZ2h0cyBhcmUgYW4gb3B0aW9uYWwgcHJpY2luZyBsZXZlciBhbmQgZXZlcnkgY2FsbCBzdGlsbCBtZXRlcnMgYXQgMS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlQWlNb2RlbFdlaWdodHMocmF3OiBzdHJpbmcgfCB1bmRlZmluZWQpOiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+IHtcbiAgY29uc3Qgb3V0OiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+ID0geyAuLi5CVUlMVF9JTl9XRUlHSFRTIH07XG4gIGlmICghcmF3IHx8IHJhdy50cmltKCkgPT09ICcnKSByZXR1cm4gb3V0O1xuICBsZXQgcGFyc2VkOiB1bmtub3duO1xuICB0cnkge1xuICAgIHBhcnNlZCA9IEpTT04ucGFyc2UocmF3KTtcbiAgfSBjYXRjaCB7XG4gICAgbG9nZ2VyLndhcm4oJ0FJX01PREVMX1dFSUdIVFMgaXMgbm90IHZhbGlkIEpTT047IGV2ZXJ5IG1vZGVsIHdlaWdocyAxJyk7XG4gICAgcmV0dXJuIG91dDtcbiAgfVxuICBpZiAocGFyc2VkID09PSBudWxsIHx8IHR5cGVvZiBwYXJzZWQgIT09ICdvYmplY3QnIHx8IEFycmF5LmlzQXJyYXkocGFyc2VkKSkge1xuICAgIGxvZ2dlci53YXJuKCdBSV9NT0RFTF9XRUlHSFRTIG11c3QgYmUgYSBKU09OIG9iamVjdDsgZXZlcnkgbW9kZWwgd2VpZ2hzIDEnKTtcbiAgICByZXR1cm4gb3V0O1xuICB9XG4gIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKHBhcnNlZCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikpIHtcbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnbnVtYmVyJyB8fCAhTnVtYmVyLmlzSW50ZWdlcih2YWx1ZSkgfHwgdmFsdWUgPCAwIHx8IHZhbHVlID4gTUFYX0FJX0NBTExfV0VJR0hUKSB7XG4gICAgICBsb2dnZXIud2FybignQUlfTU9ERUxfV0VJR0hUUyBlbnRyeSBpZ25vcmVkICh3ZWlnaHQgbXVzdCBiZSBhbiBpbnRlZ2VyIDAuLjEwMCknLCB7IGtleSB9KTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICBvdXRba2V5LnRyaW0oKS50b0xvd2VyQ2FzZSgpXSA9IHZhbHVlO1xuICB9XG4gIHJldHVybiBvdXQ7XG59XG5cbmxldCBjYWNoZWQ6IHsgcmF3OiBzdHJpbmcgfCB1bmRlZmluZWQ7IHdlaWdodHM6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gfSB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gY3VycmVudFdlaWdodHMoKTogUmVjb3JkPHN0cmluZywgbnVtYmVyPiB7XG4gIGNvbnN0IHJhdyA9IHByb2Nlc3MuZW52LkFJX01PREVMX1dFSUdIVFM7XG4gIGlmICghY2FjaGVkIHx8IGNhY2hlZC5yYXcgIT09IHJhdykgY2FjaGVkID0geyByYXcsIHdlaWdodHM6IHBhcnNlQWlNb2RlbFdlaWdodHMocmF3KSB9O1xuICByZXR1cm4gY2FjaGVkLndlaWdodHM7XG59XG5cbi8qKlxuICogSG93IG1hbnkgYGFpQ2FsbHNgIHVuaXRzIG9uZSBjYWxsIG9uIGB7IHByb3ZpZGVyLCBtb2RlbElkIH1gIHJlc2VydmVzLlxuICpcbiAqIE1PREVMLUNMQVNTIFdFSUdIVElORyBpcyBPRkYgYnkgZGVmYXVsdDogd2l0aCBgQUlfTU9ERUxfV0VJR0hUU2AgdW5zZXQgZXZlcnlcbiAqIG1vZGVsIHdlaWdocyAxIOKAlCBleGNlcHQgdGhlIHNlbGYtaG9zdGVkIHByb3ZpZGVyLCB3aGljaCB3ZWlnaHMgMCAoc2VlXG4gKiB7QGxpbmsgQlVJTFRfSU5fV0VJR0hUU30pLiBNYXRjaGluZywgZmlyc3QgaGl0IHdpbnM6XG4gKiAgMS4gYSBrZXkgZXF1YWwgdG8gdGhlIFBST1ZJREVSIGlkIChgXCJvcGVuYWktY29tcGF0aWJsZVwiOiAwYCwgYFwiYmVkcm9ja1wiOiAyYCk7XG4gKiAgMi4gdGhlIExPTkdFU1Qga2V5IGNvbnRhaW5lZCBpbiB0aGUgbW9kZWwgaWQgKGBcIm9wdXNcIjogM2AgbWF0Y2hlc1xuICogICAgIGBjbGF1ZGUtb3B1cy00LTFgOyBgXCJzb25uZXRcIjogMWAsIGBcImhhaWt1XCI6IDFgKS5cbiAqIE5vIG1hdGNoIOKGkiAxLiBBIHdlaWdodCBvZiAwIG1lYW5zIHRoZSBjYWxsIHJlc2VydmVzIG5vdGhpbmcgKGl0IGlzIHN0aWxsXG4gKiByZWNvcmRlZCBpbiB0aGUgdXNhZ2UgbGVkZ2VyIHdpdGggaXRzIHRva2VucykuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhaUNhbGxXZWlnaHQoc2VsZWN0aW9uOiB7IHByb3ZpZGVyOiBzdHJpbmc7IG1vZGVsSWQ6IHN0cmluZyB9KTogbnVtYmVyIHtcbiAgY29uc3Qgd2VpZ2h0cyA9IGN1cnJlbnRXZWlnaHRzKCk7XG4gIGNvbnN0IHByb3ZpZGVyID0gc2VsZWN0aW9uLnByb3ZpZGVyLnRvTG93ZXJDYXNlKCk7XG4gIGlmIChwcm92aWRlciBpbiB3ZWlnaHRzKSByZXR1cm4gd2VpZ2h0c1twcm92aWRlcl0hO1xuICBjb25zdCBtb2RlbCA9IHNlbGVjdGlvbi5tb2RlbElkLnRvTG93ZXJDYXNlKCk7XG4gIGxldCBiZXN0OiB7IGtleTogc3RyaW5nOyB3ZWlnaHQ6IG51bWJlciB9IHwgdW5kZWZpbmVkO1xuICBmb3IgKGNvbnN0IFtrZXksIHdlaWdodF0gb2YgT2JqZWN0LmVudHJpZXMod2VpZ2h0cykpIHtcbiAgICBpZiAoa2V5Lmxlbmd0aCA+IDAgJiYgbW9kZWwuaW5jbHVkZXMoa2V5KSAmJiAoIWJlc3QgfHwga2V5Lmxlbmd0aCA+IGJlc3Qua2V5Lmxlbmd0aCkpIGJlc3QgPSB7IGtleSwgd2VpZ2h0IH07XG4gIH1cbiAgcmV0dXJuIGJlc3QgPyBiZXN0LndlaWdodCA6IDE7XG59XG4iXX0=
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface SlackAlertsConfig {
|
|
2
|
+
/**
|
|
3
|
+
* Whether ops-team Slack delivery is DECLARED on.
|
|
4
|
+
*
|
|
5
|
+
* Defaults FALSE, matching `ONCALL_ENABLED`: both destinations are opt-in, so
|
|
6
|
+
* a deployment states what it wants rather than inheriting a channel it may
|
|
7
|
+
* not have. Note what this flag does NOT do — it cannot remove the Slack
|
|
8
|
+
* receivers, because `alertmanager.yml` is asserted byte-identical across
|
|
9
|
+
* targets and Alertmanager has no templating. A deployment with the flag off
|
|
10
|
+
* and real URLs in its Secret still delivers to Slack.
|
|
11
|
+
*/
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Whether Slack delivery CAN happen — i.e. the webhook URLs are non-empty.
|
|
15
|
+
*
|
|
16
|
+
* Derived at deploy time (`pb_check_alert_delivery` writes it) because the
|
|
17
|
+
* URLs live in Alertmanager's Secret and never enter this process: a Slack
|
|
18
|
+
* webhook URL is a bearer credential, read per notification via
|
|
19
|
+
* `api_url_file`. Platform therefore cannot inspect them and must be told.
|
|
20
|
+
*
|
|
21
|
+
* SEPARATE FROM `enabled` for the same reason the pager splits the two, and
|
|
22
|
+
* the distinction is what keeps `AlertDeliveryDisabled` honest. Keying that
|
|
23
|
+
* alert on the DECLARATION would page every deployment that left the flag at
|
|
24
|
+
* its default while Slack was quietly working — a false alarm on a healthy
|
|
25
|
+
* system, which is the fastest way to teach people to ignore an alert.
|
|
26
|
+
*/
|
|
27
|
+
configured: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function slackAlertsConfig(): SlackAlertsConfig;
|
|
30
|
+
/** Both destinations, resolved together — what `reportAlertDeliveryPosture` reads. */
|
|
31
|
+
export interface AlertDeliveryConfig {
|
|
32
|
+
slack: SlackAlertsConfig;
|
|
33
|
+
pager: {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
configured: boolean;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Whether ANY destination could actually deliver — reality, not declaration. */
|
|
39
|
+
export declare function canDeliverAnywhere(cfg: AlertDeliveryConfig): boolean;
|
|
40
|
+
export declare function alertDeliveryConfig(): AlertDeliveryConfig;
|
|
41
|
+
/**
|
|
42
|
+
* Publish the delivery posture as gauges, and shout if it is incoherent.
|
|
43
|
+
*
|
|
44
|
+
* Called once at boot. Gauges rather than counters because these are STATES of
|
|
45
|
+
* the deployment — an operator wants "will anyone hear an alert right now", not
|
|
46
|
+
* how many times a process started and wondered.
|
|
47
|
+
*
|
|
48
|
+
* The value of having this at all: before it, answering "does this deployment
|
|
49
|
+
* page anyone?" meant reading `.env`, two Secrets and four copies of
|
|
50
|
+
* `alertmanager.yml`. A deployment can now alert on its own inability to alert.
|
|
51
|
+
*/
|
|
52
|
+
export declare function reportAlertDeliveryPosture(cfg?: AlertDeliveryConfig): void;
|
|
53
|
+
/**
|
|
54
|
+
* The dead-man switch: a heartbeat whose ABSENCE is the alert.
|
|
55
|
+
*
|
|
56
|
+
* Every other alert in this system depends on something inside it still working.
|
|
57
|
+
* This one inverts that — an external service expects a POST on a schedule and
|
|
58
|
+
* raises an incident when one does not arrive, so a platform that is wedged,
|
|
59
|
+
* crashed, partitioned or never started is reported BY ITS SILENCE. Nothing
|
|
60
|
+
* inside a broken deployment has to notice its own brokenness.
|
|
61
|
+
*
|
|
62
|
+
* WHY IT IS BUILT HERE RATHER THAN BOUGHT, when the rest of on-call deliberately
|
|
63
|
+
* is not: PagerDuty's heartbeat monitoring is an Event Orchestration pattern and
|
|
64
|
+
* documented as AIOps-powered, so it may sit behind a tier and is not part of
|
|
65
|
+
* Events v2. More importantly a dead-man switch is a CLOCK, not a telecom
|
|
66
|
+
* problem — the thing that made the last mile worth buying (carrier retries,
|
|
67
|
+
* delivery receipts, a 3am failure budget) simply does not apply to sending one
|
|
68
|
+
* request on an interval. `ONCALL_DEADMAN_URL` therefore points wherever the
|
|
69
|
+
* operator likes: PagerDuty's orchestration endpoint, a purpose-built dead-man
|
|
70
|
+
* service, or an uptime monitor.
|
|
71
|
+
*
|
|
72
|
+
* TWO PROPERTIES WORTH STATING, because they are the reason this is safe:
|
|
73
|
+
*
|
|
74
|
+
* - It is NOT leader-locked. A heartbeat is per-process on purpose: with three
|
|
75
|
+
* replicas, the external service hears from whichever are alive, and silence
|
|
76
|
+
* means ALL of them stopped. Leader-locking it would make the switch report
|
|
77
|
+
* the leader's health rather than the deployment's — and would make a Redis
|
|
78
|
+
* outage look identical to a total outage.
|
|
79
|
+
* - It fails SILENTLY on its own errors, deliberately. A heartbeat that logged
|
|
80
|
+
* an error every interval during a network blip would be its own noise
|
|
81
|
+
* source, and the consequence of a missed beat is already handled: the
|
|
82
|
+
* external service alerts. The only thing it must never do is report success
|
|
83
|
+
* it did not achieve, which is why nothing here swallows a non-2xx.
|
|
84
|
+
*/
|
|
85
|
+
export interface DeadmanConfig {
|
|
86
|
+
url: string;
|
|
87
|
+
intervalMs: number;
|
|
88
|
+
}
|
|
89
|
+
export declare function deadmanConfig(): DeadmanConfig;
|
|
90
|
+
/**
|
|
91
|
+
* Start beating. No-op when `ONCALL_DEADMAN_URL` is unset, which is the default:
|
|
92
|
+
* an unconfigured dead-man switch must not beat against nothing, and must not
|
|
93
|
+
* pretend it is covering the deployment.
|
|
94
|
+
*
|
|
95
|
+
* Returns true when a timer was started, so a caller can log the posture.
|
|
96
|
+
*/
|
|
97
|
+
export declare function startDeadmanHeartbeat(cfg?: DeadmanConfig): boolean;
|
|
98
|
+
/** Stop beating — called from the unified shutdown path, not a signal handler. */
|
|
99
|
+
export declare function stopDeadmanHeartbeat(): void;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Where a platform-wide alert is meant to GO, as declared configuration.
|
|
5
|
+
*
|
|
6
|
+
* Two destinations, with different jobs. The ops-team Slack channels are the
|
|
7
|
+
* baseline: everything critical or warning lands there and that is where the
|
|
8
|
+
* thread, the context and the handover live. The pager is additive and narrow —
|
|
9
|
+
* only `severity: critical` + `component: support`, because that is the one
|
|
10
|
+
* alert backing a contractual out-of-hours promise.
|
|
11
|
+
*
|
|
12
|
+
* WHAT THIS MODULE CAN AND CANNOT DO, because the limit is not obvious.
|
|
13
|
+
* `config/alertmanager/alertmanager.yml` is in the deploy contract's
|
|
14
|
+
* REQUIRED_IDENTICAL set and asserted byte-identical across targets, and
|
|
15
|
+
* Alertmanager has no templating. So NOTHING here removes a receiver or a route
|
|
16
|
+
* — the routes are always present. What these flags do is:
|
|
17
|
+
*
|
|
18
|
+
* - declare INTENT, so "we do not use Slack" is a stated decision rather than
|
|
19
|
+
* something inferred from two empty URLs that look like an oversight;
|
|
20
|
+
* - gate the deploy-time check (`pb_check_alert_delivery`), which is what turns
|
|
21
|
+
* a half-configured channel into a refused deploy instead of a 3am surprise;
|
|
22
|
+
* - drive the posture gauges below, so a dashboard can answer "is anyone
|
|
23
|
+
* actually going to hear this" without reading four YAML files.
|
|
24
|
+
*
|
|
25
|
+
* The URLS THEMSELVES ARE NOT HERE, and that is deliberate. A Slack webhook URL
|
|
26
|
+
* is a bearer credential: anyone holding it can post to the channel. Alertmanager
|
|
27
|
+
* reads them from a mounted Secret via `api_url_file`, per notification, so they
|
|
28
|
+
* never enter a ConfigMap and never enter this process. Platform knows WHETHER
|
|
29
|
+
* Slack delivery is declared on; it does not know, and does not need, where to.
|
|
30
|
+
* The pager's routing key is different — that one IS consumed by this process, so
|
|
31
|
+
* it lives in `pagerDutyConfig()`.
|
|
32
|
+
*/
|
|
33
|
+
import { pagerDutyConfig } from './pagerduty.js';
|
|
34
|
+
import { envBool, envInt, envStr } from '../utils/env.js';
|
|
35
|
+
import { createLogger } from '../utils/logger.js';
|
|
36
|
+
import { emitCounter, emitGauge } from '../utils/metric-emitter.js';
|
|
37
|
+
import { safeFetch } from '../utils/ssrf.js';
|
|
38
|
+
const logger = createLogger('alert-delivery');
|
|
39
|
+
export function slackAlertsConfig() {
|
|
40
|
+
return {
|
|
41
|
+
enabled: envBool('SLACK_ALERTS_ENABLED', false),
|
|
42
|
+
configured: envBool('SLACK_ALERTS_CONFIGURED', false),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Whether ANY destination could actually deliver — reality, not declaration. */
|
|
46
|
+
export function canDeliverAnywhere(cfg) {
|
|
47
|
+
return cfg.slack.configured || (cfg.pager.enabled && cfg.pager.configured);
|
|
48
|
+
}
|
|
49
|
+
export function alertDeliveryConfig() {
|
|
50
|
+
const pd = pagerDutyConfig();
|
|
51
|
+
return {
|
|
52
|
+
slack: slackAlertsConfig(),
|
|
53
|
+
// `configured` separately from `enabled`: enabled-without-a-key is the
|
|
54
|
+
// half-configured state worth seeing, and it is invisible if the two are
|
|
55
|
+
// collapsed into one boolean.
|
|
56
|
+
pager: { enabled: pd.enabled, configured: pd.routingKey.length > 0 },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Publish the delivery posture as gauges, and shout if it is incoherent.
|
|
61
|
+
*
|
|
62
|
+
* Called once at boot. Gauges rather than counters because these are STATES of
|
|
63
|
+
* the deployment — an operator wants "will anyone hear an alert right now", not
|
|
64
|
+
* how many times a process started and wondered.
|
|
65
|
+
*
|
|
66
|
+
* The value of having this at all: before it, answering "does this deployment
|
|
67
|
+
* page anyone?" meant reading `.env`, two Secrets and four copies of
|
|
68
|
+
* `alertmanager.yml`. A deployment can now alert on its own inability to alert.
|
|
69
|
+
*/
|
|
70
|
+
export function reportAlertDeliveryPosture(cfg = alertDeliveryConfig()) {
|
|
71
|
+
emitGauge('alert_delivery_enabled', { channel: 'slack' }, cfg.slack.enabled ? 1 : 0);
|
|
72
|
+
emitGauge('alert_delivery_enabled', { channel: 'pager' }, cfg.pager.enabled ? 1 : 0);
|
|
73
|
+
emitGauge('alert_delivery_configured', { channel: 'slack' }, cfg.slack.configured ? 1 : 0);
|
|
74
|
+
emitGauge('alert_delivery_configured', { channel: 'pager' }, cfg.pager.configured ? 1 : 0);
|
|
75
|
+
// Enabled with no credential: somebody asked for paging and is not getting it.
|
|
76
|
+
// The deploy check refuses this combination, so reaching here means it was
|
|
77
|
+
// changed after provisioning — which is exactly when nobody is looking.
|
|
78
|
+
if (cfg.pager.enabled && !cfg.pager.configured) {
|
|
79
|
+
logger.error('ONCALL_ENABLED is true but no PAGERDUTY_ROUTING_KEY is set — support criticals will not page');
|
|
80
|
+
}
|
|
81
|
+
// Nothing CAN deliver. Keyed on reachability rather than on the two flags,
|
|
82
|
+
// because the flags are declarations: a deployment that left both at their
|
|
83
|
+
// (false) defaults while real Slack URLs sit in its Secret is delivering
|
|
84
|
+
// fine, and warning it would be a lie.
|
|
85
|
+
if (!canDeliverAnywhere(cfg)) {
|
|
86
|
+
logger.warn('No platform alert destination is enabled — alerts will reach Alertmanager and go no further', {
|
|
87
|
+
hint: 'SLACK_ALERTS_ENABLED / ONCALL_ENABLED',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function deadmanConfig() {
|
|
92
|
+
return {
|
|
93
|
+
url: envStr('ONCALL_DEADMAN_URL', ''),
|
|
94
|
+
intervalMs: envInt('ONCALL_DEADMAN_INTERVAL_MS', 60_000, { min: 1000 }),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
let deadmanTimer;
|
|
98
|
+
/**
|
|
99
|
+
* Start beating. No-op when `ONCALL_DEADMAN_URL` is unset, which is the default:
|
|
100
|
+
* an unconfigured dead-man switch must not beat against nothing, and must not
|
|
101
|
+
* pretend it is covering the deployment.
|
|
102
|
+
*
|
|
103
|
+
* Returns true when a timer was started, so a caller can log the posture.
|
|
104
|
+
*/
|
|
105
|
+
export function startDeadmanHeartbeat(cfg = deadmanConfig()) {
|
|
106
|
+
if (deadmanTimer || !cfg.url)
|
|
107
|
+
return false;
|
|
108
|
+
const beat = async () => {
|
|
109
|
+
try {
|
|
110
|
+
const resp = await safeFetch(cfg.url, { method: 'POST', timeoutMs: 5000, body: '{}', headers: { 'Content-Type': 'application/json' } });
|
|
111
|
+
emitCounter('oncall_deadman_total', { outcome: resp.ok && !resp.redirected ? 'sent' : 'failed' });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Counted, never logged per-beat — see the module note on noise.
|
|
115
|
+
emitCounter('oncall_deadman_total', { outcome: 'failed' });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
// `unref` so a pending beat cannot hold the process open during shutdown: the
|
|
119
|
+
// switch exists to report that the process STOPPED, and delaying that is
|
|
120
|
+
// precisely backwards.
|
|
121
|
+
deadmanTimer = setInterval(() => void beat(), cfg.intervalMs);
|
|
122
|
+
deadmanTimer.unref();
|
|
123
|
+
void beat();
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
/** Stop beating — called from the unified shutdown path, not a signal handler. */
|
|
127
|
+
export function stopDeadmanHeartbeat() {
|
|
128
|
+
if (deadmanTimer)
|
|
129
|
+
clearInterval(deadmanTimer);
|
|
130
|
+
deadmanTimer = undefined;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxlcnQtZGVsaXZlcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvYWxlcnQtZGVsaXZlcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Qkc7QUFFSCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDakQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDcEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdDLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBK0I5QyxNQUFNLFVBQVUsaUJBQWlCO0lBQy9CLE9BQU87UUFDTCxPQUFPLEVBQUUsT0FBTyxDQUFDLHNCQUFzQixFQUFFLEtBQUssQ0FBQztRQUMvQyxVQUFVLEVBQUUsT0FBTyxDQUFDLHlCQUF5QixFQUFFLEtBQUssQ0FBQztLQUN0RCxDQUFDO0FBQ0osQ0FBQztBQVFELGlGQUFpRjtBQUNqRixNQUFNLFVBQVUsa0JBQWtCLENBQUMsR0FBd0I7SUFDekQsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDLFVBQVUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7QUFDN0UsQ0FBQztBQUVELE1BQU0sVUFBVSxtQkFBbUI7SUFDakMsTUFBTSxFQUFFLEdBQUcsZUFBZSxFQUFFLENBQUM7SUFDN0IsT0FBTztRQUNMLEtBQUssRUFBRSxpQkFBaUIsRUFB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LFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDcEcsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLGlFQUFpRTtZQUNqRSxXQUFXLENBQUMsc0JBQXNCLEVBQUUsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUM3RCxDQUFDO0lBQ0gsQ0FBQyxDQUFDO0lBQ0YsOEVBQThFO0lBQzlFLHlFQUF5RTtJQUN6RSx1QkFBdUI7SUFDdkIsWUFBWSxHQUFHLFdBQVcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLElBQUksRUFBRSxFQUFFLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM5RCxZQUFZLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDckIsS0FBSyxJQUFJLEVBQUUsQ0FBQztJQUNaLE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztBQUVELGtGQUFrRjtBQUNsRixNQUFNLFVBQVUsb0JBQW9CO0lBQ2xDLElBQUksWUFBWTtRQUFFLGFBQWEsQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUM5QyxZQUFZLEdBQUcsU0FBUyxDQUFDO0FBQzNCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBXaGVyZSBhIHBsYXRmb3JtLXdpZGUgYWxlcnQgaXMgbWVhbnQgdG8gR08sIGFzIGRlY2xhcmVkIGNvbmZpZ3VyYXRpb24uXG4gKlxuICogVHdvIGRlc3RpbmF0aW9ucywgd2l0aCBkaWZmZXJlbnQgam9icy4gVGhlIG9wcy10ZWFtIFNsYWNrIGNoYW5uZWxzIGFyZSB0aGVcbiAqIGJhc2VsaW5lOiBldmVyeXRoaW5nIGNyaXRpY2FsIG9yIHdhcm5pbmcgbGFuZHMgdGhlcmUgYW5kIHRoYXQgaXMgd2hlcmUgdGhlXG4gKiB0aHJlYWQsIHRoZSBjb250ZXh0IGFuZCB0aGUgaGFuZG92ZXIgbGl2ZS4gVGhlIHBhZ2VyIGlzIGFkZGl0aXZlIGFuZCBuYXJyb3cg4oCUXG4gKiBvbmx5IGBzZXZlcml0eTogY3JpdGljYWxgICsgYGNvbXBvbmVudDogc3VwcG9ydGAsIGJlY2F1c2UgdGhhdCBpcyB0aGUgb25lXG4gKiBhbGVydCBiYWNraW5nIGEgY29udHJhY3R1YWwgb3V0LW9mLWhvdXJzIHByb21pc2UuXG4gKlxuICogV0hBVCBUSElTIE1PRFVMRSBDQU4gQU5EIENBTk5PVCBETywgYmVjYXVzZSB0aGUgbGltaXQgaXMgbm90IG9idmlvdXMuXG4gKiBgY29uZmlnL2FsZXJ0bWFuYWdlci9hbGVydG1hbmFnZXIueW1sYCBpcyBpbiB0aGUgZGVwbG95IGNvbnRyYWN0J3NcbiAqIFJFUVVJUkVEX0lERU5USUNBTCBzZXQgYW5kIGFzc2VydGVkIGJ5dGUtaWRlbnRpY2FsIGFjcm9zcyB0YXJnZXRzLCBhbmRcbiAqIEFsZXJ0bWFuYWdlciBoYXMgbm8gdGVtcGxhdGluZy4gU28gTk9USElORyBoZXJlIHJlbW92ZXMgYSByZWNlaXZlciBvciBhIHJvdXRlXG4gKiDigJQgdGhlIHJvdXRlcyBhcmUgYWx3YXlzIHByZXNlbnQuIFdoYXQgdGhlc2UgZmxhZ3MgZG8gaXM6XG4gKlxuICogIC0gZGVjbGFyZSBJTlRFTlQsIHNvIFwid2UgZG8gbm90IHVzZSBTbGFja1wiIGlzIGEgc3RhdGVkIGRlY2lzaW9uIHJhdGhlciB0aGFuXG4gKiAgICBzb21ldGhpbmcgaW5mZXJyZWQgZnJvbSB0d28gZW1wdHkgVVJMcyB0aGF0IGxvb2sgbGlrZSBhbiBvdmVyc2lnaHQ7XG4gKiAgLSBnYXRlIHRoZSBkZXBsb3ktdGltZSBjaGVjayAoYHBiX2NoZWNrX2FsZXJ0X2RlbGl2ZXJ5YCksIHdoaWNoIGlzIHdoYXQgdHVybnNcbiAqICAgIGEgaGFsZi1jb25maWd1cmVkIGNoYW5uZWwgaW50byBhIHJlZnVzZWQgZGVwbG95IGluc3RlYWQgb2YgYSAzYW0gc3VycHJpc2U7XG4gKiAgLSBkcml2ZSB0aGUgcG9zdHVyZSBnYXVnZXMgYmVsb3csIHNvIGEgZGFzaGJvYXJkIGNhbiBhbnN3ZXIgXCJpcyBhbnlvbmVcbiAqICAgIGFjdHVhbGx5IGdvaW5nIHRvIGhlYXIgdGhpc1wiIHdpdGhvdXQgcmVhZGluZyBmb3VyIFlBTUwgZmlsZXMuXG4gKlxuICogVGhlIFVSTFMgVEhFTVNFTFZFUyBBUkUgTk9UIEhFUkUsIGFuZCB0aGF0IGlzIGRlbGliZXJhdGUuIEEgU2xhY2sgd2ViaG9vayBVUkxcbiAqIGlzIGEgYmVhcmVyIGNyZWRlbnRpYWw6IGFueW9uZSBob2xkaW5nIGl0IGNhbiBwb3N0IHRvIHRoZSBjaGFubmVsLiBBbGVydG1hbmFnZXJcbiAqIHJlYWRzIHRoZW0gZnJvbSBhIG1vdW50ZWQgU2VjcmV0IHZpYSBgYXBpX3VybF9maWxlYCwgcGVyIG5vdGlmaWNhdGlvbiwgc28gdGhleVxuICogbmV2ZXIgZW50ZXIgYSBDb25maWdNYXAgYW5kIG5ldmVyIGVudGVyIHRoaXMgcHJvY2Vzcy4gUGxhdGZvcm0ga25vd3MgV0hFVEhFUlxuICogU2xhY2sgZGVsaXZlcnkgaXMgZGVjbGFyZWQgb247IGl0IGRvZXMgbm90IGtub3csIGFuZCBkb2VzIG5vdCBuZWVkLCB3aGVyZSB0by5cbiAqIFRoZSBwYWdlcidzIHJvdXRpbmcga2V5IGlzIGRpZmZlcmVudCDigJQgdGhhdCBvbmUgSVMgY29uc3VtZWQgYnkgdGhpcyBwcm9jZXNzLCBzb1xuICogaXQgbGl2ZXMgaW4gYHBhZ2VyRHV0eUNvbmZpZygpYC5cbiAqL1xuXG5pbXBvcnQgeyBwYWdlckR1dHlDb25maWcgfSBmcm9tICcuL3BhZ2VyZHV0eS5qcyc7XG5pbXBvcnQgeyBlbnZCb29sLCBlbnZJbnQsIGVudlN0ciB9IGZyb20gJy4uL3V0aWxzL2Vudi5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIsIGVtaXRHYXVnZSB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcbmltcG9ydCB7IHNhZmVGZXRjaCB9IGZyb20gJy4uL3V0aWxzL3NzcmYuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ2FsZXJ0LWRlbGl2ZXJ5Jyk7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2xhY2tBbGVydHNDb25maWcge1xuICAvKipcbiAgICogV2hldGhlciBvcHMtdGVhbSBTbGFjayBkZWxpdmVyeSBpcyBERUNMQVJFRCBvbi5cbiAgICpcbiAgICogRGVmYXVsdHMgRkFMU0UsIG1hdGNoaW5nIGBPTkNBTExfRU5BQkxFRGA6IGJvdGggZGVzdGluYXRpb25zIGFyZSBvcHQtaW4sIHNvXG4gICAqIGEgZGVwbG95bWVudCBzdGF0ZXMgd2hhdCBpdCB3YW50cyByYXRoZXIgdGhhbiBpbmhlcml0aW5nIGEgY2hhbm5lbCBpdCBtYXlcbiAgICogbm90IGhhdmUuIE5vdGUgd2hhdCB0aGlzIGZsYWcgZG9lcyBOT1QgZG8g4oCUIGl0IGNhbm5vdCByZW1vdmUgdGhlIFNsYWNrXG4gICAqIHJlY2VpdmVycywgYmVjYXVzZSBgYWxlcnRtYW5hZ2VyLnltbGAgaXMgYXNzZXJ0ZWQgYnl0ZS1pZGVudGljYWwgYWNyb3NzXG4gICAqIHRhcmdldHMgYW5kIEFsZXJ0bWFuYWdlciBoYXMgbm8gdGVtcGxhdGluZy4gQSBkZXBsb3ltZW50IHdpdGggdGhlIGZsYWcgb2ZmXG4gICAqIGFuZCByZWFsIFVSTHMgaW4gaXRzIFNlY3JldCBzdGlsbCBkZWxpdmVycyB0byBTbGFjay5cbiAgICovXG4gIGVuYWJsZWQ6IGJvb2xlYW47XG4gIC8qKlxuICAgKiBXaGV0aGVyIFNsYWNrIGRlbGl2ZXJ5IENBTiBoYXBwZW4g4oCUIGkuZS4gdGhlIHdlYmhvb2sgVVJMcyBhcmUgbm9uLWVtcHR5LlxuICAgKlxuICAgKiBEZXJpdmVkIGF0IGRlcGxveSB0aW1lIChgcGJfY2hlY2tfYWxlcnRfZGVsaXZlcnlgIHdyaXRlcyBpdCkgYmVjYXVzZSB0aGVcbiAgICogVVJMcyBsaXZlIGluIEFsZXJ0bWFuYWdlcidzIFNlY3JldCBhbmQgbmV2ZXIgZW50ZXIgdGhpcyBwcm9jZXNzOiBhIFNsYWNrXG4gICAqIHdlYmhvb2sgVVJMIGlzIGEgYmVhcmVyIGNyZWRlbnRpYWwsIHJlYWQgcGVyIG5vdGlmaWNhdGlvbiB2aWFcbiAgICogYGFwaV91cmxfZmlsZWAuIFBsYXRmb3JtIHRoZXJlZm9yZSBjYW5ub3QgaW5zcGVjdCB0aGVtIGFuZCBtdXN0IGJlIHRvbGQuXG4gICAqXG4gICAqIFNFUEFSQVRFIEZST00gYGVuYWJsZWRgIGZvciB0aGUgc2FtZSByZWFzb24gdGhlIHBhZ2VyIHNwbGl0cyB0aGUgdHdvLCBhbmRcbiAgICogdGhlIGRpc3RpbmN0aW9uIGlzIHdoYXQga2VlcHMgYEFsZXJ0RGVsaXZlcnlEaXNhYmxlZGAgaG9uZXN0LiBLZXlpbmcgdGhhdFxuICAgKiBhbGVydCBvbiB0aGUgREVDTEFSQVRJT04gd291bGQgcGFnZSBldmVyeSBkZXBsb3ltZW50IHRoYXQgbGVmdCB0aGUgZmxhZyBhdFxuICAgKiBpdHMgZGVmYXVsdCB3aGlsZSBTbGFjayB3YXMgcXVpZXRseSB3b3JraW5nIOKAlCBhIGZhbHNlIGFsYXJtIG9uIGEgaGVhbHRoeVxuICAgKiBzeXN0ZW0sIHdoaWNoIGlzIHRoZSBmYXN0ZXN0IHdheSB0byB0ZWFjaCBwZW9wbGUgdG8gaWdub3JlIGFuIGFsZXJ0LlxuICAgKi9cbiAgY29uZmlndXJlZDogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNsYWNrQWxlcnRzQ29uZmlnKCk6IFNsYWNrQWxlcnRzQ29uZmlnIHtcbiAgcmV0dXJuIHtcbiAgICBlbmFibGVkOiBlbnZCb29sKCdTTEFDS19BTEVSVFNfRU5BQkxFRCcsIGZhbHNlKSxcbiAgICBjb25maWd1cmVkOiBlbnZCb29sKCdTTEFDS19BTEVSVFNfQ09ORklHVVJFRCcsIGZhbHNlKSxcbiAgfTtcbn1cblxuLyoqIEJvdGggZGVzdGluYXRpb25zLCByZXNvbHZlZCB0b2dldGhlciDigJQgd2hhdCBgcmVwb3J0QWxlcnREZWxpdmVyeVBvc3R1cmVgIHJlYWRzLiAqL1xuZXhwb3J0IGludGVyZmFjZSBBbGVydERlbGl2ZXJ5Q29uZmlnIHtcbiAgc2xhY2s6IFNsYWNrQWxlcnRzQ29uZmlnO1xuICBwYWdlcjogeyBlbmFibGVkOiBib29sZWFuOyBjb25maWd1cmVkOiBib29sZWFuIH07XG59XG5cbi8qKiBXaGV0aGVyIEFOWSBkZXN0aW5hdGlvbiBjb3VsZCBhY3R1YWxseSBkZWxpdmVyIOKAlCByZWFsaXR5LCBub3QgZGVjbGFyYXRpb24uICovXG5leHBvcnQgZnVuY3Rpb24gY2FuRGVsaXZlckFueXdoZXJlKGNmZzogQWxlcnREZWxpdmVyeUNvbmZpZyk6IGJvb2xlYW4ge1xuICByZXR1cm4gY2ZnLnNsYWNrLmNvbmZpZ3VyZWQgfHwgKGNmZy5wYWdlci5lbmFibGVkICYmIGNmZy5wYWdlci5jb25maWd1cmVkKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFsZXJ0RGVsaXZlcnlDb25maWcoKTogQWxlcnREZWxpdmVyeUNvbmZpZyB7XG4gIGNvbnN0IHBkID0gcGFnZXJEdXR5Q29uZmlnKCk7XG4gIHJldHVybiB7XG4gICAgc2xhY2s6IHNsYWNrQWxlcnRzQ29uZmlnKCksXG4gICAgLy8gYGNvbmZpZ3VyZWRgIHNlcGFyYXRlbHkgZnJvbSBgZW5hYmxlZGA6IGVuYWJsZWQtd2l0aG91dC1hLWtleSBpcyB0aGVcbiAgICAvLyBoYWxmLWNvbmZpZ3VyZWQgc3RhdGUgd29ydGggc2VlaW5nLCBhbmQgaXQgaXMgaW52aXNpYmxlIGlmIHRoZSB0d28gYXJlXG4gICAgLy8gY29sbGFwc2VkIGludG8gb25lIGJvb2xlYW4uXG4gICAgcGFnZXI6IHsgZW5hYmxlZDogcGQuZW5hYmxlZCwgY29uZmlndXJlZDogcGQucm91dGluZ0tleS5sZW5ndGggPiAwIH0sXG4gIH07XG59XG5cbi8qKlxuICogUHVibGlzaCB0aGUgZGVsaXZlcnkgcG9zdHVyZSBhcyBnYXVnZXMsIGFuZCBzaG91dCBpZiBpdCBpcyBpbmNvaGVyZW50LlxuICpcbiAqIENhbGxlZCBvbmNlIGF0IGJvb3QuIEdhdWdlcyByYXRoZXIgdGhhbiBjb3VudGVycyBiZWNhdXNlIHRoZXNlIGFyZSBTVEFURVMgb2ZcbiAqIHRoZSBkZXBsb3ltZW50IOKAlCBhbiBvcGVyYXRvciB3YW50cyBcIndpbGwgYW55b25lIGhlYXIgYW4gYWxlcnQgcmlnaHQgbm93XCIsIG5vdFxuICogaG93IG1hbnkgdGltZXMgYSBwcm9jZXNzIHN0YXJ0ZWQgYW5kIHdvbmRlcmVkLlxuICpcbiAqIFRoZSB2YWx1ZSBvZiBoYXZpbmcgdGhpcyBhdCBhbGw6IGJlZm9yZSBpdCwgYW5zd2VyaW5nIFwiZG9lcyB0aGlzIGRlcGxveW1lbnRcbiAqIHBhZ2UgYW55b25lP1wiIG1lYW50IHJlYWRpbmcgYC5lbnZgLCB0d28gU2VjcmV0cyBhbmQgZm91ciBjb3BpZXMgb2ZcbiAqIGBhbGVydG1hbmFnZXIueW1sYC4gQSBkZXBsb3ltZW50IGNhbiBub3cgYWxlcnQgb24gaXRzIG93biBpbmFiaWxpdHkgdG8gYWxlcnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXBvcnRBbGVydERlbGl2ZXJ5UG9zdHVyZShjZmc6IEFsZXJ0RGVsaXZlcnlDb25maWcgPSBhbGVydERlbGl2ZXJ5Q29uZmlnKCkpOiB2b2lkIHtcbiAgZW1pdEdhdWdlKCdhbGVydF9kZWxpdmVyeV9lbmFibGVkJywgeyBjaGFubmVsOiAnc2xhY2snIH0sIGNmZy5zbGFjay5lbmFibGVkID8gMSA6IDApO1xuICBlbWl0R2F1Z2UoJ2FsZXJ0X2RlbGl2ZXJ5X2VuYWJsZWQnLCB7IGNoYW5uZWw6ICdwYWdlcicgfSwgY2ZnLnBhZ2VyLmVuYWJsZWQgPyAxIDogMCk7XG4gIGVtaXRHYXVnZSgnYWxlcnRfZGVsaXZlcnlfY29uZmlndXJlZCcsIHsgY2hhbm5lbDogJ3NsYWNrJyB9LCBjZmcuc2xhY2suY29uZmlndXJlZCA/IDEgOiAwKTtcbiAgZW1pdEdhdWdlKCdhbGVydF9kZWxpdmVyeV9jb25maWd1cmVkJywgeyBjaGFubmVsOiAncGFnZXInIH0sIGNmZy5wYWdlci5jb25maWd1cmVkID8gMSA6IDApO1xuXG4gIC8vIEVuYWJsZWQgd2l0aCBubyBjcmVkZW50aWFsOiBzb21lYm9keSBhc2tlZCBmb3IgcGFnaW5nIGFuZCBpcyBub3QgZ2V0dGluZyBpdC5cbiAgLy8gVGhlIGRlcGxveSBjaGVjayByZWZ1c2VzIHRoaXMgY29tYmluYXRpb24sIHNvIHJlYWNoaW5nIGhlcmUgbWVhbnMgaXQgd2FzXG4gIC8vIGNoYW5nZWQgYWZ0ZXIgcHJvdmlzaW9uaW5nIOKAlCB3aGljaCBpcyBleGFjdGx5IHdoZW4gbm9ib2R5IGlzIGxvb2tpbmcuXG4gIGlmIChjZmcucGFnZXIuZW5hYmxlZCAmJiAhY2ZnLnBhZ2VyLmNvbmZpZ3VyZWQpIHtcbiAgICBsb2dnZXIuZXJyb3IoJ09OQ0FMTF9FTkFCTEVEIGlzIHRydWUgYnV0IG5vIFBBR0VSRFVUWV9ST1VUSU5HX0tFWSBpcyBzZXQg4oCUIHN1cHBvcnQgY3JpdGljYWxzIHdpbGwgbm90IHBhZ2UnKTtcbiAgfVxuICAvLyBOb3RoaW5nIENBTiBkZWxpdmVyLiBLZXllZCBvbiByZWFjaGFiaWxpdHkgcmF0aGVyIHRoYW4gb24gdGhlIHR3byBmbGFncyxcbiAgLy8gYmVjYXVzZSB0aGUgZmxhZ3MgYXJlIGRlY2xhcmF0aW9uczogYSBkZXBsb3ltZW50IHRoYXQgbGVmdCBib3RoIGF0IHRoZWlyXG4gIC8vIChmYWxzZSkgZGVmYXVsdHMgd2hpbGUgcmVhbCBTbGFjayBVUkxzIHNpdCBpbiBpdHMgU2VjcmV0IGlzIGRlbGl2ZXJpbmdcbiAgLy8gZmluZSwgYW5kIHdhcm5pbmcgaXQgd291bGQgYmUgYSBsaWUuXG4gIGlmICghY2FuRGVsaXZlckFueXdoZXJlKGNmZykpIHtcbiAgICBsb2dnZXIud2FybignTm8gcGxhdGZvcm0gYWxlcnQgZGVzdGluYXRpb24gaXMgZW5hYmxlZCDigJQgYWxlcnRzIHdpbGwgcmVhY2ggQWxlcnRtYW5hZ2VyIGFuZCBnbyBubyBmdXJ0aGVyJywge1xuICAgICAgaGludDogJ1NMQUNLX0FMRVJUU19FTkFCTEVEIC8gT05DQUxMX0VOQUJMRUQnLFxuICAgIH0pO1xuICB9XG59XG5cbi8qKlxuICogVGhlIGRlYWQtbWFuIHN3aXRjaDogYSBoZWFydGJlYXQgd2hvc2UgQUJTRU5DRSBpcyB0aGUgYWxlcnQuXG4gKlxuICogRXZlcnkgb3RoZXIgYWxlcnQgaW4gdGhpcyBzeXN0ZW0gZGVwZW5kcyBvbiBzb21ldGhpbmcgaW5zaWRlIGl0IHN0aWxsIHdvcmtpbmcuXG4gKiBUaGlzIG9uZSBpbnZlcnRzIHRoYXQg4oCUIGFuIGV4dGVybmFsIHNlcnZpY2UgZXhwZWN0cyBhIFBPU1Qgb24gYSBzY2hlZHVsZSBhbmRcbiAqIHJhaXNlcyBhbiBpbmNpZGVudCB3aGVuIG9uZSBkb2VzIG5vdCBhcnJpdmUsIHNvIGEgcGxhdGZvcm0gdGhhdCBpcyB3ZWRnZWQsXG4gKiBjcmFzaGVkLCBwYXJ0aXRpb25lZCBvciBuZXZlciBzdGFydGVkIGlzIHJlcG9ydGVkIEJZIElUUyBTSUxFTkNFLiBOb3RoaW5nXG4gKiBpbnNpZGUgYSBicm9rZW4gZGVwbG95bWVudCBoYXMgdG8gbm90aWNlIGl0cyBvd24gYnJva2VubmVzcy5cbiAqXG4gKiBXSFkgSVQgSVMgQlVJTFQgSEVSRSBSQVRIRVIgVEhBTiBCT1VHSFQsIHdoZW4gdGhlIHJlc3Qgb2Ygb24tY2FsbCBkZWxpYmVyYXRlbHlcbiAqIGlzIG5vdDogUGFnZXJEdXR5J3MgaGVhcnRiZWF0IG1vbml0b3JpbmcgaXMgYW4gRXZlbnQgT3JjaGVzdHJhdGlvbiBwYXR0ZXJuIGFuZFxuICogZG9jdW1lbnRlZCBhcyBBSU9wcy1wb3dlcmVkLCBzbyBpdCBtYXkgc2l0IGJlaGluZCBhIHRpZXIgYW5kIGlzIG5vdCBwYXJ0IG9mXG4gKiBFdmVudHMgdjIuIE1vcmUgaW1wb3J0YW50bHkgYSBkZWFkLW1hbiBzd2l0Y2ggaXMgYSBDTE9DSywgbm90IGEgdGVsZWNvbVxuICogcHJvYmxlbSDigJQgdGhlIHRoaW5nIHRoYXQgbWFkZSB0aGUgbGFzdCBtaWxlIHdvcnRoIGJ1eWluZyAoY2FycmllciByZXRyaWVzLFxuICogZGVsaXZlcnkgcmVjZWlwdHMsIGEgM2FtIGZhaWx1cmUgYnVkZ2V0KSBzaW1wbHkgZG9lcyBub3QgYXBwbHkgdG8gc2VuZGluZyBvbmVcbiAqIHJlcXVlc3Qgb24gYW4gaW50ZXJ2YWwuIGBPTkNBTExfREVBRE1BTl9VUkxgIHRoZXJlZm9yZSBwb2ludHMgd2hlcmV2ZXIgdGhlXG4gKiBvcGVyYXRvciBsaWtlczogUGFnZXJEdXR5J3Mgb3JjaGVzdHJhdGlvbiBlbmRwb2ludCwgYSBwdXJwb3NlLWJ1aWx0IGRlYWQtbWFuXG4gKiBzZXJ2aWNlLCBvciBhbiB1cHRpbWUgbW9uaXRvci5cbiAqXG4gKiBUV08gUFJPUEVSVElFUyBXT1JUSCBTVEFUSU5HLCBiZWNhdXNlIHRoZXkgYXJlIHRoZSByZWFzb24gdGhpcyBpcyBzYWZlOlxuICpcbiAqICAtIEl0IGlzIE5PVCBsZWFkZXItbG9ja2VkLiBBIGhlYXJ0YmVhdCBpcyBwZXItcHJvY2VzcyBvbiBwdXJwb3NlOiB3aXRoIHRocmVlXG4gKiAgICByZXBsaWNhcywgdGhlIGV4dGVybmFsIHNlcnZpY2UgaGVhcnMgZnJvbSB3aGljaGV2ZXIgYXJlIGFsaXZlLCBhbmQgc2lsZW5jZVxuICogICAgbWVhbnMgQUxMIG9mIHRoZW0gc3RvcHBlZC4gTGVhZGVyLWxvY2tpbmcgaXQgd291bGQgbWFrZSB0aGUgc3dpdGNoIHJlcG9ydFxuICogICAgdGhlIGxlYWRlcidzIGhlYWx0aCByYXRoZXIgdGhhbiB0aGUgZGVwbG95bWVudCdzIOKAlCBhbmQgd291bGQgbWFrZSBhIFJlZGlzXG4gKiAgICBvdXRhZ2UgbG9vayBpZGVudGljYWwgdG8gYSB0b3RhbCBvdXRhZ2UuXG4gKiAgLSBJdCBmYWlscyBTSUxFTlRMWSBvbiBpdHMgb3duIGVycm9ycywgZGVsaWJlcmF0ZWx5LiBBIGhlYXJ0YmVhdCB0aGF0IGxvZ2dlZFxuICogICAgYW4gZXJyb3IgZXZlcnkgaW50ZXJ2YWwgZHVyaW5nIGEgbmV0d29yayBibGlwIHdvdWxkIGJlIGl0cyBvd24gbm9pc2VcbiAqICAgIHNvdXJjZSwgYW5kIHRoZSBjb25zZXF1ZW5jZSBvZiBhIG1pc3NlZCBiZWF0IGlzIGFscmVhZHkgaGFuZGxlZDogdGhlXG4gKiAgICBleHRlcm5hbCBzZXJ2aWNlIGFsZXJ0cy4gVGhlIG9ubHkgdGhpbmcgaXQgbXVzdCBuZXZlciBkbyBpcyByZXBvcnQgc3VjY2Vzc1xuICogICAgaXQgZGlkIG5vdCBhY2hpZXZlLCB3aGljaCBpcyB3aHkgbm90aGluZyBoZXJlIHN3YWxsb3dzIGEgbm9uLTJ4eC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEZWFkbWFuQ29uZmlnIHtcbiAgdXJsOiBzdHJpbmc7XG4gIGludGVydmFsTXM6IG51bWJlcjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGRlYWRtYW5Db25maWcoKTogRGVhZG1hbkNvbmZpZyB7XG4gIHJldHVybiB7XG4gICAgdXJsOiBlbnZTdHIoJ09OQ0FMTF9ERUFETUFOX1VSTCcsICcnKSxcbiAgICBpbnRlcnZhbE1zOiBlbnZJbnQoJ09OQ0FMTF9ERUFETUFOX0lOVEVSVkFMX01TJywgNjBfMDAwLCB7IG1pbjogMTAwMCB9KSxcbiAgfTtcbn1cblxubGV0IGRlYWRtYW5UaW1lcjogTm9kZUpTLlRpbWVvdXQgfCB1bmRlZmluZWQ7XG5cbi8qKlxuICogU3RhcnQgYmVhdGluZy4gTm8tb3Agd2hlbiBgT05DQUxMX0RFQURNQU5fVVJMYCBpcyB1bnNldCwgd2hpY2ggaXMgdGhlIGRlZmF1bHQ6XG4gKiBhbiB1bmNvbmZpZ3VyZWQgZGVhZC1tYW4gc3dpdGNoIG11c3Qgbm90IGJlYXQgYWdhaW5zdCBub3RoaW5nLCBhbmQgbXVzdCBub3RcbiAqIHByZXRlbmQgaXQgaXMgY292ZXJpbmcgdGhlIGRlcGxveW1lbnQuXG4gKlxuICogUmV0dXJucyB0cnVlIHdoZW4gYSB0aW1lciB3YXMgc3RhcnRlZCwgc28gYSBjYWxsZXIgY2FuIGxvZyB0aGUgcG9zdHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0YXJ0RGVhZG1hbkhlYXJ0YmVhdChjZmc6IERlYWRtYW5Db25maWcgPSBkZWFkbWFuQ29uZmlnKCkpOiBib29sZWFuIHtcbiAgaWYgKGRlYWRtYW5UaW1lciB8fCAhY2ZnLnVybCkgcmV0dXJuIGZhbHNlO1xuICBjb25zdCBiZWF0ID0gYXN5bmMgKCk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXNwID0gYXdhaXQgc2FmZUZldGNoKGNmZy51cmwsIHsgbWV0aG9kOiAnUE9TVCcsIHRpbWVvdXRNczogNTAwMCwgYm9keTogJ3t9JywgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0gfSk7XG4gICAgICBlbWl0Q291bnRlcignb25jYWxsX2RlYWRtYW5fdG90YWwnLCB7IG91dGNvbWU6IHJlc3Aub2sgJiYgIXJlc3AucmVkaXJlY3RlZCA/ICdzZW50JyA6ICdmYWlsZWQnIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gQ291bnRlZCwgbmV2ZXIgbG9nZ2VkIHBlci1iZWF0IOKAlCBzZWUgdGhlIG1vZHVsZSBub3RlIG9uIG5vaXNlLlxuICAgICAgZW1pdENvdW50ZXIoJ29uY2FsbF9kZWFkbWFuX3RvdGFsJywgeyBvdXRjb21lOiAnZmFpbGVkJyB9KTtcbiAgICB9XG4gIH07XG4gIC8vIGB1bnJlZmAgc28gYSBwZW5kaW5nIGJlYXQgY2Fubm90IGhvbGQgdGhlIHByb2Nlc3Mgb3BlbiBkdXJpbmcgc2h1dGRvd246IHRoZVxuICAvLyBzd2l0Y2ggZXhpc3RzIHRvIHJlcG9ydCB0aGF0IHRoZSBwcm9jZXNzIFNUT1BQRUQsIGFuZCBkZWxheWluZyB0aGF0IGlzXG4gIC8vIHByZWNpc2VseSBiYWNrd2FyZHMuXG4gIGRlYWRtYW5UaW1lciA9IHNldEludGVydmFsKCgpID0+IHZvaWQgYmVhdCgpLCBjZmcuaW50ZXJ2YWxNcyk7XG4gIGRlYWRtYW5UaW1lci51bnJlZigpO1xuICB2b2lkIGJlYXQoKTtcbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKiBTdG9wIGJlYXRpbmcg4oCUIGNhbGxlZCBmcm9tIHRoZSB1bmlmaWVkIHNodXRkb3duIHBhdGgsIG5vdCBhIHNpZ25hbCBoYW5kbGVyLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEZWFkbWFuSGVhcnRiZWF0KCk6IHZvaWQge1xuICBpZiAoZGVhZG1hblRpbWVyKSBjbGVhckludGVydmFsKGRlYWRtYW5UaW1lcik7XG4gIGRlYWRtYW5UaW1lciA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Request } from 'express';
|
|
2
|
+
import type { JwtPayload } from '../types/common.js';
|
|
3
|
+
/** Inbound correlation ids a caller (CLI, Lambda, CodeBuild) may forward. */
|
|
4
|
+
export interface AuditCorrelation {
|
|
5
|
+
/** `X-Pipeline-Execution-Id` — the CodePipeline execution that made the call. */
|
|
6
|
+
pipelineExecutionId?: string;
|
|
7
|
+
/** `X-CodeBuild-Build-Id` — the CodeBuild build that made the call. */
|
|
8
|
+
codebuildBuildId?: string;
|
|
9
|
+
/** `X-Source-Event-Id` — the upstream event (EventBridge / webhook) being handled. */
|
|
10
|
+
sourceEventId?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Background-work identity for events recorded outside any request. */
|
|
13
|
+
export interface AuditJobContext {
|
|
14
|
+
/** Stable job name (e.g. `registry-gc`, `vuln-rescan`). */
|
|
15
|
+
jobId?: string;
|
|
16
|
+
/** This run's id (a BullMQ job id, a sweep's uuid). */
|
|
17
|
+
runId?: string;
|
|
18
|
+
/** What started it: `schedule`, `queue`, `startup`, `manual`, … */
|
|
19
|
+
trigger?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AuditContext {
|
|
22
|
+
requestId?: string;
|
|
23
|
+
traceId?: string;
|
|
24
|
+
ip?: string;
|
|
25
|
+
userAgent?: string;
|
|
26
|
+
correlation?: AuditCorrelation;
|
|
27
|
+
job?: AuditJobContext;
|
|
28
|
+
/**
|
|
29
|
+
* The VERIFIED principal of the inbound request. A getter, not a value: the
|
|
30
|
+
* scope opens before `requireAuth` runs, so `req.user` is read at the moment
|
|
31
|
+
* an event is recorded.
|
|
32
|
+
*/
|
|
33
|
+
principal?: () => JwtPayload | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The credential behind an audited action — reserved `details._auth`, so it is
|
|
37
|
+
* covered by the audit hash chain (which hashes `details`). Built only from a
|
|
38
|
+
* verified token, never from caller input; the remote-audit client overwrites
|
|
39
|
+
* any `_auth` a call site put in `details`.
|
|
40
|
+
*/
|
|
41
|
+
export interface AuditAuthBlock {
|
|
42
|
+
principalType?: string;
|
|
43
|
+
/** The internal service, when the principal is a service token. */
|
|
44
|
+
service?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The revocable credential: the access key (PAT / service-account key) the
|
|
47
|
+
* token derives from (`parentKeyId`), else the token's own `jti` (an exchanged
|
|
48
|
+
* key token's key id, an impersonation session id, a service mint id).
|
|
49
|
+
*/
|
|
50
|
+
keyId?: string;
|
|
51
|
+
/** Refresh-session slot the token was minted on. */
|
|
52
|
+
sid?: string;
|
|
53
|
+
scope?: string;
|
|
54
|
+
tokenUse?: string;
|
|
55
|
+
/** The token's permissions were narrowed to a subset at credential creation. */
|
|
56
|
+
permissionsRestricted?: true;
|
|
57
|
+
/** The principal is a platform superadmin. */
|
|
58
|
+
superAdmin?: true;
|
|
59
|
+
/**
|
|
60
|
+
* The principal's active org is the system org (vendor staff acting through a
|
|
61
|
+
* system-org job Role). With `superAdmin`, this is what lets the remote-audit
|
|
62
|
+
* client claim a cross-org `affectedOrgId` (see `auditCrossOrg`).
|
|
63
|
+
*/
|
|
64
|
+
systemOrg?: true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run `fn` inside an audit-context scope. Nested scopes MERGE onto the outer
|
|
68
|
+
* one (a job started inside a request keeps the request's id), with the inner
|
|
69
|
+
* fields winning.
|
|
70
|
+
*/
|
|
71
|
+
export declare function runWithAuditContext<T>(ctx: AuditContext, fn: () => T): T;
|
|
72
|
+
/** The active audit context, or undefined outside any scope. */
|
|
73
|
+
export declare function currentAuditContext(): AuditContext | undefined;
|
|
74
|
+
/** Inbound correlation headers recorded into the audit context. */
|
|
75
|
+
export declare const AUDIT_CORRELATION_HEADERS: {
|
|
76
|
+
readonly pipelineExecutionId: 'x-pipeline-execution-id';
|
|
77
|
+
readonly codebuildBuildId: 'x-codebuild-build-id';
|
|
78
|
+
readonly sourceEventId: 'x-source-event-id';
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Validate an inbound correlation id (`X-Request-Id`, `X-CodeBuild-Build-Id`, …):
|
|
82
|
+
* a bounded opaque token, else `undefined` — an invalid value is dropped, never
|
|
83
|
+
* truncated into something that looks like a different id.
|
|
84
|
+
*
|
|
85
|
+
* No AWS account id is ever stored: an ARN (a CodeBuild build ARN, say) has its
|
|
86
|
+
* account segment emptied, and a bare 12-digit value is refused. The generic
|
|
87
|
+
* 12-digit-run scrub is NOT used here — it would redact the all-digit final
|
|
88
|
+
* group of roughly one uuid in three hundred, corrupting the id it exists to
|
|
89
|
+
* correlate.
|
|
90
|
+
*/
|
|
91
|
+
export declare function sanitizeCorrelationId(raw: unknown): string | undefined;
|
|
92
|
+
/** Strip control characters from a User-Agent and cap its length; undefined when empty. */
|
|
93
|
+
export declare function sanitizeUserAgent(raw: unknown): string | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* The audit context for an inbound request. `requestId` is the id the
|
|
96
|
+
* request-id middleware resolved (validated inbound `X-Request-Id`, else a fresh
|
|
97
|
+
* uuid); the principal is read lazily from `req.user` (auth runs later in the
|
|
98
|
+
* chain).
|
|
99
|
+
*/
|
|
100
|
+
export declare function auditContextFromRequest(req: Request, ids?: {
|
|
101
|
+
requestId?: string;
|
|
102
|
+
traceId?: string;
|
|
103
|
+
}): AuditContext;
|
|
104
|
+
/** The `_auth` block for a verified principal (undefined when there is none). */
|
|
105
|
+
export declare function auditAuthBlock(user: JwtPayload | undefined): AuditAuthBlock | undefined;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Ambient audit context: the request/job facts every audit event should carry
|
|
5
|
+
* (request id, trace id, client ip / user agent, the verified principal's
|
|
6
|
+
* credential, inbound correlation ids) without each of the ~150 `recordAudit`
|
|
7
|
+
* call sites threading them through by hand.
|
|
8
|
+
*
|
|
9
|
+
* api-server's request pipeline opens a scope per inbound request
|
|
10
|
+
* (`auditContextFromRequest` + `runWithAuditContext`); background work opens its
|
|
11
|
+
* own with `runWithAuditContext({ job: { jobId, runId, trigger } }, fn)`. The
|
|
12
|
+
* remote-audit client reads the scope when an event is RECORDED and stamps the
|
|
13
|
+
* result onto the event itself, so a spooled re-delivery minutes later still
|
|
14
|
+
* carries the context of the moment it happened.
|
|
15
|
+
*/
|
|
16
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
17
|
+
import { serviceNameOf } from '../middleware/service-tokens.js';
|
|
18
|
+
import { isSystemOrgId } from '../middleware/system-org.js';
|
|
19
|
+
const storage = new AsyncLocalStorage();
|
|
20
|
+
/**
|
|
21
|
+
* Run `fn` inside an audit-context scope. Nested scopes MERGE onto the outer
|
|
22
|
+
* one (a job started inside a request keeps the request's id), with the inner
|
|
23
|
+
* fields winning.
|
|
24
|
+
*/
|
|
25
|
+
export function runWithAuditContext(ctx, fn) {
|
|
26
|
+
const outer = storage.getStore();
|
|
27
|
+
const merged = outer
|
|
28
|
+
? { ...outer, ...ctx, correlation: ctx.correlation ?? outer.correlation, job: ctx.job ? { ...outer.job, ...ctx.job } : outer.job }
|
|
29
|
+
: ctx;
|
|
30
|
+
return storage.run(merged, fn);
|
|
31
|
+
}
|
|
32
|
+
/** The active audit context, or undefined outside any scope. */
|
|
33
|
+
export function currentAuditContext() {
|
|
34
|
+
return storage.getStore();
|
|
35
|
+
}
|
|
36
|
+
/** Inbound correlation headers recorded into the audit context. */
|
|
37
|
+
export const AUDIT_CORRELATION_HEADERS = {
|
|
38
|
+
pipelineExecutionId: 'x-pipeline-execution-id',
|
|
39
|
+
codebuildBuildId: 'x-codebuild-build-id',
|
|
40
|
+
sourceEventId: 'x-source-event-id',
|
|
41
|
+
};
|
|
42
|
+
/** Max length of a stored correlation id / request id. */
|
|
43
|
+
const MAX_CORRELATION_LEN = 128;
|
|
44
|
+
/** Ids are opaque tokens: uuids, `project:uuid` build ids, ULIDs. Nothing else is stored. */
|
|
45
|
+
const CORRELATION_ID = /^[A-Za-z0-9._:/@+=-]+$/;
|
|
46
|
+
/** A bare AWS account id. */
|
|
47
|
+
const BARE_ACCOUNT_ID = /^\d{12}$/;
|
|
48
|
+
/** Max stored User-Agent length — a hostile header must not bloat audit rows. */
|
|
49
|
+
const MAX_USER_AGENT_LEN = 512;
|
|
50
|
+
/** C0 + DEL + C1 control characters, replaced so a UA can't inject into the UI/CSV export. */
|
|
51
|
+
const CONTROL_CHARS = new RegExp('[\\u0000-\\u001f\\u007f-\\u009f]', 'g');
|
|
52
|
+
function firstHeader(raw) {
|
|
53
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate an inbound correlation id (`X-Request-Id`, `X-CodeBuild-Build-Id`, …):
|
|
57
|
+
* a bounded opaque token, else `undefined` — an invalid value is dropped, never
|
|
58
|
+
* truncated into something that looks like a different id.
|
|
59
|
+
*
|
|
60
|
+
* No AWS account id is ever stored: an ARN (a CodeBuild build ARN, say) has its
|
|
61
|
+
* account segment emptied, and a bare 12-digit value is refused. The generic
|
|
62
|
+
* 12-digit-run scrub is NOT used here — it would redact the all-digit final
|
|
63
|
+
* group of roughly one uuid in three hundred, corrupting the id it exists to
|
|
64
|
+
* correlate.
|
|
65
|
+
*/
|
|
66
|
+
export function sanitizeCorrelationId(raw) {
|
|
67
|
+
if (typeof raw !== 'string')
|
|
68
|
+
return undefined;
|
|
69
|
+
const value = raw.trim();
|
|
70
|
+
if (value.length === 0 || value.length > MAX_CORRELATION_LEN || !CORRELATION_ID.test(value))
|
|
71
|
+
return undefined;
|
|
72
|
+
if (BARE_ACCOUNT_ID.test(value))
|
|
73
|
+
return undefined;
|
|
74
|
+
if (/^arn:/i.test(value)) {
|
|
75
|
+
const parts = value.split(':');
|
|
76
|
+
if (parts.length > 4)
|
|
77
|
+
parts[4] = '';
|
|
78
|
+
return parts.join(':');
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
/** Strip control characters from a User-Agent and cap its length; undefined when empty. */
|
|
83
|
+
export function sanitizeUserAgent(raw) {
|
|
84
|
+
if (typeof raw !== 'string' || raw.length === 0)
|
|
85
|
+
return undefined;
|
|
86
|
+
const cleaned = raw.replace(CONTROL_CHARS, ' ').trim();
|
|
87
|
+
return cleaned.length === 0 ? undefined : cleaned.slice(0, MAX_USER_AGENT_LEN);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The audit context for an inbound request. `requestId` is the id the
|
|
91
|
+
* request-id middleware resolved (validated inbound `X-Request-Id`, else a fresh
|
|
92
|
+
* uuid); the principal is read lazily from `req.user` (auth runs later in the
|
|
93
|
+
* chain).
|
|
94
|
+
*/
|
|
95
|
+
export function auditContextFromRequest(req, ids = {}) {
|
|
96
|
+
const correlation = {};
|
|
97
|
+
for (const [field, header] of Object.entries(AUDIT_CORRELATION_HEADERS)) {
|
|
98
|
+
const value = sanitizeCorrelationId(firstHeader(req.headers[header]));
|
|
99
|
+
if (value)
|
|
100
|
+
correlation[field] = value;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
requestId: ids.requestId,
|
|
104
|
+
traceId: ids.traceId,
|
|
105
|
+
ip: req.ip,
|
|
106
|
+
userAgent: sanitizeUserAgent(firstHeader(req.headers['user-agent'])),
|
|
107
|
+
correlation: Object.keys(correlation).length > 0 ? correlation : undefined,
|
|
108
|
+
principal: () => req.user,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** The `_auth` block for a verified principal (undefined when there is none). */
|
|
112
|
+
export function auditAuthBlock(user) {
|
|
113
|
+
if (!user)
|
|
114
|
+
return undefined;
|
|
115
|
+
const block = {
|
|
116
|
+
principalType: user.principalType,
|
|
117
|
+
service: serviceNameOf(user),
|
|
118
|
+
keyId: user.parentKeyId ?? user.jti,
|
|
119
|
+
sid: user.sid,
|
|
120
|
+
scope: user.scope,
|
|
121
|
+
tokenUse: user.token_use,
|
|
122
|
+
...(user.permissionsRestricted === true ? { permissionsRestricted: true } : {}),
|
|
123
|
+
...(user.isSuperAdmin === true ? { superAdmin: true } : {}),
|
|
124
|
+
// A SERVICE token in the system org is the platform's own plumbing, not staff
|
|
125
|
+
// acting on a tenant; only human / service-account principals count.
|
|
126
|
+
...(user.principalType !== 'service' && isSystemOrgId(user.organizationId) ? { systemOrg: true } : {}),
|
|
127
|
+
};
|
|
128
|
+
for (const key of Object.keys(block)) {
|
|
129
|
+
if (block[key] === undefined)
|
|
130
|
+
delete block[key];
|
|
131
|
+
}
|
|
132
|
+
return Object.keys(block).length > 0 ? block : undefined;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXVkaXQtY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9hdWRpdC1jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7OztHQVlHO0FBRUgsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRWhELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNoRSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFzRTVELE1BQU0sT0FBTyxHQUFHLElBQUksaUJBQWlCLEVBQWdCLENBQUM7QUFFdEQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBSSxHQUFpQixFQUFFLEVBQVc7SUFDbkUsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ2pDLE1BQU0sTUFBTSxHQUFpQixLQUFLO1FBQ2hDLENBQUMsQ0FBQyxFQUFFLEdBQUcsS0FBSyxFQUFFLEdBQUcsR0FBRyxFQUFFLFdBQVcsRUFBRSxHQUFHLENBQUMsV0FBVyxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxLQUFLLENBQUMsR0FBRyxFQUFFLEdBQUcsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFO1FBQ2xJLENBQUMsQ0FBQyxHQUFHLENBQUM7SUFDUixPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ2pDLENBQUM7QUFFRCxnRUFBZ0U7QUFDaEUsTUFBTSxVQUFVLG1CQUFtQjtJQUNqQyxPQUFPLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQUM1QixDQUFDO0FBRUQsbUVBQW1FO0FBQ25FLE1BQU0sQ0FBQyxNQUFNLHlCQUF5QixHQUFHO0lBQ3ZDLG1CQUFtQixFQUFFLHlCQUF5QjtJQUM5QyxnQkFBZ0IsRUFBRSxzQkFBc0I7SUFDeEMsYUFBYSxFQUFFLG1CQUFtQjtDQUMxQixDQUFDO0FBRVgsMERBQTBEO0FBQzFELE1BQU0sbUJBQW1CLEdBQUcsR0FBRyxDQUFDO0FBQ2hDLDZGQUE2RjtBQUM3RixNQUFNLGNBQWMsR0FBRyx3QkFBd0IsQ0FBQztBQUNoRCw2QkFBNkI7QUFDN0IsTUFBTSxlQUFlLEdBQUcsVUFBVSxDQUFDO0FBRW5DLGlGQUFpRjtBQUNqRixNQUFNLGtCQUFrQixHQUFHLEdBQUcsQ0FBQztBQUMvQiw4RkFBOEY7QUFDOUYsTUFBTSxhQUFhLEdBQUcsSUFBSSxNQUFNLENBQUMsa0NBQWtDLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFFMUUsU0FBUyxXQUFXLENBQUMsR0FBa0M7SUFDckQsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztBQUMzQyxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7R0FVRztBQUNILE1BQU0sVUFBVSxxQkFBcUIsQ0FBQyxHQUFZO0lBQ2hELElBQUksT0FBTyxHQUFHLEtBQUssUUFBUTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQzlDLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN6QixJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLEtBQUssQ0FBQyxNQUFNLEdBQUcsbUJBQW1CLElBQUksQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQzlHLElBQUksZUFBZSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUNsRCxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUN6QixNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQy9CLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDO1lBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNwQyxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDekIsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVELDJGQUEyRjtBQUMzRixNQUFNLFVBQVUsaUJBQWlCLENBQUMsR0FBWTtJQUM1QyxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUNsRSxNQUFNLE9BQU8sR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLGFBQWEsRUFBRSxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUN2RCxPQUFPLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLGtCQUFrQixDQUFDLENBQUM7QUFDakYsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLHVCQUF1QixDQUFDLEdBQVksRUFBRSxHQUFHLEdBQTZDLEVBQUU7SUFDdEcsTUFBTSxXQUFXLEdBQXFCLEVBQUUsQ0FBQztJQUN6QyxLQUFLLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyx5QkFBeUIsQ0FBNEMsRUFBRSxDQUFDO1FBQ25ILE1BQU0sS0FBSyxHQUFHLHFCQUFxQixDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUN0RSxJQUFJLEtBQUs7WUFBRSxXQUFXLENBQUMsS0FBSyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ3hDLENBQUM7SUFDRCxPQUFPO1FBQ0wsU0FBUyxFQUFFLEdBQUcsQ0FBQyxTQUFTO1FBQ3hCLE9BQU8sRUFBRSxHQUFHLENBQUMsT0FBTztRQUNwQixFQUFFLEVBQUUsR0FBRyxDQUFDLEVBQUU7UUFDVixTQUFTLEVBQUUsaUJBQWlCLENBQUMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQztRQUNwRSxXQUFXLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLFNBQVM7UUFDMUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxJQUFJO0tBQzFCLENBQUM7QUFDSixDQUFDO0FBRUQsaUZBQWlGO0FBQ2pGLE1BQU0sVUFBVSxjQUFjLENBQUMsSUFBNEI7SUFDekQsSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUM1QixNQUFNLEtBQUssR0FBbUI7UUFDNUIsYUFBYSxFQUFFLElBQUksQ0FBQyxhQUFhO1FBQ2pDLE9BQU8sRUFBRSxhQUFhLENBQUMsSUFBSSxDQUFDO1FBQzVCLEtBQUssRUFBRSxJQUFJLENBQUMsV0FBVyxJQUFJLElBQUksQ0FBQyxHQUFHO1FBQ25DLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRztRQUNiLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSztRQUNqQixRQUFRLEVBQUUsSUFBSSxDQUFDLFNBQVM7UUFDeEIsR0FBRyxDQUFDLElBQUksQ0FBQyxxQkFBcUIsS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUscUJBQXFCLEVBQUUsSUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUN4RixHQUFHLENBQUMsSUFBSSxDQUFDLFlBQVksS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsVUFBVSxFQUFFLElBQWEsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDcEUsOEVBQThFO1FBQzlFLHFFQUFxRTtRQUNyRSxHQUFHLENBQUMsSUFBSSxDQUFDLGFBQWEsS0FBSyxTQUFTLElBQUksYUFBYSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxTQUFTLEVBQUUsSUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUNoSCxDQUFDO0lBQ0YsS0FBSyxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBZ0MsRUFBRSxDQUFDO1FBQ3BFLElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVM7WUFBRSxPQUFPLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNsRCxDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQzNELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBBbWJpZW50IGF1ZGl0IGNvbnRleHQ6IHRoZSByZXF1ZXN0L2pvYiBmYWN0cyBldmVyeSBhdWRpdCBldmVudCBzaG91bGQgY2FycnlcbiAqIChyZXF1ZXN0IGlkLCB0cmFjZSBpZCwgY2xpZW50IGlwIC8gdXNlciBhZ2VudCwgdGhlIHZlcmlmaWVkIHByaW5jaXBhbCdzXG4gKiBjcmVkZW50aWFsLCBpbmJvdW5kIGNvcnJlbGF0aW9uIGlkcykgd2l0aG91dCBlYWNoIG9mIHRoZSB+MTUwIGByZWNvcmRBdWRpdGBcbiAqIGNhbGwgc2l0ZXMgdGhyZWFkaW5nIHRoZW0gdGhyb3VnaCBieSBoYW5kLlxuICpcbiAqIGFwaS1zZXJ2ZXIncyByZXF1ZXN0IHBpcGVsaW5lIG9wZW5zIGEgc2NvcGUgcGVyIGluYm91bmQgcmVxdWVzdFxuICogKGBhdWRpdENvbnRleHRGcm9tUmVxdWVzdGAgKyBgcnVuV2l0aEF1ZGl0Q29udGV4dGApOyBiYWNrZ3JvdW5kIHdvcmsgb3BlbnMgaXRzXG4gKiBvd24gd2l0aCBgcnVuV2l0aEF1ZGl0Q29udGV4dCh7IGpvYjogeyBqb2JJZCwgcnVuSWQsIHRyaWdnZXIgfSB9LCBmbilgLiBUaGVcbiAqIHJlbW90ZS1hdWRpdCBjbGllbnQgcmVhZHMgdGhlIHNjb3BlIHdoZW4gYW4gZXZlbnQgaXMgUkVDT1JERUQgYW5kIHN0YW1wcyB0aGVcbiAqIHJlc3VsdCBvbnRvIHRoZSBldmVudCBpdHNlbGYsIHNvIGEgc3Bvb2xlZCByZS1kZWxpdmVyeSBtaW51dGVzIGxhdGVyIHN0aWxsXG4gKiBjYXJyaWVzIHRoZSBjb250ZXh0IG9mIHRoZSBtb21lbnQgaXQgaGFwcGVuZWQuXG4gKi9cblxuaW1wb3J0IHsgQXN5bmNMb2NhbFN0b3JhZ2UgfSBmcm9tICdhc3luY19ob29rcyc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IHNlcnZpY2VOYW1lT2YgfSBmcm9tICcuLi9taWRkbGV3YXJlL3NlcnZpY2UtdG9rZW5zLmpzJztcbmltcG9ydCB7IGlzU3lzdGVtT3JnSWQgfSBmcm9tICcuLi9taWRkbGV3YXJlL3N5c3RlbS1vcmcuanMnO1xuaW1wb3J0IHR5cGUgeyBKd3RQYXlsb2FkIH0gZnJvbSAnLi4vdHlwZXMvY29tbW9uLmpzJztcblxuLyoqIEluYm91bmQgY29ycmVsYXRpb24gaWRzIGEgY2FsbGVyIChDTEksIExhbWJkYSwgQ29kZUJ1aWxkKSBtYXkgZm9yd2FyZC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXVkaXRDb3JyZWxhdGlvbiB7XG4gIC8qKiBgWC1QaXBlbGluZS1FeGVjdXRpb24tSWRgIOKAlCB0aGUgQ29kZVBpcGVsaW5lIGV4ZWN1dGlvbiB0aGF0IG1hZGUgdGhlIGNhbGwuICovXG4gIHBpcGVsaW5lRXhlY3V0aW9uSWQ/OiBzdHJpbmc7XG4gIC8qKiBgWC1Db2RlQnVpbGQtQnVpbGQtSWRgIOKAlCB0aGUgQ29kZUJ1aWxkIGJ1aWxkIHRoYXQgbWFkZSB0aGUgY2FsbC4gKi9cbiAgY29kZWJ1aWxkQnVpbGRJZD86IHN0cmluZztcbiAgLyoqIGBYLVNvdXJjZS1FdmVudC1JZGAg4oCUIHRoZSB1cHN0cmVhbSBldmVudCAoRXZlbnRCcmlkZ2UgLyB3ZWJob29rKSBiZWluZyBoYW5kbGVkLiAqL1xuICBzb3VyY2VFdmVudElkPzogc3RyaW5nO1xufVxuXG4vKiogQmFja2dyb3VuZC13b3JrIGlkZW50aXR5IGZvciBldmVudHMgcmVjb3JkZWQgb3V0c2lkZSBhbnkgcmVxdWVzdC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXVkaXRKb2JDb250ZXh0IHtcbiAgLyoqIFN0YWJsZSBqb2IgbmFtZSAoZS5nLiBgcmVnaXN0cnktZ2NgLCBgdnVsbi1yZXNjYW5gKS4gKi9cbiAgam9iSWQ/OiBzdHJpbmc7XG4gIC8qKiBUaGlzIHJ1bidzIGlkIChhIEJ1bGxNUSBqb2IgaWQsIGEgc3dlZXAncyB1dWlkKS4gKi9cbiAgcnVuSWQ/OiBzdHJpbmc7XG4gIC8qKiBXaGF0IHN0YXJ0ZWQgaXQ6IGBzY2hlZHVsZWAsIGBxdWV1ZWAsIGBzdGFydHVwYCwgYG1hbnVhbGAsIOKApiAqL1xuICB0cmlnZ2VyPzogc3RyaW5nO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0Q29udGV4dCB7XG4gIHJlcXVlc3RJZD86IHN0cmluZztcbiAgdHJhY2VJZD86IHN0cmluZztcbiAgaXA/OiBzdHJpbmc7XG4gIHVzZXJBZ2VudD86IHN0cmluZztcbiAgY29ycmVsYXRpb24/OiBBdWRpdENvcnJlbGF0aW9uO1xuICBqb2I/OiBBdWRpdEpvYkNvbnRleHQ7XG4gIC8qKlxuICAgKiBUaGUgVkVSSUZJRUQgcHJpbmNpcGFsIG9mIHRoZSBpbmJvdW5kIHJlcXVlc3QuIEEgZ2V0dGVyLCBub3QgYSB2YWx1ZTogdGhlXG4gICAqIHNjb3BlIG9wZW5zIGJlZm9yZSBgcmVxdWlyZUF1dGhgIHJ1bnMsIHNvIGByZXEudXNlcmAgaXMgcmVhZCBhdCB0aGUgbW9tZW50XG4gICAqIGFuIGV2ZW50IGlzIHJlY29yZGVkLlxuICAgKi9cbiAgcHJpbmNpcGFsPzogKCkgPT4gSnd0UGF5bG9hZCB8IHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBUaGUgY3JlZGVudGlhbCBiZWhpbmQgYW4gYXVkaXRlZCBhY3Rpb24g4oCUIHJlc2VydmVkIGBkZXRhaWxzLl9hdXRoYCwgc28gaXQgaXNcbiAqIGNvdmVyZWQgYnkgdGhlIGF1ZGl0IGhhc2ggY2hhaW4gKHdoaWNoIGhhc2hlcyBgZGV0YWlsc2ApLiBCdWlsdCBvbmx5IGZyb20gYVxuICogdmVyaWZpZWQgdG9rZW4sIG5ldmVyIGZyb20gY2FsbGVyIGlucHV0OyB0aGUgcmVtb3RlLWF1ZGl0IGNsaWVudCBvdmVyd3JpdGVzXG4gKiBhbnkgYF9hdXRoYCBhIGNhbGwgc2l0ZSBwdXQgaW4gYGRldGFpbHNgLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0QXV0aEJsb2NrIHtcbiAgcHJpbmNpcGFsVHlwZT86IHN0cmluZztcbiAgLyoqIFRoZSBpbnRlcm5hbCBzZXJ2aWNlLCB3aGVuIHRoZSBwcmluY2lwYWwgaXMgYSBzZXJ2aWNlIHRva2VuLiAqL1xuICBzZXJ2aWNlPzogc3RyaW5nO1xuICAvKipcbiAgICogVGhlIHJldm9jYWJsZSBjcmVkZW50aWFsOiB0aGUgYWNjZXNzIGtleSAoUEFUIC8gc2VydmljZS1hY2NvdW50IGtleSkgdGhlXG4gICAqIHRva2VuIGRlcml2ZXMgZnJvbSAoYHBhcmVudEtleUlkYCksIGVsc2UgdGhlIHRva2VuJ3Mgb3duIGBqdGlgIChhbiBleGNoYW5nZWRcbiAgICoga2V5IHRva2VuJ3Mga2V5IGlkLCBhbiBpbXBlcnNvbmF0aW9uIHNlc3Npb24gaWQsIGEgc2VydmljZSBtaW50IGlkKS5cbiAgICovXG4gIGtleUlkPzogc3RyaW5nO1xuICAvKiogUmVmcmVzaC1zZXNzaW9uIHNsb3QgdGhlIHRva2VuIHdhcyBtaW50ZWQgb24uICovXG4gIHNpZD86IHN0cmluZztcbiAgc2NvcGU/OiBzdHJpbmc7XG4gIHRva2VuVXNlPzogc3RyaW5nO1xuICAvKiogVGhlIHRva2VuJ3MgcGVybWlzc2lvbnMgd2VyZSBuYXJyb3dlZCB0byBhIHN1YnNldCBhdCBjcmVkZW50aWFsIGNyZWF0aW9uLiAqL1xuICBwZXJtaXNzaW9uc1Jlc3RyaWN0ZWQ/OiB0cnVlO1xuICAvKiogVGhlIHByaW5jaXBhbCBpcyBhIHBsYXRmb3JtIHN1cGVyYWRtaW4uICovXG4gIHN1cGVyQWRtaW4/OiB0cnVlO1xuICAvKipcbiAgICogVGhlIHByaW5jaXBhbCdzIGFjdGl2ZSBvcmcgaXMgdGhlIHN5c3RlbSBvcmcgKHZlbmRvciBzdGFmZiBhY3RpbmcgdGhyb3VnaCBhXG4gICAqIHN5c3RlbS1vcmcgam9iIFJvbGUpLiBXaXRoIGBzdXBlckFkbWluYCwgdGhpcyBpcyB3aGF0IGxldHMgdGhlIHJlbW90ZS1hdWRpdFxuICAgKiBjbGllbnQgY2xhaW0gYSBjcm9zcy1vcmcgYGFmZmVjdGVkT3JnSWRgIChzZWUgYGF1ZGl0Q3Jvc3NPcmdgKS5cbiAgICovXG4gIHN5c3RlbU9yZz86IHRydWU7XG59XG5cbmNvbnN0IHN0b3JhZ2UgPSBuZXcgQXN5bmNMb2NhbFN0b3JhZ2U8QXVkaXRDb250ZXh0PigpO1xuXG4vKipcbiAqIFJ1biBgZm5gIGluc2lkZSBhbiBhdWRpdC1jb250ZXh0IHNjb3BlLiBOZXN0ZWQgc2NvcGVzIE1FUkdFIG9udG8gdGhlIG91dGVyXG4gKiBvbmUgKGEgam9iIHN0YXJ0ZWQgaW5zaWRlIGEgcmVxdWVzdCBrZWVwcyB0aGUgcmVxdWVzdCdzIGlkKSwgd2l0aCB0aGUgaW5uZXJcbiAqIGZpZWxkcyB3aW5uaW5nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcnVuV2l0aEF1ZGl0Q29udGV4dDxUPihjdHg6IEF1ZGl0Q29udGV4dCwgZm46ICgpID0+IFQpOiBUIHtcbiAgY29uc3Qgb3V0ZXIgPSBzdG9yYWdlLmdldFN0b3JlKCk7XG4gIGNvbnN0IG1lcmdlZDogQXVkaXRDb250ZXh0ID0gb3V0ZXJcbiAgICA/IHsgLi4ub3V0ZXIsIC4uLmN0eCwgY29ycmVsYXRpb246IGN0eC5jb3JyZWxhdGlvbiA/PyBvdXRlci5jb3JyZWxhdGlvbiwgam9iOiBjdHguam9iID8geyAuLi5vdXRlci5qb2IsIC4uLmN0eC5qb2IgfSA6IG91dGVyLmpvYiB9XG4gICAgOiBjdHg7XG4gIHJldHVybiBzdG9yYWdlLnJ1bihtZXJnZWQsIGZuKTtcbn1cblxuLyoqIFRoZSBhY3RpdmUgYXVkaXQgY29udGV4dCwgb3IgdW5kZWZpbmVkIG91dHNpZGUgYW55IHNjb3BlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGN1cnJlbnRBdWRpdENvbnRleHQoKTogQXVkaXRDb250ZXh0IHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIHN0b3JhZ2UuZ2V0U3RvcmUoKTtcbn1cblxuLyoqIEluYm91bmQgY29ycmVsYXRpb24gaGVhZGVycyByZWNvcmRlZCBpbnRvIHRoZSBhdWRpdCBjb250ZXh0LiAqL1xuZXhwb3J0IGNvbnN0IEFVRElUX0NPUlJFTEFUSU9OX0hFQURFUlMgPSB7XG4gIHBpcGVsaW5lRXhlY3V0aW9uSWQ6ICd4LXBpcGVsaW5lLWV4ZWN1dGlvbi1pZCcsXG4gIGNvZGVidWlsZEJ1aWxkSWQ6ICd4LWNvZGVidWlsZC1idWlsZC1pZCcsXG4gIHNvdXJjZUV2ZW50SWQ6ICd4LXNvdXJjZS1ldmVudC1pZCcsXG59IGFzIGNvbnN0O1xuXG4vKiogTWF4IGxlbmd0aCBvZiBhIHN0b3JlZCBjb3JyZWxhdGlvbiBpZCAvIHJlcXVlc3QgaWQuICovXG5jb25zdCBNQVhfQ09SUkVMQVRJT05fTEVOID0gMTI4O1xuLyoqIElkcyBhcmUgb3BhcXVlIHRva2VuczogdXVpZHMsIGBwcm9qZWN0OnV1aWRgIGJ1aWxkIGlkcywgVUxJRHMuIE5vdGhpbmcgZWxzZSBpcyBzdG9yZWQuICovXG5jb25zdCBDT1JSRUxBVElPTl9JRCA9IC9eW0EtWmEtejAtOS5fOi9AKz0tXSskLztcbi8qKiBBIGJhcmUgQVdTIGFjY291bnQgaWQuICovXG5jb25zdCBCQVJFX0FDQ09VTlRfSUQgPSAvXlxcZHsxMn0kLztcblxuLyoqIE1heCBzdG9yZWQgVXNlci1BZ2VudCBsZW5ndGgg4oCUIGEgaG9zdGlsZSBoZWFkZXIgbXVzdCBub3QgYmxvYXQgYXVkaXQgcm93cy4gKi9cbmNvbnN0IE1BWF9VU0VSX0FHRU5UX0xFTiA9IDUxMjtcbi8qKiBDMCArIERFTCArIEMxIGNvbnRyb2wgY2hhcmFjdGVycywgcmVwbGFjZWQgc28gYSBVQSBjYW4ndCBpbmplY3QgaW50byB0aGUgVUkvQ1NWIGV4cG9ydC4gKi9cbmNvbnN0IENPTlRST0xfQ0hBUlMgPSBuZXcgUmVnRXhwKCdbXFxcXHUwMDAwLVxcXFx1MDAxZlxcXFx1MDA3Zi1cXFxcdTAwOWZdJywgJ2cnKTtcblxuZnVuY3Rpb24gZmlyc3RIZWFkZXIocmF3OiBzdHJpbmcgfCBzdHJpbmdbXSB8IHVuZGVmaW5lZCk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHJhdykgPyByYXdbMF0gOiByYXc7XG59XG5cbi8qKlxuICogVmFsaWRhdGUgYW4gaW5ib3VuZCBjb3JyZWxhdGlvbiBpZCAoYFgtUmVxdWVzdC1JZGAsIGBYLUNvZGVCdWlsZC1CdWlsZC1JZGAsIOKApik6XG4gKiBhIGJvdW5kZWQgb3BhcXVlIHRva2VuLCBlbHNlIGB1bmRlZmluZWRgIOKAlCBhbiBpbnZhbGlkIHZhbHVlIGlzIGRyb3BwZWQsIG5ldmVyXG4gKiB0cnVuY2F0ZWQgaW50byBzb21ldGhpbmcgdGhhdCBsb29rcyBsaWtlIGEgZGlmZmVyZW50IGlkLlxuICpcbiAqIE5vIEFXUyBhY2NvdW50IGlkIGlzIGV2ZXIgc3RvcmVkOiBhbiBBUk4gKGEgQ29kZUJ1aWxkIGJ1aWxkIEFSTiwgc2F5KSBoYXMgaXRzXG4gKiBhY2NvdW50IHNlZ21lbnQgZW1wdGllZCwgYW5kIGEgYmFyZSAxMi1kaWdpdCB2YWx1ZSBpcyByZWZ1c2VkLiBUaGUgZ2VuZXJpY1xuICogMTItZGlnaXQtcnVuIHNjcnViIGlzIE5PVCB1c2VkIGhlcmUg4oCUIGl0IHdvdWxkIHJlZGFjdCB0aGUgYWxsLWRpZ2l0IGZpbmFsXG4gKiBncm91cCBvZiByb3VnaGx5IG9uZSB1dWlkIGluIHRocmVlIGh1bmRyZWQsIGNvcnJ1cHRpbmcgdGhlIGlkIGl0IGV4aXN0cyB0b1xuICogY29ycmVsYXRlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2FuaXRpemVDb3JyZWxhdGlvbklkKHJhdzogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICh0eXBlb2YgcmF3ICE9PSAnc3RyaW5nJykgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgdmFsdWUgPSByYXcudHJpbSgpO1xuICBpZiAodmFsdWUubGVuZ3RoID09PSAwIHx8IHZhbHVlLmxlbmd0aCA+IE1BWF9DT1JSRUxBVElPTl9MRU4gfHwgIUNPUlJFTEFUSU9OX0lELnRlc3QodmFsdWUpKSByZXR1cm4gdW5kZWZpbmVkO1xuICBpZiAoQkFSRV9BQ0NPVU5UX0lELnRlc3QodmFsdWUpKSByZXR1cm4gdW5kZWZpbmVkO1xuICBpZiAoL15hcm46L2kudGVzdCh2YWx1ZSkpIHtcbiAgICBjb25zdCBwYXJ0cyA9IHZhbHVlLnNwbGl0KCc6Jyk7XG4gICAgaWYgKHBhcnRzLmxlbmd0aCA+IDQpIHBhcnRzWzRdID0gJyc7XG4gICAgcmV0dXJuIHBhcnRzLmpvaW4oJzonKTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbi8qKiBTdHJpcCBjb250cm9sIGNoYXJhY3RlcnMgZnJvbSBhIFVzZXItQWdlbnQgYW5kIGNhcCBpdHMgbGVuZ3RoOyB1bmRlZmluZWQgd2hlbiBlbXB0eS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzYW5pdGl6ZVVzZXJBZ2VudChyYXc6IHVua25vd24pOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBpZiAodHlwZW9mIHJhdyAhPT0gJ3N0cmluZycgfHwgcmF3Lmxlbmd0aCA9PT0gMCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgY2xlYW5lZCA9IHJhdy5yZXBsYWNlKENPTlRST0xfQ0hBUlMsICcgJykudHJpbSgpO1xuICByZXR1cm4gY2xlYW5lZC5sZW5ndGggPT09IDAgPyB1bmRlZmluZWQgOiBjbGVhbmVkLnNsaWNlKDAsIE1BWF9VU0VSX0FHRU5UX0xFTik7XG59XG5cbi8qKlxuICogVGhlIGF1ZGl0IGNvbnRleHQgZm9yIGFuIGluYm91bmQgcmVxdWVzdC4gYHJlcXVlc3RJZGAgaXMgdGhlIGlkIHRoZVxuICogcmVxdWVzdC1pZCBtaWRkbGV3YXJlIHJlc29sdmVkICh2YWxpZGF0ZWQgaW5ib3VuZCBgWC1SZXF1ZXN0LUlkYCwgZWxzZSBhIGZyZXNoXG4gKiB1dWlkKTsgdGhlIHByaW5jaXBhbCBpcyByZWFkIGxhemlseSBmcm9tIGByZXEudXNlcmAgKGF1dGggcnVucyBsYXRlciBpbiB0aGVcbiAqIGNoYWluKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0Q29udGV4dEZyb21SZXF1ZXN0KHJlcTogUmVxdWVzdCwgaWRzOiB7IHJlcXVlc3RJZD86IHN0cmluZzsgdHJhY2VJZD86IHN0cmluZyB9ID0ge30pOiBBdWRpdENvbnRleHQge1xuICBjb25zdCBjb3JyZWxhdGlvbjogQXVkaXRDb3JyZWxhdGlvbiA9IHt9O1xuICBmb3IgKGNvbnN0IFtmaWVsZCwgaGVhZGVyXSBvZiBPYmplY3QuZW50cmllcyhBVURJVF9DT1JSRUxBVElPTl9IRUFERVJTKSBhcyBBcnJheTxba2V5b2YgQXVkaXRDb3JyZWxhdGlvbiwgc3RyaW5nXT4pIHtcbiAgICBjb25zdCB2YWx1ZSA9IHNhbml0aXplQ29ycmVsYXRpb25JZChmaXJzdEhlYWRlcihyZXEuaGVhZGVyc1toZWFkZXJdKSk7XG4gICAgaWYgKHZhbHVlKSBjb3JyZWxhdGlvbltmaWVsZF0gPSB2YWx1ZTtcbiAgfVxuICByZXR1cm4ge1xuICAgIHJlcXVlc3RJZDogaWRzLnJlcXVlc3RJZCxcbiAgICB0cmFjZUlkOiBpZHMudHJhY2VJZCxcbiAgICBpcDogcmVxLmlwLFxuICAgIHVzZXJBZ2VudDogc2FuaXRpemVVc2VyQWdlbnQoZmlyc3RIZWFkZXIocmVxLmhlYWRlcnNbJ3VzZXItYWdlbnQnXSkpLFxuICAgIGNvcnJlbGF0aW9uOiBPYmplY3Qua2V5cyhjb3JyZWxhdGlvbikubGVuZ3RoID4gMCA/IGNvcnJlbGF0aW9uIDogdW5kZWZpbmVkLFxuICAgIHByaW5jaXBhbDogKCkgPT4gcmVxLnVzZXIsXG4gIH07XG59XG5cbi8qKiBUaGUgYF9hdXRoYCBibG9jayBmb3IgYSB2ZXJpZmllZCBwcmluY2lwYWwgKHVuZGVmaW5lZCB3aGVuIHRoZXJlIGlzIG5vbmUpLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0QXV0aEJsb2NrKHVzZXI6IEp3dFBheWxvYWQgfCB1bmRlZmluZWQpOiBBdWRpdEF1dGhCbG9jayB8IHVuZGVmaW5lZCB7XG4gIGlmICghdXNlcikgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgYmxvY2s6IEF1ZGl0QXV0aEJsb2NrID0ge1xuICAgIHByaW5jaXBhbFR5cGU6IHVzZXIucHJpbmNpcGFsVHlwZSxcbiAgICBzZXJ2aWNlOiBzZXJ2aWNlTmFtZU9mKHVzZXIpLFxuICAgIGtleUlkOiB1c2VyLnBhcmVudEtleUlkID8/IHVzZXIuanRpLFxuICAgIHNpZDogdXNlci5zaWQsXG4gICAgc2NvcGU6IHVzZXIuc2NvcGUsXG4gICAgdG9rZW5Vc2U6IHVzZXIudG9rZW5fdXNlLFxuICAgIC4uLih1c2VyLnBlcm1pc3Npb25zUmVzdHJpY3RlZCA9PT0gdHJ1ZSA/IHsgcGVybWlzc2lvbnNSZXN0cmljdGVkOiB0cnVlIGFzIGNvbnN0IH0gOiB7fSksXG4gICAgLi4uKHVzZXIuaXNTdXBlckFkbWluID09PSB0cnVlID8geyBzdXBlckFkbWluOiB0cnVlIGFzIGNvbnN0IH0gOiB7fSksXG4gICAgLy8gQSBTRVJWSUNFIHRva2VuIGluIHRoZSBzeXN0ZW0gb3JnIGlzIHRoZSBwbGF0Zm9ybSdzIG93biBwbHVtYmluZywgbm90IHN0YWZmXG4gICAgLy8gYWN0aW5nIG9uIGEgdGVuYW50OyBvbmx5IGh1bWFuIC8gc2VydmljZS1hY2NvdW50IHByaW5jaXBhbHMgY291bnQuXG4gICAgLi4uKHVzZXIucHJpbmNpcGFsVHlwZSAhPT0gJ3NlcnZpY2UnICYmIGlzU3lzdGVtT3JnSWQodXNlci5vcmdhbml6YXRpb25JZCkgPyB7IHN5c3RlbU9yZzogdHJ1ZSBhcyBjb25zdCB9IDoge30pLFxuICB9O1xuICBmb3IgKGNvbnN0IGtleSBvZiBPYmplY3Qua2V5cyhibG9jaykgYXMgQXJyYXk8a2V5b2YgQXVkaXRBdXRoQmxvY2s+KSB7XG4gICAgaWYgKGJsb2NrW2tleV0gPT09IHVuZGVmaW5lZCkgZGVsZXRlIGJsb2NrW2tleV07XG4gIH1cbiAgcmV0dXJuIE9iamVjdC5rZXlzKGJsb2NrKS5sZW5ndGggPiAwID8gYmxvY2sgOiB1bmRlZmluZWQ7XG59XG4iXX0=
|