@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.
@@ -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. */