@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.
Files changed (91) hide show
  1. package/lib/constants/ai-providers.d.ts +3 -22
  2. package/lib/constants/ai-providers.js +6 -56
  3. package/lib/errors/app-errors.d.ts +14 -0
  4. package/lib/errors/app-errors.js +21 -1
  5. package/lib/helpers/index.d.ts +1 -0
  6. package/lib/helpers/index.js +2 -1
  7. package/lib/helpers/sse-helpers.d.ts +33 -3
  8. package/lib/helpers/sse-helpers.js +91 -19
  9. package/lib/helpers/support-calendar.d.ts +69 -0
  10. package/lib/helpers/support-calendar.js +179 -0
  11. package/lib/middleware/auth.js +6 -3
  12. package/lib/middleware/deferred-quota-check.d.ts +5 -0
  13. package/lib/middleware/deferred-quota-check.js +33 -0
  14. package/lib/middleware/ecosystem-guard.d.ts +3 -3
  15. package/lib/middleware/ecosystem-guard.js +5 -5
  16. package/lib/middleware/index.d.ts +5 -2
  17. package/lib/middleware/index.js +6 -3
  18. package/lib/middleware/on-behalf.d.ts +37 -0
  19. package/lib/middleware/on-behalf.js +143 -0
  20. package/lib/middleware/permission-gates.d.ts +11 -0
  21. package/lib/middleware/permission-gates.js +7 -1
  22. package/lib/middleware/quota-exempt-routes.d.ts +36 -0
  23. package/lib/middleware/quota-exempt-routes.js +47 -0
  24. package/lib/middleware/route-table.d.ts +104 -0
  25. package/lib/middleware/route-table.js +72 -2
  26. package/lib/middleware/service-tokens.d.ts +8 -0
  27. package/lib/middleware/service-tokens.js +5 -3
  28. package/lib/middleware/step-up.js +11 -1
  29. package/lib/services/ai-call-weight.d.ts +24 -0
  30. package/lib/services/ai-call-weight.js +77 -0
  31. package/lib/services/alert-delivery.d.ts +99 -0
  32. package/lib/services/alert-delivery.js +132 -0
  33. package/lib/services/audit-context.d.ts +105 -0
  34. package/lib/services/audit-context.js +134 -0
  35. package/lib/services/index.d.ts +10 -2
  36. package/lib/services/index.js +10 -2
  37. package/lib/services/org-ai-client.d.ts +61 -0
  38. package/lib/services/org-ai-client.js +112 -0
  39. package/lib/services/pagerduty-inbound.d.ts +101 -0
  40. package/lib/services/pagerduty-inbound.js +195 -0
  41. package/lib/services/pagerduty.d.ts +109 -0
  42. package/lib/services/pagerduty.js +186 -0
  43. package/lib/services/quota-increment-batcher.d.ts +81 -0
  44. package/lib/services/quota-increment-batcher.js +166 -0
  45. package/lib/services/quota.d.ts +158 -14
  46. package/lib/services/quota.js +252 -22
  47. package/lib/services/remote-audit-client.d.ts +44 -1
  48. package/lib/services/remote-audit-client.js +106 -20
  49. package/lib/services/version-vuln-policy.d.ts +174 -0
  50. package/lib/services/version-vuln-policy.js +242 -0
  51. package/lib/types/ai-catalog.d.ts +63 -0
  52. package/lib/types/ai-catalog.js +82 -0
  53. package/lib/types/ask-proposals.js +6 -6
  54. package/lib/types/build-catalog.d.ts +154 -0
  55. package/lib/types/build-catalog.js +158 -0
  56. package/lib/types/common.d.ts +37 -3
  57. package/lib/types/common.js +20 -2
  58. package/lib/types/ecosystem-notifications.d.ts +5 -5
  59. package/lib/types/ecosystem-notifications.js +9 -3
  60. package/lib/types/error-codes.d.ts +13 -6
  61. package/lib/types/error-codes.js +18 -8
  62. package/lib/types/feature-flags.d.ts +71 -1
  63. package/lib/types/feature-flags.js +131 -11
  64. package/lib/types/index.d.ts +6 -4
  65. package/lib/types/index.js +12 -5
  66. package/lib/types/metadata-keys.d.ts +6 -0
  67. package/lib/types/metadata-keys.js +4 -1
  68. package/lib/types/permissions.d.ts +351 -3
  69. package/lib/types/permissions.js +610 -21
  70. package/lib/types/pipeline.d.ts +39 -0
  71. package/lib/types/pipeline.js +57 -1
  72. package/lib/types/plugin-health.d.ts +0 -2
  73. package/lib/types/plugin-health.js +1 -11
  74. package/lib/types/plugin-scan.d.ts +192 -42
  75. package/lib/types/plugin-scan.js +159 -43
  76. package/lib/types/quota-catalog.d.ts +247 -0
  77. package/lib/types/quota-catalog.js +122 -0
  78. package/lib/types/quota-tiers.d.ts +150 -0
  79. package/lib/types/quota-tiers.js +167 -2
  80. package/lib/types/remote-audit-actions.d.ts +1 -1
  81. package/lib/types/remote-audit-actions.js +217 -4
  82. package/lib/types/wire-vocabulary.js +8 -1
  83. package/lib/utils/index.d.ts +1 -1
  84. package/lib/utils/index.js +2 -2
  85. package/lib/utils/response.d.ts +35 -2
  86. package/lib/utils/response.js +42 -3
  87. package/lib/validation/ai-schemas.d.ts +27 -0
  88. package/lib/validation/ai-schemas.js +31 -5
  89. package/lib/validation/plugin-schemas.d.ts +15 -0
  90. package/lib/validation/plugin-schemas.js +28 -1
  91. package/package.json +31 -2
@@ -0,0 +1,195 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * The INBOUND half of on-call: what the provider tells us back.
5
+ *
6
+ * Everything else in this feature pushes outward — an alert fires, a case
7
+ * approaches its deadline, an event goes to Events v2. That direction can only
8
+ * ever report that a page was ACCEPTED, which is a 202 and not a phone ringing.
9
+ * This module is the only source of the two facts that actually matter:
10
+ *
11
+ * - SOMEBODY PICKED IT UP, and who. `oncall_pages_total{outcome="accepted"}`
12
+ * cannot answer "was anyone awake at 03:14"; an acknowledgement can, and
13
+ * bound to a user id it becomes an audit record rather than a hint. The
14
+ * provider's own ack view is not auditable here — a click on a link in their
15
+ * UI is not an identity this system can attest to — so the webhook is what
16
+ * turns their state change into our record.
17
+ * - NOBODY IS SCHEDULED. A rota with a hole next Tuesday has to be visible on
18
+ * MONDAY, not at 02:00 Tuesday. That is an absence check, and absence checks
19
+ * are the ones this codebase keeps finding it had missed. It is answered by
20
+ * READING the provider's schedule rather than owning one.
21
+ *
22
+ * TWO CREDENTIALS, DELIBERATELY SEPARATE AND BOTH OPTIONAL.
23
+ * `PAGERDUTY_WEBHOOK_SECRET` verifies what they send us. `PAGERDUTY_API_TOKEN`
24
+ * is a REST token for reading the rota and is strictly more powerful than the
25
+ * per-service integration key used for sending — an integration key can only
26
+ * create events on one service, while a REST token reads incidents, schedules
27
+ * and users account-wide. So the rota check is opt-in on its own credential,
28
+ * and a deployment that does not want to hand over a REST token simply does not
29
+ * get the gauge. It must never be inferred from the routing key.
30
+ */
31
+ import { createHmac, timingSafeEqual } from 'crypto';
32
+ import { envInt, envStr } from '../utils/env.js';
33
+ import { createLogger } from '../utils/logger.js';
34
+ import { errorMessage } from '../utils/response.js';
35
+ import { safeFetch } from '../utils/ssrf.js';
36
+ const logger = createLogger('pagerduty-inbound');
37
+ export const PAGERDUTY_API_URL = 'https://api.pagerduty.com';
38
+ /**
39
+ * Verify a PagerDuty v3 webhook signature.
40
+ *
41
+ * The scheme: `X-PagerDuty-Signature: v1=<lowercase hex>`, an HMAC-SHA256 over
42
+ * the RAW request body. Three properties of it are easy to get wrong and each
43
+ * one is a hole:
44
+ *
45
+ * - THE RAW BODY, not a re-serialized object. `JSON.stringify(req.body)` after
46
+ * a JSON parser has run produces different bytes (key order, whitespace,
47
+ * number formatting), so every signature fails and the obvious "fix" is to
48
+ * stop checking. The route therefore mounts `express.raw`.
49
+ * - MULTIPLE SIGNATURES. The header may carry several comma-separated `v1=`
50
+ * values, which is how PagerDuty rotates a secret without downtime. Checking
51
+ * only the first breaks during their rotation, so ANY match is accepted —
52
+ * the same overlap this repo already runs for its own relay token.
53
+ * - CONSTANT TIME. Compared with `timingSafeEqual` on equal-length buffers,
54
+ * because a byte-at-a-time comparison leaks the expected digest to anyone
55
+ * who can post here repeatedly.
56
+ *
57
+ * NO SECRET MEANS NO. An unverified webhook that moves an audit record and a
58
+ * metric is a way for anyone who learns the URL to assert that a page was
59
+ * handled — the one lie that would make the whole feature report coverage it
60
+ * does not have. Fails closed.
61
+ */
62
+ export function verifyPagerDutySignature(rawBody, header, secret) {
63
+ if (!secret || !header)
64
+ return false;
65
+ const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
66
+ const expectedBuf = Buffer.from(expected, 'utf8');
67
+ for (const part of header.split(',')) {
68
+ const value = part.trim();
69
+ if (!value.startsWith('v1='))
70
+ continue;
71
+ const given = Buffer.from(value.slice(3), 'utf8');
72
+ // Length-guarded: timingSafeEqual THROWS on a length mismatch, and an
73
+ // attacker controls this length.
74
+ if (given.length === expectedBuf.length && timingSafeEqual(given, expectedBuf))
75
+ return true;
76
+ }
77
+ return false;
78
+ }
79
+ export function onCallInboundConfig() {
80
+ return {
81
+ webhookSecret: envStr('PAGERDUTY_WEBHOOK_SECRET', ''),
82
+ apiToken: envStr('PAGERDUTY_API_TOKEN', ''),
83
+ apiUrl: envStr('PAGERDUTY_API_URL', PAGERDUTY_API_URL),
84
+ // 48 hours: far enough to catch a hole over a weekend on the Friday, and
85
+ // short enough that a rota maintained a week out does not alert forever.
86
+ lookaheadHours: envInt('ONCALL_SCHEDULE_GAP_LOOKAHEAD_HOURS', 48, { min: 1, max: 2160 }),
87
+ escalationPolicyIds: envStr('PAGERDUTY_ESCALATION_POLICY_IDS', '')
88
+ .split(',').map((x) => x.trim()).filter(Boolean),
89
+ timeoutMs: envInt('ONCALL_EVENTS_TIMEOUT_MS', 5000, { min: 1 }),
90
+ };
91
+ }
92
+ /**
93
+ * Ask the provider whether anyone is on call across the lookahead window.
94
+ *
95
+ * WHAT THIS CAN AND CANNOT CONCLUDE, because the difference decides whether the
96
+ * alert built on it is honest. `GET /oncalls?since&until` returns the on-call
97
+ * entries OVERLAPPING a window; it does not return the holes. Reconstructing
98
+ * exact holes would mean stitching every entry's interval, and an entry's
99
+ * `start`/`end` are null for an always-on policy — so a precise
100
+ * "uncovered seconds" is not available from this endpoint without a lot of
101
+ * arithmetic that would be wrong in the always-on case.
102
+ *
103
+ * So this answers the question it CAN answer exactly: is the window covered at
104
+ * all. Zero entries over the next N hours means nobody is scheduled, which is
105
+ * the failure worth paging about and the one a rota maintainer actually makes.
106
+ * `uncoveredSeconds` is therefore the whole window or nothing, and it is a
107
+ * deliberate under-report rather than a guess — a partial hole reads as covered
108
+ * here, and claiming otherwise would be inventing precision.
109
+ *
110
+ * `known: false` when there is no API token or the call fails, and the caller
111
+ * must publish NOTHING in that case. A gauge of 0 would say "fully covered",
112
+ * which is exactly the lie this check exists to prevent.
113
+ */
114
+ export async function fetchRotaCoverage(cfg = onCallInboundConfig()) {
115
+ const windowSeconds = cfg.lookaheadHours * 3600;
116
+ if (!cfg.apiToken)
117
+ return { known: false, entries: 0, uncoveredSeconds: 0 };
118
+ const since = new Date();
119
+ const until = new Date(since.getTime() + windowSeconds * 1000);
120
+ const params = new URLSearchParams({
121
+ since: since.toISOString(),
122
+ until: until.toISOString(),
123
+ limit: '50',
124
+ });
125
+ for (const id of cfg.escalationPolicyIds)
126
+ params.append('escalation_policy_ids[]', id);
127
+ try {
128
+ const resp = await safeFetch(`${cfg.apiUrl}/oncalls?${params.toString()}`, {
129
+ method: 'GET',
130
+ headers: {
131
+ // PagerDuty's REST scheme. `Accept` pins the API version; without it
132
+ // the response shape is whatever their default happens to be.
133
+ Authorization: `Token token=${cfg.apiToken}`,
134
+ Accept: 'application/vnd.pagerduty+json;version=2',
135
+ },
136
+ timeoutMs: cfg.timeoutMs,
137
+ });
138
+ if (resp.redirected || !resp.ok) {
139
+ logger.warn('Could not read the on-call rota', { status: resp.status, redirected: resp.redirected });
140
+ return { known: false, entries: 0, uncoveredSeconds: 0 };
141
+ }
142
+ const body = await resp.json();
143
+ const entries = Array.isArray(body.oncalls) ? body.oncalls.length : 0;
144
+ return { known: true, entries, uncoveredSeconds: entries > 0 ? 0 : windowSeconds };
145
+ }
146
+ catch (err) {
147
+ logger.warn('Could not reach the on-call rota API', { error: errorMessage(err) });
148
+ return { known: false, entries: 0, uncoveredSeconds: 0 };
149
+ }
150
+ }
151
+ /**
152
+ * Reduce a v3 webhook body to the fields this system uses.
153
+ *
154
+ * Returns `null` for anything that is not a recognised incident event, which
155
+ * the caller COUNTS rather than drops silently — an unrecognised event type is
156
+ * usually a subscription configured wider than the handler, and a count is how
157
+ * that becomes visible instead of being inferred from nothing happening.
158
+ */
159
+ export function parseOnCallWebhook(body) {
160
+ const event = body?.event;
161
+ if (!event || typeof event.event_type !== 'string')
162
+ return null;
163
+ const data = (event.data ?? {});
164
+ const agency = (event.agent ?? {});
165
+ return {
166
+ eventType: event.event_type,
167
+ incidentId: typeof data.id === 'string' ? data.id : undefined,
168
+ // PagerDuty echoes the trigger's dedup_key on the incident, which is the
169
+ // only link back to WHICH case or alert this was — their incident id is
170
+ // theirs and means nothing on this side.
171
+ dedupKey: typeof data.dedup_key === 'string' ? data.dedup_key : undefined,
172
+ agentId: typeof agency.id === 'string' ? agency.id : undefined,
173
+ agentSummary: typeof agency.summary === 'string' ? agency.summary : undefined,
174
+ createdAt: typeof data.created_at === 'string' ? data.created_at : undefined,
175
+ occurredAt: typeof event.occurred_at === 'string' ? event.occurred_at : undefined,
176
+ };
177
+ }
178
+ /**
179
+ * Seconds between the incident being raised and this event.
180
+ *
181
+ * Returns `undefined` rather than 0 when either timestamp is missing or the
182
+ * arithmetic is nonsense: a 0 would land in the first histogram bucket and read
183
+ * as an instant acknowledgement, which is the most flattering possible lie
184
+ * about response time.
185
+ */
186
+ export function ackLatencySeconds(event) {
187
+ if (!event.createdAt || !event.occurredAt)
188
+ return undefined;
189
+ const from = new Date(event.createdAt).getTime();
190
+ const to = new Date(event.occurredAt).getTime();
191
+ if (!Number.isFinite(from) || !Number.isFinite(to) || to < from)
192
+ return undefined;
193
+ return (to - from) / 1000;
194
+ }
195
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFnZXJkdXR5LWluYm91bmQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvcGFnZXJkdXR5LWluYm91bmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBMkJHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxlQUFlLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDckQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNqRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbEQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUU3QyxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUVqRCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRywyQkFBMkIsQ0FBQztBQUU3RDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Qkc7QUFDSCxNQUFNLFVBQVUsd0JBQXdCLENBQUMsT0FBd0IsRUFBRSxNQUEwQixFQUFFLE1BQWM7SUFDM0csSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLE1BQU07UUFBRSxPQUFPLEtBQUssQ0FBQztJQUNyQyxNQUFNLFFBQVEsR0FBRyxVQUFVLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDNUUsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDbEQsS0FBSyxNQUFNLElBQUksSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFF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QUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLEtBQXlCO0lBQ3pELElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVU7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUM1RCxNQUFNLElBQUksR0FBRyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDakQsTUFBTSxFQUFFLEdBQUcsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2hELElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsSUFBSSxFQUFFLEdBQUcsSUFBSTtRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2xGLE9BQU8sQ0FBQyxFQUFFLEdBQUcsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDO0FBQzVCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBUaGUgSU5CT1VORCBoYWxmIG9mIG9uLWNhbGw6IHdoYXQgdGhlIHByb3ZpZGVyIHRlbGxzIHVzIGJhY2suXG4gKlxuICogRXZlcnl0aGluZyBlbHNlIGluIHRoaXMgZmVhdHVyZSBwdXNoZXMgb3V0d2FyZCDigJQgYW4gYWxlcnQgZmlyZXMsIGEgY2FzZVxuICogYXBwcm9hY2hlcyBpdHMgZGVhZGxpbmUsIGFuIGV2ZW50IGdvZXMgdG8gRXZlbnRzIHYyLiBUaGF0IGRpcmVjdGlvbiBjYW4gb25seVxuICogZXZlciByZXBvcnQgdGhhdCBhIHBhZ2Ugd2FzIEFDQ0VQVEVELCB3aGljaCBpcyBhIDIwMiBhbmQgbm90IGEgcGhvbmUgcmluZ2luZy5cbiAqIFRoaXMgbW9kdWxlIGlzIHRoZSBvbmx5IHNvdXJjZSBvZiB0aGUgdHdvIGZhY3RzIHRoYXQgYWN0dWFsbHkgbWF0dGVyOlxuICpcbiAqICAtIFNPTUVCT0RZIFBJQ0tFRCBJVCBVUCwgYW5kIHdoby4gYG9uY2FsbF9wYWdlc190b3RhbHtvdXRjb21lPVwiYWNjZXB0ZWRcIn1gXG4gKiAgICBjYW5ub3QgYW5zd2VyIFwid2FzIGFueW9uZSBhd2FrZSBhdCAwMzoxNFwiOyBhbiBhY2tub3dsZWRnZW1lbnQgY2FuLCBhbmRcbiAqICAgIGJvdW5kIHRvIGEgdXNlciBpZCBpdCBiZWNvbWVzIGFuIGF1ZGl0IHJlY29yZCByYXRoZXIgdGhhbiBhIGhpbnQuIFRoZVxuICogICAgcHJvdmlkZXIncyBvd24gYWNrIHZpZXcgaXMgbm90IGF1ZGl0YWJsZSBoZXJlIOKAlCBhIGNsaWNrIG9uIGEgbGluayBpbiB0aGVpclxuICogICAgVUkgaXMgbm90IGFuIGlkZW50aXR5IHRoaXMgc3lzdGVtIGNhbiBhdHRlc3QgdG8g4oCUIHNvIHRoZSB3ZWJob29rIGlzIHdoYXRcbiAqICAgIHR1cm5zIHRoZWlyIHN0YXRlIGNoYW5nZSBpbnRvIG91ciByZWNvcmQuXG4gKiAgLSBOT0JPRFkgSVMgU0NIRURVTEVELiBBIHJvdGEgd2l0aCBhIGhvbGUgbmV4dCBUdWVzZGF5IGhhcyB0byBiZSB2aXNpYmxlIG9uXG4gKiAgICBNT05EQVksIG5vdCBhdCAwMjowMCBUdWVzZGF5LiBUaGF0IGlzIGFuIGFic2VuY2UgY2hlY2ssIGFuZCBhYnNlbmNlIGNoZWNrc1xuICogICAgYXJlIHRoZSBvbmVzIHRoaXMgY29kZWJhc2Uga2VlcHMgZmluZGluZyBpdCBoYWQgbWlzc2VkLiBJdCBpcyBhbnN3ZXJlZCBieVxuICogICAgUkVBRElORyB0aGUgcHJvdmlkZXIncyBzY2hlZHVsZSByYXRoZXIgdGhhbiBvd25pbmcgb25lLlxuICpcbiAqIFRXTyBDUkVERU5USUFMUywgREVMSUJFUkFURUxZIFNFUEFSQVRFIEFORCBCT1RIIE9QVElPTkFMLlxuICogYFBBR0VSRFVUWV9XRUJIT09LX1NFQ1JFVGAgdmVyaWZpZXMgd2hhdCB0aGV5IHNlbmQgdXMuIGBQQUdFUkRVVFlfQVBJX1RPS0VOYFxuICogaXMgYSBSRVNUIHRva2VuIGZvciByZWFkaW5nIHRoZSByb3RhIGFuZCBpcyBzdHJpY3RseSBtb3JlIHBvd2VyZnVsIHRoYW4gdGhlXG4gKiBwZXItc2VydmljZSBpbnRlZ3JhdGlvbiBrZXkgdXNlZCBmb3Igc2VuZGluZyDigJQgYW4gaW50ZWdyYXRpb24ga2V5IGNhbiBvbmx5XG4gKiBjcmVhdGUgZXZlbnRzIG9uIG9uZSBzZXJ2aWNlLCB3aGlsZSBhIFJFU1QgdG9rZW4gcmVhZHMgaW5jaWRlbnRzLCBzY2hlZHVsZXNcbiAqIGFuZCB1c2VycyBhY2NvdW50LXdpZGUuIFNvIHRoZSByb3RhIGNoZWNrIGlzIG9wdC1pbiBvbiBpdHMgb3duIGNyZWRlbnRpYWwsXG4gKiBhbmQgYSBkZXBsb3ltZW50IHRoYXQgZG9lcyBub3Qgd2FudCB0byBoYW5kIG92ZXIgYSBSRVNUIHRva2VuIHNpbXBseSBkb2VzIG5vdFxuICogZ2V0IHRoZSBnYXVnZS4gSXQgbXVzdCBuZXZlciBiZSBpbmZlcnJlZCBmcm9tIHRoZSByb3V0aW5nIGtleS5cbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVIbWFjLCB0aW1pbmdTYWZlRXF1YWwgfSBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHsgZW52SW50LCBlbnZTdHIgfSBmcm9tICcuLi91dGlscy9lbnYuanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcbmltcG9ydCB7IHNhZmVGZXRjaCB9IGZyb20gJy4uL3V0aWxzL3NzcmYuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3BhZ2VyZHV0eS1pbmJvdW5kJyk7XG5cbmV4cG9ydCBjb25zdCBQQUdFUkRVVFlfQVBJX1VSTCA9ICdodHRwczovL2FwaS5wYWdlcmR1dHkuY29tJztcblxuLyoqXG4gKiBWZXJpZnkgYSBQYWdlckR1dHkgdjMgd2ViaG9vayBzaWduYXR1cmUuXG4gKlxuICogVGhlIHNjaGVtZTogYFgtUGFnZXJEdXR5LVNpZ25hdHVyZTogdjE9PGxvd2VyY2FzZSBoZXg+YCwgYW4gSE1BQy1TSEEyNTYgb3ZlclxuICogdGhlIFJBVyByZXF1ZXN0IGJvZHkuIFRocmVlIHByb3BlcnRpZXMgb2YgaXQgYXJlIGVhc3kgdG8gZ2V0IHdyb25nIGFuZCBlYWNoXG4gKiBvbmUgaXMgYSBob2xlOlxuICpcbiAqICAtIFRIRSBSQVcgQk9EWSwgbm90IGEgcmUtc2VyaWFsaXplZCBvYmplY3QuIGBKU09OLnN0cmluZ2lmeShyZXEuYm9keSlgIGFmdGVyXG4gKiAgICBhIEpTT04gcGFyc2VyIGhhcyBydW4gcHJvZHVjZXMgZGlmZmVyZW50IGJ5dGVzIChrZXkgb3JkZXIsIHdoaXRlc3BhY2UsXG4gKiAgICBudW1iZXIgZm9ybWF0dGluZyksIHNvIGV2ZXJ5IHNpZ25hdHVyZSBmYWlscyBhbmQgdGhlIG9idmlvdXMgXCJmaXhcIiBpcyB0b1xuICogICAgc3RvcCBjaGVja2luZy4gVGhlIHJvdXRlIHRoZXJlZm9yZSBtb3VudHMgYGV4cHJlc3MucmF3YC5cbiAqICAtIE1VTFRJUExFIFNJR05BVFVSRVMuIFRoZSBoZWFkZXIgbWF5IGNhcnJ5IHNldmVyYWwgY29tbWEtc2VwYXJhdGVkIGB2MT1gXG4gKiAgICB2YWx1ZXMsIHdoaWNoIGlzIGhvdyBQYWdlckR1dHkgcm90YXRlcyBhIHNlY3JldCB3aXRob3V0IGRvd250aW1lLiBDaGVja2luZ1xuICogICAgb25seSB0aGUgZmlyc3QgYnJlYWtzIGR1cmluZyB0aGVpciByb3RhdGlvbiwgc28gQU5ZIG1hdGNoIGlzIGFjY2VwdGVkIOKAlFxuICogICAgdGhlIHNhbWUgb3ZlcmxhcCB0aGlzIHJlcG8gYWxyZWFkeSBydW5zIGZvciBpdHMgb3duIHJlbGF5IHRva2VuLlxuICogIC0gQ09OU1RBTlQgVElNRS4gQ29tcGFyZWQgd2l0aCBgdGltaW5nU2FmZUVxdWFsYCBvbiBlcXVhbC1sZW5ndGggYnVmZmVycyxcbiAqICAgIGJlY2F1c2UgYSBieXRlLWF0LWEtdGltZSBjb21wYXJpc29uIGxlYWtzIHRoZSBleHBlY3RlZCBkaWdlc3QgdG8gYW55b25lXG4gKiAgICB3aG8gY2FuIHBvc3QgaGVyZSByZXBlYXRlZGx5LlxuICpcbiAqIE5PIFNFQ1JFVCBNRUFOUyBOTy4gQW4gdW52ZXJpZmllZCB3ZWJob29rIHRoYXQgbW92ZXMgYW4gYXVkaXQgcmVjb3JkIGFuZCBhXG4gKiBtZXRyaWMgaXMgYSB3YXkgZm9yIGFueW9uZSB3aG8gbGVhcm5zIHRoZSBVUkwgdG8gYXNzZXJ0IHRoYXQgYSBwYWdlIHdhc1xuICogaGFuZGxlZCDigJQgdGhlIG9uZSBsaWUgdGhhdCB3b3VsZCBtYWtlIHRoZSB3aG9sZSBmZWF0dXJlIHJlcG9ydCBjb3ZlcmFnZSBpdFxuICogZG9lcyBub3QgaGF2ZS4gRmFpbHMgY2xvc2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gdmVyaWZ5UGFnZXJEdXR5U2lnbmF0dXJlKHJhd0JvZHk6IEJ1ZmZlciB8IHN0cmluZywgaGVhZGVyOiBzdHJpbmcgfCB1bmRlZmluZWQsIHNlY3JldDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGlmICghc2VjcmV0IHx8ICFoZWFkZXIpIHJldHVybiBmYWxzZTtcbiAgY29uc3QgZXhwZWN0ZWQgPSBjcmVhdGVIbWFjKCdzaGEyNTYnLCBzZWNyZXQpLnVwZGF0ZShyYXdCb2R5KS5kaWdlc3QoJ2hleCcpO1xuICBjb25zdCBleHBlY3RlZEJ1ZiA9IEJ1ZmZlci5mcm9tKGV4cGVjdGVkLCAndXRmOCcpO1xuICBmb3IgKGNvbnN0IHBhcnQgb2YgaGVhZGVyLnNwbGl0KCcsJykpIHtcbiAgICBjb25zdCB2YWx1ZSA9IHBhcnQudHJpbSgpO1xuICAgIGlmICghdmFsdWUuc3RhcnRzV2l0aCgndjE9JykpIGNvbnRpbnVlO1xuICAgIGNvbnN0IGdpdmVuID0gQnVmZmVyLmZyb20odmFsdWUuc2xpY2UoMyksICd1dGY4Jyk7XG4gICAgLy8gTGVuZ3RoLWd1YXJkZWQ6IHRpbWluZ1NhZmVFcXVhbCBUSFJPV1Mgb24gYSBsZW5ndGggbWlzbWF0Y2gsIGFuZCBhblxuICAgIC8vIGF0dGFja2VyIGNvbnRyb2xzIHRoaXMgbGVuZ3RoLlxuICAgIGlmIChnaXZlbi5sZW5ndGggPT09IGV4cGVjdGVkQnVmLmxlbmd0aCAmJiB0aW1pbmdTYWZlRXF1YWwoZ2l2ZW4sIGV4cGVjdGVkQnVmKSkgcmV0dXJuIHRydWU7XG4gIH1cbiAgcmV0dXJuIGZhbHNlO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIE9uQ2FsbEluYm91bmRDb25maWcge1xuICB3ZWJob29rU2VjcmV0OiBzdHJpbmc7XG4gIGFwaVRva2VuOiBzdHJpbmc7XG4gIGFwaVVybDogc3RyaW5nO1xuICAvKiogSG93IGZhciBhaGVhZCB0aGUgcm90YSBjaGVjayBsb29rcy4gVGhlIGdhcCBhbGVydCBpcyBvbmx5IGFzIHVzZWZ1bCBhcyB0aGlzLiAqL1xuICBsb29rYWhlYWRIb3VyczogbnVtYmVyO1xuICAvKiogRXNjYWxhdGlvbiBwb2xpY3kgaWRzIHRvIGNoZWNrIGNvdmVyYWdlIGZvcjsgZW1wdHkgbWVhbnMgdGhlIHdob2xlIGFjY291bnQuICovXG4gIGVzY2FsYXRpb25Qb2xpY3lJZHM6IHN0cmluZ1tdO1xuICB0aW1lb3V0TXM6IG51bWJlcjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIG9uQ2FsbEluYm91bmRDb25maWcoKTogT25DYWxsSW5ib3VuZENvbmZpZyB7XG4gIHJldHVybiB7XG4gICAgd2ViaG9va1NlY3JldDogZW52U3RyKCdQQUdFUkRVVFlfV0VCSE9PS19TRUNSRVQnLCAnJyksXG4gICAgYXBpVG9rZW46IGVudlN0cignUEFHRVJEVVRZX0FQSV9UT0tFTicsICcnKSxcbiAgICBhcGlVcmw6IGVudlN0cignUEFHRVJEVVRZX0FQSV9VUkwnLCBQQUdFUkRVVFlfQVBJX1VSTCksXG4gICAgLy8gNDggaG91cnM6IGZhciBlbm91Z2ggdG8gY2F0Y2ggYSBob2xlIG92ZXIgYSB3ZWVrZW5kIG9uIHRoZSBGcmlkYXksIGFuZFxuICAgIC8vIHNob3J0IGVub3VnaCB0aGF0IGEgcm90YSBtYWludGFpbmVkIGEgd2VlayBvdXQgZG9lcyBub3QgYWxlcnQgZm9yZXZlci5cbiAgICBsb29rYWhlYWRIb3VyczogZW52SW50KCdPTkNBTExfU0NIRURVTEVfR0FQX0xPT0tBSEVBRF9IT1VSUycsIDQ4LCB7IG1pbjogMSwgbWF4OiAyMTYwIH0pLFxuICAgIGVzY2FsYXRpb25Qb2xpY3lJZHM6IGVudlN0cignUEFHRVJEVVRZX0VTQ0FMQVRJT05fUE9MSUNZX0lEUycsICcnKVxuICAgICAgLnNwbGl0KCcsJykubWFwKCh4KSA9PiB4LnRyaW0oKSkuZmlsdGVyKEJvb2xlYW4pLFxuICAgIHRpbWVvdXRNczogZW52SW50KCdPTkNBTExfRVZFTlRTX1RJTUVPVVRfTVMnLCA1MDAwLCB7IG1pbjogMSB9KSxcbiAgfTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBSb3RhQ292ZXJhZ2Uge1xuICAvKiogV2hldGhlciB0aGUgcXVlc3Rpb24gY291bGQgYmUgYW5zd2VyZWQgYXQgYWxsLiAqL1xuICBrbm93bjogYm9vbGVhbjtcbiAgLyoqIERpc3RpbmN0IG9uLWNhbGwgZW50cmllcyBjb3ZlcmluZyBhbnkgcGFydCBvZiB0aGUgd2luZG93LiAqL1xuICBlbnRyaWVzOiBudW1iZXI7XG4gIC8qKiBTZWNvbmRzIG9mIHRoZSBsb29rYWhlYWQgd2luZG93IHdpdGggbm9ib2R5IG9uIGNhbGwsIDAgd2hlbiBmdWxseSBjb3ZlcmVkLiAqL1xuICB1bmNvdmVyZWRTZWNvbmRzOiBudW1iZXI7XG59XG5cbi8qKlxuICogQXNrIHRoZSBwcm92aWRlciB3aGV0aGVyIGFueW9uZSBpcyBvbiBjYWxsIGFjcm9zcyB0aGUgbG9va2FoZWFkIHdpbmRvdy5cbiAqXG4gKiBXSEFUIFRISVMgQ0FOIEFORCBDQU5OT1QgQ09OQ0xVREUsIGJlY2F1c2UgdGhlIGRpZmZlcmVuY2UgZGVjaWRlcyB3aGV0aGVyIHRoZVxuICogYWxlcnQgYnVpbHQgb24gaXQgaXMgaG9uZXN0LiBgR0VUIC9vbmNhbGxzP3NpbmNlJnVudGlsYCByZXR1cm5zIHRoZSBvbi1jYWxsXG4gKiBlbnRyaWVzIE9WRVJMQVBQSU5HIGEgd2luZG93OyBpdCBkb2VzIG5vdCByZXR1cm4gdGhlIGhvbGVzLiBSZWNvbnN0cnVjdGluZ1xuICogZXhhY3QgaG9sZXMgd291bGQgbWVhbiBzdGl0Y2hpbmcgZXZlcnkgZW50cnkncyBpbnRlcnZhbCwgYW5kIGFuIGVudHJ5J3NcbiAqIGBzdGFydGAvYGVuZGAgYXJlIG51bGwgZm9yIGFuIGFsd2F5cy1vbiBwb2xpY3kg4oCUIHNvIGEgcHJlY2lzZVxuICogXCJ1bmNvdmVyZWQgc2Vjb25kc1wiIGlzIG5vdCBhdmFpbGFibGUgZnJvbSB0aGlzIGVuZHBvaW50IHdpdGhvdXQgYSBsb3Qgb2ZcbiAqIGFyaXRobWV0aWMgdGhhdCB3b3VsZCBiZSB3cm9uZyBpbiB0aGUgYWx3YXlzLW9uIGNhc2UuXG4gKlxuICogU28gdGhpcyBhbnN3ZXJzIHRoZSBxdWVzdGlvbiBpdCBDQU4gYW5zd2VyIGV4YWN0bHk6IGlzIHRoZSB3aW5kb3cgY292ZXJlZCBhdFxuICogYWxsLiBaZXJvIGVudHJpZXMgb3ZlciB0aGUgbmV4dCBOIGhvdXJzIG1lYW5zIG5vYm9keSBpcyBzY2hlZHVsZWQsIHdoaWNoIGlzXG4gKiB0aGUgZmFpbHVyZSB3b3J0aCBwYWdpbmcgYWJvdXQgYW5kIHRoZSBvbmUgYSByb3RhIG1haW50YWluZXIgYWN0dWFsbHkgbWFrZXMuXG4gKiBgdW5jb3ZlcmVkU2Vjb25kc2AgaXMgdGhlcmVmb3JlIHRoZSB3aG9sZSB3aW5kb3cgb3Igbm90aGluZywgYW5kIGl0IGlzIGFcbiAqIGRlbGliZXJhdGUgdW5kZXItcmVwb3J0IHJhdGhlciB0aGFuIGEgZ3Vlc3Mg4oCUIGEgcGFydGlhbCBob2xlIHJlYWRzIGFzIGNvdmVyZWRcbiAqIGhlcmUsIGFuZCBjbGFpbWluZyBvdGhlcndpc2Ugd291bGQgYmUgaW52ZW50aW5nIHByZWNpc2lvbi5cbiAqXG4gKiBga25vd246IGZhbHNlYCB3aGVuIHRoZXJlIGlzIG5vIEFQSSB0b2tlbiBvciB0aGUgY2FsbCBmYWlscywgYW5kIHRoZSBjYWxsZXJcbiAqIG11c3QgcHVibGlzaCBOT1RISU5HIGluIHRoYXQgY2FzZS4gQSBnYXVnZSBvZiAwIHdvdWxkIHNheSBcImZ1bGx5IGNvdmVyZWRcIixcbiAqIHdoaWNoIGlzIGV4YWN0bHkgdGhlIGxpZSB0aGlzIGNoZWNrIGV4aXN0cyB0byBwcmV2ZW50LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZmV0Y2hSb3RhQ292ZXJhZ2UoY2ZnOiBPbkNhbGxJbmJvdW5kQ29uZmlnID0gb25DYWxsSW5ib3VuZENvbmZpZygpKTogUHJvbWlzZTxSb3RhQ292ZXJhZ2U+IHtcbiAgY29uc3Qgd2luZG93U2Vjb25kcyA9IGNmZy5sb29rYWhlYWRIb3VycyAqIDM2MDA7XG4gIGlmICghY2ZnLmFwaVRva2VuKSByZXR1cm4geyBrbm93bjogZmFsc2UsIGVudHJpZXM6IDAsIHVuY292ZXJlZFNlY29uZHM6IDAgfTtcblxuICBjb25zdCBzaW5jZSA9IG5ldyBEYXRlKCk7XG4gIGNvbnN0IHVudGlsID0gbmV3IERhdGUoc2luY2UuZ2V0VGltZSgpICsgd2luZG93U2Vjb25kcyAqIDEwMDApO1xuICBjb25zdCBwYXJhbXMgPSBuZXcgVVJMU2VhcmNoUGFyYW1zKHtcbiAgICBzaW5jZTogc2luY2UudG9JU09TdHJpbmcoKSxcbiAgICB1bnRpbDogdW50aWwudG9JU09TdHJpbmcoKSxcbiAgICBsaW1pdDogJzUwJyxcbiAgfSk7XG4gIGZvciAoY29uc3QgaWQgb2YgY2ZnLmVzY2FsYXRpb25Qb2xpY3lJZHMpIHBhcmFtcy5hcHBlbmQoJ2VzY2FsYXRpb25fcG9saWN5X2lkc1tdJywgaWQpO1xuXG4gIHRyeSB7XG4gICAgY29uc3QgcmVzcCA9IGF3YWl0IHNhZmVGZXRjaChgJHtjZmcuYXBpVXJsfS9vbmNhbGxzPyR7cGFyYW1zLnRvU3RyaW5nKCl9YCwge1xuICAgICAgbWV0aG9kOiAnR0VUJyxcbiAgICAgIGhlYWRlcnM6IHtcbiAgICAgICAgLy8gUGFnZXJEdXR5J3MgUkVTVCBzY2hlbWUuIGBBY2NlcHRgIHBpbnMgdGhlIEFQSSB2ZXJzaW9uOyB3aXRob3V0IGl0XG4gICAgICAgIC8vIHRoZSByZXNwb25zZSBzaGFwZSBpcyB3aGF0ZXZlciB0aGVpciBkZWZhdWx0IGhhcHBlbnMgdG8gYmUuXG4gICAgICAgIEF1dGhvcml6YXRpb246IGBUb2tlbiB0b2tlbj0ke2NmZy5hcGlUb2tlbn1gLFxuICAgICAgICBBY2NlcHQ6ICdhcHBsaWNhdGlvbi92bmQucGFnZXJkdXR5K2pzb247dmVyc2lvbj0yJyxcbiAgICAgIH0sXG4gICAgICB0aW1lb3V0TXM6IGNmZy50aW1lb3V0TXMsXG4gICAgfSk7XG4gICAgaWYgKHJlc3AucmVkaXJlY3RlZCB8fCAhcmVzcC5vaykge1xuICAgICAgbG9nZ2VyLndhcm4oJ0NvdWxkIG5vdCByZWFkIHRoZSBvbi1jYWxsIHJvdGEnLCB7IHN0YXR1czogcmVzcC5zdGF0dXMsIHJlZGlyZWN0ZWQ6IHJlc3AucmVkaXJlY3RlZCB9KTtcbiAgICAgIHJldHVybiB7IGtub3duOiBmYWxzZSwgZW50cmllczogMCwgdW5jb3ZlcmVkU2Vjb25kczogMCB9O1xuICAgIH1cbiAgICBjb25zdCBib2R5ID0gYXdhaXQgcmVzcC5qc29uKCkgYXMgeyBvbmNhbGxzPzogdW5rbm93bltdIH07XG4gICAgY29uc3QgZW50cmllcyA9IEFycmF5LmlzQXJyYXkoYm9keS5vbmNhbGxzKSA/IGJvZHkub25jYWxscy5sZW5ndGggOiAwO1xuICAgIHJldHVybiB7IGtub3duOiB0cnVlLCBlbnRyaWVzLCB1bmNvdmVyZWRTZWNvbmRzOiBlbnRyaWVzID4gMCA/IDAgOiB3aW5kb3dTZWNvbmRzIH07XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZ2dlci53YXJuKCdDb3VsZCBub3QgcmVhY2ggdGhlIG9uLWNhbGwgcm90YSBBUEknLCB7IGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyKSB9KTtcbiAgICByZXR1cm4geyBrbm93bjogZmFsc2UsIGVudHJpZXM6IDAsIHVuY292ZXJlZFNlY29uZHM6IDAgfTtcbiAgfVxufVxuXG4vKiogVGhlIHdlYmhvb2sgZXZlbnRzIHRoaXMgc3lzdGVtIGFjdHMgb24uIEV2ZXJ5dGhpbmcgZWxzZSBpcyBjb3VudGVkIGFuZCBkcm9wcGVkLiAqL1xuZXhwb3J0IHR5cGUgT25DYWxsV2ViaG9va0V2ZW50VHlwZSA9XG4gIHwgJ2luY2lkZW50LmFja25vd2xlZGdlZCdcbiAgfCAnaW5jaWRlbnQuZXNjYWxhdGVkJ1xuICB8ICdpbmNpZGVudC5yZXNvbHZlZCdcbiAgfCAnaW5jaWRlbnQudW5hY2tub3dsZWRnZWQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIE9uQ2FsbFdlYmhvb2tFdmVudCB7XG4gIGV2ZW50VHlwZTogc3RyaW5nO1xuICAvKiogUHJvdmlkZXIgaW5jaWRlbnQgaWQg4oCUIHRoZSBqb2luIGJhY2sgdG8gd2hhdCB3ZSBzZW50LiAqL1xuICBpbmNpZGVudElkPzogc3RyaW5nO1xuICAvKiogT3VyIG93biBgZGVkdXBfa2V5YCwgd2hlbiB0aGUgcHJvdmlkZXIgZWNob2VzIGl0LiAqL1xuICBkZWR1cEtleT86IHN0cmluZztcbiAgLyoqIFdobyBhY3RlZCwgYXMgdGhlIHByb3ZpZGVyIGtub3dzIHRoZW0uIEFuIGlkIGFuZCBhbiBlbWFpbCwgbmV2ZXIgYSBzZXNzaW9uLiAqL1xuICBhZ2VudElkPzogc3RyaW5nO1xuICBhZ2VudFN1bW1hcnk/OiBzdHJpbmc7XG4gIC8qKiBXaGVuIHRoZSBpbmNpZGVudCB3YXMgY3JlYXRlZCwgZm9yIHRoZSBhY2stbGF0ZW5jeSBtZWFzdXJlbWVudC4gKi9cbiAgY3JlYXRlZEF0Pzogc3RyaW5nO1xuICBvY2N1cnJlZEF0Pzogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJlZHVjZSBhIHYzIHdlYmhvb2sgYm9keSB0byB0aGUgZmllbGRzIHRoaXMgc3lzdGVtIHVzZXMuXG4gKlxuICogUmV0dXJucyBgbnVsbGAgZm9yIGFueXRoaW5nIHRoYXQgaXMgbm90IGEgcmVjb2duaXNlZCBpbmNpZGVudCBldmVudCwgd2hpY2hcbiAqIHRoZSBjYWxsZXIgQ09VTlRTIHJhdGhlciB0aGFuIGRyb3BzIHNpbGVudGx5IOKAlCBhbiB1bnJlY29nbmlzZWQgZXZlbnQgdHlwZSBpc1xuICogdXN1YWxseSBhIHN1YnNjcmlwdGlvbiBjb25maWd1cmVkIHdpZGVyIHRoYW4gdGhlIGhhbmRsZXIsIGFuZCBhIGNvdW50IGlzIGhvd1xuICogdGhhdCBiZWNvbWVzIHZpc2libGUgaW5zdGVhZCBvZiBiZWluZyBpbmZlcnJlZCBmcm9tIG5vdGhpbmcgaGFwcGVuaW5nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VPbkNhbGxXZWJob29rKGJvZHk6IHVua25vd24pOiBPbkNhbGxXZWJob29rRXZlbnQgfCBudWxsIHtcbiAgY29uc3QgZXZlbnQgPSAoYm9keSBhcyB7IGV2ZW50PzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfSk/LmV2ZW50O1xuICBpZiAoIWV2ZW50IHx8IHR5cGVvZiBldmVudC5ldmVudF90eXBlICE9PSAnc3RyaW5nJykgcmV0dXJuIG51bGw7XG4gIGNvbnN0IGRhdGEgPSAoZXZlbnQuZGF0YSA/PyB7fSkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGNvbnN0IGFnZW5jeSA9IChldmVudC5hZ2VudCA/PyB7fSkgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIHJldHVybiB7XG4gICAgZXZlbnRUeXBlOiBldmVudC5ldmVudF90eXBlLFxuICAgIGluY2lkZW50SWQ6IHR5cGVvZiBkYXRhLmlkID09PSAnc3RyaW5nJyA/IGRhdGEuaWQgOiB1bmRlZmluZWQsXG4gICAgLy8gUGFnZXJEdXR5IGVjaG9lcyB0aGUgdHJpZ2dlcidzIGRlZHVwX2tleSBvbiB0aGUgaW5jaWRlbnQsIHdoaWNoIGlzIHRoZVxuICAgIC8vIG9ubHkgbGluayBiYWNrIHRvIFdISUNIIGNhc2Ugb3IgYWxlcnQgdGhpcyB3YXMg4oCUIHRoZWlyIGluY2lkZW50IGlkIGlzXG4gICAgLy8gdGhlaXJzIGFuZCBtZWFucyBub3RoaW5nIG9uIHRoaXMgc2lkZS5cbiAgICBkZWR1cEtleTogdHlwZW9mIGRhdGEuZGVkdXBfa2V5ID09PSAnc3RyaW5nJyA/IGRhdGEuZGVkdXBfa2V5IDogdW5kZWZpbmVkLFxuICAgIGFnZW50SWQ6IHR5cGVvZiBhZ2VuY3kuaWQgPT09ICdzdHJpbmcnID8gYWdlbmN5LmlkIDogdW5kZWZpbmVkLFxuICAgIGFnZW50U3VtbWFyeTogdHlwZW9mIGFnZW5jeS5zdW1tYXJ5ID09PSAnc3RyaW5nJyA/IGFnZW5jeS5zdW1tYXJ5IDogdW5kZWZpbmVkLFxuICAgIGNyZWF0ZWRBdDogdHlwZW9mIGRhdGEuY3JlYXRlZF9hdCA9PT0gJ3N0cmluZycgPyBkYXRhLmNyZWF0ZWRfYXQgOiB1bmRlZmluZWQsXG4gICAgb2NjdXJyZWRBdDogdHlwZW9mIGV2ZW50Lm9jY3VycmVkX2F0ID09PSAnc3RyaW5nJyA/IGV2ZW50Lm9jY3VycmVkX2F0IDogdW5kZWZpbmVkLFxuICB9O1xufVxuXG4vKipcbiAqIFNlY29uZHMgYmV0d2VlbiB0aGUgaW5jaWRlbnQgYmVpbmcgcmFpc2VkIGFuZCB0aGlzIGV2ZW50LlxuICpcbiAqIFJldHVybnMgYHVuZGVmaW5lZGAgcmF0aGVyIHRoYW4gMCB3aGVuIGVpdGhlciB0aW1lc3RhbXAgaXMgbWlzc2luZyBvciB0aGVcbiAqIGFyaXRobWV0aWMgaXMgbm9uc2Vuc2U6IGEgMCB3b3VsZCBsYW5kIGluIHRoZSBmaXJzdCBoaXN0b2dyYW0gYnVja2V0IGFuZCByZWFkXG4gKiBhcyBhbiBpbnN0YW50IGFja25vd2xlZGdlbWVudCwgd2hpY2ggaXMgdGhlIG1vc3QgZmxhdHRlcmluZyBwb3NzaWJsZSBsaWVcbiAqIGFib3V0IHJlc3BvbnNlIHRpbWUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhY2tMYXRlbmN5U2Vjb25kcyhldmVudDogT25DYWxsV2ViaG9va0V2ZW50KTogbnVtYmVyIHwgdW5kZWZpbmVkIHtcbiAgaWYgKCFldmVudC5jcmVhdGVkQXQgfHwgIWV2ZW50Lm9jY3VycmVkQXQpIHJldHVybiB1bmRlZmluZWQ7XG4gIGNvbnN0IGZyb20gPSBuZXcgRGF0ZShldmVudC5jcmVhdGVkQXQpLmdldFRpbWUoKTtcbiAgY29uc3QgdG8gPSBuZXcgRGF0ZShldmVudC5vY2N1cnJlZEF0KS5nZXRUaW1lKCk7XG4gIGlmICghTnVtYmVyLmlzRmluaXRlKGZyb20pIHx8ICFOdW1iZXIuaXNGaW5pdGUodG8pIHx8IHRvIDwgZnJvbSkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgcmV0dXJuICh0byAtIGZyb20pIC8gMTAwMDtcbn1cbiJdfQ==
@@ -0,0 +1,109 @@
1
+ /** PagerDuty's own severity enum. Ours map straight onto it. */
2
+ export type PagerDutySeverity = 'critical' | 'error' | 'warning' | 'info';
3
+ /** The three follow-up actions a single `dedupKey` moves through. */
4
+ export type PagerDutyAction = 'trigger' | 'acknowledge' | 'resolve';
5
+ export interface PagerDutyEvent {
6
+ /**
7
+ * The key that ties a trigger to its later acknowledge/resolve. Stable and
8
+ * derived, never random: `case:<caseId>` for the per-case sweep,
9
+ * Alertmanager's own group fingerprint for the alert leg — so its grouping and
10
+ * ours cannot fight over the same incident.
11
+ */
12
+ dedupKey: string;
13
+ action: PagerDutyAction;
14
+ /**
15
+ * WHICH leg raised this, as a metric dimension only — never sent to PagerDuty.
16
+ *
17
+ * The two legs have different failure modes and must be separable in one
18
+ * counter: `alertmanager` depends on Prometheus evaluating and Alertmanager
19
+ * delivering, `case` depends on this product's own sweep being alive. Collapsed
20
+ * into one series, a dead sweep is invisible for as long as the fleet alert
21
+ * still fires — which is precisely the window the per-case trigger exists to
22
+ * cover.
23
+ */
24
+ trigger?: 'alertmanager' | 'case';
25
+ /** Required by the API on a `trigger`; ignored on resolve. */
26
+ summary?: string;
27
+ severity?: PagerDutySeverity;
28
+ /** Human-readable origin. Required on a `trigger`. */
29
+ source?: string;
30
+ component?: string;
31
+ /**
32
+ * PagerDuty's grouping dimension. We put the ORG ID here and nothing else —
33
+ * see the note on `customDetails`.
34
+ */
35
+ group?: string;
36
+ class?: string;
37
+ /**
38
+ * Free-form detail. IDS AND TIMESTAMPS ONLY — never case content.
39
+ *
40
+ * This is an authorization boundary, not a style preference. The on-call
41
+ * permission is system-org scoped and grants no tenant access at all
42
+ * (`isSystemOrgId` is a content-owner check, not a privilege gate), so putting
43
+ * a case subject or body here would route tenant content to a third party
44
+ * through a channel that deliberately has no right to read it.
45
+ */
46
+ customDetails?: Record<string, string | number | boolean | null>;
47
+ }
48
+ export interface PagerDutyResult {
49
+ ok: boolean;
50
+ /** HTTP status when the request completed. */
51
+ status?: number;
52
+ /** Why it failed, for the log line — never surfaced to a tenant. */
53
+ error?: string;
54
+ /** True when paging is configured off, so a caller can tell "off" from "broke". */
55
+ skipped?: boolean;
56
+ }
57
+ /**
58
+ * Which provider the Events v2 calls go to.
59
+ *
60
+ * `pagerduty` pins the known endpoint. `custom` means "something else that
61
+ * implements this wire format" and REQUIRES `ONCALL_EVENTS_URL` — it does not
62
+ * fall back, because falling back is how a typo'd URL quietly pages the wrong
63
+ * account, or pages nobody while reporting success.
64
+ *
65
+ * WHY THERE IS NO `opsgenie` HERE, although the design notes proposed one:
66
+ * nothing documents Opsgenie accepting the Events v2 shape. Its own alert API
67
+ * takes `message`/`alias`/`priority`, not `summary`/`severity`/`dedup_key`, so
68
+ * an `opsgenie` value would name a provider this client cannot actually talk to
69
+ * — a config option that fails at 3am on the one call that had to work. A
70
+ * genuinely Events-v2-compatible vendor is reachable through `custom` with its
71
+ * endpoint stated, which is honest about who verified the compatibility.
72
+ */
73
+ export type OnCallProvider = 'pagerduty' | 'custom';
74
+ export declare const PAGERDUTY_EVENTS_URL = "https://events.pagerduty.com/v2/enqueue";
75
+ export interface PagerDutyConfig {
76
+ /**
77
+ * Whether paging runs at all. OPT-IN, against this repo's usual opt-out grain
78
+ * (`SUPPORT_ENABLED`, `BILLING_DISCOUNTS_ENABLED`): a feature needing an
79
+ * external credential defaults off here, the way `BILLING_METERING_ENABLED`
80
+ * does, and a pager with no routing key would page nobody while reporting
81
+ * success.
82
+ */
83
+ enabled: boolean;
84
+ /** Per-SERVICE integration key. NOT a REST API key — see docs/oncall-paging.md. */
85
+ routingKey: string;
86
+ provider: OnCallProvider;
87
+ /** Resolved from `provider` + `ONCALL_EVENTS_URL`. Empty means misconfigured. */
88
+ endpoint: string;
89
+ timeoutMs: number;
90
+ }
91
+ /**
92
+ * Resolve the endpoint from the provider and the override.
93
+ *
94
+ * Exported because the resolution has a failure case worth testing directly:
95
+ * `custom` with no URL returns EMPTY, and the caller treats that as
96
+ * unconfigured rather than silently reaching for PagerDuty's endpoint with
97
+ * somebody else's routing key.
98
+ */
99
+ export declare function resolveEventsEndpoint(provider: OnCallProvider, override: string): string;
100
+ export declare function pagerDutyConfig(): PagerDutyConfig;
101
+ /**
102
+ * Send one event. Returns `{ ok: true }` only when PagerDuty ACCEPTED it.
103
+ *
104
+ * A 202 is an acceptance and not a delivery receipt — it says PagerDuty has the
105
+ * event, nothing about whether a phone rang. `oncall_pages_total{outcome}`
106
+ * records what we know, which is this and no more; the only honest evidence of
107
+ * coverage is a human acknowledging.
108
+ */
109
+ export declare function sendPagerDutyEvent(event: PagerDutyEvent, cfg?: PagerDutyConfig): Promise<PagerDutyResult>;
@@ -0,0 +1,186 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * PagerDuty Events API v2 — the only thing in this repo that can wake a human.
5
+ *
6
+ * WHY THIS EXISTS. `SUPPORT_PREMIUM_ENABLED` ships on, and premium sells a first
7
+ * response "within 8 hours, any day" — the one response target in
8
+ * `SUPPORT_RESPONSE_TARGETS` with no business-hours exemption, so its clock runs
9
+ * through nights and weekends. Before this, the only thing watching it was
10
+ * `SupportPremiumFirstResponseAtRisk` routed to a Slack channel, which satisfies
11
+ * the routing and not the obligation: the alert's own description says "if no
12
+ * rota is awake, that is the cause".
13
+ *
14
+ * WHY NO SDK. The whole protocol is one HTTPS POST of JSON to a fixed endpoint.
15
+ * An SDK for that is a dependency for nothing in a repo that digest-pins,
16
+ * OSV-scans and enforces `minimumReleaseAge`.
17
+ *
18
+ * WHAT THE PROVIDER OWNS, AND WHY THAT DECIDED THE DESIGN. `dedupKey` plus
19
+ * `event_action` means PagerDuty holds the incident state, so three things this
20
+ * repo would otherwise have had to build are simply theirs:
21
+ *
22
+ * - DEDUPLICATION. Fifty premium cases breaching together — the normal shape
23
+ * after any outage — is one incident, not fifty pages. Fifty pages and no
24
+ * pages are the same thing.
25
+ * - RESOLUTION. `resolve` on the same key closes it, so a case that finally
26
+ * gets a first response stops paging without a local state machine.
27
+ * - CONTACT METHODS and their verification, the rota, and escalation.
28
+ *
29
+ * So there is no rota in this codebase on purpose. What stays here is the
30
+ * TRIGGER, because the product knows something PagerDuty cannot: a support case
31
+ * carries an exact `response_due_at`, which beats a p90 histogram that can only
32
+ * say the distribution already drifted.
33
+ *
34
+ * NEVER THROWS. Every caller is on an alerting path — a webhook handler or a
35
+ * sweep — and an exception there turns "could not page" into "crashed instead of
36
+ * paging". Failures come back as a typed result and are counted.
37
+ */
38
+ import { envBool, envInt, envStr } from '../utils/env.js';
39
+ import { createLogger } from '../utils/logger.js';
40
+ import { emitCounter } from '../utils/metric-emitter.js';
41
+ import { errorMessage } from '../utils/response.js';
42
+ import { safeFetch } from '../utils/ssrf.js';
43
+ const logger = createLogger('pagerduty');
44
+ export const PAGERDUTY_EVENTS_URL = 'https://events.pagerduty.com/v2/enqueue';
45
+ /**
46
+ * Resolve the endpoint from the provider and the override.
47
+ *
48
+ * Exported because the resolution has a failure case worth testing directly:
49
+ * `custom` with no URL returns EMPTY, and the caller treats that as
50
+ * unconfigured rather than silently reaching for PagerDuty's endpoint with
51
+ * somebody else's routing key.
52
+ */
53
+ export function resolveEventsEndpoint(provider, override) {
54
+ if (override)
55
+ return override;
56
+ return provider === 'pagerduty' ? PAGERDUTY_EVENTS_URL : '';
57
+ }
58
+ /**
59
+ * Whether paging is BOTH asked for and coherent.
60
+ *
61
+ * ON-CALL REQUIRES SUPPORT. `ONCALL_ENABLED` exists to page about a support
62
+ * response target, so with `SUPPORT_ENABLED=false` there is no product to page
63
+ * about — the bundles are inactive, no case can be opened, and the only thing
64
+ * left is a deployment paying per seat for a pager wired to nothing.
65
+ *
66
+ * VALIDATED FOR THE ACTIVE FEATURE ONLY, which is the precedent that matters
67
+ * here: a bundle validator once crashed billing at boot by checking every
68
+ * bundle rather than every ACTIVE one. So this neither throws nor warns when
69
+ * paging is off — it is not a conflict until somebody asks for the thing. When
70
+ * they do, the prerequisite is reported once and paging stays off rather than
71
+ * half-on, because a pager that fires for a product nobody sells is the same
72
+ * failure as a pager with no key: it reports coverage of nothing.
73
+ */
74
+ function onCallCoherentlyEnabled() {
75
+ if (!envBool('ONCALL_ENABLED', false))
76
+ return false;
77
+ if (envBool('SUPPORT_ENABLED', true))
78
+ return true;
79
+ logger.error('ONCALL_ENABLED=true with SUPPORT_ENABLED=false — paging stays OFF', {
80
+ why: 'on-call pages about a support response target; with support off there are no cases and no target',
81
+ });
82
+ return false;
83
+ }
84
+ export function pagerDutyConfig() {
85
+ const provider = envStr('ONCALL_PROVIDER', 'pagerduty') === 'custom' ? 'custom' : 'pagerduty';
86
+ return {
87
+ enabled: onCallCoherentlyEnabled(),
88
+ // Read from the environment rather than a mounted file: unlike the
89
+ // Alertmanager Slack URLs (api_url_file), this key is consumed by OUR
90
+ // process, which already receives its secrets as env from a Secret.
91
+ routingKey: envStr('PAGERDUTY_ROUTING_KEY', ''),
92
+ provider,
93
+ endpoint: resolveEventsEndpoint(provider, envStr('ONCALL_EVENTS_URL', '')),
94
+ timeoutMs: envInt('ONCALL_EVENTS_TIMEOUT_MS', 5000, { min: 1 }),
95
+ };
96
+ }
97
+ /**
98
+ * Send one event. Returns `{ ok: true }` only when PagerDuty ACCEPTED it.
99
+ *
100
+ * A 202 is an acceptance and not a delivery receipt — it says PagerDuty has the
101
+ * event, nothing about whether a phone rang. `oncall_pages_total{outcome}`
102
+ * records what we know, which is this and no more; the only honest evidence of
103
+ * coverage is a human acknowledging.
104
+ */
105
+ export async function sendPagerDutyEvent(event, cfg = pagerDutyConfig()) {
106
+ const labels = { action: event.action, trigger: event.trigger ?? 'alertmanager' };
107
+ // Configured OFF is a SKIP, distinct from a failure. The Alertmanager leg
108
+ // relies on this: its route is always present (alertmanager.yml is asserted
109
+ // byte-identical across targets, so it cannot be toggled per deployment), and
110
+ // this endpoint accepting-and-dropping is what makes the knob work without a
111
+ // second config file and without a stream of notification failures.
112
+ if (!cfg.enabled) {
113
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'disabled' });
114
+ return { ok: true, skipped: true };
115
+ }
116
+ if (!cfg.endpoint) {
117
+ // `ONCALL_PROVIDER=custom` with no `ONCALL_EVENTS_URL`. Counted under the
118
+ // same outcome as a missing key because it is the same class of failure —
119
+ // somebody asked for paging and the client has nowhere to send it — and
120
+ // because quietly reaching for PagerDuty's endpoint instead would send this
121
+ // deployment's routing key to a provider nobody chose.
122
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'unconfigured' });
123
+ logger.error('ONCALL_PROVIDER=custom but ONCALL_EVENTS_URL is empty — nobody will be paged', {
124
+ dedupKey: event.dedupKey,
125
+ });
126
+ return { ok: false, error: 'no events endpoint' };
127
+ }
128
+ if (!cfg.routingKey) {
129
+ // Enabled with no key is the half-configured state the knob exists to
130
+ // prevent, and it is the one failure worth shouting about: somebody asked
131
+ // for paging and is not getting it.
132
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'unconfigured' });
133
+ logger.error('ONCALL_ENABLED is true but PAGERDUTY_ROUTING_KEY is empty — nobody will be paged', {
134
+ dedupKey: event.dedupKey,
135
+ });
136
+ return { ok: false, error: 'no routing key' };
137
+ }
138
+ // `resolve` and `acknowledge` need only the key and the action; a trigger
139
+ // needs summary/severity/source. Built conditionally so a resolve cannot be
140
+ // rejected for a missing field it never required.
141
+ const body = {
142
+ routing_key: cfg.routingKey,
143
+ event_action: event.action,
144
+ dedup_key: event.dedupKey,
145
+ };
146
+ if (event.action === 'trigger') {
147
+ body.payload = {
148
+ summary: event.summary ?? 'Pipeline Builder alert',
149
+ severity: event.severity ?? 'critical',
150
+ source: event.source ?? 'pipeline-builder',
151
+ ...(event.component ? { component: event.component } : {}),
152
+ ...(event.group ? { group: event.group } : {}),
153
+ ...(event.class ? { class: event.class } : {}),
154
+ ...(event.customDetails ? { custom_details: event.customDetails } : {}),
155
+ };
156
+ }
157
+ try {
158
+ const resp = await safeFetch(cfg.endpoint, {
159
+ method: 'POST',
160
+ headers: { 'Content-Type': 'application/json' },
161
+ body: JSON.stringify(body),
162
+ timeoutMs: cfg.timeoutMs,
163
+ });
164
+ // `safeFetch` refuses redirects rather than following them, so a 3xx arrives
165
+ // as `redirected` and is a FAILED send — never a pivot to an unvetted host
166
+ // and never recorded green.
167
+ if (resp.redirected) {
168
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'redirected' });
169
+ logger.error('PagerDuty endpoint redirected — refused', { status: resp.status });
170
+ return { ok: false, status: resp.status, error: 'endpoint redirected (refused)' };
171
+ }
172
+ if (!resp.ok) {
173
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'rejected' });
174
+ logger.error('PagerDuty rejected an event', { status: resp.status, dedupKey: event.dedupKey });
175
+ return { ok: false, status: resp.status, error: `status ${resp.status}` };
176
+ }
177
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'accepted' });
178
+ return { ok: true, status: resp.status };
179
+ }
180
+ catch (err) {
181
+ emitCounter('oncall_pages_total', { ...labels, outcome: 'error' });
182
+ logger.error('Could not reach PagerDuty', { error: errorMessage(err), dedupKey: event.dedupKey });
183
+ return { ok: false, error: errorMessage(err) };
184
+ }
185
+ }
186
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGFnZXJkdXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL3BhZ2VyZHV0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0NHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDcEQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdDLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztBQWdGekMsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcseUNBQXlDLENBQUM7QUFtQjlFOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQUMsUUFBd0IsRUFBRSxRQUFnQjtJQUM5RSxJQUFJLFFBQVE7UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUM5QixPQUFPLFFBQVEsS0FBSyxXQUFXLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDOUQsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILFNBQVMsdUJBQXVCO0lBQzlCLElBQUksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLEVBQUUsS0FBSyxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDcEQsSUFBSSxPQUFPLENBQUMsaUJBQWlCLEVBQUUsSUFBSSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDbEQsTUFBTSxDQUFDLEtBQUssQ0FBQyxtRUFB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Q25FLE1BQU0sQ0FBQyxLQUFLLENBQUMsMkJBQTJCLEVBQUUsRUFBRSxLQUFLLEVBQUUsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUFFLFFBQVEsRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUNsRyxPQUFPLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7SUFDakQsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBQYWdlckR1dHkgRXZlbnRzIEFQSSB2MiDigJQgdGhlIG9ubHkgdGhpbmcgaW4gdGhpcyByZXBvIHRoYXQgY2FuIHdha2UgYSBodW1hbi5cbiAqXG4gKiBXSFkgVEhJUyBFWElTVFMuIGBTVVBQT1JUX1BSRU1JVU1fRU5BQkxFRGAgc2hpcHMgb24sIGFuZCBwcmVtaXVtIHNlbGxzIGEgZmlyc3RcbiAqIHJlc3BvbnNlIFwid2l0aGluIDggaG91cnMsIGFueSBkYXlcIiDigJQgdGhlIG9uZSByZXNwb25zZSB0YXJnZXQgaW5cbiAqIGBTVVBQT1JUX1JFU1BPTlNFX1RBUkdFVFNgIHdpdGggbm8gYnVzaW5lc3MtaG91cnMgZXhlbXB0aW9uLCBzbyBpdHMgY2xvY2sgcnVuc1xuICogdGhyb3VnaCBuaWdodHMgYW5kIHdlZWtlbmRzLiBCZWZvcmUgdGhpcywgdGhlIG9ubHkgdGhpbmcgd2F0Y2hpbmcgaXQgd2FzXG4gKiBgU3VwcG9ydFByZW1pdW1GaXJzdFJlc3BvbnNlQXRSaXNrYCByb3V0ZWQgdG8gYSBTbGFjayBjaGFubmVsLCB3aGljaCBzYXRpc2ZpZXNcbiAqIHRoZSByb3V0aW5nIGFuZCBub3QgdGhlIG9ibGlnYXRpb246IHRoZSBhbGVydCdzIG93biBkZXNjcmlwdGlvbiBzYXlzIFwiaWYgbm9cbiAqIHJvdGEgaXMgYXdha2UsIHRoYXQgaXMgdGhlIGNhdXNlXCIuXG4gKlxuICogV0hZIE5PIFNESy4gVGhlIHdob2xlIHByb3RvY29sIGlzIG9uZSBIVFRQUyBQT1NUIG9mIEpTT04gdG8gYSBmaXhlZCBlbmRwb2ludC5cbiAqIEFuIFNESyBmb3IgdGhhdCBpcyBhIGRlcGVuZGVuY3kgZm9yIG5vdGhpbmcgaW4gYSByZXBvIHRoYXQgZGlnZXN0LXBpbnMsXG4gKiBPU1Ytc2NhbnMgYW5kIGVuZm9yY2VzIGBtaW5pbXVtUmVsZWFzZUFnZWAuXG4gKlxuICogV0hBVCBUSEUgUFJPVklERVIgT1dOUywgQU5EIFdIWSBUSEFUIERFQ0lERUQgVEhFIERFU0lHTi4gYGRlZHVwS2V5YCBwbHVzXG4gKiBgZXZlbnRfYWN0aW9uYCBtZWFucyBQYWdlckR1dHkgaG9sZHMgdGhlIGluY2lkZW50IHN0YXRlLCBzbyB0aHJlZSB0aGluZ3MgdGhpc1xuICogcmVwbyB3b3VsZCBvdGhlcndpc2UgaGF2ZSBoYWQgdG8gYnVpbGQgYXJlIHNpbXBseSB0aGVpcnM6XG4gKlxuICogIC0gREVEVVBMSUNBVElPTi4gRmlmdHkgcHJlbWl1bSBjYXNlcyBicmVhY2hpbmcgdG9nZXRoZXIg4oCUIHRoZSBub3JtYWwgc2hhcGVcbiAqICAgIGFmdGVyIGFueSBvdXRhZ2Ug4oCUIGlzIG9uZSBpbmNpZGVudCwgbm90IGZpZnR5IHBhZ2VzLiBGaWZ0eSBwYWdlcyBhbmQgbm9cbiAqICAgIHBhZ2VzIGFyZSB0aGUgc2FtZSB0aGluZy5cbiAqICAtIFJFU09MVVRJT04uIGByZXNvbHZlYCBvbiB0aGUgc2FtZSBrZXkgY2xvc2VzIGl0LCBzbyBhIGNhc2UgdGhhdCBmaW5hbGx5XG4gKiAgICBnZXRzIGEgZmlyc3QgcmVzcG9uc2Ugc3RvcHMgcGFnaW5nIHdpdGhvdXQgYSBsb2NhbCBzdGF0ZSBtYWNoaW5lLlxuICogIC0gQ09OVEFDVCBNRVRIT0RTIGFuZCB0aGVpciB2ZXJpZmljYXRpb24sIHRoZSByb3RhLCBhbmQgZXNjYWxhdGlvbi5cbiAqXG4gKiBTbyB0aGVyZSBpcyBubyByb3RhIGluIHRoaXMgY29kZWJhc2Ugb24gcHVycG9zZS4gV2hhdCBzdGF5cyBoZXJlIGlzIHRoZVxuICogVFJJR0dFUiwgYmVjYXVzZSB0aGUgcHJvZHVjdCBrbm93cyBzb21ldGhpbmcgUGFnZXJEdXR5IGNhbm5vdDogYSBzdXBwb3J0IGNhc2VcbiAqIGNhcnJpZXMgYW4gZXhhY3QgYHJlc3BvbnNlX2R1ZV9hdGAsIHdoaWNoIGJlYXRzIGEgcDkwIGhpc3RvZ3JhbSB0aGF0IGNhbiBvbmx5XG4gKiBzYXkgdGhlIGRpc3RyaWJ1dGlvbiBhbHJlYWR5IGRyaWZ0ZWQuXG4gKlxuICogTkVWRVIgVEhST1dTLiBFdmVyeSBjYWxsZXIgaXMgb24gYW4gYWxlcnRpbmcgcGF0aCDigJQgYSB3ZWJob29rIGhhbmRsZXIgb3IgYVxuICogc3dlZXAg4oCUIGFuZCBhbiBleGNlcHRpb24gdGhlcmUgdHVybnMgXCJjb3VsZCBub3QgcGFnZVwiIGludG8gXCJjcmFzaGVkIGluc3RlYWQgb2ZcbiAqIHBhZ2luZ1wiLiBGYWlsdXJlcyBjb21lIGJhY2sgYXMgYSB0eXBlZCByZXN1bHQgYW5kIGFyZSBjb3VudGVkLlxuICovXG5cbmltcG9ydCB7IGVudkJvb2wsIGVudkludCwgZW52U3RyIH0gZnJvbSAnLi4vdXRpbHMvZW52LmpzJztcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBlbWl0Q291bnRlciB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcbmltcG9ydCB7IHNhZmVGZXRjaCB9IGZyb20gJy4uL3V0aWxzL3NzcmYuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3BhZ2VyZHV0eScpO1xuXG4vKiogUGFnZXJEdXR5J3Mgb3duIHNldmVyaXR5IGVudW0uIE91cnMgbWFwIHN0cmFpZ2h0IG9udG8gaXQuICovXG5leHBvcnQgdHlwZSBQYWdlckR1dHlTZXZlcml0eSA9ICdjcml0aWNhbCcgfCAnZXJyb3InIHwgJ3dhcm5pbmcnIHwgJ2luZm8nO1xuXG4vKiogVGhlIHRocmVlIGZvbGxvdy11cCBhY3Rpb25zIGEgc2luZ2xlIGBkZWR1cEtleWAgbW92ZXMgdGhyb3VnaC4gKi9cbmV4cG9ydCB0eXBlIFBhZ2VyRHV0eUFjdGlvbiA9ICd0cmlnZ2VyJyB8ICdhY2tub3dsZWRnZScgfCAncmVzb2x2ZSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGFnZXJEdXR5RXZlbnQge1xuICAvKipcbiAgICogVGhlIGtleSB0aGF0IHRpZXMgYSB0cmlnZ2VyIHRvIGl0cyBsYXRlciBhY2tub3dsZWRnZS9yZXNvbHZlLiBTdGFibGUgYW5kXG4gICAqIGRlcml2ZWQsIG5ldmVyIHJhbmRvbTogYGNhc2U6PGNhc2VJZD5gIGZvciB0aGUgcGVyLWNhc2Ugc3dlZXAsXG4gICAqIEFsZXJ0bWFuYWdlcidzIG93biBncm91cCBmaW5nZXJwcmludCBmb3IgdGhlIGFsZXJ0IGxlZyDigJQgc28gaXRzIGdyb3VwaW5nIGFuZFxuICAgKiBvdXJzIGNhbm5vdCBmaWdodCBvdmVyIHRoZSBzYW1lIGluY2lkZW50LlxuICAgKi9cbiAgZGVkdXBLZXk6IHN0cmluZztcbiAgYWN0aW9uOiBQYWdlckR1dHlBY3Rpb247XG4gIC8qKlxuICAgKiBXSElDSCBsZWcgcmFpc2VkIHRoaXMsIGFzIGEgbWV0cmljIGRpbWVuc2lvbiBvbmx5IOKAlCBuZXZlciBzZW50IHRvIFBhZ2VyRHV0eS5cbiAgICpcbiAgICogVGhlIHR3byBsZWdzIGhhdmUgZGlmZmVyZW50IGZhaWx1cmUgbW9kZXMgYW5kIG11c3QgYmUgc2VwYXJhYmxlIGluIG9uZVxuICAgKiBjb3VudGVyOiBgYWxlcnRtYW5hZ2VyYCBkZXBlbmRzIG9uIFByb21ldGhldXMgZXZhbHVhdGluZyBhbmQgQWxlcnRtYW5hZ2VyXG4gICAqIGRlbGl2ZXJpbmcsIGBjYXNlYCBkZXBlbmRzIG9uIHRoaXMgcHJvZHVjdCdzIG93biBzd2VlcCBiZWluZyBhbGl2ZS4gQ29sbGFwc2VkXG4gICAqIGludG8gb25lIHNlcmllcywgYSBkZWFkIHN3ZWVwIGlzIGludmlzaWJsZSBmb3IgYXMgbG9uZyBhcyB0aGUgZmxlZXQgYWxlcnRcbiAgICogc3RpbGwgZmlyZXMg4oCUIHdoaWNoIGlzIHByZWNpc2VseSB0aGUgd2luZG93IHRoZSBwZXItY2FzZSB0cmlnZ2VyIGV4aXN0cyB0b1xuICAgKiBjb3Zlci5cbiAgICovXG4gIHRyaWdnZXI/OiAnYWxlcnRtYW5hZ2VyJyB8ICdjYXNlJztcbiAgLyoqIFJlcXVpcmVkIGJ5IHRoZSBBUEkgb24gYSBgdHJpZ2dlcmA7IGlnbm9yZWQgb24gcmVzb2x2ZS4gKi9cbiAgc3VtbWFyeT86IHN0cmluZztcbiAgc2V2ZXJpdHk/OiBQYWdlckR1dHlTZXZlcml0eTtcbiAgLyoqIEh1bWFuLXJlYWRhYmxlIG9yaWdpbi4gUmVxdWlyZWQgb24gYSBgdHJpZ2dlcmAuICovXG4gIHNvdXJjZT86IHN0cmluZztcbiAgY29tcG9uZW50Pzogc3RyaW5nO1xuICAvKipcbiAgICogUGFnZXJEdXR5J3MgZ3JvdXBpbmcgZGltZW5zaW9uLiBXZSBwdXQgdGhlIE9SRyBJRCBoZXJlIGFuZCBub3RoaW5nIGVsc2Ug4oCUXG4gICAqIHNlZSB0aGUgbm90ZSBvbiBgY3VzdG9tRGV0YWlsc2AuXG4gICAqL1xuICBncm91cD86IHN0cmluZztcbiAgY2xhc3M/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBGcmVlLWZvcm0gZGV0YWlsLiBJRFMgQU5EIFRJTUVTVEFNUFMgT05MWSDigJQgbmV2ZXIgY2FzZSBjb250ZW50LlxuICAgKlxuICAgKiBUaGlzIGlzIGFuIGF1dGhvcml6YXRpb24gYm91bmRhcnksIG5vdCBhIHN0eWxlIHByZWZlcmVuY2UuIFRoZSBvbi1jYWxsXG4gICAqIHBlcm1pc3Npb24gaXMgc3lzdGVtLW9yZyBzY29wZWQgYW5kIGdyYW50cyBubyB0ZW5hbnQgYWNjZXNzIGF0IGFsbFxuICAgKiAoYGlzU3lzdGVtT3JnSWRgIGlzIGEgY29udGVudC1vd25lciBjaGVjaywgbm90IGEgcHJpdmlsZWdlIGdhdGUpLCBzbyBwdXR0aW5nXG4gICAqIGEgY2FzZSBzdWJqZWN0IG9yIGJvZHkgaGVyZSB3b3VsZCByb3V0ZSB0ZW5hbnQgY29udGVudCB0byBhIHRoaXJkIHBhcnR5XG4gICAqIHRocm91Z2ggYSBjaGFubmVsIHRoYXQgZGVsaWJlcmF0ZWx5IGhhcyBubyByaWdodCB0byByZWFkIGl0LlxuICAgKi9cbiAgY3VzdG9tRGV0YWlscz86IFJlY29yZDxzdHJpbmcsIHN0cmluZyB8IG51bWJlciB8IGJvb2xlYW4gfCBudWxsPjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBQYWdlckR1dHlSZXN1bHQge1xuICBvazogYm9vbGVhbjtcbiAgLyoqIEhUVFAgc3RhdHVzIHdoZW4gdGhlIHJlcXVlc3QgY29tcGxldGVkLiAqL1xuICBzdGF0dXM/OiBudW1iZXI7XG4gIC8qKiBXaHkgaXQgZmFpbGVkLCBmb3IgdGhlIGxvZyBsaW5lIOKAlCBuZXZlciBzdXJmYWNlZCB0byBhIHRlbmFudC4gKi9cbiAgZXJyb3I/OiBzdHJpbmc7XG4gIC8qKiBUcnVlIHdoZW4gcGFnaW5nIGlzIGNvbmZpZ3VyZWQgb2ZmLCBzbyBhIGNhbGxlciBjYW4gdGVsbCBcIm9mZlwiIGZyb20gXCJicm9rZVwiLiAqL1xuICBza2lwcGVkPzogYm9vbGVhbjtcbn1cblxuLyoqXG4gKiBXaGljaCBwcm92aWRlciB0aGUgRXZlbnRzIHYyIGNhbGxzIGdvIHRvLlxuICpcbiAqIGBwYWdlcmR1dHlgIHBpbnMgdGhlIGtub3duIGVuZHBvaW50LiBgY3VzdG9tYCBtZWFucyBcInNvbWV0aGluZyBlbHNlIHRoYXRcbiAqIGltcGxlbWVudHMgdGhpcyB3aXJlIGZvcm1hdFwiIGFuZCBSRVFVSVJFUyBgT05DQUxMX0VWRU5UU19VUkxgIOKAlCBpdCBkb2VzIG5vdFxuICogZmFsbCBiYWNrLCBiZWNhdXNlIGZhbGxpbmcgYmFjayBpcyBob3cgYSB0eXBvJ2QgVVJMIHF1aWV0bHkgcGFnZXMgdGhlIHdyb25nXG4gKiBhY2NvdW50LCBvciBwYWdlcyBub2JvZHkgd2hpbGUgcmVwb3J0aW5nIHN1Y2Nlc3MuXG4gKlxuICogV0hZIFRIRVJFIElTIE5PIGBvcHNnZW5pZWAgSEVSRSwgYWx0aG91Z2ggdGhlIGRlc2lnbiBub3RlcyBwcm9wb3NlZCBvbmU6XG4gKiBub3RoaW5nIGRvY3VtZW50cyBPcHNnZW5pZSBhY2NlcHRpbmcgdGhlIEV2ZW50cyB2MiBzaGFwZS4gSXRzIG93biBhbGVydCBBUElcbiAqIHRha2VzIGBtZXNzYWdlYC9gYWxpYXNgL2Bwcmlvcml0eWAsIG5vdCBgc3VtbWFyeWAvYHNldmVyaXR5YC9gZGVkdXBfa2V5YCwgc29cbiAqIGFuIGBvcHNnZW5pZWAgdmFsdWUgd291bGQgbmFtZSBhIHByb3ZpZGVyIHRoaXMgY2xpZW50IGNhbm5vdCBhY3R1YWxseSB0YWxrIHRvXG4gKiDigJQgYSBjb25maWcgb3B0aW9uIHRoYXQgZmFpbHMgYXQgM2FtIG9uIHRoZSBvbmUgY2FsbCB0aGF0IGhhZCB0byB3b3JrLiBBXG4gKiBnZW51aW5lbHkgRXZlbnRzLXYyLWNvbXBhdGlibGUgdmVuZG9yIGlzIHJlYWNoYWJsZSB0aHJvdWdoIGBjdXN0b21gIHdpdGggaXRzXG4gKiBlbmRwb2ludCBzdGF0ZWQsIHdoaWNoIGlzIGhvbmVzdCBhYm91dCB3aG8gdmVyaWZpZWQgdGhlIGNvbXBhdGliaWxpdHkuXG4gKi9cbmV4cG9ydCB0eXBlIE9uQ2FsbFByb3ZpZGVyID0gJ3BhZ2VyZHV0eScgfCAnY3VzdG9tJztcblxuZXhwb3J0IGNvbnN0IFBBR0VSRFVUWV9FVkVOVFNfVVJMID0gJ2h0dHBzOi8vZXZlbnRzLnBhZ2VyZHV0eS5jb20vdjIvZW5xdWV1ZSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUGFnZXJEdXR5Q29uZmlnIHtcbiAgLyoqXG4gICAqIFdoZXRoZXIgcGFnaW5nIHJ1bnMgYXQgYWxsLiBPUFQtSU4sIGFnYWluc3QgdGhpcyByZXBvJ3MgdXN1YWwgb3B0LW91dCBncmFpblxuICAgKiAoYFNVUFBPUlRfRU5BQkxFRGAsIGBCSUxMSU5HX0RJU0NPVU5UU19FTkFCTEVEYCk6IGEgZmVhdHVyZSBuZWVkaW5nIGFuXG4gICAqIGV4dGVybmFsIGNyZWRlbnRpYWwgZGVmYXVsdHMgb2ZmIGhlcmUsIHRoZSB3YXkgYEJJTExJTkdfTUVURVJJTkdfRU5BQkxFRGBcbiAgICogZG9lcywgYW5kIGEgcGFnZXIgd2l0aCBubyByb3V0aW5nIGtleSB3b3VsZCBwYWdlIG5vYm9keSB3aGlsZSByZXBvcnRpbmdcbiAgICogc3VjY2Vzcy5cbiAgICovXG4gIGVuYWJsZWQ6IGJvb2xlYW47XG4gIC8qKiBQZXItU0VSVklDRSBpbnRlZ3JhdGlvbiBrZXkuIE5PVCBhIFJFU1QgQVBJIGtleSDigJQgc2VlIGRvY3Mvb25jYWxsLXBhZ2luZy5tZC4gKi9cbiAgcm91dGluZ0tleTogc3RyaW5nO1xuICBwcm92aWRlcjogT25DYWxsUHJvdmlkZXI7XG4gIC8qKiBSZXNvbHZlZCBmcm9tIGBwcm92aWRlcmAgKyBgT05DQUxMX0VWRU5UU19VUkxgLiBFbXB0eSBtZWFucyBtaXNjb25maWd1cmVkLiAqL1xuICBlbmRwb2ludDogc3RyaW5nO1xuICB0aW1lb3V0TXM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIHRoZSBlbmRwb2ludCBmcm9tIHRoZSBwcm92aWRlciBhbmQgdGhlIG92ZXJyaWRlLlxuICpcbiAqIEV4cG9ydGVkIGJlY2F1c2UgdGhlIHJlc29sdXRpb24gaGFzIGEgZmFpbHVyZSBjYXNlIHdvcnRoIHRlc3RpbmcgZGlyZWN0bHk6XG4gKiBgY3VzdG9tYCB3aXRoIG5vIFVSTCByZXR1cm5zIEVNUFRZLCBhbmQgdGhlIGNhbGxlciB0cmVhdHMgdGhhdCBhc1xuICogdW5jb25maWd1cmVkIHJhdGhlciB0aGFuIHNpbGVudGx5IHJlYWNoaW5nIGZvciBQYWdlckR1dHkncyBlbmRwb2ludCB3aXRoXG4gKiBzb21lYm9keSBlbHNlJ3Mgcm91dGluZyBrZXkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRXZlbnRzRW5kcG9pbnQocHJvdmlkZXI6IE9uQ2FsbFByb3ZpZGVyLCBvdmVycmlkZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgaWYgKG92ZXJyaWRlKSByZXR1cm4gb3ZlcnJpZGU7XG4gIHJldHVybiBwcm92aWRlciA9PT0gJ3BhZ2VyZHV0eScgPyBQQUdFUkRVVFlfRVZFTlRTX1VSTCA6ICcnO1xufVxuXG4vKipcbiAqIFdoZXRoZXIgcGFnaW5nIGlzIEJPVEggYXNrZWQgZm9yIGFuZCBjb2hlcmVudC5cbiAqXG4gKiBPTi1DQUxMIFJFUVVJUkVTIFNVUFBPUlQuIGBPTkNBTExfRU5BQkxFRGAgZXhpc3RzIHRvIHBhZ2UgYWJvdXQgYSBzdXBwb3J0XG4gKiByZXNwb25zZSB0YXJnZXQsIHNvIHdpdGggYFNVUFBPUlRfRU5BQkxFRD1mYWxzZWAgdGhlcmUgaXMgbm8gcHJvZHVjdCB0byBwYWdlXG4gKiBhYm91dCDigJQgdGhlIGJ1bmRsZXMgYXJlIGluYWN0aXZlLCBubyBjYXNlIGNhbiBiZSBvcGVuZWQsIGFuZCB0aGUgb25seSB0aGluZ1xuICogbGVmdCBpcyBhIGRlcGxveW1lbnQgcGF5aW5nIHBlciBzZWF0IGZvciBhIHBhZ2VyIHdpcmVkIHRvIG5vdGhpbmcuXG4gKlxuICogVkFMSURBVEVEIEZPUiBUSEUgQUNUSVZFIEZFQVRVUkUgT05MWSwgd2hpY2ggaXMgdGhlIHByZWNlZGVudCB0aGF0IG1hdHRlcnNcbiAqIGhlcmU6IGEgYnVuZGxlIHZhbGlkYXRvciBvbmNlIGNyYXNoZWQgYmlsbGluZyBhdCBib290IGJ5IGNoZWNraW5nIGV2ZXJ5XG4gKiBidW5kbGUgcmF0aGVyIHRoYW4gZXZlcnkgQUNUSVZFIG9uZS4gU28gdGhpcyBuZWl0aGVyIHRocm93cyBub3Igd2FybnMgd2hlblxuICogcGFnaW5nIGlzIG9mZiDigJQgaXQgaXMgbm90IGEgY29uZmxpY3QgdW50aWwgc29tZWJvZHkgYXNrcyBmb3IgdGhlIHRoaW5nLiBXaGVuXG4gKiB0aGV5IGRvLCB0aGUgcHJlcmVxdWlzaXRlIGlzIHJlcG9ydGVkIG9uY2UgYW5kIHBhZ2luZyBzdGF5cyBvZmYgcmF0aGVyIHRoYW5cbiAqIGhhbGYtb24sIGJlY2F1c2UgYSBwYWdlciB0aGF0IGZpcmVzIGZvciBhIHByb2R1Y3Qgbm9ib2R5IHNlbGxzIGlzIHRoZSBzYW1lXG4gKiBmYWlsdXJlIGFzIGEgcGFnZXIgd2l0aCBubyBrZXk6IGl0IHJlcG9ydHMgY292ZXJhZ2Ugb2Ygbm90aGluZy5cbiAqL1xuZnVuY3Rpb24gb25DYWxsQ29oZXJlbnRseUVuYWJsZWQoKTogYm9vbGVhbiB7XG4gIGlmICghZW52Qm9vbCgnT05DQUxMX0VOQUJMRUQnLCBmYWxzZSkpIHJldHVybiBmYWxzZTtcbiAgaWYgKGVudkJvb2woJ1NVUFBPUlRfRU5BQkxFRCcsIHRydWUpKSByZXR1cm4gdHJ1ZTtcbiAgbG9nZ2VyLmVycm9yKCdPTkNBTExfRU5BQkxFRD10cnVlIHdpdGggU1VQUE9SVF9FTkFCTEVEPWZhbHNlIOKAlCBwYWdpbmcgc3RheXMgT0ZGJywge1xuICAgIHdoeTogJ29uLWNhbGwgcGFnZXMgYWJvdXQgYSBzdXBwb3J0IHJlc3BvbnNlIHRhcmdldDsgd2l0aCBzdXBwb3J0IG9mZiB0aGVyZSBhcmUgbm8gY2FzZXMgYW5kIG5vIHRhcmdldCcsXG4gIH0pO1xuICByZXR1cm4gZmFsc2U7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBwYWdlckR1dHlDb25maWcoKTogUGFnZXJEdXR5Q29uZmlnIHtcbiAgY29uc3QgcHJvdmlkZXIgPSBlbnZTdHIoJ09OQ0FMTF9QUk9WSURFUicsICdwYWdlcmR1dHknKSA9PT0gJ2N1c3RvbScgPyAnY3VzdG9tJyA6ICdwYWdlcmR1dHknO1xuICByZXR1cm4ge1xuICAgIGVuYWJsZWQ6IG9uQ2FsbENvaGVyZW50bHlFbmFibGVkKCksXG4gICAgLy8gUmVhZCBmcm9tIHRoZSBlbnZpcm9ubWVudCByYXRoZXIgdGhhbiBhIG1vdW50ZWQgZmlsZTogdW5saWtlIHRoZVxuICAgIC8vIEFsZXJ0bWFuYWdlciBTbGFjayBVUkxzIChhcGlfdXJsX2ZpbGUpLCB0aGlzIGtleSBpcyBjb25zdW1lZCBieSBPVVJcbiAgICAvLyBwcm9jZXNzLCB3aGljaCBhbHJlYWR5IHJlY2VpdmVzIGl0cyBzZWNyZXRzIGFzIGVudiBmcm9tIGEgU2VjcmV0LlxuICAgIHJvdXRpbmdLZXk6IGVudlN0cignUEFHRVJEVVRZX1JPVVRJTkdfS0VZJywgJycpLFxuICAgIHByb3ZpZGVyLFxuICAgIGVuZHBvaW50OiByZXNvbHZlRXZlbnRzRW5kcG9pbnQocHJvdmlkZXIsIGVudlN0cignT05DQUxMX0VWRU5UU19VUkwnLCAnJykpLFxuICAgIHRpbWVvdXRNczogZW52SW50KCdPTkNBTExfRVZFTlRTX1RJTUVPVVRfTVMnLCA1MDAwLCB7IG1pbjogMSB9KSxcbiAgfTtcbn1cblxuLyoqXG4gKiBTZW5kIG9uZSBldmVudC4gUmV0dXJucyBgeyBvazogdHJ1ZSB9YCBvbmx5IHdoZW4gUGFnZXJEdXR5IEFDQ0VQVEVEIGl0LlxuICpcbiAqIEEgMjAyIGlzIGFuIGFjY2VwdGFuY2UgYW5kIG5vdCBhIGRlbGl2ZXJ5IHJlY2VpcHQg4oCUIGl0IHNheXMgUGFnZXJEdXR5IGhhcyB0aGVcbiAqIGV2ZW50LCBub3RoaW5nIGFib3V0IHdoZXRoZXIgYSBwaG9uZSByYW5nLiBgb25jYWxsX3BhZ2VzX3RvdGFse291dGNvbWV9YFxuICogcmVjb3JkcyB3aGF0IHdlIGtub3csIHdoaWNoIGlzIHRoaXMgYW5kIG5vIG1vcmU7IHRoZSBvbmx5IGhvbmVzdCBldmlkZW5jZSBvZlxuICogY292ZXJhZ2UgaXMgYSBodW1hbiBhY2tub3dsZWRnaW5nLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VuZFBhZ2VyRHV0eUV2ZW50KFxuICBldmVudDogUGFnZXJEdXR5RXZlbnQsXG4gIGNmZzogUGFnZXJEdXR5Q29uZmlnID0gcGFnZXJEdXR5Q29uZmlnKCksXG4pOiBQcm9taXNlPFBhZ2VyRHV0eVJlc3VsdD4ge1xuICBjb25zdCBsYWJlbHMgPSB7IGFjdGlvbjogZXZlbnQuYWN0aW9uLCB0cmlnZ2VyOiBldmVudC50cmlnZ2VyID8/ICdhbGVydG1hbmFnZXInIH07XG5cbiAgLy8gQ29uZmlndXJlZCBPRkYgaXMgYSBTS0lQLCBkaXN0aW5jdCBmcm9tIGEgZmFpbHVyZS4gVGhlIEFsZXJ0bWFuYWdlciBsZWdcbiAgLy8gcmVsaWVzIG9uIHRoaXM6IGl0cyByb3V0ZSBpcyBhbHdheXMgcHJlc2VudCAoYWxlcnRtYW5hZ2VyLnltbCBpcyBhc3NlcnRlZFxuICAvLyBieXRlLWlkZW50aWNhbCBhY3Jvc3MgdGFyZ2V0cywgc28gaXQgY2Fubm90IGJlIHRvZ2dsZWQgcGVyIGRlcGxveW1lbnQpLCBhbmRcbiAgLy8gdGhpcyBlbmRwb2ludCBhY2NlcHRpbmctYW5kLWRyb3BwaW5nIGlzIHdoYXQgbWFrZXMgdGhlIGtub2Igd29yayB3aXRob3V0IGFcbiAgLy8gc2Vjb25kIGNvbmZpZyBmaWxlIGFuZCB3aXRob3V0IGEgc3RyZWFtIG9mIG5vdGlmaWNhdGlvbiBmYWlsdXJlcy5cbiAgaWYgKCFjZmcuZW5hYmxlZCkge1xuICAgIGVtaXRDb3VudGVyKCdvbmNhbGxfcGFnZXNfdG90YWwnLCB7IC4uLmxhYmVscywgb3V0Y29tZTogJ2Rpc2FibGVkJyB9KTtcbiAgICByZXR1cm4geyBvazogdHJ1ZSwgc2tpcHBlZDogdHJ1ZSB9O1xuICB9XG4gIGlmICghY2ZnLmVuZHBvaW50KSB7XG4gICAgLy8gYE9OQ0FMTF9QUk9WSURFUj1jdXN0b21gIHdpdGggbm8gYE9OQ0FMTF9FVkVOVFNfVVJMYC4gQ291bnRlZCB1bmRlciB0aGVcbiAgICAvLyBzYW1lIG91dGNvbWUgYXMgYSBtaXNzaW5nIGtleSBiZWNhdXNlIGl0IGlzIHRoZSBzYW1lIGNsYXNzIG9mIGZhaWx1cmUg4oCUXG4gICAgLy8gc29tZWJvZHkgYXNrZWQgZm9yIHBhZ2luZyBhbmQgdGhlIGNsaWVudCBoYXMgbm93aGVyZSB0byBzZW5kIGl0IOKAlCBhbmRcbiAgICAvLyBiZWNhdXNlIHF1aWV0bHkgcmVhY2hpbmcgZm9yIFBhZ2VyRHV0eSdzIGVuZHBvaW50IGluc3RlYWQgd291bGQgc2VuZCB0aGlzXG4gICAgLy8gZGVwbG95bWVudCdzIHJvdXRpbmcga2V5IHRvIGEgcHJvdmlkZXIgbm9ib2R5IGNob3NlLlxuICAgIGVtaXRDb3VudGVyKCdvbmNhbGxfcGFnZXNfdG90YWwnLCB7IC4uLmxhYmVscywgb3V0Y29tZTogJ3VuY29uZmlndXJlZCcgfSk7XG4gICAgbG9nZ2VyLmVycm9yKCdPTkNBTExfUFJPVklERVI9Y3VzdG9tIGJ1dCBPTkNBTExfRVZFTlRTX1VSTCBpcyBlbXB0eSDigJQgbm9ib2R5IHdpbGwgYmUgcGFnZWQnLCB7XG4gICAgICBkZWR1cEtleTogZXZlbnQuZGVkdXBLZXksXG4gICAgfSk7XG4gICAgcmV0dXJuIHsgb2s6IGZhbHNlLCBlcnJvcjogJ25vIGV2ZW50cyBlbmRwb2ludCcgfTtcbiAgfVxuICBpZiAoIWNmZy5yb3V0aW5nS2V5KSB7XG4gICAgLy8gRW5hYmxlZCB3aXRoIG5vIGtleSBpcyB0aGUgaGFsZi1jb25maWd1cmVkIHN0YXRlIHRoZSBrbm9iIGV4aXN0cyB0b1xuICAgIC8vIHByZXZlbnQsIGFuZCBpdCBpcyB0aGUgb25lIGZhaWx1cmUgd29ydGggc2hvdXRpbmcgYWJvdXQ6IHNvbWVib2R5IGFza2VkXG4gICAgLy8gZm9yIHBhZ2luZyBhbmQgaXMgbm90IGdldHRpbmcgaXQuXG4gICAgZW1pdENvdW50ZXIoJ29uY2FsbF9wYWdlc190b3RhbCcsIHsgLi4ubGFiZWxzLCBvdXRjb21lOiAndW5jb25maWd1cmVkJyB9KTtcbiAgICBsb2dnZXIuZXJyb3IoJ09OQ0FMTF9FTkFCTEVEIGlzIHRydWUgYnV0IFBBR0VSRFVUWV9ST1VUSU5HX0tFWSBpcyBlbXB0eSDigJQgbm9ib2R5IHdpbGwgYmUgcGFnZWQnLCB7XG4gICAgICBkZWR1cEtleTogZXZlbnQuZGVkdXBLZXksXG4gICAgfSk7XG4gICAgcmV0dXJuIHsgb2s6IGZhbHNlLCBlcnJvcjogJ25vIHJvdXRpbmcga2V5JyB9O1xuICB9XG5cbiAgLy8gYHJlc29sdmVgIGFuZCBgYWNrbm93bGVkZ2VgIG5lZWQgb25seSB0aGUga2V5IGFuZCB0aGUgYWN0aW9uOyBhIHRyaWdnZXJcbiAgLy8gbmVlZHMgc3VtbWFyeS9zZXZlcml0eS9zb3VyY2UuIEJ1aWx0IGNvbmRpdGlvbmFsbHkgc28gYSByZXNvbHZlIGNhbm5vdCBiZVxuICAvLyByZWplY3RlZCBmb3IgYSBtaXNzaW5nIGZpZWxkIGl0IG5ldmVyIHJlcXVpcmVkLlxuICBjb25zdCBib2R5OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiA9IHtcbiAgICByb3V0aW5nX2tleTogY2ZnLnJvdXRpbmdLZXksXG4gICAgZXZlbnRfYWN0aW9uOiBldmVudC5hY3Rpb24sXG4gICAgZGVkdXBfa2V5OiBldmVudC5kZWR1cEtleSxcbiAgfTtcbiAgaWYgKGV2ZW50LmFjdGlvbiA9PT0gJ3RyaWdnZXInKSB7XG4gICAgYm9keS5wYXlsb2FkID0ge1xuICAgICAgc3VtbWFyeTogZXZlbnQuc3VtbWFyeSA/PyAnUGlwZWxpbmUgQnVpbGRlciBhbGVydCcsXG4gICAgICBzZXZlcml0eTogZXZlbnQuc2V2ZXJpdHkgPz8gJ2NyaXRpY2FsJyxcbiAgICAgIHNvdXJjZTogZXZlbnQuc291cmNlID8/ICdwaXBlbGluZS1idWlsZGVyJyxcbiAgICAgIC4uLihldmVudC5jb21wb25lbnQgPyB7IGNvbXBvbmVudDogZXZlbnQuY29tcG9uZW50IH0gOiB7fSksXG4gICAgICAuLi4oZXZlbnQuZ3JvdXAgPyB7IGdyb3VwOiBldmVudC5ncm91cCB9IDoge30pLFxuICAgICAgLi4uKGV2ZW50LmNsYXNzID8geyBjbGFzczogZXZlbnQuY2xhc3MgfSA6IHt9KSxcbiAgICAgIC4uLihldmVudC5jdXN0b21EZXRhaWxzID8geyBjdXN0b21fZGV0YWlsczogZXZlbnQuY3VzdG9tRGV0YWlscyB9IDoge30pLFxuICAgIH07XG4gIH1cblxuICB0cnkge1xuICAgIGNvbnN0IHJlc3AgPSBhd2FpdCBzYWZlRmV0Y2goY2ZnLmVuZHBvaW50LCB7XG4gICAgICBtZXRob2Q6ICdQT1NUJyxcbiAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9LFxuICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoYm9keSksXG4gICAgICB0aW1lb3V0TXM6IGNmZy50aW1lb3V0TXMsXG4gICAgfSk7XG4gICAgLy8gYHNhZmVGZXRjaGAgcmVmdXNlcyByZWRpcmVjdHMgcmF0aGVyIHRoYW4gZm9sbG93aW5nIHRoZW0sIHNvIGEgM3h4IGFycml2ZXNcbiAgICAvLyBhcyBgcmVkaXJlY3RlZGAgYW5kIGlzIGEgRkFJTEVEIHNlbmQg4oCUIG5ldmVyIGEgcGl2b3QgdG8gYW4gdW52ZXR0ZWQgaG9zdFxuICAgIC8vIGFuZCBuZXZlciByZWNvcmRlZCBncmVlbi5cbiAgICBpZiAocmVzcC5yZWRpcmVjdGVkKSB7XG4gICAgICBlbWl0Q291bnRlcignb25jYWxsX3BhZ2VzX3RvdGFsJywgeyAuLi5sYWJlbHMsIG91dGNvbWU6ICdyZWRpcmVjdGVkJyB9KTtcbiAgICAgIGxvZ2dlci5lcnJvcignUGFnZXJEdXR5IGVuZHBvaW50IHJlZGlyZWN0ZWQg4oCUIHJlZnVzZWQnLCB7IHN0YXR1czogcmVzcC5zdGF0dXMgfSk7XG4gICAgICByZXR1cm4geyBvazogZmFsc2UsIHN0YXR1czogcmVzcC5zdGF0dXMsIGVycm9yOiAnZW5kcG9pbnQgcmVkaXJlY3RlZCAocmVmdXNlZCknIH07XG4gICAgfVxuICAgIGlmICghcmVzcC5vaykge1xuICAgICAgZW1pdENvdW50ZXIoJ29uY2FsbF9wYWdlc190b3RhbCcsIHsgLi4ubGFiZWxzLCBvdXRjb21lOiAncmVqZWN0ZWQnIH0pO1xuICAgICAgbG9nZ2VyLmVycm9yKCdQYWdlckR1dHkgcmVqZWN0ZWQgYW4gZXZlbnQnLCB7IHN0YXR1czogcmVzcC5zdGF0dXMsIGRlZHVwS2V5OiBldmVudC5kZWR1cEtleSB9KTtcbiAgICAgIHJldHVybiB7IG9rOiBmYWxzZSwgc3RhdHVzOiByZXNwLnN0YXR1cywgZXJyb3I6IGBzdGF0dXMgJHtyZXNwLnN0YXR1c31gIH07XG4gICAgfVxuICAgIGVtaXRDb3VudGVyKCdvbmNhbGxfcGFnZXNfdG90YWwnLCB7IC4uLmxhYmVscywgb3V0Y29tZTogJ2FjY2VwdGVkJyB9KTtcbiAgICByZXR1cm4geyBvazogdHJ1ZSwgc3RhdHVzOiByZXNwLnN0YXR1cyB9O1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBlbWl0Q291bnRlcignb25jYWxsX3BhZ2VzX3RvdGFsJywgeyAuLi5sYWJlbHMsIG91dGNvbWU6ICdlcnJvcicgfSk7XG4gICAgbG9nZ2VyLmVycm9yKCdDb3VsZCBub3QgcmVhY2ggUGFnZXJEdXR5JywgeyBlcnJvcjogZXJyb3JNZXNzYWdlKGVyciksIGRlZHVwS2V5OiBldmVudC5kZWR1cEtleSB9KTtcbiAgICByZXR1cm4geyBvazogZmFsc2UsIGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyKSB9O1xuICB9XG59XG4iXX0=