@pipeline-builder/pipeline-data 4.1.1 → 4.1.3
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/api/reporting/caches.d.ts +2 -0
- package/lib/api/reporting/caches.js +14 -0
- package/lib/api/reporting/dora-scoring.d.ts +71 -0
- package/lib/api/reporting/dora-scoring.js +126 -0
- package/lib/api/reporting/dora.d.ts +47 -0
- package/lib/api/reporting/dora.js +501 -0
- package/lib/api/reporting/query-scope.d.ts +21 -0
- package/lib/api/reporting/query-scope.js +62 -0
- package/lib/api/reporting/retention.d.ts +44 -0
- package/lib/api/reporting/retention.js +64 -0
- package/lib/api/reporting/sql-helpers.d.ts +47 -0
- package/lib/api/reporting/sql-helpers.js +65 -0
- package/lib/api/reporting/types.d.ts +469 -0
- package/lib/api/reporting/types.js +4 -0
- package/lib/api/reporting-service.d.ts +7 -500
- package/lib/api/reporting-service.js +46 -760
- package/package.json +2 -2
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* DORA metrics: the four headline measures, their per-environment breakdown,
|
|
5
|
+
* the trend series, and per-pipeline build health.
|
|
6
|
+
*
|
|
7
|
+
* By far the largest domain in the reporting service (~490 lines of query +
|
|
8
|
+
* shaping against six row shapes), which is why it lives in its own module.
|
|
9
|
+
* ReportingService delegates to these; the banding thresholds they apply are in
|
|
10
|
+
* ./dora-scoring.ts, deliberately separate so they can be tested without a DB.
|
|
11
|
+
*/
|
|
12
|
+
import { sql } from 'drizzle-orm';
|
|
13
|
+
import { HEADLINE_ENV, DORA_INCIDENT_WINDOW_HOURS, resolveIncidentWindowHours, doraLevelForFrequency, doraLevelForChangeFailure, doraLevelForRestore, doraLevelForLeadTime, round, median, } from './dora-scoring.js';
|
|
14
|
+
import { orgScope, runReport } from './query-scope.js';
|
|
15
|
+
import { assertReportInterval, terminalStatusRollup } from './sql-helpers.js';
|
|
16
|
+
import { schema } from '../../database/drizzle-schema.js';
|
|
17
|
+
import { withTenantTx } from '../../database/tenancy.js';
|
|
18
|
+
import { drizzleRows } from '../crud-service.js';
|
|
19
|
+
/**
|
|
20
|
+
* 1.9 DORA metrics over a [from,to] deploy-completion window, org-scoped +
|
|
21
|
+
* rollup-aware. DEPLOY-BASIS ONLY — every metric derives from real deploy-stage
|
|
22
|
+
* executions (`event_type='STAGE'` with a non-null `environment`, set by the
|
|
23
|
+
* forwarder for the stages a user declared in `pb.deploys`). No run-based
|
|
24
|
+
* fallback: a pipeline with no deploy stage produces no DORA data.
|
|
25
|
+
*
|
|
26
|
+
* Four scoped SQL scans run inside one tx (each org-gated by `p.org_id ${pred}`
|
|
27
|
+
* / `o.org_id ${pred}` so a rollup passes the org→team subtree and a foreign
|
|
28
|
+
* org's rows never enter): (1) terminal deploy rows PER (execution, env) — one
|
|
29
|
+
* "deployment" = one execution reaching an env (D1) — → DF / deploy-time CFR /
|
|
30
|
+
* measured lead time (lead time joins the execution's earliest commit, D2);
|
|
31
|
+
* the scan also reaches back `windowHours` before `from` so incidents opened
|
|
32
|
+
* near the window start can correlate to a just-prior deploy; (2) `deployment_outcomes`
|
|
33
|
+
* in-window → post-deploy CFR component; (3) production restored/failed
|
|
34
|
+
* outcomes joined to their deploy → MTTR; (4) registry vs deploying → coverage.
|
|
35
|
+
* The cross-source medians (lead time, MTTR) are computed in JS from clamped
|
|
36
|
+
* (≥0) deltas.
|
|
37
|
+
*
|
|
38
|
+
* @param opts.pipelineId restrict to one pipeline (per-pipeline DORA)
|
|
39
|
+
* @param opts.environment restrict to one deploy environment
|
|
40
|
+
*/
|
|
41
|
+
export async function getDoraMetrics(orgId, from, to, orgIds, opts = {}) {
|
|
42
|
+
const { pred, multi } = orgScope(orgId, orgIds);
|
|
43
|
+
const { pipelineId, environment } = opts;
|
|
44
|
+
const pipelineClause = pipelineId ? sql `AND e.pipeline_id = ${pipelineId}` : sql ``;
|
|
45
|
+
const pipelineClauseR = pipelineId ? sql `AND r.pipeline_id = ${pipelineId}` : sql ``;
|
|
46
|
+
const envClause = environment ? sql `AND e.environment = ${environment}` : sql ``;
|
|
47
|
+
const outcomeEnvClause = environment ? sql `AND o.environment = ${environment}` : sql ``;
|
|
48
|
+
const incidentEnvClause = environment ? sql `AND i.environment = ${environment}` : sql ``;
|
|
49
|
+
// Effective incident-correlation window (per-org override or the global
|
|
50
|
+
// default). Computed here (not just in shapeDora) because the deploy scan
|
|
51
|
+
// needs it for BOTH the correlation look-back lower bound AND the cache key.
|
|
52
|
+
const effectiveWindowHours = resolveIncidentWindowHours(opts.incidentWindowHours);
|
|
53
|
+
// Correlation look-back: incidents opened just inside `from` can attribute to
|
|
54
|
+
// a deploy that completed up to `windowHours` BEFORE `from` (mirrors the
|
|
55
|
+
// LATERAL look-back in listIncidents). Widen the deploy scan's lower bound to
|
|
56
|
+
// `from − windowHours`; DF/CFR/lead still count only the in-`from`..`to`
|
|
57
|
+
// subset (flagged by `in_window`), while correlation sees the wider set.
|
|
58
|
+
const fromMs = Date.parse(from);
|
|
59
|
+
const lookbackFrom = Number.isFinite(fromMs)
|
|
60
|
+
? new Date(fromMs - effectiveWindowHours * 3600 * 1000).toISOString()
|
|
61
|
+
: from;
|
|
62
|
+
// (1) Terminal deploy rows: PER-EXECUTION (D1), one row per (environment,
|
|
63
|
+
// execution) — every deploy STAGE targeting that env within the execution is
|
|
64
|
+
// rolled up to a terminal status (FAILED wins, then SUCCEEDED). A single
|
|
65
|
+
// execution with two deploy stages to the same env is ONE deployment, not two.
|
|
66
|
+
// completed_at = the deploy's completion (MAX over its stages). `commit_ts`
|
|
67
|
+
// (D2) is the execution's EARLIEST commit time across ALL its events (commit
|
|
68
|
+
// enrichment rides the PIPELINE/source event where environment IS NULL, never
|
|
69
|
+
// the deploy STAGE row), joined by execution_id. `in_window` marks whether the
|
|
70
|
+
// deploy completed inside [from,to] (vs. a look-back-only row kept solely for
|
|
71
|
+
// incident correlation). execution_id is projected so post-deploy failures
|
|
72
|
+
// (manual outcomes + correlated incidents) can dedup against their deploy.
|
|
73
|
+
const deploySql = sql `
|
|
74
|
+
WITH deploy_stages AS (
|
|
75
|
+
SELECT
|
|
76
|
+
e.environment AS environment,
|
|
77
|
+
e.execution_id AS execution_id,
|
|
78
|
+
${terminalStatusRollup} AS status,
|
|
79
|
+
MAX(e.completed_at) AS completed_at
|
|
80
|
+
FROM ${schema.pipelineEvent} e
|
|
81
|
+
JOIN ${schema.pipeline} p ON p.id = e.pipeline_id
|
|
82
|
+
WHERE p.org_id ${pred} AND e.event_type = 'STAGE' AND e.environment IS NOT NULL
|
|
83
|
+
${pipelineClause} ${envClause}
|
|
84
|
+
AND e.completed_at >= ${lookbackFrom}::timestamptz AND e.completed_at <= ${to}::timestamptz
|
|
85
|
+
GROUP BY e.environment, e.execution_id
|
|
86
|
+
),
|
|
87
|
+
exec_commits AS (
|
|
88
|
+
SELECT c.execution_id AS execution_id, MIN(c.commit_timestamp) AS commit_ts
|
|
89
|
+
FROM ${schema.pipelineEvent} c
|
|
90
|
+
JOIN ${schema.pipeline} p ON p.id = c.pipeline_id
|
|
91
|
+
WHERE p.org_id ${pred} AND c.commit_timestamp IS NOT NULL
|
|
92
|
+
AND c.execution_id IN (SELECT execution_id FROM deploy_stages)
|
|
93
|
+
GROUP BY c.execution_id
|
|
94
|
+
)
|
|
95
|
+
SELECT
|
|
96
|
+
ds.environment AS environment,
|
|
97
|
+
ds.execution_id AS execution_id,
|
|
98
|
+
ds.status AS status,
|
|
99
|
+
ds.completed_at::text AS completed_at,
|
|
100
|
+
(ds.completed_at >= ${from}::timestamptz) AS in_window,
|
|
101
|
+
ec.commit_ts::text AS commit_ts
|
|
102
|
+
FROM deploy_stages ds
|
|
103
|
+
LEFT JOIN exec_commits ec ON ec.execution_id = ds.execution_id`;
|
|
104
|
+
// (2) Post-deploy outcome markers in-window (per env). Post-deploy failures
|
|
105
|
+
// (outcome='failed') add to the CFR numerator without changing attempts.
|
|
106
|
+
// execution_id lets the CFR dedup a manual failure against an incident that
|
|
107
|
+
// correlates to the same deploy (no double-count).
|
|
108
|
+
const outcomeSql = sql `
|
|
109
|
+
SELECT o.environment AS environment, o.outcome AS outcome, o.execution_id AS execution_id
|
|
110
|
+
FROM ${schema.deploymentOutcome} o
|
|
111
|
+
WHERE o.org_id ${pred} ${outcomeEnvClause}
|
|
112
|
+
AND o.at >= ${from}::timestamptz AND o.at <= ${to}::timestamptz`;
|
|
113
|
+
// (3) MTTR — PRODUCTION-ONLY, independent of the env filter. Every production
|
|
114
|
+
// outcome in-window; for a 'restored' one, correlate to its deploy's
|
|
115
|
+
// completion via execution_id to measure restored.at − deployed.completed_at.
|
|
116
|
+
// execution_id is projected so an incident-sourced recovery for the same
|
|
117
|
+
// deploy takes precedence over the manual restored gap.
|
|
118
|
+
const mttrSql = sql `
|
|
119
|
+
SELECT
|
|
120
|
+
o.outcome AS outcome,
|
|
121
|
+
o.execution_id AS execution_id,
|
|
122
|
+
o.at::text AS restored_at,
|
|
123
|
+
(SELECT MAX(d.completed_at) FROM ${schema.pipelineEvent} d
|
|
124
|
+
JOIN ${schema.pipeline} p2 ON p2.id = d.pipeline_id
|
|
125
|
+
WHERE p2.org_id ${pred} AND d.execution_id = o.execution_id
|
|
126
|
+
AND d.event_type = 'STAGE' AND d.environment = ${HEADLINE_ENV})::text AS deployed_at
|
|
127
|
+
FROM ${schema.deploymentOutcome} o
|
|
128
|
+
WHERE o.org_id ${pred} AND o.environment = ${HEADLINE_ENV}
|
|
129
|
+
AND o.at >= ${from}::timestamptz AND o.at <= ${to}::timestamptz`;
|
|
130
|
+
// (5) Incidents opened in-window (per env). Correlated in JS to the most
|
|
131
|
+
// recent successful deploy → automated post-deploy failure (CFR) + real
|
|
132
|
+
// recovery time (resolved_at − opened_at) for MTTR (production).
|
|
133
|
+
const incidentSql = sql `
|
|
134
|
+
SELECT i.environment AS environment, i.opened_at::text AS opened_at, i.resolved_at::text AS resolved_at
|
|
135
|
+
FROM ${schema.incident} i
|
|
136
|
+
WHERE i.org_id ${pred} ${incidentEnvClause}
|
|
137
|
+
AND i.opened_at >= ${from}::timestamptz AND i.opened_at <= ${to}::timestamptz`;
|
|
138
|
+
// (4) Coverage: registered pipelines vs those that actually deployed in-window.
|
|
139
|
+
const coverageSql = sql `
|
|
140
|
+
SELECT
|
|
141
|
+
(SELECT COUNT(*)::int FROM ${schema.pipelineRegistry} r
|
|
142
|
+
WHERE r.org_id ${pred} ${pipelineClauseR}) AS registered,
|
|
143
|
+
(SELECT COUNT(DISTINCT e.pipeline_id)::int FROM ${schema.pipelineEvent} e
|
|
144
|
+
JOIN ${schema.pipeline} p ON p.id = e.pipeline_id
|
|
145
|
+
WHERE p.org_id ${pred} AND e.event_type = 'STAGE' AND e.environment IS NOT NULL
|
|
146
|
+
${pipelineClause} ${envClause}
|
|
147
|
+
AND e.completed_at >= ${from}::timestamptz AND e.completed_at <= ${to}::timestamptz) AS deploying`;
|
|
148
|
+
const exec = () => withTenantTx(async (tx) => {
|
|
149
|
+
// Scan order is load-bearing for the generated-SQL tests: 0=deploy,
|
|
150
|
+
// 1=outcomes, 2=mttr, 3=coverage, 4=incidents (appended last so the
|
|
151
|
+
// earlier indices stay stable).
|
|
152
|
+
const deployRows = drizzleRows((await tx.execute(deploySql)).rows);
|
|
153
|
+
const outcomeRows = drizzleRows((await tx.execute(outcomeSql)).rows);
|
|
154
|
+
const mttrRows = drizzleRows((await tx.execute(mttrSql)).rows);
|
|
155
|
+
const coverageRow = drizzleRows((await tx.execute(coverageSql)).rows)[0];
|
|
156
|
+
const incidentRows = drizzleRows((await tx.execute(incidentSql)).rows);
|
|
157
|
+
return shapeDora(deployRows, outcomeRows, mttrRows, coverageRow, incidentRows, from, to, {
|
|
158
|
+
pipelineId, environment,
|
|
159
|
+
}, effectiveWindowHours);
|
|
160
|
+
});
|
|
161
|
+
// Cache key MUST include the effective incident window: the scorecard reads
|
|
162
|
+
// /dora with the org's default window while an explicit /dora call may pass an
|
|
163
|
+
// override — different windows yield different CFR/MTTR, so they must not
|
|
164
|
+
// collide on the same key.
|
|
165
|
+
const key = `${orgId}:dora:${from}:${to}:${pipelineId ?? ''}:${environment ?? ''}:${effectiveWindowHours}`;
|
|
166
|
+
return runReport(key, multi, exec);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Shape the raw DORA scan rows into the public {@link DoraMetrics}. Buckets the
|
|
170
|
+
* terminal deploy rows by environment (DF / deploy-time CFR / measured lead),
|
|
171
|
+
* folds in the post-deploy failure counts (manual outcomes + webhook-ingested
|
|
172
|
+
* incidents, deduped by deploy execution), computes production MTTR from both
|
|
173
|
+
* sources (incident `resolved_at − opened_at` taking precedence over the manual
|
|
174
|
+
* `restored − deployed`), and reconciles coverage — all cross-source medians
|
|
175
|
+
* over deltas clamped ≥0.
|
|
176
|
+
*
|
|
177
|
+
* Phase 5 incident correlation: each incident is attributed to the most recent
|
|
178
|
+
* SUCCESSFUL deploy to its environment with `completed_at ≤ opened_at` within
|
|
179
|
+
* {@link DORA_INCIDENT_WINDOW_HOURS}. That deploy is a post-deploy failure; an
|
|
180
|
+
* uncorrelated incident (no eligible deploy) contributes nothing.
|
|
181
|
+
*/
|
|
182
|
+
function shapeDora(deployRows, outcomeRows, mttrRows, coverageRow, incidentRows, from, to, filters, windowHours = DORA_INCIDENT_WINDOW_HOURS) {
|
|
183
|
+
// Window length in days (floored at 1 so a sub-day window yields the count as
|
|
184
|
+
// the rate, never /0 or an extrapolation). Guard unparseable dates → 1 day.
|
|
185
|
+
const spanDays = (Date.parse(to) - Date.parse(from)) / 86400000;
|
|
186
|
+
const days = Number.isFinite(spanDays) ? Math.max(spanDays, 1) : 1;
|
|
187
|
+
// Window end instant for MTTR right-censoring (a resolution after `to` is
|
|
188
|
+
// unobserved). +Infinity when `to` is unparseable ⇒ no censoring (fail-open).
|
|
189
|
+
const toParsed = Date.parse(to);
|
|
190
|
+
const toMs = Number.isFinite(toParsed) ? toParsed : Number.POSITIVE_INFINITY;
|
|
191
|
+
const envs = new Map();
|
|
192
|
+
const accFor = (env) => {
|
|
193
|
+
let a = envs.get(env);
|
|
194
|
+
if (!a) {
|
|
195
|
+
a = { deployments: 0, deployTimeFailures: 0, attempts: 0, leadGaps: [] };
|
|
196
|
+
envs.set(env, a);
|
|
197
|
+
}
|
|
198
|
+
return a;
|
|
199
|
+
};
|
|
200
|
+
// A deploy row counts toward DF/CFR/lead only if it completed inside [from,to].
|
|
201
|
+
// Look-back-only rows (in_window=false, kept for incident correlation) are
|
|
202
|
+
// skipped here. Absent in a fixture ⇒ in-window (unit tests omit the flag).
|
|
203
|
+
const isInWindow = (row) => row.in_window == null ? true : (row.in_window === true || row.in_window === 't');
|
|
204
|
+
for (const row of deployRows) {
|
|
205
|
+
const env = row.environment;
|
|
206
|
+
if (!env)
|
|
207
|
+
continue;
|
|
208
|
+
if (!isInWindow(row))
|
|
209
|
+
continue; // look-back-only row → correlation, not DF/CFR
|
|
210
|
+
const a = accFor(env);
|
|
211
|
+
if (row.status === 'SUCCEEDED') {
|
|
212
|
+
a.deployments++;
|
|
213
|
+
a.attempts++;
|
|
214
|
+
// Measured lead time: deploy completion − execution's earliest commit time.
|
|
215
|
+
if (row.commit_ts != null && row.completed_at != null) {
|
|
216
|
+
const gap = (Date.parse(row.completed_at) - Date.parse(row.commit_ts)) / 1000;
|
|
217
|
+
if (Number.isFinite(gap))
|
|
218
|
+
a.leadGaps.push(Math.max(gap, 0));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (row.status === 'FAILED') {
|
|
222
|
+
a.deployTimeFailures++;
|
|
223
|
+
a.attempts++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Incident→deploy correlation (Phase 5). Index the SUCCESSFUL deploys per env
|
|
227
|
+
// (execution id + completion instant), newest first, so each incident can be
|
|
228
|
+
// attributed to the most recent deploy that completed within the window
|
|
229
|
+
// before it opened. That execution is a post-deploy failure.
|
|
230
|
+
const windowMs = windowHours * 3600 * 1000;
|
|
231
|
+
const successfulByEnv = new Map();
|
|
232
|
+
for (const row of deployRows) {
|
|
233
|
+
if (row.status !== 'SUCCEEDED' || !row.environment || row.execution_id == null || row.completed_at == null)
|
|
234
|
+
continue;
|
|
235
|
+
const ms = Date.parse(row.completed_at);
|
|
236
|
+
if (!Number.isFinite(ms))
|
|
237
|
+
continue;
|
|
238
|
+
const list = successfulByEnv.get(row.environment) ?? [];
|
|
239
|
+
list.push({ exec: row.execution_id, completedMs: ms });
|
|
240
|
+
successfulByEnv.set(row.environment, list);
|
|
241
|
+
}
|
|
242
|
+
for (const list of successfulByEnv.values())
|
|
243
|
+
list.sort((a, b) => b.completedMs - a.completedMs);
|
|
244
|
+
/** Correlate an incident to the most recent in-window successful deploy execution. */
|
|
245
|
+
const correlate = (env, openedMs) => {
|
|
246
|
+
const list = successfulByEnv.get(env);
|
|
247
|
+
if (!list)
|
|
248
|
+
return null;
|
|
249
|
+
for (const d of list) {
|
|
250
|
+
if (d.completedMs <= openedMs && openedMs - d.completedMs <= windowMs)
|
|
251
|
+
return d.exec;
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
};
|
|
255
|
+
const correlatedIncidents = [];
|
|
256
|
+
for (const inc of incidentRows) {
|
|
257
|
+
if (!inc.environment || inc.opened_at == null)
|
|
258
|
+
continue;
|
|
259
|
+
const openedMs = Date.parse(inc.opened_at);
|
|
260
|
+
if (!Number.isFinite(openedMs))
|
|
261
|
+
continue;
|
|
262
|
+
const exec = correlate(inc.environment, openedMs);
|
|
263
|
+
if (exec == null)
|
|
264
|
+
continue; // uncorrelated → not attributable to a deploy
|
|
265
|
+
let resolvedGap = null;
|
|
266
|
+
if (inc.resolved_at != null) {
|
|
267
|
+
const resolvedMs = Date.parse(inc.resolved_at);
|
|
268
|
+
// Right-censoring: a resolution AFTER the window end (`to`) is not observed
|
|
269
|
+
// within the window — treat the incident as still open (gap stays null) so
|
|
270
|
+
// MTTR never reads unbounded past `to`. The incident still counts toward the
|
|
271
|
+
// incident total (unresolved), it just contributes no recovery time.
|
|
272
|
+
if (Number.isFinite(resolvedMs) && resolvedMs <= toMs) {
|
|
273
|
+
const g = (resolvedMs - openedMs) / 1000;
|
|
274
|
+
if (Number.isFinite(g))
|
|
275
|
+
resolvedGap = Math.max(g, 0);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
correlatedIncidents.push({ environment: inc.environment, exec, resolvedGap });
|
|
279
|
+
}
|
|
280
|
+
// Post-deploy failures per env — the DISTINCT set of deploy executions flagged
|
|
281
|
+
// by a manual `failed` outcome OR a correlated incident (deduped by execution,
|
|
282
|
+
// so a deploy flagged by both counts once). A manual outcome without an
|
|
283
|
+
// execution id (legacy/unresolvable) still counts via a per-row sentinel.
|
|
284
|
+
const postDeployExecsByEnv = new Map();
|
|
285
|
+
let sentinel = 0;
|
|
286
|
+
const flagFor = (env) => {
|
|
287
|
+
let s = postDeployExecsByEnv.get(env);
|
|
288
|
+
if (!s) {
|
|
289
|
+
s = new Set();
|
|
290
|
+
postDeployExecsByEnv.set(env, s);
|
|
291
|
+
}
|
|
292
|
+
return s;
|
|
293
|
+
};
|
|
294
|
+
for (const o of outcomeRows) {
|
|
295
|
+
if (o.outcome !== 'failed' || !o.environment)
|
|
296
|
+
continue;
|
|
297
|
+
flagFor(o.environment).add(o.execution_id ?? `manual-${sentinel++}`);
|
|
298
|
+
accFor(o.environment); // ensure env appears even with no deploy events
|
|
299
|
+
}
|
|
300
|
+
for (const ci of correlatedIncidents) {
|
|
301
|
+
flagFor(ci.environment).add(ci.exec);
|
|
302
|
+
accFor(ci.environment); // ensure env appears even if the deploy is out-of-window
|
|
303
|
+
}
|
|
304
|
+
const postDeployByEnv = new Map();
|
|
305
|
+
for (const [env, set] of postDeployExecsByEnv)
|
|
306
|
+
postDeployByEnv.set(env, set.size);
|
|
307
|
+
const environments = [...envs.entries()]
|
|
308
|
+
.map(([environment, a]) => {
|
|
309
|
+
const postDeployFailures = postDeployByEnv.get(environment) ?? 0;
|
|
310
|
+
const rawPerDay = a.deployments / days;
|
|
311
|
+
const numerator = a.deployTimeFailures + postDeployFailures;
|
|
312
|
+
// Clamp to [0,100]: post-deploy failures can correlate to a deploy that
|
|
313
|
+
// completed just before the window (kept for correlation), so the numerator
|
|
314
|
+
// can transiently exceed in-window attempts — a CFR > 100% is never valid.
|
|
315
|
+
const rawRate = a.attempts > 0 ? Math.min(100, (numerator / a.attempts) * 100) : 0;
|
|
316
|
+
const ltMedian = median(a.leadGaps);
|
|
317
|
+
return {
|
|
318
|
+
environment,
|
|
319
|
+
deploymentFrequency: {
|
|
320
|
+
deployments: a.deployments,
|
|
321
|
+
perDay: round(rawPerDay, 2),
|
|
322
|
+
level: doraLevelForFrequency(rawPerDay, a.deployments),
|
|
323
|
+
},
|
|
324
|
+
changeFailureRate: {
|
|
325
|
+
rate: round(rawRate, 1),
|
|
326
|
+
deployTimeFailures: a.deployTimeFailures,
|
|
327
|
+
postDeployFailures,
|
|
328
|
+
attempts: a.attempts,
|
|
329
|
+
level: doraLevelForChangeFailure(rawRate, a.attempts),
|
|
330
|
+
},
|
|
331
|
+
leadTime: {
|
|
332
|
+
deployments: a.leadGaps.length,
|
|
333
|
+
medianSeconds: ltMedian != null ? round(ltMedian, 1) : null,
|
|
334
|
+
level: doraLevelForLeadTime(ltMedian),
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
})
|
|
338
|
+
.sort((x, y) => {
|
|
339
|
+
if (x.environment === HEADLINE_ENV)
|
|
340
|
+
return -1;
|
|
341
|
+
if (y.environment === HEADLINE_ENV)
|
|
342
|
+
return 1;
|
|
343
|
+
return x.environment.localeCompare(y.environment);
|
|
344
|
+
});
|
|
345
|
+
// MTTR — PRODUCTION-ONLY, from BOTH sources. Incidents take precedence: a
|
|
346
|
+
// production incident correlated to a deploy contributes the real recovery
|
|
347
|
+
// (`resolved_at − opened_at`), and any manual outcome on the SAME deploy is
|
|
348
|
+
// skipped. Manual outcomes on other deploys keep the Phase-2 behavior
|
|
349
|
+
// (`failed` → an incident; `restored` → a recovery gap of `restored − deployed`).
|
|
350
|
+
let incidents = 0, restored = 0;
|
|
351
|
+
const mttrGaps = [];
|
|
352
|
+
// Executions already attributed to a production incident (precedence guard).
|
|
353
|
+
const incidentExecs = new Set();
|
|
354
|
+
for (const ci of correlatedIncidents) {
|
|
355
|
+
if (ci.environment !== HEADLINE_ENV)
|
|
356
|
+
continue; // MTTR is production-only
|
|
357
|
+
incidents++;
|
|
358
|
+
incidentExecs.add(ci.exec);
|
|
359
|
+
if (ci.resolvedGap != null) {
|
|
360
|
+
restored++;
|
|
361
|
+
mttrGaps.push(ci.resolvedGap);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const m of mttrRows) {
|
|
365
|
+
// Incident precedence: don't double-count a deploy already flagged by an incident.
|
|
366
|
+
if (m.execution_id != null && incidentExecs.has(m.execution_id))
|
|
367
|
+
continue;
|
|
368
|
+
if (m.outcome === 'failed') {
|
|
369
|
+
incidents++;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (m.outcome === 'restored') {
|
|
373
|
+
restored++;
|
|
374
|
+
if (m.restored_at != null && m.deployed_at != null) {
|
|
375
|
+
const gap = (Date.parse(m.restored_at) - Date.parse(m.deployed_at)) / 1000;
|
|
376
|
+
if (Number.isFinite(gap))
|
|
377
|
+
mttrGaps.push(Math.max(gap, 0));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const mttrMedian = median(mttrGaps);
|
|
382
|
+
const registered = Number(coverageRow?.registered) || 0;
|
|
383
|
+
const deploying = Number(coverageRow?.deploying) || 0;
|
|
384
|
+
return {
|
|
385
|
+
window: { from, to },
|
|
386
|
+
filters: { pipelineId: filters.pipelineId ?? null, environment: filters.environment ?? null },
|
|
387
|
+
headline: HEADLINE_ENV,
|
|
388
|
+
environments,
|
|
389
|
+
meanTimeToRestore: {
|
|
390
|
+
incidents,
|
|
391
|
+
restored,
|
|
392
|
+
medianSeconds: mttrMedian != null ? round(mttrMedian, 1) : null,
|
|
393
|
+
level: doraLevelForRestore(mttrMedian),
|
|
394
|
+
},
|
|
395
|
+
coverage: {
|
|
396
|
+
registered,
|
|
397
|
+
deploying,
|
|
398
|
+
withoutDeploys: Math.max(registered - deploying, 0),
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* 1.9b DORA trend — deployment frequency + deploy-time change-failure rate
|
|
404
|
+
* bucketed by `interval` (day/week/month) for a sparkline. Deploy-basis like
|
|
405
|
+
* getDoraMetrics: buckets terminal deploy-stage executions on their
|
|
406
|
+
* `completed_at`. Shares the org/rollup + pipelineId/environment scoping. MTTR,
|
|
407
|
+
* lead time, and post-deploy CFR are intentionally omitted (too heavy to bucket).
|
|
408
|
+
*/
|
|
409
|
+
export async function getDoraTrend(orgId, interval, from, to, orgIds, opts = {}) {
|
|
410
|
+
assertReportInterval(interval);
|
|
411
|
+
const { pred, multi } = orgScope(orgId, orgIds);
|
|
412
|
+
const { pipelineId, environment } = opts;
|
|
413
|
+
const pipelineClause = pipelineId ? sql `AND e.pipeline_id = ${pipelineId}` : sql ``;
|
|
414
|
+
const envClause = environment ? sql `AND e.environment = ${environment}` : sql ``;
|
|
415
|
+
const exec = () => withTenantTx((tx) => tx.execute(sql `
|
|
416
|
+
WITH deploys AS (
|
|
417
|
+
-- PER-EXECUTION deploy unit (D1): one row per (environment, execution),
|
|
418
|
+
-- every deploy STAGE to that env within the execution rolled up to a
|
|
419
|
+
-- single terminal status — NOT one row per stage.
|
|
420
|
+
SELECT
|
|
421
|
+
${terminalStatusRollup} AS status,
|
|
422
|
+
MAX(e.completed_at) AS completed_at
|
|
423
|
+
FROM ${schema.pipelineEvent} e
|
|
424
|
+
JOIN ${schema.pipeline} p ON p.id = e.pipeline_id
|
|
425
|
+
WHERE p.org_id ${pred} AND e.event_type = 'STAGE' AND e.environment IS NOT NULL
|
|
426
|
+
${pipelineClause} ${envClause}
|
|
427
|
+
AND e.completed_at >= ${from}::timestamptz AND e.completed_at <= ${to}::timestamptz
|
|
428
|
+
GROUP BY e.environment, e.execution_id
|
|
429
|
+
)
|
|
430
|
+
SELECT
|
|
431
|
+
DATE_TRUNC(${interval}, completed_at)::text AS period,
|
|
432
|
+
COUNT(*) FILTER (WHERE status = 'SUCCEEDED')::int AS deployments,
|
|
433
|
+
COUNT(*) FILTER (WHERE status = 'FAILED')::int AS failed,
|
|
434
|
+
COUNT(*) FILTER (WHERE status IN ('SUCCEEDED', 'FAILED'))::int AS total,
|
|
435
|
+
-- quoted alias so the raw row key is exactly the DoraTrendPoint field
|
|
436
|
+
COALESCE(ROUND(COUNT(*) FILTER (WHERE status = 'FAILED')::numeric
|
|
437
|
+
/ NULLIF(COUNT(*) FILTER (WHERE status IN ('SUCCEEDED', 'FAILED')), 0) * 100, 1), 0)::float AS "changeFailurePct"
|
|
438
|
+
FROM deploys
|
|
439
|
+
GROUP BY period ORDER BY period
|
|
440
|
+
`).then(r => drizzleRows(r.rows)));
|
|
441
|
+
const key = `${orgId}:dora-trend:${interval}:${from}:${to}:${pipelineId ?? ''}:${environment ?? ''}`;
|
|
442
|
+
return runReport(key, multi, exec);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* 1.10 Per-pipeline BUILD HEALTH (Phase 6) — a standard (NOT `advanced_reporting`)
|
|
446
|
+
* per-stage breakdown for one pipeline over a [from,to] window. Aggregates the
|
|
447
|
+
* existing `pipeline_events` STAGE rows: each stage is rolled up per execution
|
|
448
|
+
* to a terminal status (FAILED wins, then SUCCEEDED) + its max duration, then
|
|
449
|
+
* grouped per stage into run/success/failure counts, a success rate, and
|
|
450
|
+
* duration percentiles (p50/p90/p99). Totals sum across stages. Org-scoped via
|
|
451
|
+
* the pipeline join (`p.org_id ${pred}`) + rollup-aware like the sibling reports;
|
|
452
|
+
* a pipelineId owned by another org returns an empty breakdown.
|
|
453
|
+
*/
|
|
454
|
+
export async function getBuildHealth(orgId, pipelineId, from, to, orgIds) {
|
|
455
|
+
const { pred, multi } = orgScope(orgId, orgIds);
|
|
456
|
+
const exec = () => withTenantTx(async (tx) => {
|
|
457
|
+
const rows = drizzleRows((await tx.execute(sql `
|
|
458
|
+
WITH stage_runs AS (
|
|
459
|
+
SELECT
|
|
460
|
+
e.stage_name AS stage,
|
|
461
|
+
${terminalStatusRollup} AS status,
|
|
462
|
+
MAX(e.duration_ms) AS duration_ms
|
|
463
|
+
FROM ${schema.pipelineEvent} e
|
|
464
|
+
JOIN ${schema.pipeline} p ON p.id = e.pipeline_id
|
|
465
|
+
WHERE p.org_id ${pred} AND e.pipeline_id = ${pipelineId}
|
|
466
|
+
AND e.event_type = 'STAGE' AND e.stage_name IS NOT NULL
|
|
467
|
+
AND e.started_at >= ${from}::timestamptz AND e.started_at <= ${to}::timestamptz
|
|
468
|
+
GROUP BY e.stage_name, e.execution_id
|
|
469
|
+
)
|
|
470
|
+
SELECT
|
|
471
|
+
stage,
|
|
472
|
+
COUNT(*)::int AS runs,
|
|
473
|
+
COUNT(*) FILTER (WHERE status = 'SUCCEEDED')::int AS successes,
|
|
474
|
+
COUNT(*) FILTER (WHERE status = 'FAILED')::int AS failures,
|
|
475
|
+
COALESCE(ROUND(COUNT(*) FILTER (WHERE status = 'SUCCEEDED')::numeric
|
|
476
|
+
/ NULLIF(COUNT(*), 0) * 100, 1), 0)::float AS "successRate",
|
|
477
|
+
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY duration_ms)::int AS "p50Ms",
|
|
478
|
+
PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY duration_ms)::int AS "p90Ms",
|
|
479
|
+
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY duration_ms)::int AS "p99Ms"
|
|
480
|
+
FROM stage_runs
|
|
481
|
+
WHERE status IN ('SUCCEEDED', 'FAILED')
|
|
482
|
+
GROUP BY stage
|
|
483
|
+
ORDER BY runs DESC, stage
|
|
484
|
+
`)).rows);
|
|
485
|
+
// Totals sum across stages (total stage-runs / stage-failures in the window).
|
|
486
|
+
let runs = 0, failures = 0;
|
|
487
|
+
for (const s of rows) {
|
|
488
|
+
runs += s.runs;
|
|
489
|
+
failures += s.failures;
|
|
490
|
+
}
|
|
491
|
+
const failureRate = runs > 0 ? round((failures / runs) * 100, 1) : 0;
|
|
492
|
+
return { stages: rows, totals: { runs, failures, failureRate } };
|
|
493
|
+
});
|
|
494
|
+
return runReport(`${orgId}:build-health:${pipelineId}:${from}:${to}`, multi, exec);
|
|
495
|
+
}
|
|
496
|
+
// ── Category 2: Plugin Inventory & Builds ──
|
|
497
|
+
/** 2.1 Plugin summary — counts and breakdowns.
|
|
498
|
+
* INTENTIONALLY SINGLE-ORG (no rollup): plugin inventory is an org-owned
|
|
499
|
+
* asset count, not an execution/build activity report, so a parent's view is
|
|
500
|
+
* its own plugins — teams manage their own inventory. Single `= $org` scope. */
|
|
501
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9yYS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hcGkvcmVwb3J0aW5nL2RvcmEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7R0FRRztBQUVILE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEMsT0FBTyxFQUNMLFlBQVksRUFBRSwwQkFBMEIsRUFBRSwwQkFBMEIsRUFDcEUscUJBQXFCLEVBQUUseUJBQXlCLEVBQUUsbUJBQW1CLEVBQUUsb0JBQW9CLEVBQzNGLEtBQUssRUFBRSxNQUFNLEdBQ2QsTUFBTSxtQkFBbUIsQ0FBQztBQUMzQixPQUFPLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBSzlFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUMxRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDekQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWpEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FxQkc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLGNBQWMsQ0FDbEMsS0FBYSxFQUNiLElBQVksRUFDWixFQUFVLEVBQ1YsTUFBaUIsRUFDakIsSUFBSSxHQUFnQixFQUFFO0lBRXRCLE1BQU0sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLEdBQUcsUUFBUSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNoRCxNQUFNLEVBQUUsVUFBVSxFQUFFLFdBQVcsRUFBRSxHQUFHLElBQUksQ0FBQztJQUN6QyxNQUFNLGNBQWMsR0FBRyxVQUFVLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQSx1QkFBdUIsVUFBVSxFQUFFLENB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LE1BQU0sSUFBSSxHQUFHLFdBQVcsQ0FBbUIsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFBOzs7O1lBSXhELG9CQUFvQjs7ZUFFakIsTUFBTSxDQUFDLGFBQWE7ZUFDcEIsTUFBTSxDQUFDLFFBQVE7eUJBQ0wsSUFBSSx3QkFBd0IsVUFBVTs7Z0NBRS9CLElBQUkscUNBQXFDLEVBQUU7Ozs7Ozs7Ozs7Ozs7Ozs7O0tBaUJ0RSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVWLDhFQUE4RTtRQUM5RSxJQUFJLElBQUksR0FBRyxDQUFDLEVBQUUsUUFBUSxHQUFHLENBQUMsQ0FBQztRQUMzQixLQUFLLE1BQU0sQ0FBQyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQUMsSUFBSSxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUM7WUFBQyxRQUFRLElBQUksQ0FBQyxDQUFDLFFBQVEsQ0FBQztRQUFDLENBQUM7UUFDakUsTUFBTSxXQUFXLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxHQUFHLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JFLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLEVBQUUsQ0FBQztJQUNuRSxDQUFDLENBQUMsQ0FBQztJQUNILE9BQU8sU0FBUyxDQUFDLEdBQUcsS0FBSyxpQkFBaUIsVUFBVSxJQUFJLElBQUksSUFBSSxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDckYsQ0FBQztBQUVELDhDQUE4QztBQUU5Qzs7O2lGQUdpRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIERPUkEgbWV0cmljczogdGhlIGZvdXIgaGVhZGxpbmUgbWVhc3VyZXMsIHRoZWlyIHBlci1lbnZpcm9ubWVudCBicmVha2Rvd24sXG4gKiB0aGUgdHJlbmQgc2VyaWVzLCBhbmQgcGVyLXBpcGVsaW5lIGJ1aWxkIGhlYWx0aC5cbiAqXG4gKiBCeSBmYXIgdGhlIGxhcmdlc3QgZG9tYWluIGluIHRoZSByZXBvcnRpbmcgc2VydmljZSAofjQ5MCBsaW5lcyBvZiBxdWVyeSArXG4gKiBzaGFwaW5nIGFnYWluc3Qgc2l4IHJvdyBzaGFwZXMpLCB3aGljaCBpcyB3aHkgaXQgbGl2ZXMgaW4gaXRzIG93biBtb2R1bGUuXG4gKiBSZXBvcnRpbmdTZXJ2aWNlIGRlbGVnYXRlcyB0byB0aGVzZTsgdGhlIGJhbmRpbmcgdGhyZXNob2xkcyB0aGV5IGFwcGx5IGFyZSBpblxuICogLi9kb3JhLXNjb3JpbmcudHMsIGRlbGliZXJhdGVseSBzZXBhcmF0ZSBzbyB0aGV5IGNhbiBiZSB0ZXN0ZWQgd2l0aG91dCBhIERCLlxuICovXG5cbmltcG9ydCB7IHNxbCB9IGZyb20gJ2RyaXp6bGUtb3JtJztcbmltcG9ydCB7XG4gIEhFQURMSU5FX0VOViwgRE9SQV9JTkNJREVOVF9XSU5ET1dfSE9VUlMsIHJlc29sdmVJbmNpZGVudFdpbmRvd0hvdXJzLFxuICBkb3JhTGV2ZWxGb3JGcmVxdWVuY3ksIGRvcmFMZXZlbEZvckNoYW5nZUZhaWx1cmUsIGRvcmFMZXZlbEZvclJlc3RvcmUsIGRvcmFMZXZlbEZvckxlYWRUaW1lLFxuICByb3VuZCwgbWVkaWFuLFxufSBmcm9tICcuL2RvcmEtc2NvcmluZy5qcyc7XG5pbXBvcnQgeyBvcmdTY29wZSwgcnVuUmVwb3J0IH0gZnJvbSAnLi9xdWVyeS1zY29wZS5qcyc7XG5pbXBvcnQgeyBhc3NlcnRSZXBvcnRJbnRlcnZhbCwgdGVybWluYWxTdGF0dXNSb2xsdXAgfSBmcm9tICcuL3NxbC1oZWxwZXJzLmpzJztcbmltcG9ydCB0eXBlIHtcbiAgRG9yYU9wdGlvbnMsIERvcmFNZXRyaWNzLCBEb3JhRW52TWV0cmljcywgRG9yYVRyZW5kUG9pbnQsIEJ1aWxkSGVhbHRoLCBCdWlsZEhlYWx0aFN0YWdlLFxuICBEZXBsb3lSb3csIE91dGNvbWVSb3csIE10dHJQYWlyUm93LCBJbmNpZGVudFJvdywgQ292ZXJhZ2VSb3csXG59IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgc2NoZW1hIH0gZnJvbSAnLi4vLi4vZGF0YWJhc2UvZHJpenpsZS1zY2hlbWEuanMnO1xuaW1wb3J0IHsgd2l0aFRlbmFudFR4IH0gZnJvbSAnLi4vLi4vZGF0YWJhc2UvdGVuYW5jeS5qcyc7XG5pbXBvcnQgeyBkcml6emxlUm93cyB9IGZyb20gJy4uL2NydWQtc2VydmljZS5qcyc7XG5cbi8qKlxuICogMS45IERPUkEgbWV0cmljcyBvdmVyIGEgW2Zyb20sdG9dIGRlcGxveS1jb21wbGV0aW9uIHdpbmRvdywgb3JnLXNjb3BlZCArXG4gKiByb2xsdXAtYXdhcmUuIERFUExPWS1CQVNJUyBPTkxZIOKAlCBldmVyeSBtZXRyaWMgZGVyaXZlcyBmcm9tIHJlYWwgZGVwbG95LXN0YWdlXG4gKiBleGVjdXRpb25zIChgZXZlbnRfdHlwZT0nU1RBR0UnYCB3aXRoIGEgbm9uLW51bGwgYGVudmlyb25tZW50YCwgc2V0IGJ5IHRoZVxuICogZm9yd2FyZGVyIGZvciB0aGUgc3RhZ2VzIGEgdXNlciBkZWNsYXJlZCBpbiBgcGIuZGVwbG95c2ApLiBObyBydW4tYmFzZWRcbiAqIGZhbGxiYWNrOiBhIHBpcGVsaW5lIHdpdGggbm8gZGVwbG95IHN0YWdlIHByb2R1Y2VzIG5vIERPUkEgZGF0YS5cbiAqXG4gKiBGb3VyIHNjb3BlZCBTUUwgc2NhbnMgcnVuIGluc2lkZSBvbmUgdHggKGVhY2ggb3JnLWdhdGVkIGJ5IGBwLm9yZ19pZCAke3ByZWR9YFxuICogLyBgby5vcmdfaWQgJHtwcmVkfWAgc28gYSByb2xsdXAgcGFzc2VzIHRoZSBvcmfihpJ0ZWFtIHN1YnRyZWUgYW5kIGEgZm9yZWlnblxuICogb3JnJ3Mgcm93cyBuZXZlciBlbnRlcik6ICgxKSB0ZXJtaW5hbCBkZXBsb3kgcm93cyBQRVIgKGV4ZWN1dGlvbiwgZW52KSDigJQgb25lXG4gKiBcImRlcGxveW1lbnRcIiA9IG9uZSBleGVjdXRpb24gcmVhY2hpbmcgYW4gZW52IChEMSkg4oCUIOKGkiBERiAvIGRlcGxveS10aW1lIENGUiAvXG4gKiBtZWFzdXJlZCBsZWFkIHRpbWUgKGxlYWQgdGltZSBqb2lucyB0aGUgZXhlY3V0aW9uJ3MgZWFybGllc3QgY29tbWl0LCBEMik7XG4gKiB0aGUgc2NhbiBhbHNvIHJlYWNoZXMgYmFjayBgd2luZG93SG91cnNgIGJlZm9yZSBgZnJvbWAgc28gaW5jaWRlbnRzIG9wZW5lZFxuICogbmVhciB0aGUgd2luZG93IHN0YXJ0IGNhbiBjb3JyZWxhdGUgdG8gYSBqdXN0LXByaW9yIGRlcGxveTsgKDIpIGBkZXBsb3ltZW50X291dGNvbWVzYFxuICogaW4td2luZG93IOKGkiBwb3N0LWRlcGxveSBDRlIgY29tcG9uZW50OyAoMykgcHJvZHVjdGlvbiByZXN0b3JlZC9mYWlsZWRcbiAqIG91dGNvbWVzIGpvaW5lZCB0byB0aGVpciBkZXBsb3kg4oaSIE1UVFI7ICg0KSByZWdpc3RyeSB2cyBkZXBsb3lpbmcg4oaSIGNvdmVyYWdlLlxuICogVGhlIGNyb3NzLXNvdXJjZSBtZWRpYW5zIChsZWFkIHRpbWUsIE1UVFIpIGFyZSBjb21wdXRlZCBpbiBKUyBmcm9tIGNsYW1wZWRcbiAqICjiiaUwKSBkZWx0YXMuXG4gKlxuICogQHBhcmFtIG9wdHMucGlwZWxpbmVJZCAgICByZXN0cmljdCB0byBvbmUgcGlwZWxpbmUgKHBlci1waXBlbGluZSBET1JBKVxuICogQHBhcmFtIG9wdHMuZW52aXJvbm1lbnQgICByZXN0cmljdCB0byBvbmUgZGVwbG95IGVudmlyb25tZW50XG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBnZXREb3JhTWV0cmljcyhcbiAgb3JnSWQ6IHN0cmluZyxcbiAgZnJvbTogc3RyaW5nLFxuICB0bzogc3RyaW5nLFxuICBvcmdJZHM/OiBzdHJpbmdbXSxcbiAgb3B0czogRG9yYU9wdGlvbnMgPSB7fSxcbik6IFByb21pc2U8RG9yYU1ldHJpY3M+IHtcbiAgY29uc3QgeyBwcmVkLCBtdWx0aSB9ID0gb3JnU2NvcGUob3JnSWQsIG9yZ0lkcyk7XG4gIGNvbnN0IHsgcGlwZWxpbmVJZCwgZW52aXJvbm1lbnQgfSA9IG9wdHM7XG4gIGNvbnN0IHBpcGVsaW5lQ2xhdXNlID0gcGlwZWxpbmVJZCA/IHNxbGBBTkQgZS5waXBlbGluZV9pZCA9ICR7cGlwZWxpbmVJZH1gIDogc3FsYGA7XG4gIGNvbnN0IHBpcGVsaW5lQ2xhdXNlUiA9IHBpcGVsaW5lSWQgPyBzcWxgQU5EIHIucGlwZWxpbmVfaWQgPSAke3BpcGVsaW5lSWR9YCA6IHNxbGBgO1xuICBjb25zdCBlbnZDbGF1c2UgPSBlbnZpcm9ubWVudCA/IHNxbGBBTkQgZS5lbnZpcm9ubWVudCA9ICR7ZW52aXJvbm1lbnR9YCA6IHNxbGBgO1xuICBjb25zdCBvdXRjb21lRW52Q2xhdXNlID0gZW52aXJvbm1lbnQgPyBzcWxgQU5EIG8uZW52aXJvbm1lbnQgPSAke2Vudmlyb25tZW50fWAgOiBzcWxgYDtcbiAgY29uc3QgaW5jaWRlbnRFbnZDbGF1c2UgPSBlbnZpcm9ubWVudCA/IHNxbGBBTkQgaS5lbnZpcm9ubWVudCA9ICR7ZW52aXJvbm1lbnR9YCA6IHNxbGBgO1xuXG4gIC8vIEVmZmVjdGl2ZSBpbmNpZGVudC1jb3JyZWxhdGlvbiB3aW5kb3cgKHBlci1vcmcgb3ZlcnJpZGUgb3IgdGhlIGdsb2JhbFxuICAvLyBkZWZhdWx0KS4gQ29tcHV0ZWQgaGVyZSAobm90IGp1c3QgaW4gc2hhcGVEb3JhKSBiZWNhdXNlIHRoZSBkZXBsb3kgc2NhblxuICAvLyBuZWVkcyBpdCBmb3IgQk9USCB0aGUgY29ycmVsYXRpb24gbG9vay1iYWNrIGxvd2VyIGJvdW5kIEFORCB0aGUgY2FjaGUga2V5LlxuICBjb25zdCBlZmZlY3RpdmVXaW5kb3dIb3VycyA9IHJlc29sdmVJbmNpZGVudFdpbmRvd0hvdXJzKG9wdHMuaW5jaWRlbnRXaW5kb3dIb3Vycyk7XG4gIC8vIENvcnJlbGF0aW9uIGxvb2stYmFjazogaW5jaWRlbnRzIG9wZW5lZCBqdXN0IGluc2lkZSBgZnJvbWAgY2FuIGF0dHJpYnV0ZSB0b1xuICAvLyBhIGRlcGxveSB0aGF0IGNvbXBsZXRlZCB1cCB0byBgd2luZG93SG91cnNgIEJFRk9SRSBgZnJvbWAgKG1pcnJvcnMgdGhlXG4gIC8vIExBVEVSQUwgbG9vay1iYWNrIGluIGxpc3RJbmNpZGVudHMpLiBXaWRlbiB0aGUgZGVwbG95IHNjYW4ncyBsb3dlciBib3VuZCB0b1xuICAvLyBgZnJvbSDiiJIgd2luZG93SG91cnNgOyBERi9DRlIvbGVhZCBzdGlsbCBjb3VudCBvbmx5IHRoZSBpbi1gZnJvbWAuLmB0b2BcbiAgLy8gc3Vic2V0IChmbGFnZ2VkIGJ5IGBpbl93aW5kb3dgKSwgd2hpbGUgY29ycmVsYXRpb24gc2VlcyB0aGUgd2lkZXIgc2V0LlxuICBjb25zdCBmcm9tTXMgPSBEYXRlLnBhcnNlKGZyb20pO1xuICBjb25zdCBsb29rYmFja0Zyb20gPSBOdW1iZXIuaXNGaW5pdGUoZnJvbU1zKVxuICAgID8gbmV3IERhdGUoZnJvbU1zIC0gZWZmZWN0aXZlV2luZG93SG91cnMgKiAzNjAwICogMTAwMCkudG9JU09TdHJpbmcoKVxuICAgIDogZnJvbTtcblxuICAvLyAoMSkgVGVybWluYWwgZGVwbG95IHJvd3M6IFBFUi1FWEVDVVRJT04gKEQxKSwgb25lIHJvdyBwZXIgKGVudmlyb25tZW50LFxuICAvLyBleGVjdXRpb24pIOKAlCBldmVyeSBkZXBsb3kgU1RBR0UgdGFyZ2V0aW5nIHRoYXQgZW52IHdpdGhpbiB0aGUgZXhlY3V0aW9uIGlzXG4gIC8vIHJvbGxlZCB1cCB0byBhIHRlcm1pbmFsIHN0YXR1cyAoRkFJTEVEIHdpbnMsIHRoZW4gU1VDQ0VFREVEKS4gQSBzaW5nbGVcbiAgLy8gZXhlY3V0aW9uIHdpdGggdHdvIGRlcGxveSBzdGFnZXMgdG8gdGhlIHNhbWUgZW52IGlzIE9ORSBkZXBsb3ltZW50LCBub3QgdHdvLlxuICAvLyBjb21wbGV0ZWRfYXQgPSB0aGUgZGVwbG95J3MgY29tcGxldGlvbiAoTUFYIG92ZXIgaXRzIHN0YWdlcykuIGBjb21taXRfdHNgXG4gIC8vIChEMikgaXMgdGhlIGV4ZWN1dGlvbidzIEVBUkxJRVNUIGNvbW1pdCB0aW1lIGFjcm9zcyBBTEwgaXRzIGV2ZW50cyAoY29tbWl0XG4gIC8vIGVucmljaG1lbnQgcmlkZXMgdGhlIFBJUEVMSU5FL3NvdXJjZSBldmVudCB3aGVyZSBlbnZpcm9ubWVudCBJUyBOVUxMLCBuZXZlclxuICAvLyB0aGUgZGVwbG95IFNUQUdFIHJvdyksIGpvaW5lZCBieSBleGVjdXRpb25faWQuIGBpbl93aW5kb3dgIG1hcmtzIHdoZXRoZXIgdGhlXG4gIC8vIGRlcGxveSBjb21wbGV0ZWQgaW5zaWRlIFtmcm9tLHRvXSAodnMuIGEgbG9vay1iYWNrLW9ubHkgcm93IGtlcHQgc29sZWx5IGZvclxuICAvLyBpbmNpZGVudCBjb3JyZWxhdGlvbikuIGV4ZWN1dGlvbl9pZCBpcyBwcm9qZWN0ZWQgc28gcG9zdC1kZXBsb3kgZmFpbHVyZXNcbiAgLy8gKG1hbnVhbCBvdXRjb21lcyArIGNvcnJlbGF0ZWQgaW5jaWRlbnRzKSBjYW4gZGVkdXAgYWdhaW5zdCB0aGVpciBkZXBsb3kuXG4gIGNvbnN0IGRlcGxveVNxbCA9IHNxbGBcbiAgICAgIFdJVEggZGVwbG95X3N0YWdlcyBBUyAoXG4gICAgICAgIFNFTEVDVFxuICAgICAgICAgIGUuZW52aXJvbm1lbnQgQVMgZW52aXJvbm1lbnQsXG4gICAgICAgICAgZS5leGVjdXRpb25faWQgQVMgZXhlY3V0aW9uX2lkLFxuICAgICAgICAgICR7dGVybWluYWxTdGF0dXNSb2xsdXB9IEFTIHN0YXR1cyxcbiAgICAgICAgICBNQVgoZS5jb21wbGV0ZWRfYXQpIEFTIGNvbXBsZXRlZF9hdFxuICAgICAgICBGUk9NICR7c2NoZW1hLnBpcGVsaW5lRXZlbnR9IGVcbiAgICAgICAgSk9JTiAke3NjaGVtYS5waXBlbGluZX0gcCBPTiBwLmlkID0gZS5waXBlbGluZV9pZFxuICAgICAgICBXSEVSRSBwLm9yZ19pZCAke3ByZWR9IEFORCBlLmV2ZW50X3R5cGUgPSAnU1RBR0UnIEFORCBlLmVudmlyb25tZW50IElTIE5PVCBOVUxMXG4gICAgICAgICAgJHtwaXBlbGluZUNsYXVzZX0gJHtlbnZDbGF1c2V9XG4gICAgICAgICAgQU5EIGUuY29tcGxldGVkX2F0ID49ICR7bG9va2JhY2tGcm9tfTo6dGltZXN0YW1wdHogQU5EIGUuY29tcGxldGVkX2F0IDw9ICR7dG99Ojp0aW1lc3RhbXB0elxuICAgICAgICBHUk9VUCBCWSBlLmVudmlyb25tZW50LCBlLmV4ZWN1dGlvbl9pZFxuICAgICAgKSxcbiAgICAgIGV4ZWNfY29tbWl0cyBBUyAoXG4gICAgICAgIFNFTEVDVCBjLmV4ZWN1dGlvbl9pZCBBUyBleGVjdXRpb25faWQsIE1JTihjLmNvbW1pdF90aW1lc3RhbXApIEFTIGNvbW1pdF90c1xuICAgICAgICBGUk9NICR7c2NoZW1hLnBpcGVsaW5lRXZlbnR9IGNcbiAgICAgICAgSk9JTiAke3NjaGVtYS5waXBlbGluZX0gcCBPTiBwLmlkID0gYy5waXBlbGluZV9pZFxuICAgICAgICBXSEVSRSBwLm9yZ19pZCAke3ByZWR9IEFORCBjLmNvbW1pdF90aW1lc3RhbXAgSVMgTk9UIE5VTExcbiAgICAgICAgICBBTkQgYy5leGVjdXRpb25faWQgSU4gKFNFTEVDVCBleGVjdXRpb25faWQgRlJPTSBkZXBsb3lfc3RhZ2VzKVxuICAgICAgICBHUk9VUCBCWSBjLmV4ZWN1dGlvbl9pZFxuICAgICAgKVxuICAgICAgU0VMRUNUXG4gICAgICAgIGRzLmVudmlyb25tZW50IEFTIGVudmlyb25tZW50LFxuICAgICAgICBkcy5leGVjdXRpb25faWQgQVMgZXhlY3V0aW9uX2lkLFxuICAgICAgICBkcy5zdGF0dXMgQVMgc3RhdHVzLFxuICAgICAgICBkcy5jb21wbGV0ZWRfYXQ6OnRleHQgQVMgY29tcGxldGVkX2F0LFxuICAgICAgICAoZHMuY29tcGxldGVkX2F0ID49ICR7ZnJvbX06OnRpbWVzdGFtcHR6KSBBUyBpbl93aW5kb3csXG4gICAgICAgIGVjLmNvbW1pdF90czo6dGV4dCBBUyBjb21taXRfdHNcbiAgICAgIEZST00gZGVwbG95X3N0YWdlcyBkc1xuICAgICAgTEVGVCBKT0lOIGV4ZWNfY29tbWl0cyBlYyBPTiBlYy5leGVjdXRpb25faWQgPSBkcy5leGVjdXRpb25faWRgO1xuXG4gIC8vICgyKSBQb3N0LWRlcGxveSBvdXRjb21lIG1hcmtlcnMgaW4td2luZG93IChwZXIgZW52KS4gUG9zdC1kZXBsb3kgZmFpbHVyZXNcbiAgLy8gKG91dGNvbWU9J2ZhaWxlZCcpIGFkZCB0byB0aGUgQ0ZSIG51bWVyYXRvciB3aXRob3V0IGNoYW5naW5nIGF0dGVtcHRzLlxuICAvLyBleGVjdXRpb25faWQgbGV0cyB0aGUgQ0ZSIGRlZHVwIGEgbWFudWFsIGZhaWx1cmUgYWdhaW5zdCBhbiBpbmNpZGVudCB0aGF0XG4gIC8vIGNvcnJlbGF0ZXMgdG8gdGhlIHNhbWUgZGVwbG95IChubyBkb3VibGUtY291bnQpLlxuICBjb25zdCBvdXRjb21lU3FsID0gc3FsYFxuICAgICAgU0VMRUNUIG8uZW52aXJvbm1lbnQgQVMgZW52aXJvbm1lbnQsIG8ub3V0Y29tZSBBUyBvdXRjb21lLCBvLmV4ZWN1dGlvbl9pZCBBUyBleGVjdXRpb25faWRcbiAgICAgIEZST00gJHtzY2hlbWEuZGVwbG95bWVudE91dGNvbWV9IG9cbiAgICAgIFdIRVJFIG8ub3JnX2lkICR7cHJlZH0gJHtvdXRjb21lRW52Q2xhdXNlfVxuICAgICAgICBBTkQgby5hdCA+PSAke2Zyb219Ojp0aW1lc3RhbXB0eiBBTkQgby5hdCA8PSAke3RvfTo6dGltZXN0YW1wdHpgO1xuXG4gIC8vICgzKSBNVFRSIOKAlCBQUk9EVUNUSU9OLU9OTFksIGluZGVwZW5kZW50IG9mIHRoZSBlbnYgZmlsdGVyLiBFdmVyeSBwcm9kdWN0aW9uXG4gIC8vIG91dGNvbWUgaW4td2luZG93OyBmb3IgYSAncmVzdG9yZWQnIG9uZSwgY29ycmVsYXRlIHRvIGl0cyBkZXBsb3knc1xuICAvLyBjb21wbGV0aW9uIHZpYSBleGVjdXRpb25faWQgdG8gbWVhc3VyZSByZXN0b3JlZC5hdCDiiJIgZGVwbG95ZWQuY29tcGxldGVkX2F0LlxuICAvLyBleGVjdXRpb25faWQgaXMgcHJvamVjdGVkIHNvIGFuIGluY2lkZW50LXNvdXJjZWQgcmVjb3ZlcnkgZm9yIHRoZSBzYW1lXG4gIC8vIGRlcGxveSB0YWtlcyBwcmVjZWRlbmNlIG92ZXIgdGhlIG1hbnVhbCByZXN0b3JlZCBnYXAuXG4gIGNvbnN0IG10dHJTcWwgPSBzcWxgXG4gICAgICBTRUxFQ1RcbiAgICAgICAgby5vdXRjb21lIEFTIG91dGNvbWUsXG4gICAgICAgIG8uZXhlY3V0aW9uX2lkIEFTIGV4ZWN1dGlvbl9pZCxcbiAgICAgICAgby5hdDo6dGV4dCBBUyByZXN0b3JlZF9hdCxcbiAgICAgICAgKFNFTEVDVCBNQVgoZC5jb21wbGV0ZWRfYXQpIEZST00gJHtzY2hlbWEucGlwZWxpbmVFdmVudH0gZFxuICAgICAgICAgICBKT0lOICR7c2NoZW1hLnBpcGVsaW5lfSBwMiBPTiBwMi5pZCA9IGQucGlwZWxpbmVfaWRcbiAgICAgICAgICAgV0hFUkUgcDIub3JnX2lkICR7cHJlZH0gQU5EIGQuZXhlY3V0aW9uX2lkID0gby5leGVjdXRpb25faWRcbiAgICAgICAgICAgICBBTkQgZC5ldmVudF90eXBlID0gJ1NUQUdFJyBBTkQgZC5lbnZpcm9ubWVudCA9ICR7SEVBRExJTkVfRU5WfSk6OnRleHQgQVMgZGVwbG95ZWRfYXRcbiAgICAgIEZST00gJHtzY2hlbWEuZGVwbG95bWVudE91dGNvbWV9IG9cbiAgICAgIFdIRVJFIG8ub3JnX2lkICR7cHJlZH0gQU5EIG8uZW52aXJvbm1lbnQgPSAke0hFQURMSU5FX0VOVn1cbiAgICAgICAgQU5EIG8uYXQgPj0gJHtmcm9tfTo6dGltZXN0YW1wdHogQU5EIG8uYXQgPD0gJHt0b306OnRpbWVzdGFtcHR6YDtcblxuICAvLyAoNSkgSW5jaWRlbnRzIG9wZW5lZCBpbi13aW5kb3cgKHBlciBlbnYpLiBDb3JyZWxhdGVkIGluIEpTIHRvIHRoZSBtb3N0XG4gIC8vIHJlY2VudCBzdWNjZXNzZnVsIGRlcGxveSDihpIgYXV0b21hdGVkIHBvc3QtZGVwbG95IGZhaWx1cmUgKENGUikgKyByZWFsXG4gIC8vIHJlY292ZXJ5IHRpbWUgKHJlc29sdmVkX2F0IOKIkiBvcGVuZWRfYXQpIGZvciBNVFRSIChwcm9kdWN0aW9uKS5cbiAgY29uc3QgaW5jaWRlbnRTcWwgPSBzcWxgXG4gICAgICBTRUxFQ1QgaS5lbnZpcm9ubWVudCBBUyBlbnZpcm9ubWVudCwgaS5vcGVuZWRfYXQ6OnRleHQgQVMgb3BlbmVkX2F0LCBpLnJlc29sdmVkX2F0Ojp0ZXh0IEFTIHJlc29sdmVkX2F0XG4gICAgICBGUk9NICR7c2NoZW1hLmluY2lkZW50fSBpXG4gICAgICBXSEVSRSBpLm9yZ19pZCAke3ByZWR9ICR7aW5jaWRlbnRFbnZDbGF1c2V9XG4gICAgICAgIEFORCBpLm9wZW5lZF9hdCA+PSAke2Zyb219Ojp0aW1lc3RhbXB0eiBBTkQgaS5vcGVuZWRfYXQgPD0gJHt0b306OnRpbWVzdGFtcHR6YDtcblxuICAvLyAoNCkgQ292ZXJhZ2U6IHJlZ2lzdGVyZWQgcGlwZWxpbmVzIHZzIHRob3NlIHRoYXQgYWN0dWFsbHkgZGVwbG95ZWQgaW4td2luZG93LlxuICBjb25zdCBjb3ZlcmFnZVNxbCA9IHNxbGBcbiAgICAgIFNFTEVDVFxuICAgICAgICAoU0VMRUNUIENPVU5UKCopOjppbnQgRlJPTSAke3NjaGVtYS5waXBlbGluZVJlZ2lzdHJ5fSByXG4gICAgICAgICAgIFdIRVJFIHIub3JnX2lkICR7cHJlZH0gJHtwaXBlbGluZUNsYXVzZVJ9KSBBUyByZWdpc3RlcmVkLFxuICAgICAgICAoU0VMRUNUIENPVU5UKERJU1RJTkNUIGUucGlwZWxpbmVfaWQpOjppbnQgRlJPTSAke3NjaGVtYS5waXBlbGluZUV2ZW50fSBlXG4gICAgICAgICAgIEpPSU4gJHtzY2hlbWEucGlwZWxpbmV9IHAgT04gcC5pZCA9IGUucGlwZWxpbmVfaWRcbiAgICAgICAgICAgV0hFUkUgcC5vcmdfaWQgJHtwcmVkfSBBTkQgZS5ldmVudF90eXBlID0gJ1NUQUdFJyBBTkQgZS5lbnZpcm9ubWVudCBJUyBOT1QgTlVMTFxuICAgICAgICAgICAgICR7cGlwZWxpbmVDbGF1c2V9ICR7ZW52Q2xhdXNlfVxuICAgICAgICAgICAgIEFORCBlLmNvbXBsZXRlZF9hdCA+PSAke2Zyb219Ojp0aW1lc3RhbXB0eiBBTkQgZS5jb21wbGV0ZWRfYXQgPD0gJHt0b306OnRpbWVzdGFtcHR6KSBBUyBkZXBsb3lpbmdgO1xuXG4gIGNvbnN0IGV4ZWMgPSAoKSA9PiB3aXRoVGVuYW50VHgoYXN5bmMgKHR4KSA9PiB7XG4gICAgLy8gU2NhbiBvcmRlciBpcyBsb2FkLWJlYXJpbmcgZm9yIHRoZSBnZW5lcmF0ZWQtU1FMIHRlc3RzOiAwPWRlcGxveSxcbiAgICAvLyAxPW91dGNvbWVzLCAyPW10dHIsIDM9Y292ZXJhZ2UsIDQ9aW5jaWRlbnRzIChhcHBlbmRlZCBsYXN0IHNvIHRoZVxuICAgIC8vIGVhcmxpZXIgaW5kaWNlcyBzdGF5IHN0YWJsZSkuXG4gICAgY29uc3QgZGVwbG95Um93cyA9IGRyaXp6bGVSb3dzPERlcGxveVJvdz4oKGF3YWl0IHR4LmV4ZWN1dGUoZGVwbG95U3FsKSkucm93cyk7XG4gICAgY29uc3Qgb3V0Y29tZVJvd3MgPSBkcml6emxlUm93czxPdXRjb21lUm93PigoYXdhaXQgdHguZXhlY3V0ZShvdXRjb21lU3FsKSkucm93cyk7XG4gICAgY29uc3QgbXR0clJvd3MgPSBkcml6emxlUm93czxNdHRyUGFpclJvdyAmIHsgb3V0Y29tZTogc3RyaW5nIH0+KChhd2FpdCB0eC5leGVjdXRlKG10dHJTcWwpKS5yb3dzKTtcbiAgICBjb25zdCBjb3ZlcmFnZVJvdyA9IGRyaXp6bGVSb3dzPENvdmVyYWdlUm93PigoYXdhaXQgdHguZXhlY3V0ZShjb3ZlcmFnZVNxbCkpLnJvd3MpWzBdO1xuICAgIGNvbnN0IGluY2lkZW50Um93cyA9IGRyaXp6bGVSb3dzPEluY2lkZW50Um93PigoYXdhaXQgdHguZXhlY3V0ZShpbmNpZGVudFNxbCkpLnJvd3MpO1xuICAgIHJldHVybiBzaGFwZURvcmEoZGVwbG95Um93cywgb3V0Y29tZVJvd3MsIG10dHJSb3dzLCBjb3ZlcmFnZVJvdywgaW5jaWRlbnRSb3dzLCBmcm9tLCB0bywge1xuICAgICAgcGlwZWxpbmVJZCwgZW52aXJvbm1lbnQsXG4gICAgfSwgZWZmZWN0aXZlV2luZG93SG91cnMpO1xuICB9KTtcbiAgLy8gQ2FjaGUga2V5IE1VU1QgaW5jbHVkZSB0aGUgZWZmZWN0aXZlIGluY2lkZW50IHdpbmRvdzogdGhlIHNjb3JlY2FyZCByZWFkc1xuICAvLyAvZG9yYSB3aXRoIHRoZSBvcmcncyBkZWZhdWx0IHdpbmRvdyB3aGlsZSBhbiBleHBsaWNpdCAvZG9yYSBjYWxsIG1heSBwYXNzIGFuXG4gIC8vIG92ZXJyaWRlIOKAlCBkaWZmZXJlbnQgd2luZG93cyB5aWVsZCBkaWZmZXJlbnQgQ0ZSL01UVFIsIHNvIHRoZXkgbXVzdCBub3RcbiAgLy8gY29sbGlkZSBvbiB0aGUgc2FtZSBrZXkuXG4gIGNvbnN0IGtleSA9IGAke29yZ0lkfTpkb3JhOiR7ZnJvbX06JHt0b306JHtwaXBlbGluZUlkID8/ICcnfToke2Vudmlyb25tZW50ID8/ICcnfToke2VmZmVjdGl2ZVdpbmRvd0hvdXJzfWA7XG4gIHJldHVybiBydW5SZXBvcnQoa2V5LCBtdWx0aSwgZXhlYyk7XG59XG5cbi8qKlxuICogU2hhcGUgdGhlIHJhdyBET1JBIHNjYW4gcm93cyBpbnRvIHRoZSBwdWJsaWMge0BsaW5rIERvcmFNZXRyaWNzfS4gQnVja2V0cyB0aGVcbiAqIHRlcm1pbmFsIGRlcGxveSByb3dzIGJ5IGVudmlyb25tZW50IChERiAvIGRlcGxveS10aW1lIENGUiAvIG1lYXN1cmVkIGxlYWQpLFxuICogZm9sZHMgaW4gdGhlIHBvc3QtZGVwbG95IGZhaWx1cmUgY291bnRzIChtYW51YWwgb3V0Y29tZXMgKyB3ZWJob29rLWluZ2VzdGVkXG4gKiBpbmNpZGVudHMsIGRlZHVwZWQgYnkgZGVwbG95IGV4ZWN1dGlvbiksIGNvbXB1dGVzIHByb2R1Y3Rpb24gTVRUUiBmcm9tIGJvdGhcbiAqIHNvdXJjZXMgKGluY2lkZW50IGByZXNvbHZlZF9hdCDiiJIgb3BlbmVkX2F0YCB0YWtpbmcgcHJlY2VkZW5jZSBvdmVyIHRoZSBtYW51YWxcbiAqIGByZXN0b3JlZCDiiJIgZGVwbG95ZWRgKSwgYW5kIHJlY29uY2lsZXMgY292ZXJhZ2Ug4oCUIGFsbCBjcm9zcy1zb3VyY2UgbWVkaWFuc1xuICogb3ZlciBkZWx0YXMgY2xhbXBlZCDiiaUwLlxuICpcbiAqIFBoYXNlIDUgaW5jaWRlbnQgY29ycmVsYXRpb246IGVhY2ggaW5jaWRlbnQgaXMgYXR0cmlidXRlZCB0byB0aGUgbW9zdCByZWNlbnRcbiAqIFNVQ0NFU1NGVUwgZGVwbG95IHRvIGl0cyBlbnZpcm9ubWVudCB3aXRoIGBjb21wbGV0ZWRfYXQg4omkIG9wZW5lZF9hdGAgd2l0aGluXG4gKiB7QGxpbmsgRE9SQV9JTkNJREVOVF9XSU5ET1dfSE9VUlN9LiBUaGF0IGRlcGxveSBpcyBhIHBvc3QtZGVwbG95IGZhaWx1cmU7IGFuXG4gKiB1bmNvcnJlbGF0ZWQgaW5jaWRlbnQgKG5vIGVsaWdpYmxlIGRlcGxveSkgY29udHJpYnV0ZXMgbm90aGluZy5cbiAqL1xuZnVuY3Rpb24gc2hhcGVEb3JhKFxuICBkZXBsb3lSb3dzOiBEZXBsb3lSb3dbXSxcbiAgb3V0Y29tZVJvd3M6IE91dGNvbWVSb3dbXSxcbiAgbXR0clJvd3M6IEFycmF5PE10dHJQYWlyUm93ICYgeyBvdXRjb21lOiBzdHJpbmcgfT4sXG4gIGNvdmVyYWdlUm93OiBDb3ZlcmFnZVJvdyB8IHVuZGVmaW5lZCxcbiAgaW5jaWRlbnRSb3dzOiBJbmNpZGVudFJvd1tdLFxuICBmcm9tOiBzdHJpbmcsXG4gIHRvOiBzdHJpbmcsXG4gIGZpbHRlcnM6IHsgcGlwZWxpbmVJZD86IHN0cmluZzsgZW52aXJvbm1lbnQ/OiBzdHJpbmcgfSxcbiAgd2luZG93SG91cnM6IG51bWJlciA9IERPUkFfSU5DSURFTlRfV0lORE9XX0hPVVJTLFxuKTogRG9yYU1ldHJpY3Mge1xuICAvLyBXaW5kb3cgbGVuZ3RoIGluIGRheXMgKGZsb29yZWQgYXQgMSBzbyBhIHN1Yi1kYXkgd2luZG93IHlpZWxkcyB0aGUgY291bnQgYXNcbiAgLy8gdGhlIHJhdGUsIG5ldmVyIC8wIG9yIGFuIGV4dHJhcG9sYXRpb24pLiBHdWFyZCB1bnBhcnNlYWJsZSBkYXRlcyDihpIgMSBkYXkuXG4gIGNvbnN0IHNwYW5EYXlzID0gKERhdGUucGFyc2UodG8pIC0gRGF0ZS5wYXJzZShmcm9tKSkgLyA4NjQwMDAwMDtcbiAgY29uc3QgZGF5cyA9IE51bWJlci5pc0Zpbml0ZShzcGFuRGF5cykgPyBNYXRoLm1heChzcGFuRGF5cywgMSkgOiAxO1xuICAvLyBXaW5kb3cgZW5kIGluc3RhbnQgZm9yIE1UVFIgcmlnaHQtY2Vuc29yaW5nIChhIHJlc29sdXRpb24gYWZ0ZXIgYHRvYCBpc1xuICAvLyB1bm9ic2VydmVkKS4gK0luZmluaXR5IHdoZW4gYHRvYCBpcyB1bnBhcnNlYWJsZSDih5Igbm8gY2Vuc29yaW5nIChmYWlsLW9wZW4pLlxuICBjb25zdCB0b1BhcnNlZCA9IERhdGUucGFyc2UodG8pO1xuICBjb25zdCB0b01zID0gTnVtYmVyLmlzRmluaXRlKHRvUGFyc2VkKSA/IHRvUGFyc2VkIDogTnVtYmVyLlBPU0lUSVZFX0lORklOSVRZO1xuXG4gIC8vIFBlci1lbnYgYWNjdW11bGF0b3IuXG4gIGludGVyZmFjZSBBY2Mge1xuICAgIGRlcGxveW1lbnRzOiBudW1iZXI7XG4gICAgZGVwbG95VGltZUZhaWx1cmVzOiBudW1iZXI7XG4gICAgYXR0ZW1wdHM6IG51bWJlcjtcbiAgICBsZWFkR2FwczogbnVtYmVyW107XG4gIH1cbiAgY29uc3QgZW52cyA9IG5ldyBNYXA8c3RyaW5nLCBBY2M+KCk7XG4gIGNvbnN0IGFjY0ZvciA9IChlbnY6IHN0cmluZyk6IEFjYyA9PiB7XG4gICAgbGV0IGEgPSBlbnZzLmdldChlbnYpO1xuICAgIGlmICghYSkgeyBhID0geyBkZXBsb3ltZW50czogMCwgZGVwbG95VGltZUZhaWx1cmVzOiAwLCBhdHRlbXB0czogMCwgbGVhZEdhcHM6IFtdIH07IGVudnMuc2V0KGVudiwgYSk7IH1cbiAgICByZXR1cm4gYTtcbiAgfTtcblxuICAvLyBBIGRlcGxveSByb3cgY291bnRzIHRvd2FyZCBERi9DRlIvbGVhZCBvbmx5IGlmIGl0IGNvbXBsZXRlZCBpbnNpZGUgW2Zyb20sdG9dLlxuICAvLyBMb29rLWJhY2stb25seSByb3dzIChpbl93aW5kb3c9ZmFsc2UsIGtlcHQgZm9yIGluY2lkZW50IGNvcnJlbGF0aW9uKSBhcmVcbiAgLy8gc2tpcHBlZCBoZXJlLiBBYnNlbnQgaW4gYSBmaXh0dXJlIOKHkiBpbi13aW5kb3cgKHVuaXQgdGVzdHMgb21pdCB0aGUgZmxhZykuXG4gIGNvbnN0IGlzSW5XaW5kb3cgPSAocm93OiBEZXBsb3lSb3cpOiBib29sZWFuID0+XG4gICAgcm93LmluX3dpbmRvdyA9PSBudWxsID8gdHJ1ZSA6IChyb3cuaW5fd2luZG93ID09PSB0cnVlIHx8IHJvdy5pbl93aW5kb3cgPT09ICd0Jyk7XG5cbiAgZm9yIChjb25zdCByb3cgb2YgZGVwbG95Um93cykge1xuICAgIGNvbnN0IGVudiA9IHJvdy5lbnZpcm9ubWVudDtcbiAgICBpZiAoIWVudikgY29udGludWU7XG4gICAgaWYgKCFpc0luV2luZG93KHJvdykpIGNvbnRpbnVlOyAvLyBsb29rLWJhY2stb25seSByb3cg4oaSIGNvcnJlbGF0aW9uLCBub3QgREYvQ0ZSXG4gICAgY29uc3QgYSA9IGFjY0ZvcihlbnYpO1xuICAgIGlmIChyb3cuc3RhdHVzID09PSAnU1VDQ0VFREVEJykge1xuICAgICAgYS5kZXBsb3ltZW50cysrO1xuICAgICAgYS5hdHRlbXB0cysrO1xuICAgICAgLy8gTWVhc3VyZWQgbGVhZCB0aW1lOiBkZXBsb3kgY29tcGxldGlvbiDiiJIgZXhlY3V0aW9uJ3MgZWFybGllc3QgY29tbWl0IHRpbWUuXG4gICAgICBpZiAocm93LmNvbW1pdF90cyAhPSBudWxsICYmIHJvdy5jb21wbGV0ZWRfYXQgIT0gbnVsbCkge1xuICAgICAgICBjb25zdCBnYXAgPSAoRGF0ZS5wYXJzZShyb3cuY29tcGxldGVkX2F0KSAtIERhdGUucGFyc2Uocm93LmNvbW1pdF90cykpIC8gMTAwMDtcbiAgICAgICAgaWYgKE51bWJlci5pc0Zpbml0ZShnYXApKSBhLmxlYWRHYXBzLnB1c2goTWF0aC5tYXgoZ2FwLCAwKSk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChyb3cuc3RhdHVzID09PSAnRkFJTEVEJykge1xuICAgICAgYS5kZXBsb3lUaW1lRmFpbHVyZXMrKztcbiAgICAgIGEuYXR0ZW1wdHMrKztcbiAgICB9XG4gIH1cblxuICAvLyBJbmNpZGVudOKGkmRlcGxveSBjb3JyZWxhdGlvbiAoUGhhc2UgNSkuIEluZGV4IHRoZSBTVUNDRVNTRlVMIGRlcGxveXMgcGVyIGVudlxuICAvLyAoZXhlY3V0aW9uIGlkICsgY29tcGxldGlvbiBpbnN0YW50KSwgbmV3ZXN0IGZpcnN0LCBzbyBlYWNoIGluY2lkZW50IGNhbiBiZVxuICAvLyBhdHRyaWJ1dGVkIHRvIHRoZSBtb3N0IHJlY2VudCBkZXBsb3kgdGhhdCBjb21wbGV0ZWQgd2l0aGluIHRoZSB3aW5kb3dcbiAgLy8gYmVmb3JlIGl0IG9wZW5lZC4gVGhhdCBleGVjdXRpb24gaXMgYSBwb3N0LWRlcGxveSBmYWlsdXJlLlxuICBjb25zdCB3aW5kb3dNcyA9IHdpbmRvd0hvdXJzICogMzYwMCAqIDEwMDA7XG4gIGNvbnN0IHN1Y2Nlc3NmdWxCeUVudiA9IG5ldyBNYXA8c3RyaW5nLCBBcnJheTx7IGV4ZWM6IHN0cmluZzsgY29tcGxldGVkTXM6IG51bWJlciB9Pj4oKTtcbiAgZm9yIChjb25zdCByb3cgb2YgZGVwbG95Um93cykge1xuICAgIGlmIChyb3cuc3RhdHVzICE9PSAnU1VDQ0VFREVEJyB8fCAhcm93LmVudmlyb25tZW50IHx8IHJvdy5leGVjdXRpb25faWQgPT0gbnVsbCB8fCByb3cuY29tcGxldGVkX2F0ID09IG51bGwpIGNvbnRpbnVlO1xuICAgIGNvbnN0IG1zID0gRGF0ZS5wYXJzZShyb3cuY29tcGxldGVkX2F0KTtcbiAgICBpZiAoIU51bWJlci5pc0Zpbml0ZShtcykpIGNvbnRpbnVlO1xuICAgIGNvbnN0IGxpc3QgPSBzdWNjZXNzZnVsQnlFbnYuZ2V0KHJvdy5lbnZpcm9ubWVudCkgPz8gW107XG4gICAgbGlzdC5wdXNoKHsgZXhlYzogcm93LmV4ZWN1dGlvbl9pZCwgY29tcGxldGVkTXM6IG1zIH0pO1xuICAgIHN1Y2Nlc3NmdWxCeUVudi5zZXQocm93LmVudmlyb25tZW50LCBsaXN0KTtcbiAgfVxuICBmb3IgKGNvbnN0IGxpc3Qgb2Ygc3VjY2Vzc2Z1bEJ5RW52LnZhbHVlcygpKSBsaXN0LnNvcnQoKGEsIGIpID0+IGIuY29tcGxldGVkTXMgLSBhLmNvbXBsZXRlZE1zKTtcblxuICAvKiogQ29ycmVsYXRlIGFuIGluY2lkZW50IHRvIHRoZSBtb3N0IHJlY2VudCBpbi13aW5kb3cgc3VjY2Vzc2Z1bCBkZXBsb3kgZXhlY3V0aW9uLiAqL1xuICBjb25zdCBjb3JyZWxhdGUgPSAoZW52OiBzdHJpbmcsIG9wZW5lZE1zOiBudW1iZXIpOiBzdHJpbmcgfCBudWxsID0+IHtcbiAgICBjb25zdCBsaXN0ID0gc3VjY2Vzc2Z1bEJ5RW52LmdldChlbnYpO1xuICAgIGlmICghbGlzdCkgcmV0dXJuIG51bGw7XG4gICAgZm9yIChjb25zdCBkIG9mIGxpc3QpIHtcbiAgICAgIGlmIChkLmNvbXBsZXRlZE1zIDw9IG9wZW5lZE1zICYmIG9wZW5lZE1zIC0gZC5jb21wbGV0ZWRNcyA8PSB3aW5kb3dNcykgcmV0dXJuIGQuZXhlYztcbiAgICB9XG4gICAgcmV0dXJuIG51bGw7XG4gIH07XG5cbiAgLy8gQ29ycmVsYXRlZCBpbmNpZGVudHMgKGFsbCBlbnZzIOKGkiBDRlI7IHByb2R1Y3Rpb24gc3Vic2V0IOKGkiBNVFRSKS4gRWFjaCBjYXJyaWVzXG4gIC8vIHRoZSBkZXBsb3kgZXhlY3V0aW9uIGl0IGF0dHJpYnV0ZXMgdG8gKyBpdHMgcmVhbCByZWNvdmVyeSBnYXAgKHdoZW4gcmVzb2x2ZWQpLlxuICBpbnRlcmZhY2UgQ29ycmVsYXRlZEluY2lkZW50IHsgZW52aXJvbm1lbnQ6IHN0cmluZzsgZXhlYzogc3RyaW5nOyByZXNvbHZlZEdhcDogbnVtYmVyIHwgbnVsbCB9XG4gIGNvbnN0IGNvcnJlbGF0ZWRJbmNpZGVudHM6IENvcnJlbGF0ZWRJbmNpZGVudFtdID0gW107XG4gIGZvciAoY29uc3QgaW5jIG9mIGluY2lkZW50Um93cykge1xuICAgIGlmICghaW5jLmVudmlyb25tZW50IHx8IGluYy5vcGVuZWRfYXQgPT0gbnVsbCkgY29udGludWU7XG4gICAgY29uc3Qgb3BlbmVkTXMgPSBEYXRlLnBhcnNlKGluYy5vcGVuZWRfYXQpO1xuICAgIGlmICghTnVtYmVyLmlzRmluaXRlKG9wZW5lZE1zKSkgY29udGludWU7XG4gICAgY29uc3QgZXhlYyA9IGNvcnJlbGF0ZShpbmMuZW52aXJvbm1lbnQsIG9wZW5lZE1zKTtcbiAgICBpZiAoZXhlYyA9PSBudWxsKSBjb250aW51ZTsgLy8gdW5jb3JyZWxhdGVkIOKGkiBub3QgYXR0cmlidXRhYmxlIHRvIGEgZGVwbG95XG4gICAgbGV0IHJlc29sdmVkR2FwOiBudW1iZXIgfCBudWxsID0gbnVsbDtcbiAgICBpZiAoaW5jLnJlc29sdmVkX2F0ICE9IG51bGwpIHtcbiAgICAgIGNvbnN0IHJlc29sdmVkTXMgPSBEYXRlLnBhcnNlKGluYy5yZXNvbHZlZF9hdCk7XG4gICAgICAvLyBSaWdodC1jZW5zb3Jpbmc6IGEgcmVzb2x1dGlvbiBBRlRFUiB0aGUgd2luZG93IGVuZCAoYHRvYCkgaXMgbm90IG9ic2VydmVkXG4gICAgICAvLyB3aXRoaW4gdGhlIHdpbmRvdyDigJQgdHJlYXQgdGhlIGluY2lkZW50IGFzIHN0aWxsIG9wZW4gKGdhcCBzdGF5cyBudWxsKSBzb1xuICAgICAgLy8gTVRUUiBuZXZlciByZWFkcyB1bmJvdW5kZWQgcGFzdCBgdG9gLiBUaGUgaW5jaWRlbnQgc3RpbGwgY291bnRzIHRvd2FyZCB0aGVcbiAgICAgIC8vIGluY2lkZW50IHRvdGFsICh1bnJlc29sdmVkKSwgaXQganVzdCBjb250cmlidXRlcyBubyByZWNvdmVyeSB0aW1lLlxuICAgICAgaWYgKE51bWJlci5pc0Zpbml0ZShyZXNvbHZlZE1zKSAmJiByZXNvbHZlZE1zIDw9IHRvTXMpIHtcbiAgICAgICAgY29uc3QgZyA9IChyZXNvbHZlZE1zIC0gb3BlbmVkTXMpIC8gMTAwMDtcbiAgICAgICAgaWYgKE51bWJlci5pc0Zpbml0ZShnKSkgcmVzb2x2ZWRHYXAgPSBNYXRoLm1heChnLCAwKTtcbiAgICAgIH1cbiAgICB9XG4gICAgY29ycmVsYXRlZEluY2lkZW50cy5wdXNoKHsgZW52aXJvbm1lbnQ6IGluYy5lbnZpcm9ubWVudCwgZXhlYywgcmVzb2x2ZWRHYXAgfSk7XG4gIH1cblxuICAvLyBQb3N0LWRlcGxveSBmYWlsdXJlcyBwZXIgZW52IOKAlCB0aGUgRElTVElOQ1Qgc2V0IG9mIGRlcGxveSBleGVjdXRpb25zIGZsYWdnZWRcbiAgLy8gYnkgYSBtYW51YWwgYGZhaWxlZGAgb3V0Y29tZSBPUiBhIGNvcnJlbGF0ZWQgaW5jaWRlbnQgKGRlZHVwZWQgYnkgZXhlY3V0aW9uLFxuICAvLyBzbyBhIGRlcGxveSBmbGFnZ2VkIGJ5IGJvdGggY291bnRzIG9uY2UpLiBBIG1hbnVhbCBvdXRjb21lIHdpdGhvdXQgYW5cbiAgLy8gZXhlY3V0aW9uIGlkIChsZWdhY3kvdW5yZXNvbHZhYmxlKSBzdGlsbCBjb3VudHMgdmlhIGEgcGVyLXJvdyBzZW50aW5lbC5cbiAgY29uc3QgcG9zdERlcGxveUV4ZWNzQnlFbnYgPSBuZXcgTWFwPHN0cmluZywgU2V0PHN0cmluZz4+KCk7XG4gIGxldCBzZW50aW5lbCA9IDA7XG4gIGNvbnN0IGZsYWdGb3IgPSAoZW52OiBzdHJpbmcpOiBTZXQ8c3RyaW5nPiA9PiB7XG4gICAgbGV0IHMgPSBwb3N0RGVwbG95RXhlY3NCeUVudi5nZXQoZW52KTtcbiAgICBpZiAoIXMpIHsgcyA9IG5ldyBTZXQ8c3RyaW5nPigpOyBwb3N0RGVwbG95RXhlY3NCeUVudi5zZXQoZW52LCBzKTsgfVxuICAgIHJldHVybiBzO1xuICB9O1xuICBmb3IgKGNvbnN0IG8gb2Ygb3V0Y29tZVJvd3MpIHtcbiAgICBpZiAoby5vdXRjb21lICE9PSAnZmFpbGVkJyB8fCAhby5lbnZpcm9ubWVudCkgY29udGludWU7XG4gICAgZmxhZ0ZvcihvLmVudmlyb25tZW50KS5hZGQoby5leGVjdXRpb25faWQgPz8gYG1hbnVhbC0ke3NlbnRpbmVsKyt9YCk7XG4gICAgYWNjRm9yKG8uZW52aXJvbm1lbnQpOyAvLyBlbnN1cmUgZW52IGFwcGVhcnMgZXZlbiB3aXRoIG5vIGRlcGxveSBldmVudHNcbiAgfVxuICBmb3IgKGNvbnN0IGNpIG9mIGNvcnJlbGF0ZWRJbmNpZGVudHMpIHtcbiAgICBmbGFnRm9yKGNpLmVudmlyb25tZW50KS5hZGQoY2kuZXhlYyk7XG4gICAgYWNjRm9yKGNpLmVudmlyb25tZW50KTsgLy8gZW5zdXJlIGVudiBhcHBlYXJzIGV2ZW4gaWYgdGhlIGRlcGxveSBpcyBvdXQtb2Ytd2luZG93XG4gIH1cbiAgY29uc3QgcG9zdERlcGxveUJ5RW52ID0gbmV3IE1hcDxzdHJpbmcsIG51bWJlcj4oKTtcbiAgZm9yIChjb25zdCBbZW52LCBzZXRdIG9mIHBvc3REZXBsb3lFeGVjc0J5RW52KSBwb3N0RGVwbG95QnlFbnYuc2V0KGVudiwgc2V0LnNpemUpO1xuXG4gIGNvbnN0IGVudmlyb25tZW50czogRG9yYUVudk1ldHJpY3NbXSA9IFsuLi5lbnZzLmVudHJpZXMoKV1cbiAgICAubWFwKChbZW52aXJvbm1lbnQsIGFdKSA9PiB7XG4gICAgICBjb25zdCBwb3N0RGVwbG95RmFpbHVyZXMgPSBwb3N0RGVwbG95QnlFbnYuZ2V0KGVudmlyb25tZW50KSA/PyAwO1xuICAgICAgY29uc3QgcmF3UGVyRGF5ID0gYS5kZXBsb3ltZW50cyAvIGRheXM7XG4gICAgICBjb25zdCBudW1lcmF0b3IgPSBhLmRlcGxveVRpbWVGYWlsdXJlcyArIHBvc3REZXBsb3lGYWlsdXJlcztcbiAgICAgIC8vIENsYW1wIHRvIFswLDEwMF06IHBvc3QtZGVwbG95IGZhaWx1cmVzIGNhbiBjb3JyZWxhdGUgdG8gYSBkZXBsb3kgdGhhdFxuICAgICAgLy8gY29tcGxldGVkIGp1c3QgYmVmb3JlIHRoZSB3aW5kb3cgKGtlcHQgZm9yIGNvcnJlbGF0aW9uKSwgc28gdGhlIG51bWVyYXRvclxuICAgICAgLy8gY2FuIHRyYW5zaWVudGx5IGV4Y2VlZCBpbi13aW5kb3cgYXR0ZW1wdHMg4oCUIGEgQ0ZSID4gMTAwJSBpcyBuZXZlciB2YWxpZC5cbiAgICAgIGNvbnN0IHJhd1JhdGUgPSBhLmF0dGVtcHRzID4gMCA/IE1hdGgubWluKDEwMCwgKG51bWVyYXRvciAvIGEuYXR0ZW1wdHMpICogMTAwKSA6IDA7XG4gICAgICBjb25zdCBsdE1lZGlhbiA9IG1lZGlhbihhLmxlYWRHYXBzKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGVudmlyb25tZW50LFxuICAgICAgICBkZXBsb3ltZW50RnJlcXVlbmN5OiB7XG4gICAgICAgICAgZGVwbG95bWVudHM6IGEuZGVwbG95bWVudHMsXG4gICAgICAgICAgcGVyRGF5OiByb3VuZChyYXdQZXJEYXksIDIpLFxuICAgICAgICAgIGxldmVsOiBkb3JhTGV2ZWxGb3JGcmVxdWVuY3kocmF3UGVyRGF5LCBhLmRlcGxveW1lbnRzKSxcbiAgICAgICAgfSxcbiAgICAgICAgY2hhbmdlRmFpbHVyZVJhdGU6IHtcbiAgICAgICAgICByYXRlOiByb3VuZChyYXdSYXRlLCAxKSxcbiAgICAgICAgICBkZXBsb3lUaW1lRmFpbHVyZXM6IGEuZGVwbG95VGltZUZhaWx1cmVzLFxuICAgICAgICAgIHBvc3REZXBsb3lGYWlsdXJlcyxcbiAgICAgICAgICBhdHRlbXB0czogYS5hdHRlbXB0cyxcbiAgICAgICAgICBsZXZlbDogZG9yYUxldmVsRm9yQ2hhbmdlRmFpbHVyZShyYXdSYXRlLCBhLmF0dGVtcHRzKSxcbiAgICAgICAgfSxcbiAgICAgICAgbGVhZFRpbWU6IHtcbiAgICAgICAgICBkZXBsb3ltZW50czogYS5sZWFkR2Fwcy5sZW5ndGgsXG4gICAgICAgICAgbWVkaWFuU2Vjb25kczogbHRNZWRpYW4gIT0gbnVsbCA/IHJvdW5kKGx0TWVkaWFuLCAxKSA6IG51bGwsXG4gICAgICAgICAgbGV2ZWw6IGRvcmFMZXZlbEZvckxlYWRUaW1lKGx0TWVkaWFuKSxcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgfSlcbiAgICAuc29ydCgoeCwgeSkgPT4ge1xuICAgICAgaWYgKHguZW52aXJvbm1lbnQgPT09IEhFQURMSU5FX0VOVikgcmV0dXJuIC0xO1xuICAgICAgaWYgKHkuZW52aXJvbm1lbnQgPT09IEhFQURMSU5FX0VOVikgcmV0dXJuIDE7XG4gICAgICByZXR1cm4geC5lbnZpcm9ubWVudC5sb2NhbGVDb21wYXJlKHkuZW52aXJvbm1lbnQpO1xuICAgIH0pO1xuXG4gIC8vIE1UVFIg4oCUIFBST0RVQ1RJT04tT05MWSwgZnJvbSBCT1RIIHNvdXJjZXMuIEluY2lkZW50cyB0YWtlIHByZWNlZGVuY2U6IGFcbiAgLy8gcHJvZHVjdGlvbiBpbmNpZGVudCBjb3JyZWxhdGVkIHRvIGEgZGVwbG95IGNvbnRyaWJ1dGVzIHRoZSByZWFsIHJlY292ZXJ5XG4gIC8vIChgcmVzb2x2ZWRfYXQg4oiSIG9wZW5lZF9hdGApLCBhbmQgYW55IG1hbnVhbCBvdXRjb21lIG9uIHRoZSBTQU1FIGRlcGxveSBpc1xuICAvLyBza2lwcGVkLiBNYW51YWwgb3V0Y29tZXMgb24gb3RoZXIgZGVwbG95cyBrZWVwIHRoZSBQaGFzZS0yIGJlaGF2aW9yXG4gIC8vIChgZmFpbGVkYCDihpIgYW4gaW5jaWRlbnQ7IGByZXN0b3JlZGAg4oaSIGEgcmVjb3ZlcnkgZ2FwIG9mIGByZXN0b3JlZCDiiJIgZGVwbG95ZWRgKS5cbiAgbGV0IGluY2lkZW50cyA9IDAsIHJlc3RvcmVkID0gMDtcbiAgY29uc3QgbXR0ckdhcHM6IG51bWJlcltdID0gW107XG4gIC8vIEV4ZWN1dGlvbnMgYWxyZWFkeSBhdHRyaWJ1dGVkIHRvIGEgcHJvZHVjdGlvbiBpbmNpZGVudCAocHJlY2VkZW5jZSBndWFyZCkuXG4gIGNvbnN0IGluY2lkZW50RXhlY3MgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgZm9yIChjb25zdCBjaSBvZiBjb3JyZWxhdGVkSW5jaWRlbnRzKSB7XG4gICAgaWYgKGNpLmVudmlyb25tZW50ICE9PSBIRUFETElORV9FTlYpIGNvbnRpbnVlOyAvLyBNVFRSIGlzIHByb2R1Y3Rpb24tb25seVxuICAgIGluY2lkZW50cysrO1xuICAgIGluY2lkZW50RXhlY3MuYWRkKGNpLmV4ZWMpO1xuICAgIGlmIChjaS5yZXNvbHZlZEdhcCAhPSBudWxsKSB7IHJlc3RvcmVkKys7IG10dHJHYXBzLnB1c2goY2kucmVzb2x2ZWRHYXApOyB9XG4gIH1cbiAgZm9yIChjb25zdCBtIG9mIG10dHJSb3dzKSB7XG4gICAgLy8gSW5jaWRlbnQgcHJlY2VkZW5jZTogZG9uJ3QgZG91YmxlLWNvdW50IGEgZGVwbG95IGFscmVhZHkgZmxhZ2dlZCBieSBhbiBpbmNpZGVudC5cbiAgICBpZiAobS5leGVjdXRpb25faWQgIT0gbnVsbCAmJiBpbmNpZGVudEV4ZWNzLmhhcyhtLmV4ZWN1dGlvbl9pZCkpIGNvbnRpbnVlO1xuICAgIGlmIChtLm91dGNvbWUgPT09ICdmYWlsZWQnKSB7IGluY2lkZW50cysrOyBjb250aW51ZTsgfVxuICAgIGlmIChtLm91dGNvbWUgPT09ICdyZXN0b3JlZCcpIHtcbiAgICAgIHJlc3RvcmVkKys7XG4gICAgICBpZiAobS5yZXN0b3JlZF9hdCAhPSBudWxsICYmIG0uZGVwbG95ZWRfYXQgIT0gbnVsbCkge1xuICAgICAgICBjb25zdCBnYXAgPSAoRGF0ZS5wYXJzZShtLnJlc3RvcmVkX2F0KSAtIERhdGUucGFyc2UobS5kZXBsb3llZF9hdCkpIC8gMTAwMDtcbiAgICAgICAgaWYgKE51bWJlci5pc0Zpbml0ZShnYXApKSBtdHRyR2Fwcy5wdXNoKE1hdGgubWF4KGdhcCwgMCkpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuICBjb25zdCBtdHRyTWVkaWFuID0gbWVkaWFuKG10dHJHYXBzKTtcblxuICBjb25zdCByZWdpc3RlcmVkID0gTnVtYmVyKGNvdmVyYWdlUm93Py5yZWdpc3RlcmVkKSB8fCAwO1xuICBjb25zdCBkZXBsb3lpbmcgPSBOdW1iZXIoY292ZXJhZ2VSb3c/LmRlcGxveWluZykgfHwgMDtcblxuICByZXR1cm4ge1xuICAgIHdpbmRvdzogeyBmcm9tLCB0byB9LFxuICAgIGZpbHRlcnM6IHsgcGlwZWxpbmVJZDogZmlsdGVycy5waXBlbGluZUlkID8/IG51bGwsIGVudmlyb25tZW50OiBmaWx0ZXJzLmVudmlyb25tZW50ID8/IG51bGwgfSxcbiAgICBoZWFkbGluZTogSEVBRExJTkVfRU5WLFxuICAgIGVudmlyb25tZW50cyxcbiAgICBtZWFuVGltZVRvUmVzdG9yZToge1xuICAgICAgaW5jaWRlbnRzLFxuICAgICAgcmVzdG9yZWQsXG4gICAgICBtZWRpYW5TZWNvbmRzOiBtdHRyTWVkaWFuICE9IG51bGwgPyByb3VuZChtdHRyTWVkaWFuLCAxKSA6IG51bGwsXG4gICAgICBsZXZlbDogZG9yYUxldmVsRm9yUmVzdG9yZShtdHRyTWVkaWFuKSxcbiAgICB9LFxuICAgIGNvdmVyYWdlOiB7XG4gICAgICByZWdpc3RlcmVkLFxuICAgICAgZGVwbG95aW5nLFxuICAgICAgd2l0aG91dERlcGxveXM6IE1hdGgubWF4KHJlZ2lzdGVyZWQgLSBkZXBsb3lpbmcsIDApLFxuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogMS45YiBET1JBIHRyZW5kIOKAlCBkZXBsb3ltZW50IGZyZXF1ZW5jeSArIGRlcGxveS10aW1lIGNoYW5nZS1mYWlsdXJlIHJhdGVcbiAqIGJ1Y2tldGVkIGJ5IGBpbnRlcnZhbGAgKGRheS93ZWVrL21vbnRoKSBmb3IgYSBzcGFya2xpbmUuIERlcGxveS1iYXNpcyBsaWtlXG4gKiBnZXREb3JhTWV0cmljczogYnVja2V0cyB0ZXJtaW5hbCBkZXBsb3ktc3RhZ2UgZXhlY3V0aW9ucyBvbiB0aGVpclxuICogYGNvbXBsZXRlZF9hdGAuIFNoYXJlcyB0aGUgb3JnL3JvbGx1cCArIHBpcGVsaW5lSWQvZW52aXJvbm1lbnQgc2NvcGluZy4gTVRUUixcbiAqIGxlYWQgdGltZSwgYW5kIHBvc3QtZGVwbG95IENGUiBhcmUgaW50ZW50aW9uYWxseSBvbWl0dGVkICh0b28gaGVhdnkgdG8gYnVja2V0KS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldERvcmFUcmVuZChcbiAgb3JnSWQ6IHN0cmluZyxcbiAgaW50ZXJ2YWw6IHN0cmluZyxcbiAgZnJvbTogc3RyaW5nLFxuICB0bzogc3RyaW5nLFxuICBvcmdJZHM/OiBzdHJpbmdbXSxcbiAgb3B0czogRG9yYU9wdGlvbnMgPSB7fSxcbik6IFByb21pc2U8RG9yYVRyZW5kUG9pbnRbXT4ge1xuICBhc3NlcnRSZXBvcnRJbnRlcnZhbChpbnRlcnZhbCk7XG4gIGNvbnN0IHsgcHJlZCwgbXVsdGkgfSA9IG9yZ1Njb3BlKG9yZ0lkLCBvcmdJZHMpO1xuICBjb25zdCB7IHBpcGVsaW5lSWQsIGVudmlyb25tZW50IH0gPSBvcHRzO1xuICBjb25zdCBwaXBlbGluZUNsYXVzZSA9IHBpcGVsaW5lSWQgPyBzcWxgQU5EIGUucGlwZWxpbmVfaWQgPSAke3BpcGVsaW5lSWR9YCA6IHNxbGBgO1xuICBjb25zdCBlbnZDbGF1c2UgPSBlbnZpcm9ubWVudCA/IHNxbGBBTkQgZS5lbnZpcm9ubWVudCA9ICR7ZW52aXJvbm1lbnR9YCA6IHNxbGBgO1xuICBjb25zdCBleGVjID0gKCkgPT4gd2l0aFRlbmFudFR4KCh0eCkgPT4gdHguZXhlY3V0ZShzcWxgXG4gICAgICBXSVRIIGRlcGxveXMgQVMgKFxuICAgICAgICAtLSBQRVItRVhFQ1VUSU9OIGRlcGxveSB1bml0IChEMSk6IG9uZSByb3cgcGVyIChlbnZpcm9ubWVudCwgZXhlY3V0aW9uKSxcbiAgICAgICAgLS0gZXZlcnkgZGVwbG95IFNUQUdFIHRvIHRoYXQgZW52IHdpdGhpbiB0aGUgZXhlY3V0aW9uIHJvbGxlZCB1cCB0byBhXG4gICAgICAgIC0tIHNpbmdsZSB0ZXJtaW5hbCBzdGF0dXMg4oCUIE5PVCBvbmUgcm93IHBlciBzdGFnZS5cbiAgICAgICAgU0VMRUNUXG4gICAgICAgICAgJHt0ZXJtaW5hbFN0YXR1c1JvbGx1cH0gQVMgc3RhdHVzLFxuICAgICAgICAgIE1BWChlLmNvbXBsZXRlZF9hdCkgQVMgY29tcGxldGVkX2F0XG4gICAgICAgIEZST00gJHtzY2hlbWEucGlwZWxpbmVFdmVudH0gZVxuICAgICAgICBKT0lOICR7c2NoZW1hLnBpcGVsaW5lfSBwIE9OIHAuaWQgPSBlLnBpcGVsaW5lX2lkXG4gICAgICAgIFdIRVJFIHAub3JnX2lkICR7cHJlZH0gQU5EIGUuZXZlbnRfdHlwZSA9ICdTVEFHRScgQU5EIGUuZW52aXJvbm1lbnQgSVMgTk9UIE5VTExcbiAgICAgICAgICAke3BpcGVsaW5lQ2xhdXNlfSAke2VudkNsYXVzZX1cbiAgICAgICAgICBBTkQgZS5jb21wbGV0ZWRfYXQgPj0gJHtmcm9tfTo6dGltZXN0YW1wdHogQU5EIGUuY29tcGxldGVkX2F0IDw9ICR7dG99Ojp0aW1lc3RhbXB0elxuICAgICAgICBHUk9VUCBCWSBlLmVudmlyb25tZW50LCBlLmV4ZWN1dGlvbl9pZFxuICAgICAgKVxuICAgICAgU0VMRUNUXG4gICAgICAgIERBVEVfVFJVTkMoJHtpbnRlcnZhbH0sIGNvbXBsZXRlZF9hdCk6OnRleHQgQVMgcGVyaW9kLFxuICAgICAgICBDT1VOVCgqKSBGSUxURVIgKFdIRVJFIHN0YXR1cyA9ICdTVUNDRUVERUQnKTo6aW50IEFTIGRlcGxveW1lbnRzLFxuICAgICAgICBDT1VOVCgqKSBGSUxURVIgKFdIRVJFIHN0YXR1cyA9ICdGQUlMRUQnKTo6aW50IEFTIGZhaWxlZCxcbiAgICAgICAgQ09VTlQoKikgRklMVEVSIChXSEVSRSBzdGF0dXMgSU4gKCdTVUNDRUVERUQnLCAnRkFJTEVEJykpOjppbnQgQVMgdG90YWwsXG4gICAgICAgIC0tIHF1b3RlZCBhbGlhcyBzbyB0aGUgcmF3IHJvdyBrZXkgaXMgZXhhY3RseSB0aGUgRG9yYVRyZW5kUG9pbnQgZmllbGRcbiAgICAgICAgQ09BTEVTQ0UoUk9VTkQoQ09VTlQoKikgRklMVEVSIChXSEVSRSBzdGF0dXMgPSAnRkFJTEVEJyk6Om51bWVyaWNcbiAgICAgICAgICAvIE5VTExJRihDT1VOVCgqKSBGSUxURVIgKFdIRVJFIHN0YXR1cyBJTiAoJ1NVQ0NFRURFRCcsICdGQUlMRUQnKSksIDApICogMTAwLCAxKSwgMCk6OmZsb2F0IEFTIFwiY2hhbmdlRmFpbHVyZVBjdFwiXG4gICAgICBGUk9NIGRlcGxveXNcbiAgICAgIEdST1VQIEJZIHBlcmlvZCBPUkRFUiBCWSBwZXJpb2RcbiAgICBgKS50aGVuKHIgPT4gZHJpenpsZVJvd3M8RG9yYVRyZW5kUG9pbnQ+KHIucm93cykpKTtcbiAgY29uc3Qga2V5ID0gYCR7b3JnSWR9OmRvcmEtdHJlbmQ6JHtpbnRlcnZhbH06JHtmcm9tfToke3RvfToke3BpcGVsaW5lSWQgPz8gJyd9OiR7ZW52aXJvbm1lbnQgPz8gJyd9YDtcbiAgcmV0dXJuIHJ1blJlcG9ydChrZXksIG11bHRpLCBleGVjKTtcbn1cblxuLyoqXG4gKiAxLjEwIFBlci1waXBlbGluZSBCVUlMRCBIRUFMVEggKFBoYXNlIDYpIOKAlCBhIHN0YW5kYXJkIChOT1QgYGFkdmFuY2VkX3JlcG9ydGluZ2ApXG4gKiBwZXItc3RhZ2UgYnJlYWtkb3duIGZvciBvbmUgcGlwZWxpbmUgb3ZlciBhIFtmcm9tLHRvXSB3aW5kb3cuIEFnZ3JlZ2F0ZXMgdGhlXG4gKiBleGlzdGluZyBgcGlwZWxpbmVfZXZlbnRzYCBTVEFHRSByb3dzOiBlYWNoIHN0YWdlIGlzIHJvbGxlZCB1cCBwZXIgZXhlY3V0aW9uXG4gKiB0byBhIHRlcm1pbmFsIHN0YXR1cyAoRkFJTEVEIHdpbnMsIHRoZW4gU1VDQ0VFREVEKSArIGl0cyBtYXggZHVyYXRpb24sIHRoZW5cbiAqIGdyb3VwZWQgcGVyIHN0YWdlIGludG8gcnVuL3N1Y2Nlc3MvZmFpbHVyZSBjb3VudHMsIGEgc3VjY2VzcyByYXRlLCBhbmRcbiAqIGR1cmF0aW9uIHBlcmNlbnRpbGVzIChwNTAvcDkwL3A5OSkuIFRvdGFscyBzdW0gYWNyb3NzIHN0YWdlcy4gT3JnLXNjb3BlZCB2aWFcbiAqIHRoZSBwaXBlbGluZSBqb2luIChgcC5vcmdfaWQgJHtwcmVkfWApICsgcm9sbHVwLWF3YXJlIGxpa2UgdGhlIHNpYmxpbmcgcmVwb3J0cztcbiAqIGEgcGlwZWxpbmVJZCBvd25lZCBieSBhbm90aGVyIG9yZyByZXR1cm5zIGFuIGVtcHR5IGJyZWFrZG93bi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldEJ1aWxkSGVhbHRoKFxuICBvcmdJZDogc3RyaW5nLFxuICBwaXBlbGluZUlkOiBzdHJpbmcsXG4gIGZyb206IHN0cmluZyxcbiAgdG86IHN0cmluZyxcbiAgb3JnSWRzPzogc3RyaW5nW10sXG4pOiBQcm9taXNlPEJ1aWxkSGVhbHRoPiB7XG4gIGNvbnN0IHsgcHJlZCwgbXVsdGkgfSA9IG9yZ1Njb3BlKG9yZ0lkLCBvcmdJZHMpO1xuICBjb25zdCBleGVjID0gKCkgPT4gd2l0aFRlbmFudFR4KGFzeW5jICh0eCkgPT4ge1xuICAgIGNvbnN0IHJvd3MgPSBkcml6emxlUm93czxCdWlsZEhlYWx0aFN0YWdlPigoYXdhaXQgdHguZXhlY3V0ZShzcWxgXG4gICAgICBXSVRIIHN0YWdlX3J1bnMgQVMgKFxuICAgICAgICBTRUxFQ1RcbiAgICAgICAgICBlLnN0YWdlX25hbWUgQVMgc3RhZ2UsXG4gICAgICAgICAgJHt0ZXJtaW5hbFN0YXR1c1JvbGx1cH0gQVMgc3RhdHVzLFxuICAgICAgICAgIE1BWChlLmR1cmF0aW9uX21zKSBBUyBkdXJhdGlvbl9tc1xuICAgICAgICBGUk9NICR7c2NoZW1hLnBpcGVsaW5lRXZlbnR9IGVcbiAgICAgICAgSk9JTiAke3NjaGVtYS5waXBlbGluZX0gcCBPTiBwLmlkID0gZS5waXBlbGluZV9pZFxuICAgICAgICBXSEVSRSBwLm9yZ19pZCAke3ByZWR9IEFORCBlLnBpcGVsaW5lX2lkID0gJHtwaXBlbGluZUlkfVxuICAgICAgICAgIEFORCBlLmV2ZW50X3R5cGUgPSAnU1RBR0UnIEFORCBlLnN0YWdlX25hbWUgSVMgTk9UIE5VTExcbiAgICAgICAgICBBTkQgZS5zdGFydGVkX2F0ID49ICR7ZnJvbX06OnRpbWVzdGFtcHR6IEFORCBlLnN0YXJ0ZWRfYXQgPD0gJHt0b306OnRpbWVzdGFtcHR6XG4gICAgICAgIEdST1VQIEJZIGUuc3RhZ2VfbmFtZSwgZS5leGVjdXRpb25faWRcbiAgICAgIClcbiAgICAgIFNFTEVDVFxuICAgICAgICBzdGFnZSxcbiAgICAgICAgQ09VTlQoKik6OmludCBBUyBydW5zLFxuICAgICAgICBDT1VOVCgqKSBGSUxURVIgKFdIRVJFIHN0YXR1cyA9ICdTVUNDRUVERUQnKTo6aW50IEFTIHN1Y2Nlc3NlcyxcbiAgICAgICAgQ09VTlQoKikgRklMVEVSIChXSEVSRSBzdGF0dXMgPSAnRkFJTEVEJyk6OmludCBBUyBmYWlsdXJlcyxcbiAgICAgICAgQ09BTEVTQ0UoUk9VTkQoQ09VTlQoKikgRklMVEVSIChXSEVSRSBzdGF0dXMgPSAnU1VDQ0VFREVEJyk6Om51bWVyaWNcbiAgICAgICAgICAvIE5VTExJRihDT1VOVCgqKSwgMCkgKiAxMDAsIDEpLCAwKTo6ZmxvYXQgQVMgXCJzdWNjZXNzUmF0ZVwiLFxuICAgICAgICBQRVJDRU5USUxFX0NPTlQoMC41KSBXSVRISU4gR1JPVVAgKE9SREVSIEJZIGR1cmF0aW9uX21zKTo6aW50IEFTIFwicDUwTXNcIixcbiAgICAgICAgUEVSQ0VOVElMRV9DT05UKDAuOSkgV0lUSElOIEdST1VQIChPUkRFUiBCWSBkdXJhdGlvbl9tcyk6OmludCBBUyBcInA5ME1zXCIsXG4gICAgICAgIFBFUkNFTlRJTEVfQ09OVCgwLjk5KSBXSVRISU4gR1JPVVAgKE9SREVSIEJZIGR1cmF0aW9uX21zKTo6aW50IEFTIFwicDk5TXNcIlxuICAgICAgRlJPTSBzdGFnZV9ydW5zXG4gICAgICBXSEVSRSBzdGF0dXMgSU4gKCdTVUNDRUVERUQnLCAnRkFJTEVEJylcbiAgICAgIEdST1VQIEJZIHN0YWdlXG4gICAgICBPUkRFUiBCWSBydW5zIERFU0MsIHN0YWdlXG4gICAgYCkpLnJvd3MpO1xuXG4gICAgLy8gVG90YWxzIHN1bSBhY3Jvc3Mgc3RhZ2VzICh0b3RhbCBzdGFnZS1ydW5zIC8gc3RhZ2UtZmFpbHVyZXMgaW4gdGhlIHdpbmRvdykuXG4gICAgbGV0IHJ1bnMgPSAwLCBmYWlsdXJlcyA9IDA7XG4gICAgZm9yIChjb25zdCBzIG9mIHJvd3MpIHsgcnVucyArPSBzLnJ1bnM7IGZhaWx1cmVzICs9IHMuZmFpbHVyZXM7IH1cbiAgICBjb25zdCBmYWlsdXJlUmF0ZSA9IHJ1bnMgPiAwID8gcm91bmQoKGZhaWx1cmVzIC8gcnVucykgKiAxMDAsIDEpIDogMDtcbiAgICByZXR1cm4geyBzdGFnZXM6IHJvd3MsIHRvdGFsczogeyBydW5zLCBmYWlsdXJlcywgZmFpbHVyZVJhdGUgfSB9O1xuICB9KTtcbiAgcmV0dXJuIHJ1blJlcG9ydChgJHtvcmdJZH06YnVpbGQtaGVhbHRoOiR7cGlwZWxpbmVJZH06JHtmcm9tfToke3RvfWAsIG11bHRpLCBleGVjKTtcbn1cblxuLy8g4pSA4pSAIENhdGVnb3J5IDI6IFBsdWdpbiBJbnZlbnRvcnkgJiBCdWlsZHMg4pSA4pSAXG5cbi8qKiAyLjEgUGx1Z2luIHN1bW1hcnkg4oCUIGNvdW50cyBhbmQgYnJlYWtkb3ducy5cbiAqICBJTlRFTlRJT05BTExZIFNJTkdMRS1PUkcgKG5vIHJvbGx1cCk6IHBsdWdpbiBpbnZlbnRvcnkgaXMgYW4gb3JnLW93bmVkXG4gKiAgYXNzZXQgY291bnQsIG5vdCBhbiBleGVjdXRpb24vYnVpbGQgYWN0aXZpdHkgcmVwb3J0LCBzbyBhIHBhcmVudCdzIHZpZXcgaXNcbiAqICBpdHMgb3duIHBsdWdpbnMg4oCUIHRlYW1zIG1hbmFnZSB0aGVpciBvd24gaW52ZW50b3J5LiBTaW5nbGUgYD0gJG9yZ2Agc2NvcGUuICovXG4iXX0=
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the org-scope predicate for a report query. With `orgIds` (the
|
|
3
|
+
* org → team rollup — a parent's `[self, ...descendants]`) it becomes an
|
|
4
|
+
* `IN (...)` over the subtree; otherwise the single-org `= $org`. Returns a
|
|
5
|
+
* `multi` flag so callers can run multi-org reads under sysadmin context
|
|
6
|
+
* (the subtree spans orgs outside the request's RLS scope) and salt the
|
|
7
|
+
* cache key.
|
|
8
|
+
*/
|
|
9
|
+
export declare function orgScope(orgId: string, orgIds?: string[]): {
|
|
10
|
+
pred: import("drizzle-orm").SQL<unknown>;
|
|
11
|
+
multi: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Run a report read. Single-org reads use the per-org cache (invalidated on
|
|
15
|
+
* that org's event ingest). Rollup (multi-org) reads **bypass the cache** and
|
|
16
|
+
* run under sysadmin context: they're admin-only and lower-frequency, and a
|
|
17
|
+
* child org's event ingest can't invalidate a parent's rollup entry (reporting
|
|
18
|
+
* has no org tree), so caching them would serve stale aggregates. Always fresh.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runReport<T>(cacheKey: string, multi: boolean, exec: () => Promise<T>): Promise<T>;
|
|
21
|
+
/** 1.1 Execution count per pipeline with status breakdown. */
|