@pipeline-builder/pipeline-data 4.1.22 → 4.1.23
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/crud-service.js +2 -71
- package/lib/api/keyset-cursor.d.ts +38 -0
- package/lib/api/keyset-cursor.js +82 -0
- package/lib/api/plugin-advisories.d.ts +30 -0
- package/lib/api/plugin-advisories.js +89 -0
- package/lib/api/plugin-consumption-policy.d.ts +44 -0
- package/lib/api/plugin-consumption-policy.js +133 -0
- package/lib/api/plugin-resolution.d.ts +2 -54
- package/lib/api/plugin-resolution.js +5 -211
- package/lib/api/public-directory.d.ts +3 -7
- package/lib/api/public-directory.js +2 -2
- package/lib/api/reporting/caches.d.ts +5 -0
- package/lib/api/reporting/caches.js +11 -1
- package/lib/api/reporting/dora-scoring.d.ts +1 -1
- package/lib/api/reporting/dora-scoring.js +2 -2
- package/lib/api/reporting/ingest.d.ts +11 -0
- package/lib/api/reporting/ingest.js +210 -0
- package/lib/api/reporting/retention-sweep.d.ts +17 -0
- package/lib/api/reporting/retention-sweep.js +155 -0
- package/lib/api/reporting/retention.d.ts +1 -1
- package/lib/api/reporting/retention.js +2 -2
- package/lib/api/reporting-service.d.ts +5 -18
- package/lib/api/reporting-service.js +17 -333
- package/lib/database/index.d.ts +2 -2
- package/lib/database/index.js +2 -2
- package/lib/database/retry-strategy.d.ts +0 -6
- package/lib/database/retry-strategy.js +4 -12
- package/lib/database/schema/ecosystem.d.ts +9 -7
- package/lib/database/schema/ecosystem.js +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.js +4 -2
- package/lib/testing/exports.json +0 -1
- package/package.json +2 -2
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Event ingest: the WRITE half of reporting. Resolves incoming pipeline events
|
|
5
|
+
* against the pipeline registry, scrubs every free-form string, batch-inserts
|
|
6
|
+
* the matched rows idempotently, and invalidates the affected orgs' report
|
|
7
|
+
* caches. Separate from the report queries because this is reporting's only
|
|
8
|
+
* durable persistence boundary — everything else in the service only reads.
|
|
9
|
+
*/
|
|
10
|
+
import { createLogger, errorMessage, scrubAwsIdentifiers } from '@pipeline-builder/api-core';
|
|
11
|
+
import { inArray } from 'drizzle-orm';
|
|
12
|
+
import { invalidateOrgReports } from './caches.js';
|
|
13
|
+
import { scrubOptional } from './sql-helpers.js';
|
|
14
|
+
import { schema } from '../../database/drizzle-schema.js';
|
|
15
|
+
import { withTenantTx } from '../../database/tenancy.js';
|
|
16
|
+
const logger = createLogger('reporting-ingest');
|
|
17
|
+
/** An action/build event that can be attributed to a plugin step. */
|
|
18
|
+
function isAttributable(e) {
|
|
19
|
+
return (e.eventType === 'ACTION' || e.eventType === 'BUILD')
|
|
20
|
+
&& (e.eventSource === 'codepipeline' || e.eventSource === 'codebuild')
|
|
21
|
+
&& !!e.stageName && !!e.actionName;
|
|
22
|
+
}
|
|
23
|
+
function stepKey(pipelineId, stageName, actionName) {
|
|
24
|
+
return `${pipelineId}\u0000${stageName}\u0000${actionName}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Plugin attribution: the step manifests of every pipeline with an attributable
|
|
28
|
+
* event, in ONE read per batch, keyed for an in-memory join. Names are compared
|
|
29
|
+
* post-scrub on both sides (the manifest stores them scrubbed the same way).
|
|
30
|
+
*/
|
|
31
|
+
async function loadStepPlugins(tx, attributable) {
|
|
32
|
+
const pipelineIds = [...new Set(attributable.map((e) => e.pipelineId))];
|
|
33
|
+
if (pipelineIds.length === 0)
|
|
34
|
+
return new Map();
|
|
35
|
+
const rows = await tx
|
|
36
|
+
.select({
|
|
37
|
+
pipelineId: schema.pipelineStepManifest.pipelineId,
|
|
38
|
+
orgId: schema.pipelineStepManifest.orgId,
|
|
39
|
+
stageName: schema.pipelineStepManifest.stageName,
|
|
40
|
+
actionName: schema.pipelineStepManifest.actionName,
|
|
41
|
+
pluginPublisher: schema.pipelineStepManifest.pluginPublisher,
|
|
42
|
+
pluginPublisherId: schema.pipelineStepManifest.pluginPublisherId,
|
|
43
|
+
pluginName: schema.pipelineStepManifest.pluginName,
|
|
44
|
+
pluginVersion: schema.pipelineStepManifest.pluginVersion,
|
|
45
|
+
})
|
|
46
|
+
.from(schema.pipelineStepManifest)
|
|
47
|
+
.where(inArray(schema.pipelineStepManifest.pipelineId, pipelineIds));
|
|
48
|
+
return new Map(rows.map((m) => [stepKey(m.pipelineId, m.stageName, m.actionName), m]));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The persisted row for one ingested event. This is the DURABLE persistence
|
|
52
|
+
* boundary: every user/AWS-derived free-form string is scrubbed here, and
|
|
53
|
+
* tenancy comes from the pipeline registry, never the caller's claimed org.
|
|
54
|
+
*/
|
|
55
|
+
function toEventRow(event, registry, plugin) {
|
|
56
|
+
return {
|
|
57
|
+
// registry.pipelineId === event.pipelineId; the registry's is always a row
|
|
58
|
+
// that exists (FK) and its orgId is the trusted tenant.
|
|
59
|
+
pipelineId: registry.pipelineId,
|
|
60
|
+
orgId: registry.orgId,
|
|
61
|
+
eventSource: event.eventSource,
|
|
62
|
+
eventType: event.eventType,
|
|
63
|
+
status: event.status,
|
|
64
|
+
// An AWS-ASSIGNED UUID, not free-form text: it can't carry an account id,
|
|
65
|
+
// and scrubbing a 12-digit run inside it would corrupt the correlation key.
|
|
66
|
+
executionId: event.executionId,
|
|
67
|
+
// USER-AUTHORED pipeline-structure names — same untrusted origin as
|
|
68
|
+
// errorMessage, so scrubbed (a stage named with an ARN must not persist).
|
|
69
|
+
stageName: scrubOptional(event.stageName),
|
|
70
|
+
actionName: scrubOptional(event.actionName),
|
|
71
|
+
// HARD CONSTRAINT: an AWS account id must NEVER be persisted. Failure detail
|
|
72
|
+
// and messages routinely carry ARNs and bare 12-digit account ids.
|
|
73
|
+
errorMessage: scrubOptional(event.errorMessage),
|
|
74
|
+
startedAt: event.startedAt ? new Date(event.startedAt) : undefined,
|
|
75
|
+
completedAt: event.completedAt ? new Date(event.completedAt) : undefined,
|
|
76
|
+
durationMs: event.durationMs,
|
|
77
|
+
// Deploy attribution — scrubbed like the other free-form strings.
|
|
78
|
+
commitSha: scrubOptional(event.commitSha),
|
|
79
|
+
commitRef: scrubOptional(event.commitRef),
|
|
80
|
+
environment: scrubOptional(event.environment),
|
|
81
|
+
// Measured lead time: the oldest unshipped commit's time + the count. An
|
|
82
|
+
// SCM-derived instant and a plain integer — nothing to scrub.
|
|
83
|
+
commitTimestamp: event.commitTimestamp ? new Date(event.commitTimestamp) : undefined,
|
|
84
|
+
commitCount: event.commitCount,
|
|
85
|
+
// NULL for non-plugin actions and for pipelines deployed without a manifest.
|
|
86
|
+
pluginPublisher: plugin?.pluginPublisher ?? null,
|
|
87
|
+
pluginPublisherId: plugin?.pluginPublisherId ?? null,
|
|
88
|
+
pluginName: plugin?.pluginName ?? null,
|
|
89
|
+
pluginVersion: plugin?.pluginVersion ?? null,
|
|
90
|
+
detail: event.detail !== undefined ? scrubAwsIdentifiers(event.detail) : undefined,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Fan terminal STAGE outcomes into the route's metric hook (pipeline-data can't
|
|
95
|
+
* import api-server's registry) after the insert commits. Driven off the
|
|
96
|
+
* INSERTED rows, so a re-delivered event the dedup index swallowed is never
|
|
97
|
+
* counted twice. A non-null environment marks a deploy.
|
|
98
|
+
*/
|
|
99
|
+
function emitStageMetrics(rows, onMetric) {
|
|
100
|
+
for (const r of rows) {
|
|
101
|
+
if (r.eventType !== 'STAGE' || (r.status !== 'SUCCEEDED' && r.status !== 'FAILED') || !r.pipelineId)
|
|
102
|
+
continue;
|
|
103
|
+
onMetric({
|
|
104
|
+
pipelineId: r.pipelineId,
|
|
105
|
+
orgId: r.orgId,
|
|
106
|
+
stage: r.stageName ?? '',
|
|
107
|
+
environment: r.environment ?? null,
|
|
108
|
+
result: r.status === 'SUCCEEDED' ? 'succeeded' : 'failed',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve incoming events against the pipeline registry, batch-insert the
|
|
114
|
+
* matched ones, and invalidate reporting caches for affected orgs.
|
|
115
|
+
* Events for unregistered pipeline ids are dropped (and logged at WARN
|
|
116
|
+
* with sample ids so an operator can see when EventBridge is delivering
|
|
117
|
+
* events for pipelines that haven't called POST /pipelines/registry yet).
|
|
118
|
+
*
|
|
119
|
+
* Returns counts + a sample of unregistered pipeline ids for observability.
|
|
120
|
+
*/
|
|
121
|
+
export async function ingestEvents(events, onMetric) {
|
|
122
|
+
// Multi-org batch insert: the caller resolves to multiple orgs via the
|
|
123
|
+
// pipeline-registry lookup below, so the route layer MUST establish a
|
|
124
|
+
// `runWithTenantContext({ isSuperAdmin: true }, ...)` scope before calling
|
|
125
|
+
// this function. Under FORCE'd RLS, a single tx with `app.org_id = <one
|
|
126
|
+
// org>` could only write events for that org; bypass via sysadmin is
|
|
127
|
+
// the right gate for this server-internal cross-tenant endpoint. See
|
|
128
|
+
// api/reporting/src/routes/event-ingest.ts for the wrapper.
|
|
129
|
+
// Caches are invalidated AFTER the tx resolves: doing it inside held the pg
|
|
130
|
+
// locks open across unrelated cache round-trips. The TTL is 2-5 min, so a
|
|
131
|
+
// fire-and-forget post-commit invalidation is an acceptable trade.
|
|
132
|
+
const { inserted, skipped, unregisteredPipelineIds, affectedOrgs, insertedRows } = await withTenantTx(async (tx) => {
|
|
133
|
+
// Batch-resolve all unique pipeline ids in one query
|
|
134
|
+
const uniqueIds = [...new Set(events.map(e => e.pipelineId))];
|
|
135
|
+
const registryRows = await tx
|
|
136
|
+
.select({
|
|
137
|
+
pipelineId: schema.pipelineRegistry.pipelineId,
|
|
138
|
+
orgId: schema.pipelineRegistry.orgId,
|
|
139
|
+
})
|
|
140
|
+
.from(schema.pipelineRegistry)
|
|
141
|
+
.where(inArray(schema.pipelineRegistry.pipelineId, uniqueIds));
|
|
142
|
+
const idMap = new Map(registryRows.map(r => [r.pipelineId, r]));
|
|
143
|
+
const stepPlugins = await loadStepPlugins(tx, events.filter((e) => isAttributable(e) && idMap.has(e.pipelineId)));
|
|
144
|
+
// Build insert batch (skip events whose pipeline isn't registered)
|
|
145
|
+
const rows = [];
|
|
146
|
+
let skippedLocal = 0;
|
|
147
|
+
const unregisteredLocal = [];
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
const registry = idMap.get(event.pipelineId);
|
|
150
|
+
if (!registry) {
|
|
151
|
+
skippedLocal++;
|
|
152
|
+
unregisteredLocal.push(event.pipelineId);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const stageName = scrubOptional(event.stageName);
|
|
156
|
+
const actionName = scrubOptional(event.actionName);
|
|
157
|
+
const step = isAttributable(event) && stageName && actionName
|
|
158
|
+
? stepPlugins.get(stepKey(registry.pipelineId, stageName, actionName))
|
|
159
|
+
: undefined;
|
|
160
|
+
// Same-org guard: a manifest row is only ever written by the org that
|
|
161
|
+
// owns the pipeline, but the ingest runs cross-tenant, so don't lean on it.
|
|
162
|
+
rows.push(toEventRow(event, registry, step && step.orgId === registry.orgId ? step : undefined));
|
|
163
|
+
}
|
|
164
|
+
// SQS is at-least-once, so EventBridge can deliver the same state-change
|
|
165
|
+
// twice. `onConflictDoNothing` + the partial unique index on
|
|
166
|
+
// (pipeline_id, execution_id, event_type, status, stage_name, action_name)
|
|
167
|
+
// makes re-delivery idempotent. `returning` gives the REAL inserted set so
|
|
168
|
+
// counts, metrics and cache invalidation all ignore duplicates.
|
|
169
|
+
const landed = rows.length > 0
|
|
170
|
+
? await tx.insert(schema.pipelineEvent).values(rows)
|
|
171
|
+
.onConflictDoNothing()
|
|
172
|
+
.returning({
|
|
173
|
+
orgId: schema.pipelineEvent.orgId,
|
|
174
|
+
pipelineId: schema.pipelineEvent.pipelineId,
|
|
175
|
+
eventType: schema.pipelineEvent.eventType,
|
|
176
|
+
status: schema.pipelineEvent.status,
|
|
177
|
+
stageName: schema.pipelineEvent.stageName,
|
|
178
|
+
environment: schema.pipelineEvent.environment,
|
|
179
|
+
})
|
|
180
|
+
: [];
|
|
181
|
+
return {
|
|
182
|
+
inserted: landed.length,
|
|
183
|
+
skipped: skippedLocal,
|
|
184
|
+
unregisteredPipelineIds: unregisteredLocal,
|
|
185
|
+
affectedOrgs: [...new Set(landed.map(r => r.orgId))],
|
|
186
|
+
insertedRows: landed,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
if (onMetric)
|
|
190
|
+
emitStageMetrics(insertedRows, onMetric);
|
|
191
|
+
// Surface the silent skip: an unregistered pipeline id usually means the
|
|
192
|
+
// pipeline hasn't called POST /pipelines/registry yet (or its
|
|
193
|
+
// pb.pipeline-id tag is missing/unreadable by the Lambda). Logging it
|
|
194
|
+
// makes a broken join visible instead of looking like "no activity".
|
|
195
|
+
if (unregisteredPipelineIds.length > 0) {
|
|
196
|
+
logger.warn('Pipeline events skipped: pipeline id not found in registry', {
|
|
197
|
+
count: unregisteredPipelineIds.length,
|
|
198
|
+
sample: unregisteredPipelineIds.slice(0, 3),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// Post-commit cache invalidation. Fire-and-forget with logging — TTL is
|
|
202
|
+
// short enough that a missed invalidation self-heals.
|
|
203
|
+
if (affectedOrgs.length > 0) {
|
|
204
|
+
void Promise.all(affectedOrgs.map((org) => invalidateOrgReports(org).catch((err) => {
|
|
205
|
+
logger.warn('Reporting cache invalidation failed', { orgId: org, error: errorMessage(err) });
|
|
206
|
+
})));
|
|
207
|
+
}
|
|
208
|
+
return { inserted, skipped, unregisteredPipelineIds, affectedOrgs };
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5nZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwaS9yZXBvcnRpbmcvaW5nZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUM3RixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNuRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFakQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQzFELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUd6RCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsa0JBQWtCLENBQUMsQ0FBQztBQWNoRCxxRUFBcUU7QUFDckUsU0FBUyxjQUFjLENBQUMsQ0FBYztJQUNwQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsS0FBSyxRQUFRLElBQUksQ0FBQyxDQUFDLFNBQVMsS0FBSyxPQUFPLENBQUM7V0FDdkQsQ0FBQyxDQUFDLENBQUMsV0FBVyxLQUFLLGNBQWMsSUFBSSxDQUFDLENBQUMsV0FBVyxLQUFLLFdBQVcsQ0FBQztXQUNuRSxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQztBQUN2QyxDQUFDO0FBRUQsU0FBUyxPQUFPLENBQUMsVUFBa0IsRUFBRSxTQUFpQixFQUFFLFVBQWtCO0lBQ3hFLE9BQU8sR0FBRyxVQUFVLFNBQVMsU0FBUyxTQUFTLFVBQVUsRUFBRSxDQUFDO0FBQzlELENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsS0FBSyxVQUFVLGVBQWUsQ0FBQyxF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QUFDLFlBQVksRUFBRSxRQUFRLENBQUMsQ0FBQztJQUV2RCx5RUFBeUU7SUFDekUsOERBQThEO0lBQzlELHNFQUFzRTtJQUN0RSxxRUFBcUU7SUFDckUsSUFBSSx1QkFBdUIsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDdkMsTUFBTSxDQUFDLElBQUksQ0FBQyw0REFBNEQsRUFBRTtZQUN4RSxLQUFLLEVBQUUsdUJBQXVCLENBQUMsTUFBTTtZQUNyQyxNQUFNLEVBQUUsdUJBQXVCLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDNUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELHdFQUF3RTtJQUN4RSxzREFBc0Q7SUFDdEQsSUFBSSxZQUFZLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzVCLEtBQUssT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FDeEMsb0JBQW9CLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7WUFDdEMsTUFBTSxDQUFDLElBQUksQ0FBQyxxQ0FBcUMsRUFBRSxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLFlBQVksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDL0YsQ0FBQyxDQUFDLENBQ0gsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLHVCQUF1QixFQUFFLFlBQVksRUFBRSxDQUFDO0FBQ3RFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBFdmVudCBpbmdlc3Q6IHRoZSBXUklURSBoYWxmIG9mIHJlcG9ydGluZy4gUmVzb2x2ZXMgaW5jb21pbmcgcGlwZWxpbmUgZXZlbnRzXG4gKiBhZ2FpbnN0IHRoZSBwaXBlbGluZSByZWdpc3RyeSwgc2NydWJzIGV2ZXJ5IGZyZWUtZm9ybSBzdHJpbmcsIGJhdGNoLWluc2VydHNcbiAqIHRoZSBtYXRjaGVkIHJvd3MgaWRlbXBvdGVudGx5LCBhbmQgaW52YWxpZGF0ZXMgdGhlIGFmZmVjdGVkIG9yZ3MnIHJlcG9ydFxuICogY2FjaGVzLiBTZXBhcmF0ZSBmcm9tIHRoZSByZXBvcnQgcXVlcmllcyBiZWNhdXNlIHRoaXMgaXMgcmVwb3J0aW5nJ3Mgb25seVxuICogZHVyYWJsZSBwZXJzaXN0ZW5jZSBib3VuZGFyeSDigJQgZXZlcnl0aGluZyBlbHNlIGluIHRoZSBzZXJ2aWNlIG9ubHkgcmVhZHMuXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyLCBlcnJvck1lc3NhZ2UsIHNjcnViQXdzSWRlbnRpZmllcnMgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBpbkFycmF5IH0gZnJvbSAnZHJpenpsZS1vcm0nO1xuaW1wb3J0IHsgaW52YWxpZGF0ZU9yZ1JlcG9ydHMgfSBmcm9tICcuL2NhY2hlcy5qcyc7XG5pbXBvcnQgeyBzY3J1Yk9wdGlvbmFsIH0gZnJvbSAnLi9zcWwtaGVscGVycy5qcyc7XG5pbXBvcnQgdHlwZSB7IEluZ2VzdEV2ZW50LCBJbmdlc3RNZXRyaWMsIEluZ2VzdFJlc3VsdCB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgc2NoZW1hIH0gZnJvbSAnLi4vLi4vZGF0YWJhc2UvZHJpenpsZS1zY2hlbWEuanMnO1xuaW1wb3J0IHsgd2l0aFRlbmFudFR4IH0gZnJvbSAnLi4vLi4vZGF0YWJhc2UvdGVuYW5jeS5qcyc7XG5pbXBvcnQgdHlwZSB7IENydWRUeCB9IGZyb20gJy4uL2NydWQtc2VydmljZS5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcigncmVwb3J0aW5nLWluZ2VzdCcpO1xuXG50eXBlIFBpcGVsaW5lRXZlbnRJbnNlcnQgPSB0eXBlb2Ygc2NoZW1hLnBpcGVsaW5lRXZlbnQuJGluZmVySW5zZXJ0O1xuXG5cbi8qKiBUaGUgcGx1Z2luIGEgcGlwZWxpbmUgc3RlcCBydW5zLCBmcm9tIHRoZSBzdGVwIG1hbmlmZXN0IGl0cyBsYXN0IGRlcGxveSByZWdpc3RlcmVkLiAqL1xuaW50ZXJmYWNlIFN0ZXBQbHVnaW4ge1xuICBvcmdJZDogc3RyaW5nO1xuICBwbHVnaW5QdWJsaXNoZXI6IHN0cmluZyB8IG51bGw7XG4gIHBsdWdpblB1Ymxpc2hlcklkOiBzdHJpbmcgfCBudWxsO1xuICBwbHVnaW5OYW1lOiBzdHJpbmcgfCBudWxsO1xuICBwbHVnaW5WZXJzaW9uOiBzdHJpbmcgfCBudWxsO1xufVxuXG4vKiogQW4gYWN0aW9uL2J1aWxkIGV2ZW50IHRoYXQgY2FuIGJlIGF0dHJpYnV0ZWQgdG8gYSBwbHVnaW4gc3RlcC4gKi9cbmZ1bmN0aW9uIGlzQXR0cmlidXRhYmxlKGU6IEluZ2VzdEV2ZW50KTogYm9vbGVhbiB7XG4gIHJldHVybiAoZS5ldmVudFR5cGUgPT09ICdBQ1RJT04nIHx8IGUuZXZlbnRUeXBlID09PSAnQlVJTEQnKVxuICAgICYmIChlLmV2ZW50U291cmNlID09PSAnY29kZXBpcGVsaW5lJyB8fCBlLmV2ZW50U291cmNlID09PSAnY29kZWJ1aWxkJylcbiAgICAmJiAhIWUuc3RhZ2VOYW1lICYmICEhZS5hY3Rpb25OYW1lO1xufVxuXG5mdW5jdGlvbiBzdGVwS2V5KHBpcGVsaW5lSWQ6IHN0cmluZywgc3RhZ2VOYW1lOiBzdHJpbmcsIGFjdGlvbk5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBgJHtwaXBlbGluZUlkfVxcdTAwMDAke3N0YWdlTmFtZX1cXHUwMDAwJHthY3Rpb25OYW1lfWA7XG59XG5cbi8qKlxuICogUGx1Z2luIGF0dHJpYnV0aW9uOiB0aGUgc3RlcCBtYW5pZmVzdHMgb2YgZXZlcnkgcGlwZWxpbmUgd2l0aCBhbiBhdHRyaWJ1dGFibGVcbiAqIGV2ZW50LCBpbiBPTkUgcmVhZCBwZXIgYmF0Y2gsIGtleWVkIGZvciBhbiBpbi1tZW1vcnkgam9pbi4gTmFtZXMgYXJlIGNvbXBhcmVkXG4gKiBwb3N0LXNjcnViIG9uIGJvdGggc2lkZXMgKHRoZSBtYW5pZmVzdCBzdG9yZXMgdGhlbSBzY3J1YmJlZCB0aGUgc2FtZSB3YXkpLlxuICovXG5hc3luYyBmdW5jdGlvbiBsb2FkU3RlcFBsdWdpbnModHg6IENydWRUeCwgYXR0cmlidXRhYmxlOiBJbmdlc3RFdmVudFtdKTogUHJvbWlzZTxNYXA8c3RyaW5nLCBTdGVwUGx1Z2luPj4ge1xuICBjb25zdCBwaXBlbGluZUlkcyA9IFsuLi5uZXcgU2V0KGF0dHJpYnV0YWJsZS5tYXAoKGUpID0+IGUucGlwZWxpbmVJZCkpXTtcbiAgaWYgKHBpcGVsaW5lSWRzLmxlbmd0aCA9PT0gMCkgcmV0dXJuIG5ldyBNYXAoKTtcbiAgY29uc3Qgcm93cyA9IGF3YWl0IHR4XG4gICAgLnNlbGVjdCh7XG4gICAgICBwaXBlbGluZUlkOiBzY2hlbWEucGlwZWxpbmVTdGVwTWFuaWZlc3QucGlwZWxpbmVJZCxcbiAgICAgIG9yZ0lkOiBzY2hlbWEucGlwZWxpbmVTdGVwTWFuaWZlc3Qub3JnSWQsXG4gICAgICBzdGFnZU5hbWU6IHNjaGVtYS5waXBlbGluZVN0ZXBNYW5pZmVzdC5zdGFnZU5hbWUsXG4gICAgICBhY3Rpb25OYW1lOiBzY2hlbWEucGlwZWxpbmVTdGVwTWFuaWZlc3QuYWN0aW9uTmFtZSxcbiAgICAgIHBsdWdpblB1Ymxpc2hlcjogc2NoZW1hLnBpcGVsaW5lU3RlcE1hbmlmZXN0LnBsdWdpblB1Ymxpc2hlcixcbiAgICAgIHBsdWdpblB1Ymxpc2hlcklkOiBzY2hlbWEucGlwZWxpbmVTdGVwTWFuaWZlc3QucGx1Z2luUHVibGlzaGVySWQsXG4gICAgICBwbHVnaW5OYW1lOiBzY2hlbWEucGlwZWxpbmVTdGVwTWFuaWZlc3QucGx1Z2luTmFtZSxcbiAgICAgIHBsdWdpblZlcnNpb246IHNjaGVtYS5waXBlbGluZVN0ZXBNYW5pZmVzdC5wbHVnaW5WZXJzaW9uLFxuICAgIH0pXG4gICAgLmZyb20oc2NoZW1hLnBpcGVsaW5lU3RlcE1hbmlmZXN0KVxuICAgIC53aGVyZShpbkFycmF5KHNjaGVtYS5waXBlbGluZVN0ZXBNYW5pZmVzdC5waXBlbGluZUlkLCBwaXBlbGluZUlkcykpO1xuICByZXR1cm4gbmV3IE1hcChyb3dzLm1hcCgobSkgPT4gW3N0ZXBLZXkobS5waXBlbGluZUlkLCBtLnN0YWdlTmFtZSwgbS5hY3Rpb25OYW1lKSwgbV0pKTtcbn1cblxuLyoqXG4gKiBUaGUgcGVyc2lzdGVkIHJvdyBmb3Igb25lIGluZ2VzdGVkIGV2ZW50LiBUaGlzIGlzIHRoZSBEVVJBQkxFIHBlcnNpc3RlbmNlXG4gKiBib3VuZGFyeTogZXZlcnkgdXNlci9BV1MtZGVyaXZlZCBmcmVlLWZvcm0gc3RyaW5nIGlzIHNjcnViYmVkIGhlcmUsIGFuZFxuICogdGVuYW5jeSBjb21lcyBmcm9tIHRoZSBwaXBlbGluZSByZWdpc3RyeSwgbmV2ZXIgdGhlIGNhbGxlcidzIGNsYWltZWQgb3JnLlxuICovXG5mdW5jdGlvbiB0b0V2ZW50Um93KGV2ZW50OiBJbmdlc3RFdmVudCwgcmVnaXN0cnk6IHsgcGlwZWxpbmVJZDogc3RyaW5nOyBvcmdJZDogc3RyaW5nIH0sIHBsdWdpbjogU3RlcFBsdWdpbiB8IHVuZGVmaW5lZCk6IFBpcGVsaW5lRXZlbnRJbnNlcnQge1xuICByZXR1cm4ge1xuICAgIC8vIHJlZ2lzdHJ5LnBpcGVsaW5lSWQgPT09IGV2ZW50LnBpcGVsaW5lSWQ7IHRoZSByZWdpc3RyeSdzIGlzIGFsd2F5cyBhIHJvd1xuICAgIC8vIHRoYXQgZXhpc3RzIChGSykgYW5kIGl0cyBvcmdJZCBpcyB0aGUgdHJ1c3RlZCB0ZW5hbnQuXG4gICAgcGlwZWxpbmVJZDogcmVnaXN0cnkucGlwZWxpbmVJZCxcbiAgICBvcmdJZDogcmVnaXN0cnkub3JnSWQsXG4gICAgZXZlbnRTb3VyY2U6IGV2ZW50LmV2ZW50U291cmNlLFxuICAgIGV2ZW50VHlwZTogZXZlbnQuZXZlbnRUeXBlLFxuICAgIHN0YXR1czogZXZlbnQuc3RhdHVzLFxuICAgIC8vIEFuIEFXUy1BU1NJR05FRCBVVUlELCBub3QgZnJlZS1mb3JtIHRleHQ6IGl0IGNhbid0IGNhcnJ5IGFuIGFjY291bnQgaWQsXG4gICAgLy8gYW5kIHNjcnViYmluZyBhIDEyLWRpZ2l0IHJ1biBpbnNpZGUgaXQgd291bGQgY29ycnVwdCB0aGUgY29ycmVsYXRpb24ga2V5LlxuICAgIGV4ZWN1dGlvbklkOiBldmVudC5leGVjdXRpb25JZCxcbiAgICAvLyBVU0VSLUFVVEhPUkVEIHBpcGVsaW5lLXN0cnVjdHVyZSBuYW1lcyDigJQgc2FtZSB1bnRydXN0ZWQgb3JpZ2luIGFzXG4gICAgLy8gZXJyb3JNZXNzYWdlLCBzbyBzY3J1YmJlZCAoYSBzdGFnZSBuYW1lZCB3aXRoIGFuIEFSTiBtdXN0IG5vdCBwZXJzaXN0KS5cbiAgICBzdGFnZU5hbWU6IHNjcnViT3B0aW9uYWwoZXZlbnQuc3RhZ2VOYW1lKSxcbiAgICBhY3Rpb25OYW1lOiBzY3J1Yk9wdGlvbmFsKGV2ZW50LmFjdGlvbk5hbWUpLFxuICAgIC8vIEhBUkQgQ09OU1RSQUlOVDogYW4gQVdTIGFjY291bnQgaWQgbXVzdCBORVZFUiBiZSBwZXJzaXN0ZWQuIEZhaWx1cmUgZGV0YWlsXG4gICAgLy8gYW5kIG1lc3NhZ2VzIHJvdXRpbmVseSBjYXJyeSBBUk5zIGFuZCBiYXJlIDEyLWRpZ2l0IGFjY291bnQgaWRzLlxuICAgIGVycm9yTWVzc2FnZTogc2NydWJPcHRpb25hbChldmVudC5lcnJvck1lc3NhZ2UpLFxuICAgIHN0YXJ0ZWRBdDogZXZlbnQuc3RhcnRlZEF0ID8gbmV3IERhdGUoZXZlbnQuc3RhcnRlZEF0KSA6IHVuZGVmaW5lZCxcbiAgICBjb21wbGV0ZWRBdDogZXZlbnQuY29tcGxldGVkQXQgPyBuZXcgRGF0ZShldmVudC5jb21wbGV0ZWRBdCkgOiB1bmRlZmluZWQsXG4gICAgZHVyYXRpb25NczogZXZlbnQuZHVyYXRpb25NcyxcbiAgICAvLyBEZXBsb3kgYXR0cmlidXRpb24g4oCUIHNjcnViYmVkIGxpa2UgdGhlIG90aGVyIGZyZWUtZm9ybSBzdHJpbmdzLlxuICAgIGNvbW1pdFNoYTogc2NydWJPcHRpb25hbChldmVudC5jb21taXRTaGEpLFxuICAgIGNvbW1pdFJlZjogc2NydWJPcHRpb25hbChldmVudC5jb21taXRSZWYpLFxuICAgIGVudmlyb25tZW50OiBzY3J1Yk9wdGlvbmFsKGV2ZW50LmVudmlyb25tZW50KSxcbiAgICAvLyBNZWFzdXJlZCBsZWFkIHRpbWU6IHRoZSBvbGRlc3QgdW5zaGlwcGVkIGNvbW1pdCdzIHRpbWUgKyB0aGUgY291bnQuIEFuXG4gICAgLy8gU0NNLWRlcml2ZWQgaW5zdGFudCBhbmQgYSBwbGFpbiBpbnRlZ2VyIOKAlCBub3RoaW5nIHRvIHNjcnViLlxuICAgIGNvbW1pdFRpbWVzdGFtcDogZXZlbnQuY29tbWl0VGltZXN0YW1wID8gbmV3IERhdGUoZXZlbnQuY29tbWl0VGltZXN0YW1wKSA6IHVuZGVmaW5lZCxcbiAgICBjb21taXRDb3VudDogZXZlbnQuY29tbWl0Q291bnQsXG4gICAgLy8gTlVMTCBmb3Igbm9uLXBsdWdpbiBhY3Rpb25zIGFuZCBmb3IgcGlwZWxpbmVzIGRlcGxveWVkIHdpdGhvdXQgYSBtYW5pZmVzdC5cbiAgICBwbHVnaW5QdWJsaXNoZXI6IHBsdWdpbj8ucGx1Z2luUHVibGlzaGVyID8/IG51bGwsXG4gICAgcGx1Z2luUHVibGlzaGVySWQ6IHBsdWdpbj8ucGx1Z2luUHVibGlzaGVySWQgPz8gbnVsbCxcbiAgICBwbHVnaW5OYW1lOiBwbHVnaW4/LnBsdWdpbk5hbWUgPz8gbnVsbCxcbiAgICBwbHVnaW5WZXJzaW9uOiBwbHVnaW4/LnBsdWdpblZlcnNpb24gPz8gbnVsbCxcbiAgICBkZXRhaWw6IGV2ZW50LmRldGFpbCAhPT0gdW5kZWZpbmVkID8gc2NydWJBd3NJZGVudGlmaWVycyhldmVudC5kZXRhaWwpIDogdW5kZWZpbmVkLFxuICB9O1xufVxuXG4vKipcbiAqIEZhbiB0ZXJtaW5hbCBTVEFHRSBvdXRjb21lcyBpbnRvIHRoZSByb3V0ZSdzIG1ldHJpYyBob29rIChwaXBlbGluZS1kYXRhIGNhbid0XG4gKiBpbXBvcnQgYXBpLXNlcnZlcidzIHJlZ2lzdHJ5KSBhZnRlciB0aGUgaW5zZXJ0IGNvbW1pdHMuIERyaXZlbiBvZmYgdGhlXG4gKiBJTlNFUlRFRCByb3dzLCBzbyBhIHJlLWRlbGl2ZXJlZCBldmVudCB0aGUgZGVkdXAgaW5kZXggc3dhbGxvd2VkIGlzIG5ldmVyXG4gKiBjb3VudGVkIHR3aWNlLiBBIG5vbi1udWxsIGVudmlyb25tZW50IG1hcmtzIGEgZGVwbG95LlxuICovXG5mdW5jdGlvbiBlbWl0U3RhZ2VNZXRyaWNzKFxuICByb3dzOiBSZWFkb25seUFycmF5PHsgb3JnSWQ6IHN0cmluZzsgcGlwZWxpbmVJZDogc3RyaW5nIHwgbnVsbDsgZXZlbnRUeXBlOiBzdHJpbmc7IHN0YXR1czogc3RyaW5nOyBzdGFnZU5hbWU6IHN0cmluZyB8IG51bGw7IGVudmlyb25tZW50OiBzdHJpbmcgfCBudWxsIH0+LFxuICBvbk1ldHJpYzogKG06IEluZ2VzdE1ldHJpYykgPT4gdm9pZCxcbik6IHZvaWQge1xuICBmb3IgKGNvbnN0IHIgb2Ygcm93cykge1xuICAgIGlmIChyLmV2ZW50VHlwZSAhPT0gJ1NUQUdFJyB8fCAoci5zdGF0dXMgIT09ICdTVUNDRUVERUQnICYmIHIuc3RhdHVzICE9PSAnRkFJTEVEJykgfHwgIXIucGlwZWxpbmVJZCkgY29udGludWU7XG4gICAgb25NZXRyaWMoe1xuICAgICAgcGlwZWxpbmVJZDogci5waXBlbGluZUlkLFxuICAgICAgb3JnSWQ6IHIub3JnSWQsXG4gICAgICBzdGFnZTogci5zdGFnZU5hbWUgPz8gJycsXG4gICAgICBlbnZpcm9ubWVudDogci5lbnZpcm9ubWVudCA/PyBudWxsLFxuICAgICAgcmVzdWx0OiByLnN0YXR1cyA9PT0gJ1NVQ0NFRURFRCcgPyAnc3VjY2VlZGVkJyA6ICdmYWlsZWQnLFxuICAgIH0pO1xuICB9XG59XG5cbi8qKlxuICogUmVzb2x2ZSBpbmNvbWluZyBldmVudHMgYWdhaW5zdCB0aGUgcGlwZWxpbmUgcmVnaXN0cnksIGJhdGNoLWluc2VydCB0aGVcbiAqIG1hdGNoZWQgb25lcywgYW5kIGludmFsaWRhdGUgcmVwb3J0aW5nIGNhY2hlcyBmb3IgYWZmZWN0ZWQgb3Jncy5cbiAqIEV2ZW50cyBmb3IgdW5yZWdpc3RlcmVkIHBpcGVsaW5lIGlkcyBhcmUgZHJvcHBlZCAoYW5kIGxvZ2dlZCBhdCBXQVJOXG4gKiB3aXRoIHNhbXBsZSBpZHMgc28gYW4gb3BlcmF0b3IgY2FuIHNlZSB3aGVuIEV2ZW50QnJpZGdlIGlzIGRlbGl2ZXJpbmdcbiAqIGV2ZW50cyBmb3IgcGlwZWxpbmVzIHRoYXQgaGF2ZW4ndCBjYWxsZWQgUE9TVCAvcGlwZWxpbmVzL3JlZ2lzdHJ5IHlldCkuXG4gKlxuICogUmV0dXJucyBjb3VudHMgKyBhIHNhbXBsZSBvZiB1bnJlZ2lzdGVyZWQgcGlwZWxpbmUgaWRzIGZvciBvYnNlcnZhYmlsaXR5LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaW5nZXN0RXZlbnRzKGV2ZW50czogSW5nZXN0RXZlbnRbXSwgb25NZXRyaWM/OiAobTogSW5nZXN0TWV0cmljKSA9PiB2b2lkKTogUHJvbWlzZTxJbmdlc3RSZXN1bHQ+IHtcbiAgLy8gTXVsdGktb3JnIGJhdGNoIGluc2VydDogdGhlIGNhbGxlciByZXNvbHZlcyB0byBtdWx0aXBsZSBvcmdzIHZpYSB0aGVcbiAgLy8gcGlwZWxpbmUtcmVnaXN0cnkgbG9va3VwIGJlbG93LCBzbyB0aGUgcm91dGUgbGF5ZXIgTVVTVCBlc3RhYmxpc2ggYVxuICAvLyBgcnVuV2l0aFRlbmFudENvbnRleHQoeyBpc1N1cGVyQWRtaW46IHRydWUgfSwgLi4uKWAgc2NvcGUgYmVmb3JlIGNhbGxpbmdcbiAgLy8gdGhpcyBmdW5jdGlvbi4gVW5kZXIgRk9SQ0UnZCBSTFMsIGEgc2luZ2xlIHR4IHdpdGggYGFwcC5vcmdfaWQgPSA8b25lXG4gIC8vIG9yZz5gIGNvdWxkIG9ubHkgd3JpdGUgZXZlbnRzIGZvciB0aGF0IG9yZzsgYnlwYXNzIHZpYSBzeXNhZG1pbiBpc1xuICAvLyB0aGUgcmlnaHQgZ2F0ZSBmb3IgdGhpcyBzZXJ2ZXItaW50ZXJuYWwgY3Jvc3MtdGVuYW50IGVuZHBvaW50LiBTZWVcbiAgLy8gYXBpL3JlcG9ydGluZy9zcmMvcm91dGVzL2V2ZW50LWluZ2VzdC50cyBmb3IgdGhlIHdyYXBwZXIuXG4gIC8vIENhY2hlcyBhcmUgaW52YWxpZGF0ZWQgQUZURVIgdGhlIHR4IHJlc29sdmVzOiBkb2luZyBpdCBpbnNpZGUgaGVsZCB0aGUgcGdcbiAgLy8gbG9ja3Mgb3BlbiBhY3Jvc3MgdW5yZWxhdGVkIGNhY2hlIHJvdW5kLXRyaXBzLiBUaGUgVFRMIGlzIDItNSBtaW4sIHNvIGFcbiAgLy8gZmlyZS1hbmQtZm9yZ2V0IHBvc3QtY29tbWl0IGludmFsaWRhdGlvbiBpcyBhbiBhY2NlcHRhYmxlIHRyYWRlLlxuICBjb25zdCB7IGluc2VydGVkLCBza2lwcGVkLCB1bnJlZ2lzdGVyZWRQaXBlbGluZUlkcywgYWZmZWN0ZWRPcmdzLCBpbnNlcnRlZFJvd3MgfSA9IGF3YWl0IHdpdGhUZW5hbnRUeChhc3luYyAodHgpID0+IHtcbiAgICAvLyBCYXRjaC1yZXNvbHZlIGFsbCB1bmlxdWUgcGlwZWxpbmUgaWRzIGluIG9uZSBxdWVyeVxuICAgIGNvbnN0IHVuaXF1ZUlkcyA9IFsuLi5uZXcgU2V0KGV2ZW50cy5tYXAoZSA9PiBlLnBpcGVsaW5lSWQpKV07XG4gICAgY29uc3QgcmVnaXN0cnlSb3dzID0gYXdhaXQgdHhcbiAgICAgIC5zZWxlY3Qoe1xuICAgICAgICBwaXBlbGluZUlkOiBzY2hlbWEucGlwZWxpbmVSZWdpc3RyeS5waXBlbGluZUlkLFxuICAgICAgICBvcmdJZDogc2NoZW1hLnBpcGVsaW5lUmVnaXN0cnkub3JnSWQsXG4gICAgICB9KVxuICAgICAgLmZyb20oc2NoZW1hLnBpcGVsaW5lUmVnaXN0cnkpXG4gICAgICAud2hlcmUoaW5BcnJheShzY2hlbWEucGlwZWxpbmVSZWdpc3RyeS5waXBlbGluZUlkLCB1bmlxdWVJZHMpKTtcblxuICAgIGNvbnN0IGlkTWFwID0gbmV3IE1hcChyZWdpc3RyeVJvd3MubWFwKHIgPT4gW3IucGlwZWxpbmVJZCwgcl0pKTtcbiAgICBjb25zdCBzdGVwUGx1Z2lucyA9IGF3YWl0IGxvYWRTdGVwUGx1Z2lucyh0eCwgZXZlbnRzLmZpbHRlcigoZSkgPT4gaXNBdHRyaWJ1dGFibGUoZSkgJiYgaWRNYXAuaGFzKGUucGlwZWxpbmVJZCkpKTtcblxuICAgIC8vIEJ1aWxkIGluc2VydCBiYXRjaCAoc2tpcCBldmVudHMgd2hvc2UgcGlwZWxpbmUgaXNuJ3QgcmVnaXN0ZXJlZClcbiAgICBjb25zdCByb3dzOiBQaXBlbGluZUV2ZW50SW5zZXJ0W10gPSBbXTtcbiAgICBsZXQgc2tpcHBlZExvY2FsID0gMDtcbiAgICBjb25zdCB1bnJlZ2lzdGVyZWRMb2NhbDogc3RyaW5nW10gPSBbXTtcblxuICAgIGZvciAoY29uc3QgZXZlbnQgb2YgZXZlbnRzKSB7XG4gICAgICBjb25zdCByZWdpc3RyeSA9IGlkTWFwLmdldChldmVudC5waXBlbGluZUlkKTtcbiAgICAgIGlmICghcmVnaXN0cnkpIHtcbiAgICAgICAgc2tpcHBlZExvY2FsKys7XG4gICAgICAgIHVucmVnaXN0ZXJlZExvY2FsLnB1c2goZXZlbnQucGlwZWxpbmVJZCk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgY29uc3Qgc3RhZ2VOYW1lID0gc2NydWJPcHRpb25hbChldmVudC5zdGFnZU5hbWUpO1xuICAgICAgY29uc3QgYWN0aW9uTmFtZSA9IHNjcnViT3B0aW9uYWwoZXZlbnQuYWN0aW9uTmFtZSk7XG4gICAgICBjb25zdCBzdGVwID0gaXNBdHRyaWJ1dGFibGUoZXZlbnQpICYmIHN0YWdlTmFtZSAmJiBhY3Rpb25OYW1lXG4gICAgICAgID8gc3RlcFBsdWdpbnMuZ2V0KHN0ZXBLZXkocmVnaXN0cnkucGlwZWxpbmVJZCwgc3RhZ2VOYW1lLCBhY3Rpb25OYW1lKSlcbiAgICAgICAgOiB1bmRlZmluZWQ7XG4gICAgICAvLyBTYW1lLW9yZyBndWFyZDogYSBtYW5pZmVzdCByb3cgaXMgb25seSBldmVyIHdyaXR0ZW4gYnkgdGhlIG9yZyB0aGF0XG4gICAgICAvLyBvd25zIHRoZSBwaXBlbGluZSwgYnV0IHRoZSBpbmdlc3QgcnVucyBjcm9zcy10ZW5hbnQsIHNvIGRvbid0IGxlYW4gb24gaXQuXG4gICAgICByb3dzLnB1c2godG9FdmVudFJvdyhldmVudCwgcmVnaXN0cnksIHN0ZXAgJiYgc3RlcC5vcmdJZCA9PT0gcmVnaXN0cnkub3JnSWQgPyBzdGVwIDogdW5kZWZpbmVkKSk7XG4gICAgfVxuXG4gICAgLy8gU1FTIGlzIGF0LWxlYXN0LW9uY2UsIHNvIEV2ZW50QnJpZGdlIGNhbiBkZWxpdmVyIHRoZSBzYW1lIHN0YXRlLWNoYW5nZVxuICAgIC8vIHR3aWNlLiBgb25Db25mbGljdERvTm90aGluZ2AgKyB0aGUgcGFydGlhbCB1bmlxdWUgaW5kZXggb25cbiAgICAvLyAocGlwZWxpbmVfaWQsIGV4ZWN1dGlvbl9pZCwgZXZlbnRfdHlwZSwgc3RhdHVzLCBzdGFnZV9uYW1lLCBhY3Rpb25fbmFtZSlcbiAgICAvLyBtYWtlcyByZS1kZWxpdmVyeSBpZGVtcG90ZW50LiBgcmV0dXJuaW5nYCBnaXZlcyB0aGUgUkVBTCBpbnNlcnRlZCBzZXQgc29cbiAgICAvLyBjb3VudHMsIG1ldHJpY3MgYW5kIGNhY2hlIGludmFsaWRhdGlvbiBhbGwgaWdub3JlIGR1cGxpY2F0ZXMuXG4gICAgY29uc3QgbGFuZGVkID0gcm93cy5sZW5ndGggPiAwXG4gICAgICA/IGF3YWl0IHR4Lmluc2VydChzY2hlbWEucGlwZWxpbmVFdmVudCkudmFsdWVzKHJvd3MpXG4gICAgICAgIC5vbkNvbmZsaWN0RG9Ob3RoaW5nKClcbiAgICAgICAgLnJldHVybmluZyh7XG4gICAgICAgICAgb3JnSWQ6IHNjaGVtYS5waXBlbGluZUV2ZW50Lm9yZ0lkLFxuICAgICAgICAgIHBpcGVsaW5lSWQ6IHNjaGVtYS5waXBlbGluZUV2ZW50LnBpcGVsaW5lSWQsXG4gICAgICAgICAgZXZlbnRUeXBlOiBzY2hlbWEucGlwZWxpbmVFdmVudC5ldmVudFR5cGUsXG4gICAgICAgICAgc3RhdHVzOiBzY2hlbWEucGlwZWxpbmVFdmVudC5zdGF0dXMsXG4gICAgICAgICAgc3RhZ2VOYW1lOiBzY2hlbWEucGlwZWxpbmVFdmVudC5zdGFnZU5hbWUsXG4gICAgICAgICAgZW52aXJvbm1lbnQ6IHNjaGVtYS5waXBlbGluZUV2ZW50LmVudmlyb25tZW50LFxuICAgICAgICB9KVxuICAgICAgOiBbXTtcblxuICAgIHJldHVybiB7XG4gICAgICBpbnNlcnRlZDogbGFuZGVkLmxlbmd0aCxcbiAgICAgIHNraXBwZWQ6IHNraXBwZWRMb2NhbCxcbiAgICAgIHVucmVnaXN0ZXJlZFBpcGVsaW5lSWRzOiB1bnJlZ2lzdGVyZWRMb2NhbCxcbiAgICAgIGFmZmVjdGVkT3JnczogWy4uLm5ldyBTZXQobGFuZGVkLm1hcChyID0+IHIub3JnSWQpKV0sXG4gICAgICBpbnNlcnRlZFJvd3M6IGxhbmRlZCxcbiAgICB9O1xuICB9KTtcblxuICBpZiAob25NZXRyaWMpIGVtaXRTdGFnZU1ldHJpY3MoaW5zZXJ0ZWRSb3dzLCBvbk1ldHJpYyk7XG5cbiAgLy8gU3VyZmFjZSB0aGUgc2lsZW50IHNraXA6IGFuIHVucmVnaXN0ZXJlZCBwaXBlbGluZSBpZCB1c3VhbGx5IG1lYW5zIHRoZVxuICAvLyBwaXBlbGluZSBoYXNuJ3QgY2FsbGVkIFBPU1QgL3BpcGVsaW5lcy9yZWdpc3RyeSB5ZXQgKG9yIGl0c1xuICAvLyBwYi5waXBlbGluZS1pZCB0YWcgaXMgbWlzc2luZy91bnJlYWRhYmxlIGJ5IHRoZSBMYW1iZGEpLiBMb2dnaW5nIGl0XG4gIC8vIG1ha2VzIGEgYnJva2VuIGpvaW4gdmlzaWJsZSBpbnN0ZWFkIG9mIGxvb2tpbmcgbGlrZSBcIm5vIGFjdGl2aXR5XCIuXG4gIGlmICh1bnJlZ2lzdGVyZWRQaXBlbGluZUlkcy5sZW5ndGggPiAwKSB7XG4gICAgbG9nZ2VyLndhcm4oJ1BpcGVsaW5lIGV2ZW50cyBza2lwcGVkOiBwaXBlbGluZSBpZCBub3QgZm91bmQgaW4gcmVnaXN0cnknLCB7XG4gICAgICBjb3VudDogdW5yZWdpc3RlcmVkUGlwZWxpbmVJZHMubGVuZ3RoLFxuICAgICAgc2FtcGxlOiB1bnJlZ2lzdGVyZWRQaXBlbGluZUlkcy5zbGljZSgwLCAzKSxcbiAgICB9KTtcbiAgfVxuXG4gIC8vIFBvc3QtY29tbWl0IGNhY2hlIGludmFsaWRhdGlvbi4gRmlyZS1hbmQtZm9yZ2V0IHdpdGggbG9nZ2luZyDigJQgVFRMIGlzXG4gIC8vIHNob3J0IGVub3VnaCB0aGF0IGEgbWlzc2VkIGludmFsaWRhdGlvbiBzZWxmLWhlYWxzLlxuICBpZiAoYWZmZWN0ZWRPcmdzLmxlbmd0aCA+IDApIHtcbiAgICB2b2lkIFByb21pc2UuYWxsKGFmZmVjdGVkT3Jncy5tYXAoKG9yZykgPT5cbiAgICAgIGludmFsaWRhdGVPcmdSZXBvcnRzKG9yZykuY2F0Y2goKGVycikgPT4ge1xuICAgICAgICBsb2dnZXIud2FybignUmVwb3J0aW5nIGNhY2hlIGludmFsaWRhdGlvbiBmYWlsZWQnLCB7IG9yZ0lkOiBvcmcsIGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyKSB9KTtcbiAgICAgIH0pLFxuICAgICkpO1xuICB9XG5cbiAgcmV0dXJuIHsgaW5zZXJ0ZWQsIHNraXBwZWQsIHVucmVnaXN0ZXJlZFBpcGVsaW5lSWRzLCBhZmZlY3RlZE9yZ3MgfTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReportingRetentionCounts, ReportingRetentionOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reporting retention sweep. Hard-deletes rows older than their
|
|
4
|
+
* retention window, by `created_at`, across every org that has reporting data —
|
|
5
|
+
* a **split** policy so high-volume standard events expire faster than the
|
|
6
|
+
* low-volume DORA source:
|
|
7
|
+
* - `pipeline_events WHERE environment IS NULL` → standard-event window.
|
|
8
|
+
* - `pipeline_events WHERE environment IS NOT NULL` (deploy stages),
|
|
9
|
+
* `deployment_outcomes`, and `incidents` → DORA-source window.
|
|
10
|
+
* Each org's windows come from its `dora_settings` override, else the global
|
|
11
|
+
* env defaults (see {@link resolveEventRetentionDays} / {@link resolveDoraRetentionDays}).
|
|
12
|
+
* `ingest_health` and `dora_settings` are never purged. One `now` for the whole
|
|
13
|
+
* tick (rows crossing the boundary mid-sweep wait for the next). Establishes a
|
|
14
|
+
* sysadmin tenant scope so the deletes span all orgs / bypass RLS — this is a
|
|
15
|
+
* cross-tenant housekeeping job. Returns (and logs) per-window purge tallies.
|
|
16
|
+
*/
|
|
17
|
+
export declare function purgeExpiredReportingData(opts?: ReportingRetentionOptions): Promise<ReportingRetentionCounts>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* The reporting retention SWEEP: the batched hard-delete that enforces the
|
|
5
|
+
* windows `./retention.ts` resolves. Split from the windows module so the pure,
|
|
6
|
+
* env-only policy stays free of database code, and from the reporting service
|
|
7
|
+
* because this is cross-tenant housekeeping, not a report.
|
|
8
|
+
*/
|
|
9
|
+
import { createLogger, errorMessage } from '@pipeline-builder/api-core';
|
|
10
|
+
import { sql } from 'drizzle-orm';
|
|
11
|
+
import { resolveEventRetentionDays, resolveDoraRetentionDays, retentionCutoff } from './retention.js';
|
|
12
|
+
import { schema } from '../../database/drizzle-schema.js';
|
|
13
|
+
import { withTenantTx, runWithTenantContext } from '../../database/tenancy.js';
|
|
14
|
+
import { drizzleRows } from '../crud-service.js';
|
|
15
|
+
const logger = createLogger('reporting-retention');
|
|
16
|
+
/**
|
|
17
|
+
* Batched hard-DELETE of one reporting table's rows expired past a `created_at`
|
|
18
|
+
* cutoff, scoped to an org and an optional row predicate. Uses a
|
|
19
|
+
* `ctid`-in-subquery LIMIT so each statement touches at most `batchSize` rows
|
|
20
|
+
* (short lock windows, no long table scan under lock); `RETURNING 1` lets us
|
|
21
|
+
* count via `.rows.length` without depending on the driver's `rowCount`. Loops
|
|
22
|
+
* until a short batch (drained) or the per-tick cap, then defers the rest.
|
|
23
|
+
* MUST run inside a sysadmin tenant scope (see purgeExpiredReportingData) so it
|
|
24
|
+
* bypasses RLS and can delete across the org's rows.
|
|
25
|
+
*/
|
|
26
|
+
async function purgeReportingTableBatched(tableSql, predicate, batchSize, maxBatches) {
|
|
27
|
+
let total = 0;
|
|
28
|
+
for (let i = 0; i < maxBatches; i++) {
|
|
29
|
+
const res = await withTenantTx((tx) => tx.execute(sql `
|
|
30
|
+
DELETE FROM ${tableSql}
|
|
31
|
+
WHERE ctid IN (
|
|
32
|
+
SELECT ctid FROM ${tableSql}
|
|
33
|
+
WHERE ${predicate}
|
|
34
|
+
LIMIT ${batchSize}
|
|
35
|
+
)
|
|
36
|
+
RETURNING 1
|
|
37
|
+
`));
|
|
38
|
+
const purged = drizzleRows(res.rows).length;
|
|
39
|
+
total += purged;
|
|
40
|
+
if (purged < batchSize)
|
|
41
|
+
break; // drained
|
|
42
|
+
if (i === maxBatches - 1) {
|
|
43
|
+
logger.warn('Reporting retention hit per-tick cap; remaining rows deferred', {
|
|
44
|
+
purgedThisTick: total,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reporting retention sweep. Hard-deletes rows older than their
|
|
52
|
+
* retention window, by `created_at`, across every org that has reporting data —
|
|
53
|
+
* a **split** policy so high-volume standard events expire faster than the
|
|
54
|
+
* low-volume DORA source:
|
|
55
|
+
* - `pipeline_events WHERE environment IS NULL` → standard-event window.
|
|
56
|
+
* - `pipeline_events WHERE environment IS NOT NULL` (deploy stages),
|
|
57
|
+
* `deployment_outcomes`, and `incidents` → DORA-source window.
|
|
58
|
+
* Each org's windows come from its `dora_settings` override, else the global
|
|
59
|
+
* env defaults (see {@link resolveEventRetentionDays} / {@link resolveDoraRetentionDays}).
|
|
60
|
+
* `ingest_health` and `dora_settings` are never purged. One `now` for the whole
|
|
61
|
+
* tick (rows crossing the boundary mid-sweep wait for the next). Establishes a
|
|
62
|
+
* sysadmin tenant scope so the deletes span all orgs / bypass RLS — this is a
|
|
63
|
+
* cross-tenant housekeeping job. Returns (and logs) per-window purge tallies.
|
|
64
|
+
*/
|
|
65
|
+
export async function purgeExpiredReportingData(opts = {}) {
|
|
66
|
+
const batchSize = Math.max(1, opts.batchSize ?? 1000);
|
|
67
|
+
const maxBatches = Math.max(1, opts.maxBatchesPerTable ?? 50);
|
|
68
|
+
const now = opts.now ?? new Date();
|
|
69
|
+
const resolveRetentionOrgId = opts.resolveRetentionOrgId ?? (async (orgId) => orgId);
|
|
70
|
+
const counts = {
|
|
71
|
+
orgs: 0, standardEvents: 0, doraEvents: 0, deploymentOutcomes: 0, incidents: 0,
|
|
72
|
+
};
|
|
73
|
+
return runWithTenantContext({ isSuperAdmin: true }, async () => {
|
|
74
|
+
// Enumerate every org with reporting data (union across the three tables).
|
|
75
|
+
const orgRows = drizzleRows((await withTenantTx((tx) => tx.execute(sql `
|
|
76
|
+
SELECT DISTINCT org_id FROM (
|
|
77
|
+
SELECT ${schema.pipelineEvent.orgId} AS org_id FROM ${schema.pipelineEvent}
|
|
78
|
+
UNION
|
|
79
|
+
SELECT ${schema.deploymentOutcome.orgId} AS org_id FROM ${schema.deploymentOutcome}
|
|
80
|
+
UNION
|
|
81
|
+
SELECT ${schema.incident.orgId} AS org_id FROM ${schema.incident}
|
|
82
|
+
) AS orgs
|
|
83
|
+
`))).rows);
|
|
84
|
+
if (orgRows.length === 0)
|
|
85
|
+
return counts;
|
|
86
|
+
// Per-org retention overrides (a single read; orgs without a row use defaults).
|
|
87
|
+
const overrideRows = drizzleRows((await withTenantTx((tx) => tx.execute(sql `
|
|
88
|
+
SELECT ${schema.doraSettings.orgId} AS org_id,
|
|
89
|
+
${schema.doraSettings.eventRetentionDays} AS event_retention_days,
|
|
90
|
+
${schema.doraSettings.doraRetentionDays} AS dora_retention_days
|
|
91
|
+
FROM ${schema.doraSettings}
|
|
92
|
+
`))).rows);
|
|
93
|
+
const overrides = new Map(overrideRows.map((r) => [r.org_id, r]));
|
|
94
|
+
const eventsTable = sql `${schema.pipelineEvent}`;
|
|
95
|
+
const outcomesTable = sql `${schema.deploymentOutcome}`;
|
|
96
|
+
const incidentsTable = sql `${schema.incident}`;
|
|
97
|
+
for (const { org_id: orgId } of orgRows) {
|
|
98
|
+
// Retention follows the account ROOT's entitlement (billing syncs it onto
|
|
99
|
+
// the root only). An unresolvable root ⇒ skip this org this tick rather
|
|
100
|
+
// than purge a team's rows on the (shorter) env default.
|
|
101
|
+
let retentionOrgId;
|
|
102
|
+
try {
|
|
103
|
+
retentionOrgId = await resolveRetentionOrgId(orgId);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
logger.warn('Reporting retention: root resolution threw; skipping org this tick', { orgId, error: errorMessage(err) });
|
|
107
|
+
retentionOrgId = null;
|
|
108
|
+
}
|
|
109
|
+
if (retentionOrgId === null) {
|
|
110
|
+
logger.warn('Reporting retention: could not resolve the retention root; org skipped this tick', { orgId });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const ov = overrides.get(retentionOrgId);
|
|
114
|
+
const eventDays = resolveEventRetentionDays(ov?.event_retention_days);
|
|
115
|
+
const doraDays = resolveDoraRetentionDays(ov?.dora_retention_days);
|
|
116
|
+
// `-1` = unlimited: keep forever, skip that window's deletes for
|
|
117
|
+
// this org. Standard-event and DORA-source windows are independent; an org
|
|
118
|
+
// with both `-1` is fully skipped. Log the skip so it's observable.
|
|
119
|
+
const skipEvents = eventDays === -1;
|
|
120
|
+
const skipDora = doraDays === -1;
|
|
121
|
+
if (skipEvents || skipDora) {
|
|
122
|
+
logger.info('Reporting retention sweep skipping unlimited window(s)', {
|
|
123
|
+
orgId,
|
|
124
|
+
standardEvents: skipEvents ? 'unlimited' : eventDays,
|
|
125
|
+
doraSource: skipDora ? 'unlimited' : doraDays,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (!skipEvents) {
|
|
129
|
+
const eventCutoff = retentionCutoff(now, eventDays);
|
|
130
|
+
counts.standardEvents += await purgeReportingTableBatched(eventsTable,
|
|
131
|
+
// Spare rows carrying a commit timestamp: they are DORA SOURCE data.
|
|
132
|
+
// Lead time joins deploys to the execution's earliest
|
|
133
|
+
// `commit_timestamp`, and that enrichment rides the PIPELINE/source
|
|
134
|
+
// event (environment IS NULL), never the deploy STAGE row. Purging
|
|
135
|
+
// them on this short window while the deploys they explain live on
|
|
136
|
+
// the DORA window made lead time read "unknown" for every range
|
|
137
|
+
// older than the standard retention — silently, with the deploys
|
|
138
|
+
// still counted. They follow the DORA window below instead.
|
|
139
|
+
sql `org_id = ${orgId} AND environment IS NULL AND commit_timestamp IS NULL AND created_at < ${eventCutoff}`, batchSize, maxBatches);
|
|
140
|
+
}
|
|
141
|
+
if (!skipDora) {
|
|
142
|
+
const doraCutoff = retentionCutoff(now, doraDays);
|
|
143
|
+
counts.doraEvents += await purgeReportingTableBatched(eventsTable, sql `org_id = ${orgId} AND (environment IS NOT NULL OR commit_timestamp IS NOT NULL) AND created_at < ${doraCutoff}`, batchSize, maxBatches);
|
|
144
|
+
counts.deploymentOutcomes += await purgeReportingTableBatched(outcomesTable, sql `org_id = ${orgId} AND created_at < ${doraCutoff}`, batchSize, maxBatches);
|
|
145
|
+
counts.incidents += await purgeReportingTableBatched(incidentsTable, sql `org_id = ${orgId} AND created_at < ${doraCutoff}`, batchSize, maxBatches);
|
|
146
|
+
}
|
|
147
|
+
counts.orgs += 1;
|
|
148
|
+
}
|
|
149
|
+
const purgedAny = counts.standardEvents + counts.doraEvents + counts.deploymentOutcomes + counts.incidents;
|
|
150
|
+
if (purgedAny > 0)
|
|
151
|
+
logger.info('Reporting retention sweep purged expired rows', { ...counts });
|
|
152
|
+
return counts;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0ZW50aW9uLXN3ZWVwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwaS9yZXBvcnRpbmcvcmV0ZW50aW9uLXN3ZWVwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7O0dBS0c7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLFlBQVksRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEMsT0FBTyxFQUFFLHlCQUF5QixFQUFFLHdCQUF3QixFQUFFLGVBQWUsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRXRHLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQUMxRCxPQUFPLEVBQUUsWUFBWSxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDL0UsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWpELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBRW5EOzs7Ozs7Ozs7R0FTRztBQUNILEtBQUssVUFBVSwwQkFBMEIsQ0FDdkMsUUFBZ0MsRUFDaEMsU0FBaUMsRUFDakMsU0FBaUIsRUFDakIsVUFBa0I7SUFFbEIsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDO0lBQ2QsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLFVBQVUsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ3BDLE1BQU0sR0FBRyxHQUFHLE1BQU0sWUFBWSxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQTtvQkFDckMsUUFBUTs7MkJBRUQsUUFBUTtnQkFDbkIsU0FBUztnQkFDVCxTQUFTOzs7S0FHcEIsQ0FBQyxDQUFDLENBQUM7UUFDSixNQUFNLE1BQU0s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TUFBTSwwQkFBMEIsQ0FDbEQsY0FBYyxFQUNkLEdBQUcsQ0FBQSxZQUFZLEtBQUsscUJBQXFCLFVBQVUsRUFBRSxFQUNyRCxTQUFTLEVBQUUsVUFBVSxDQUN0QixDQUFDO1lBQ0osQ0FBQztZQUNELE1BQU0sQ0FBQyxJQUFJLElBQUksQ0FBQyxDQUFDO1FBQ25CLENBQUM7UUFFRCxNQUFNLFNBQVMsR0FBRyxNQUFNLENBQUMsY0FBYyxHQUFHLE1BQU0sQ0FBQyxVQUFVLEdBQUcsTUFBTSxDQUFDLGtCQUFrQixHQUFHLE1BQU0sQ0FBQyxTQUFTLENBQUM7UUFDM0csSUFBSSxTQUFTLEdBQUcsQ0FBQztZQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsK0NBQStDLEVBQUUsRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDL0YsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogVGhlIHJlcG9ydGluZyByZXRlbnRpb24gU1dFRVA6IHRoZSBiYXRjaGVkIGhhcmQtZGVsZXRlIHRoYXQgZW5mb3JjZXMgdGhlXG4gKiB3aW5kb3dzIGAuL3JldGVudGlvbi50c2AgcmVzb2x2ZXMuIFNwbGl0IGZyb20gdGhlIHdpbmRvd3MgbW9kdWxlIHNvIHRoZSBwdXJlLFxuICogZW52LW9ubHkgcG9saWN5IHN0YXlzIGZyZWUgb2YgZGF0YWJhc2UgY29kZSwgYW5kIGZyb20gdGhlIHJlcG9ydGluZyBzZXJ2aWNlXG4gKiBiZWNhdXNlIHRoaXMgaXMgY3Jvc3MtdGVuYW50IGhvdXNla2VlcGluZywgbm90IGEgcmVwb3J0LlxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciwgZXJyb3JNZXNzYWdlIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgc3FsIH0gZnJvbSAnZHJpenpsZS1vcm0nO1xuaW1wb3J0IHsgcmVzb2x2ZUV2ZW50UmV0ZW50aW9uRGF5cywgcmVzb2x2ZURvcmFSZXRlbnRpb25EYXlzLCByZXRlbnRpb25DdXRvZmYgfSBmcm9tICcuL3JldGVudGlvbi5qcyc7XG5pbXBvcnQgdHlwZSB7IFJlcG9ydGluZ1JldGVudGlvbkNvdW50cywgUmVwb3J0aW5nUmV0ZW50aW9uT3B0aW9ucyB9IGZyb20gJy4vdHlwZXMuanMnO1xuaW1wb3J0IHsgc2NoZW1hIH0gZnJvbSAnLi4vLi4vZGF0YWJhc2UvZHJpenpsZS1zY2hlbWEuanMnO1xuaW1wb3J0IHsgd2l0aFRlbmFudFR4LCBydW5XaXRoVGVuYW50Q29udGV4dCB9IGZyb20gJy4uLy4uL2RhdGFiYXNlL3RlbmFuY3kuanMnO1xuaW1wb3J0IHsgZHJpenpsZVJvd3MgfSBmcm9tICcuLi9jcnVkLXNlcnZpY2UuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ3JlcG9ydGluZy1yZXRlbnRpb24nKTtcblxuLyoqXG4gKiBCYXRjaGVkIGhhcmQtREVMRVRFIG9mIG9uZSByZXBvcnRpbmcgdGFibGUncyByb3dzIGV4cGlyZWQgcGFzdCBhIGBjcmVhdGVkX2F0YFxuICogY3V0b2ZmLCBzY29wZWQgdG8gYW4gb3JnIGFuZCBhbiBvcHRpb25hbCByb3cgcHJlZGljYXRlLiBVc2VzIGFcbiAqIGBjdGlkYC1pbi1zdWJxdWVyeSBMSU1JVCBzbyBlYWNoIHN0YXRlbWVudCB0b3VjaGVzIGF0IG1vc3QgYGJhdGNoU2l6ZWAgcm93c1xuICogKHNob3J0IGxvY2sgd2luZG93cywgbm8gbG9uZyB0YWJsZSBzY2FuIHVuZGVyIGxvY2spOyBgUkVUVVJOSU5HIDFgIGxldHMgdXNcbiAqIGNvdW50IHZpYSBgLnJvd3MubGVuZ3RoYCB3aXRob3V0IGRlcGVuZGluZyBvbiB0aGUgZHJpdmVyJ3MgYHJvd0NvdW50YC4gTG9vcHNcbiAqIHVudGlsIGEgc2hvcnQgYmF0Y2ggKGRyYWluZWQpIG9yIHRoZSBwZXItdGljayBjYXAsIHRoZW4gZGVmZXJzIHRoZSByZXN0LlxuICogTVVTVCBydW4gaW5zaWRlIGEgc3lzYWRtaW4gdGVuYW50IHNjb3BlIChzZWUgcHVyZ2VFeHBpcmVkUmVwb3J0aW5nRGF0YSkgc28gaXRcbiAqIGJ5cGFzc2VzIFJMUyBhbmQgY2FuIGRlbGV0ZSBhY3Jvc3MgdGhlIG9yZydzIHJvd3MuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHB1cmdlUmVwb3J0aW5nVGFibGVCYXRjaGVkKFxuICB0YWJsZVNxbDogUmV0dXJuVHlwZTx0eXBlb2Ygc3FsPixcbiAgcHJlZGljYXRlOiBSZXR1cm5UeXBlPHR5cGVvZiBzcWw+LFxuICBiYXRjaFNpemU6IG51bWJlcixcbiAgbWF4QmF0Y2hlczogbnVtYmVyLFxuKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgbGV0IHRvdGFsID0gMDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBtYXhCYXRjaGVzOyBpKyspIHtcbiAgICBjb25zdCByZXMgPSBhd2FpdCB3aXRoVGVuYW50VHgoKHR4KSA9PiB0eC5leGVjdXRlKHNxbGBcbiAgICAgIERFTEVURSBGUk9NICR7dGFibGVTcWx9XG4gICAgICBXSEVSRSBjdGlkIElOIChcbiAgICAgICAgU0VMRUNUIGN0aWQgRlJPTSAke3RhYmxlU3FsfVxuICAgICAgICBXSEVSRSAke3ByZWRpY2F0ZX1cbiAgICAgICAgTElNSVQgJHtiYXRjaFNpemV9XG4gICAgICApXG4gICAgICBSRVRVUk5JTkcgMVxuICAgIGApKTtcbiAgICBjb25zdCBwdXJnZWQgPSBkcml6emxlUm93czx1bmtub3duPihyZXMucm93cykubGVuZ3RoO1xuICAgIHRvdGFsICs9IHB1cmdlZDtcbiAgICBpZiAocHVyZ2VkIDwgYmF0Y2hTaXplKSBicmVhazsgLy8gZHJhaW5lZFxuICAgIGlmIChpID09PSBtYXhCYXRjaGVzIC0gMSkge1xuICAgICAgbG9nZ2VyLndhcm4oJ1JlcG9ydGluZyByZXRlbnRpb24gaGl0IHBlci10aWNrIGNhcDsgcmVtYWluaW5nIHJvd3MgZGVmZXJyZWQnLCB7XG4gICAgICAgIHB1cmdlZFRoaXNUaWNrOiB0b3RhbCxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICByZXR1cm4gdG90YWw7XG59XG5cbi8qKlxuICogUmVwb3J0aW5nIHJldGVudGlvbiBzd2VlcC4gSGFyZC1kZWxldGVzIHJvd3Mgb2xkZXIgdGhhbiB0aGVpclxuICogcmV0ZW50aW9uIHdpbmRvdywgYnkgYGNyZWF0ZWRfYXRgLCBhY3Jvc3MgZXZlcnkgb3JnIHRoYXQgaGFzIHJlcG9ydGluZyBkYXRhIOKAlFxuICogYSAqKnNwbGl0KiogcG9saWN5IHNvIGhpZ2gtdm9sdW1lIHN0YW5kYXJkIGV2ZW50cyBleHBpcmUgZmFzdGVyIHRoYW4gdGhlXG4gKiBsb3ctdm9sdW1lIERPUkEgc291cmNlOlxuICogIC0gYHBpcGVsaW5lX2V2ZW50cyBXSEVSRSBlbnZpcm9ubWVudCBJUyBOVUxMYCDihpIgc3RhbmRhcmQtZXZlbnQgd2luZG93LlxuICogIC0gYHBpcGVsaW5lX2V2ZW50cyBXSEVSRSBlbnZpcm9ubWVudCBJUyBOT1QgTlVMTGAgKGRlcGxveSBzdGFnZXMpLFxuICogICAgYGRlcGxveW1lbnRfb3V0Y29tZXNgLCBhbmQgYGluY2lkZW50c2Ag4oaSIERPUkEtc291cmNlIHdpbmRvdy5cbiAqIEVhY2ggb3JnJ3Mgd2luZG93cyBjb21lIGZyb20gaXRzIGBkb3JhX3NldHRpbmdzYCBvdmVycmlkZSwgZWxzZSB0aGUgZ2xvYmFsXG4gKiBlbnYgZGVmYXVsdHMgKHNlZSB7QGxpbmsgcmVzb2x2ZUV2ZW50UmV0ZW50aW9uRGF5c30gLyB7QGxpbmsgcmVzb2x2ZURvcmFSZXRlbnRpb25EYXlzfSkuXG4gKiBgaW5nZXN0X2hlYWx0aGAgYW5kIGBkb3JhX3NldHRpbmdzYCBhcmUgbmV2ZXIgcHVyZ2VkLiBPbmUgYG5vd2AgZm9yIHRoZSB3aG9sZVxuICogdGljayAocm93cyBjcm9zc2luZyB0aGUgYm91bmRhcnkgbWlkLXN3ZWVwIHdhaXQgZm9yIHRoZSBuZXh0KS4gRXN0YWJsaXNoZXMgYVxuICogc3lzYWRtaW4gdGVuYW50IHNjb3BlIHNvIHRoZSBkZWxldGVzIHNwYW4gYWxsIG9yZ3MgLyBieXBhc3MgUkxTIOKAlCB0aGlzIGlzIGFcbiAqIGNyb3NzLXRlbmFudCBob3VzZWtlZXBpbmcgam9iLiBSZXR1cm5zIChhbmQgbG9ncykgcGVyLXdpbmRvdyBwdXJnZSB0YWxsaWVzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcHVyZ2VFeHBpcmVkUmVwb3J0aW5nRGF0YShvcHRzOiBSZXBvcnRpbmdSZXRlbnRpb25PcHRpb25zID0ge30pOiBQcm9taXNlPFJlcG9ydGluZ1JldGVudGlvbkNvdW50cz4ge1xuICBjb25zdCBiYXRjaFNpemUgPSBNYXRoLm1heCgxLCBvcHRzLmJhdGNoU2l6ZSA/PyAxMDAwKTtcbiAgY29uc3QgbWF4QmF0Y2hlcyA9IE1hdGgubWF4KDEsIG9wdHMubWF4QmF0Y2hlc1BlclRhYmxlID8/IDUwKTtcbiAgY29uc3Qgbm93ID0gb3B0cy5ub3cgPz8gbmV3IERhdGUoKTtcbiAgY29uc3QgcmVzb2x2ZVJldGVudGlvbk9yZ0lkID0gb3B0cy5yZXNvbHZlUmV0ZW50aW9uT3JnSWQgPz8gKGFzeW5jIChvcmdJZDogc3RyaW5nKSA9PiBvcmdJZCk7XG4gIGNvbnN0IGNvdW50czogUmVwb3J0aW5nUmV0ZW50aW9uQ291bnRzID0ge1xuICAgIG9yZ3M6IDAsIHN0YW5kYXJkRXZlbnRzOiAwLCBkb3JhRXZlbnRzOiAwLCBkZXBsb3ltZW50T3V0Y29tZXM6IDAsIGluY2lkZW50czogMCxcbiAgfTtcblxuICByZXR1cm4gcnVuV2l0aFRlbmFudENvbnRleHQoeyBpc1N1cGVyQWRtaW46IHRydWUgfSwgYXN5bmMgKCkgPT4ge1xuICAgIC8vIEVudW1lcmF0ZSBldmVyeSBvcmcgd2l0aCByZXBvcnRpbmcgZGF0YSAodW5pb24gYWNyb3NzIHRoZSB0aHJlZSB0YWJsZXMpLlxuICAgIGNvbnN0IG9yZ1Jvd3MgPSBkcml6emxlUm93czx7IG9yZ19pZDogc3RyaW5nIH0+KChhd2FpdCB3aXRoVGVuYW50VHgoKHR4KSA9PiB0eC5leGVjdXRlKHNxbGBcbiAgICAgIFNFTEVDVCBESVNUSU5DVCBvcmdfaWQgRlJPTSAoXG4gICAgICAgIFNFTEVDVCAke3NjaGVtYS5waXBlbGluZUV2ZW50Lm9yZ0lkfSBBUyBvcmdfaWQgRlJPTSAke3NjaGVtYS5waXBlbGluZUV2ZW50fVxuICAgICAgICBVTklPTlxuICAgICAgICBTRUxFQ1QgJHtzY2hlbWEuZGVwbG95bWVudE91dGNvbWUub3JnSWR9IEFTIG9yZ19pZCBGUk9NICR7c2NoZW1hLmRlcGxveW1lbnRPdXRjb21lfVxuICAgICAgICBVTklPTlxuICAgICAgICBTRUxFQ1QgJHtzY2hlbWEuaW5jaWRlbnQub3JnSWR9IEFTIG9yZ19pZCBGUk9NICR7c2NoZW1hLmluY2lkZW50fVxuICAgICAgKSBBUyBvcmdzXG4gICAgYCkpKS5yb3dzKTtcbiAgICBpZiAob3JnUm93cy5sZW5ndGggPT09IDApIHJldHVybiBjb3VudHM7XG5cbiAgICAvLyBQZXItb3JnIHJldGVudGlvbiBvdmVycmlkZXMgKGEgc2luZ2xlIHJlYWQ7IG9yZ3Mgd2l0aG91dCBhIHJvdyB1c2UgZGVmYXVsdHMpLlxuICAgIGNvbnN0IG92ZXJyaWRlUm93cyA9IGRyaXp6bGVSb3dzPHtcbiAgICAgIG9yZ19pZDogc3RyaW5nO1xuICAgICAgZXZlbnRfcmV0ZW50aW9uX2RheXM6IG51bWJlciB8IG51bGw7XG4gICAgICBkb3JhX3JldGVudGlvbl9kYXlzOiBudW1iZXIgfCBudWxsO1xuICAgIH0+KChhd2FpdCB3aXRoVGVuYW50VHgoKHR4KSA9PiB0eC5leGVjdXRlKHNxbGBcbiAgICAgIFNFTEVDVCAke3NjaGVtYS5kb3JhU2V0dGluZ3Mub3JnSWR9IEFTIG9yZ19pZCxcbiAgICAgICAgICAgICAke3NjaGVtYS5kb3JhU2V0dGluZ3MuZXZlbnRSZXRlbnRpb25EYXlzfSBBUyBldmVudF9yZXRlbnRpb25fZGF5cyxcbiAgICAgICAgICAgICAke3NjaGVtYS5kb3JhU2V0dGluZ3MuZG9yYVJldGVudGlvbkRheXN9IEFTIGRvcmFfcmV0ZW50aW9uX2RheXNcbiAgICAgIEZST00gJHtzY2hlbWEuZG9yYVNldHRpbmdzfVxuICAgIGApKSkucm93cyk7XG4gICAgY29uc3Qgb3ZlcnJpZGVzID0gbmV3IE1hcChvdmVycmlkZVJvd3MubWFwKChyKSA9PiBbci5vcmdfaWQsIHJdKSk7XG5cbiAgICBjb25zdCBldmVudHNUYWJsZSA9IHNxbGAke3NjaGVtYS5waXBlbGluZUV2ZW50fWA7XG4gICAgY29uc3Qgb3V0Y29tZXNUYWJsZSA9IHNxbGAke3NjaGVtYS5kZXBsb3ltZW50T3V0Y29tZX1gO1xuICAgIGNvbnN0IGluY2lkZW50c1RhYmxlID0gc3FsYCR7c2NoZW1hLmluY2lkZW50fWA7XG5cbiAgICBmb3IgKGNvbnN0IHsgb3JnX2lkOiBvcmdJZCB9IG9mIG9yZ1Jvd3MpIHtcbiAgICAgIC8vIFJldGVudGlvbiBmb2xsb3dzIHRoZSBhY2NvdW50IFJPT1QncyBlbnRpdGxlbWVudCAoYmlsbGluZyBzeW5jcyBpdCBvbnRvXG4gICAgICAvLyB0aGUgcm9vdCBvbmx5KS4gQW4gdW5yZXNvbHZhYmxlIHJvb3Qg4oeSIHNraXAgdGhpcyBvcmcgdGhpcyB0aWNrIHJhdGhlclxuICAgICAgLy8gdGhhbiBwdXJnZSBhIHRlYW0ncyByb3dzIG9uIHRoZSAoc2hvcnRlcikgZW52IGRlZmF1bHQuXG4gICAgICBsZXQgcmV0ZW50aW9uT3JnSWQ6IHN0cmluZyB8IG51bGw7XG4gICAgICB0cnkge1xuICAgICAgICByZXRlbnRpb25PcmdJZCA9IGF3YWl0IHJlc29sdmVSZXRlbnRpb25PcmdJZChvcmdJZCk7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1JlcG9ydGluZyByZXRlbnRpb246IHJvb3QgcmVzb2x1dGlvbiB0aHJldzsgc2tpcHBpbmcgb3JnIHRoaXMgdGljaycsIHsgb3JnSWQsIGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyKSB9KTtcbiAgICAgICAgcmV0ZW50aW9uT3JnSWQgPSBudWxsO1xuICAgICAgfVxuICAgICAgaWYgKHJldGVudGlvbk9yZ0lkID09PSBudWxsKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdSZXBvcnRpbmcgcmV0ZW50aW9uOiBjb3VsZCBub3QgcmVzb2x2ZSB0aGUgcmV0ZW50aW9uIHJvb3Q7IG9yZyBza2lwcGVkIHRoaXMgdGljaycsIHsgb3JnSWQgfSk7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgY29uc3Qgb3YgPSBvdmVycmlkZXMuZ2V0KHJldGVudGlvbk9yZ0lkKTtcbiAgICAgIGNvbnN0IGV2ZW50RGF5cyA9IHJlc29sdmVFdmVudFJldGVudGlvbkRheXMob3Y/LmV2ZW50X3JldGVudGlvbl9kYXlzKTtcbiAgICAgIGNvbnN0IGRvcmFEYXlzID0gcmVzb2x2ZURvcmFSZXRlbnRpb25EYXlzKG92Py5kb3JhX3JldGVudGlvbl9kYXlzKTtcbiAgICAgIC8vIGAtMWAgPSB1bmxpbWl0ZWQ6IGtlZXAgZm9yZXZlciwgc2tpcCB0aGF0IHdpbmRvdydzIGRlbGV0ZXMgZm9yXG4gICAgICAvLyB0aGlzIG9yZy4gU3RhbmRhcmQtZXZlbnQgYW5kIERPUkEtc291cmNlIHdpbmRvd3MgYXJlIGluZGVwZW5kZW50OyBhbiBvcmdcbiAgICAgIC8vIHdpdGggYm90aCBgLTFgIGlzIGZ1bGx5IHNraXBwZWQuIExvZyB0aGUgc2tpcCBzbyBpdCdzIG9ic2VydmFibGUuXG4gICAgICBjb25zdCBza2lwRXZlbnRzID0gZXZlbnREYXlzID09PSAtMTtcbiAgICAgIGNvbnN0IHNraXBEb3JhID0gZG9yYURheXMgPT09IC0xO1xuICAgICAgaWYgKHNraXBFdmVudHMgfHwgc2tpcERvcmEpIHtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ1JlcG9ydGluZyByZXRlbnRpb24gc3dlZXAgc2tpcHBpbmcgdW5saW1pdGVkIHdpbmRvdyhzKScsIHtcbiAgICAgICAgICBvcmdJZCxcbiAgICAgICAgICBzdGFuZGFyZEV2ZW50czogc2tpcEV2ZW50cyA/ICd1bmxpbWl0ZWQnIDogZXZlbnREYXlzLFxuICAgICAgICAgIGRvcmFTb3VyY2U6IHNraXBEb3JhID8gJ3VubGltaXRlZCcgOiBkb3JhRGF5cyxcbiAgICAgICAgfSk7XG4gICAgICB9XG5cbiAgICAgIGlmICghc2tpcEV2ZW50cykge1xuICAgICAgICBjb25zdCBldmVudEN1dG9mZiA9IHJldGVudGlvbkN1dG9mZihub3csIGV2ZW50RGF5cyk7XG4gICAgICAgIGNvdW50cy5zdGFuZGFyZEV2ZW50cyArPSBhd2FpdCBwdXJnZVJlcG9ydGluZ1RhYmxlQmF0Y2hlZChcbiAgICAgICAgICBldmVudHNUYWJsZSxcbiAgICAgICAgICAvLyBTcGFyZSByb3dzIGNhcnJ5aW5nIGEgY29tbWl0IHRpbWVzdGFtcDogdGhleSBhcmUgRE9SQSBTT1VSQ0UgZGF0YS5cbiAgICAgICAgICAvLyBMZWFkIHRpbWUgam9pbnMgZGVwbG95cyB0byB0aGUgZXhlY3V0aW9uJ3MgZWFybGllc3RcbiAgICAgICAgICAvLyBgY29tbWl0X3RpbWVzdGFtcGAsIGFuZCB0aGF0IGVucmljaG1lbnQgcmlkZXMgdGhlIFBJUEVMSU5FL3NvdXJjZVxuICAgICAgICAgIC8vIGV2ZW50IChlbnZpcm9ubWVudCBJUyBOVUxMKSwgbmV2ZXIgdGhlIGRlcGxveSBTVEFHRSByb3cuIFB1cmdpbmdcbiAgICAgICAgICAvLyB0aGVtIG9uIHRoaXMgc2hvcnQgd2luZG93IHdoaWxlIHRoZSBkZXBsb3lzIHRoZXkgZXhwbGFpbiBsaXZlIG9uXG4gICAgICAgICAgLy8gdGhlIERPUkEgd2luZG93IG1hZGUgbGVhZCB0aW1lIHJlYWQgXCJ1bmtub3duXCIgZm9yIGV2ZXJ5IHJhbmdlXG4gICAgICAgICAgLy8gb2xkZXIgdGhhbiB0aGUgc3RhbmRhcmQgcmV0ZW50aW9uIOKAlCBzaWxlbnRseSwgd2l0aCB0aGUgZGVwbG95c1xuICAgICAgICAgIC8vIHN0aWxsIGNvdW50ZWQuIFRoZXkgZm9sbG93IHRoZSBET1JBIHdpbmRvdyBiZWxvdyBpbnN0ZWFkLlxuICAgICAgICAgIHNxbGBvcmdfaWQgPSAke29yZ0lkfSBBTkQgZW52aXJvbm1lbnQgSVMgTlVMTCBBTkQgY29tbWl0X3RpbWVzdGFtcCBJUyBOVUxMIEFORCBjcmVhdGVkX2F0IDwgJHtldmVudEN1dG9mZn1gLFxuICAgICAgICAgIGJhdGNoU2l6ZSwgbWF4QmF0Y2hlcyxcbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgaWYgKCFza2lwRG9yYSkge1xuICAgICAgICBjb25zdCBkb3JhQ3V0b2ZmID0gcmV0ZW50aW9uQ3V0b2ZmKG5vdywgZG9yYURheXMpO1xuICAgICAgICBjb3VudHMuZG9yYUV2ZW50cyArPSBhd2FpdCBwdXJnZVJlcG9ydGluZ1RhYmxlQmF0Y2hlZChcbiAgICAgICAgICBldmVudHNUYWJsZSxcbiAgICAgICAgICBzcWxgb3JnX2lkID0gJHtvcmdJZH0gQU5EIChlbnZpcm9ubWVudCBJUyBOT1QgTlVMTCBPUiBjb21taXRfdGltZXN0YW1wIElTIE5PVCBOVUxMKSBBTkQgY3JlYXRlZF9hdCA8ICR7ZG9yYUN1dG9mZn1gLFxuICAgICAgICAgIGJhdGNoU2l6ZSwgbWF4QmF0Y2hlcyxcbiAgICAgICAgKTtcbiAgICAgICAgY291bnRzLmRlcGxveW1lbnRPdXRjb21lcyArPSBhd2FpdCBwdXJnZVJlcG9ydGluZ1RhYmxlQmF0Y2hlZChcbiAgICAgICAgICBvdXRjb21lc1RhYmxlLFxuICAgICAgICAgIHNxbGBvcmdfaWQgPSAke29yZ0lkfSBBTkQgY3JlYXRlZF9hdCA8ICR7ZG9yYUN1dG9mZn1gLFxuICAgICAgICAgIGJhdGNoU2l6ZSwgbWF4QmF0Y2hlcyxcbiAgICAgICAgKTtcbiAgICAgICAgY291bnRzLmluY2lkZW50cyArPSBhd2FpdCBwdXJnZVJlcG9ydGluZ1RhYmxlQmF0Y2hlZChcbiAgICAgICAgICBpbmNpZGVudHNUYWJsZSxcbiAgICAgICAgICBzcWxgb3JnX2lkID0gJHtvcmdJZH0gQU5EIGNyZWF0ZWRfYXQgPCAke2RvcmFDdXRvZmZ9YCxcbiAgICAgICAgICBiYXRjaFNpemUsIG1heEJhdGNoZXMsXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgICBjb3VudHMub3JncyArPSAxO1xuICAgIH1cblxuICAgIGNvbnN0IHB1cmdlZEFueSA9IGNvdW50cy5zdGFuZGFyZEV2ZW50cyArIGNvdW50cy5kb3JhRXZlbnRzICsgY291bnRzLmRlcGxveW1lbnRPdXRjb21lcyArIGNvdW50cy5pbmNpZGVudHM7XG4gICAgaWYgKHB1cmdlZEFueSA+IDApIGxvZ2dlci5pbmZvKCdSZXBvcnRpbmcgcmV0ZW50aW9uIHN3ZWVwIHB1cmdlZCBleHBpcmVkIHJvd3MnLCB7IC4uLmNvdW50cyB9KTtcbiAgICByZXR1cm4gY291bnRzO1xuICB9KTtcbn1cbiJdfQ==
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reporting retention windows. Records in `pipeline_events`,
|
|
3
3
|
* `deployment_outcomes`, and `incidents` grow unbounded without a sweep, so a
|
|
4
|
-
* split, per-org retention purge (see
|
|
4
|
+
* split, per-org retention purge (see `purgeExpiredReportingData` in ./retention-sweep.ts)
|
|
5
5
|
* hard-deletes rows older than these windows, by `created_at`:
|
|
6
6
|
* - **Standard events** — `pipeline_events` with `environment IS NULL`
|
|
7
7
|
* (non-deploy STAGE/ACTION/build). High volume → short default (30 days).
|
|
@@ -8,7 +8,7 @@ import { envInt, RETENTION_MAX_DAYS, RETENTION_MIN_DAYS } from '@pipeline-builde
|
|
|
8
8
|
/**
|
|
9
9
|
* Reporting retention windows. Records in `pipeline_events`,
|
|
10
10
|
* `deployment_outcomes`, and `incidents` grow unbounded without a sweep, so a
|
|
11
|
-
* split, per-org retention purge (see
|
|
11
|
+
* split, per-org retention purge (see `purgeExpiredReportingData` in ./retention-sweep.ts)
|
|
12
12
|
* hard-deletes rows older than these windows, by `created_at`:
|
|
13
13
|
* - **Standard events** — `pipeline_events` with `environment IS NULL`
|
|
14
14
|
* (non-deploy STAGE/ACTION/build). High volume → short default (30 days).
|
|
@@ -58,4 +58,4 @@ export function retentionCutoff(now, days) {
|
|
|
58
58
|
return new Date(now.getTime() - days * 86_400_000);
|
|
59
59
|
}
|
|
60
60
|
/** Per-org retention override row. Both nullable ⇒ global defaults. */
|
|
61
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
61
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0ZW50aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwaS9yZXBvcnRpbmcvcmV0ZW50aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7OztHQUdHO0FBRUgsT0FBTyxFQUFFLE1BQU0sRUFBRSxrQkFBa0IsRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRTVGOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBRUgsa0ZBQWtGO0FBQ2xGLE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxJQUFZLEVBQUUsUUFBZ0I7SUFDN0QsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztJQUNwQyxPQUFPLElBQUksR0FBRyxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO0FBQ25GLENBQUM7QUFFRCxNQUFNLENBQUMsTUFBTSw4QkFBOEIsR0FBRyxnQkFBZ0IsQ0FBQyxnQ0FBZ0MsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUNyRyxNQUFNLENBQUMsTUFBTSw2QkFBNkIsR0FBRyxnQkFBZ0IsQ0FBQywrQkFBK0IsRUFBRSxHQUFHLENBQUMsQ0FBQztBQUVwRzs7Ozs7O21CQU1tQjtBQUNuQixNQUFNLFVBQVUseUJBQXlCLENBQUMsUUFBd0I7SUFDaEUsSUFBSSxRQUFRLEtBQUssQ0FBQyxDQUFDO1FBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUMvQixPQUFPLFFBQVEsSUFBSSxJQUFJLElBQUksTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsSUFBSSxRQUFRLElBQUksa0JBQWtCO1FBQ3JGLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyw4QkFBOEIsQ0FBQztBQUM5RSxDQUFDO0FBRUQ7Ozs7O21CQUttQjtBQUNuQixNQUFNLFVBQVUsd0JBQXdCLENBQUMsUUFBd0I7SUFDL0QsSUFBSSxRQUFRLEtBQUssQ0FBQyxDQUFDO1FBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUMvQixPQUFPLFFBQVEsSUFBSSxJQUFJLElBQUksTUFBTSxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsSUFBSSxRQUFRLElBQUksa0JBQWtCO1FBQ3JGLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyw2QkFBNkIsQ0FBQztBQUM3RSxDQUFDO0FBRUQ7NkVBQzZFO0FBQzdFLE1BQU0sVUFBVSxlQUFlLENBQUMsR0FBUyxFQUFFLElBQVk7SUFDckQsT0FBTyxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEdBQUcsSUFBSSxHQUFHLFVBQVUsQ0FBQyxDQUFDO0FBQ3JELENBQUM7QUFFRCx1RUFBdUUiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBSZXRlbnRpb24gd2luZG93cyBmb3IgcmVwb3J0aW5nIGRhdGE6IGVudi1jb25maWd1cmFibGUsIGNsYW1wZWQgdG8gYSBzYW5lXG4gKiByYW5nZSwgYW5kIG92ZXJyaWRhYmxlIHBlci1vcmcgYnkgdGhlIHNldHRpbmdzIHJvdy5cbiAqL1xuXG5pbXBvcnQgeyBlbnZJbnQsIFJFVEVOVElPTl9NQVhfREFZUywgUkVURU5USU9OX01JTl9EQVlTIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuXG4vKipcbiAqIFJlcG9ydGluZyByZXRlbnRpb24gd2luZG93cy4gUmVjb3JkcyBpbiBgcGlwZWxpbmVfZXZlbnRzYCxcbiAqIGBkZXBsb3ltZW50X291dGNvbWVzYCwgYW5kIGBpbmNpZGVudHNgIGdyb3cgdW5ib3VuZGVkIHdpdGhvdXQgYSBzd2VlcCwgc28gYVxuICogc3BsaXQsIHBlci1vcmcgcmV0ZW50aW9uIHB1cmdlIChzZWUgYHB1cmdlRXhwaXJlZFJlcG9ydGluZ0RhdGFgIGluIC4vcmV0ZW50aW9uLXN3ZWVwLnRzKVxuICogaGFyZC1kZWxldGVzIHJvd3Mgb2xkZXIgdGhhbiB0aGVzZSB3aW5kb3dzLCBieSBgY3JlYXRlZF9hdGA6XG4gKiAgLSAqKlN0YW5kYXJkIGV2ZW50cyoqIOKAlCBgcGlwZWxpbmVfZXZlbnRzYCB3aXRoIGBlbnZpcm9ubWVudCBJUyBOVUxMYFxuICogICAgKG5vbi1kZXBsb3kgU1RBR0UvQUNUSU9OL2J1aWxkKS4gSGlnaCB2b2x1bWUg4oaSIHNob3J0IGRlZmF1bHQgKDMwIGRheXMpLlxuICogIC0gKipET1JBIHNvdXJjZSoqIOKAlCBgcGlwZWxpbmVfZXZlbnRzYCB3aXRoIGBlbnZpcm9ubWVudCBJUyBOT1QgTlVMTGAgKGRlcGxveVxuICogICAgc3RhZ2VzKSBwbHVzIGFsbCBvZiBgZGVwbG95bWVudF9vdXRjb21lc2AgYW5kIGBpbmNpZGVudHNgLiBMb3cgdm9sdW1lIOKGklxuICogICAgbG9uZ2VyIGRlZmF1bHQgKDE4MCBkYXlzKS4gRE9SQSBoaXN0b3J5IGlzIHRoZXJlZm9yZSBib3VuZGVkIGJ5IHRoaXMgd2luZG93O1xuICogICAgdGhlIHJlcG9ydCBxdWVyeSBzdGlsbCBoYXJkLWNhcHMgYXQgMzY1IGRheXMgKGEgbG9uZ2VyIHBlci1vcmcgb3ZlcnJpZGUgb25seVxuICogICAgcmV0YWlucyByYXcgc291cmNlIHJvd3MsIG5ldmVyIHdpZGVucyBhIHJlcG9ydCkuXG4gKiBgaW5nZXN0X2hlYWx0aGAgYW5kIGBkb3JhX3NldHRpbmdzYCBhcmUgbmV2ZXIgcHVyZ2VkLiBFbnYtb3ZlcnJpZGFibGUgZ2xvYmFscztcbiAqIGEgcGVyLW9yZyBvdmVycmlkZSBpbiBgZG9yYV9zZXR0aW5nc2Agd2lucyAoc2VlIHtAbGluayByZXNvbHZlRXZlbnRSZXRlbnRpb25EYXlzfSkuXG4gKi9cblxuLyoqIFBhcnNlIGEgcG9zaXRpdmUtaW50IGVudiBkYXktY291bnQsIGNsYW1wZWQgdG8gW01JTiwgTUFYXSwgZWxzZSBgZmFsbGJhY2tgLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJldGVudGlvbkVudkRheXMobmFtZTogc3RyaW5nLCBmYWxsYmFjazogbnVtYmVyKTogbnVtYmVyIHtcbiAgY29uc3QgZGF5cyA9IGVudkludChuYW1lLCBmYWxsYmFjayk7XG4gIHJldHVybiBkYXlzIDwgUkVURU5USU9OX01JTl9EQVlTID8gZmFsbGJhY2sgOiBNYXRoLm1pbihkYXlzLCBSRVRFTlRJT05fTUFYX0RBWVMpO1xufVxuXG5leHBvcnQgY29uc3QgUkVQT1JUSU5HX0VWRU5UX1JFVEVOVElPTl9EQVlTID0gcmV0ZW50aW9uRW52RGF5cygnUkVQT1JUSU5HX0VWRU5UX1JFVEVOVElPTl9EQVlTJywgMzApO1xuZXhwb3J0IGNvbnN0IFJFUE9SVElOR19ET1JBX1JFVEVOVElPTl9EQVlTID0gcmV0ZW50aW9uRW52RGF5cygnUkVQT1JUSU5HX0RPUkFfUkVURU5USU9OX0RBWVMnLCAxODApO1xuXG4vKiogRWZmZWN0aXZlIHN0YW5kYXJkLWV2ZW50IHJldGVudGlvbiAoZGF5cyk6IGEgdmFsaWQgcGVyLW9yZyBvdmVycmlkZSB3aW5zLFxuICogIGVsc2UgdGhlIGdsb2JhbCBlbnYgZGVmYXVsdC4gU2hhcmVkIGJ5IHRoZSBzd2VlcCArIHRoZSBzZXR0aW5ncyBzdXJmYWNlIHNvXG4gKiAgdGhleSBjYW4ndCBkcmlmdC4gVGhlIGAtMWAgdW5saW1pdGVkIHNlbnRpbmVsIHBhc3NlcyBzdHJhaWdodFxuICogIHRocm91Z2gg4oCUIHRoZSBzd2VlcCByZWFkcyBpdCBhcyBcImtlZXAgZm9yZXZlciwgc2tpcCB0aGlzIG9yZydzIHN0YW5kYXJkXG4gKiAgZXZlbnRzXCIuIEEgcG9zaXRpdmUgb3ZlcnJpZGUgY2xhbXBzIHRvIFsxLCBSRVRFTlRJT05fTUFYX0RBWVNdOyBhIGBudWxsYCwgYVxuICogIG5vbi1pbnRlZ2VyLCBvciBhbnkgb3RoZXIgb3V0LW9mLXJhbmdlIHZhbHVlIChlLmcuIDAsIC0yKSBmYWxscyBiYWNrIHRvIHRoZVxuICogIGVudiBkZWZhdWx0LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVFdmVudFJldGVudGlvbkRheXMob3ZlcnJpZGU/OiBudW1iZXIgfCBudWxsKTogbnVtYmVyIHtcbiAgaWYgKG92ZXJyaWRlID09PSAtMSkgcmV0dXJuIC0xO1xuICByZXR1cm4gb3ZlcnJpZGUgIT0gbnVsbCAmJiBOdW1iZXIuaXNJbnRlZ2VyKG92ZXJyaWRlKSAmJiBvdmVycmlkZSA+PSBSRVRFTlRJT05fTUlOX0RBWVNcbiAgICA/IE1hdGgubWluKG92ZXJyaWRlLCBSRVRFTlRJT05fTUFYX0RBWVMpIDogUkVQT1JUSU5HX0VWRU5UX1JFVEVOVElPTl9EQVlTO1xufVxuXG4vKiogRWZmZWN0aXZlIERPUkEtc291cmNlIHJldGVudGlvbiAoZGF5cyk6IGEgdmFsaWQgcGVyLW9yZyBvdmVycmlkZSB3aW5zLCBlbHNlXG4gKiAgdGhlIGdsb2JhbCBlbnYgZGVmYXVsdC4gVGhlIGAtMWAgdW5saW1pdGVkIHNlbnRpbmVsIHBhc3NlcyBzdHJhaWdodFxuICogIHRocm91Z2gg4oCUIHRoZSBzd2VlcCByZWFkcyBpdCBhcyBcImtlZXAgZm9yZXZlciwgc2tpcCB0aGlzIG9yZydzIERPUkEtc291cmNlXG4gKiAgd2luZG93c1wiLiBBIHBvc2l0aXZlIG92ZXJyaWRlIGNsYW1wcyB0byBbMSwgUkVURU5USU9OX01BWF9EQVlTXTsgYSBgbnVsbGAsIGFcbiAqICBub24taW50ZWdlciwgb3IgYW55IG90aGVyIG91dC1vZi1yYW5nZSB2YWx1ZSAoZS5nLiAwLCAtMikgZmFsbHMgYmFjayB0byB0aGVcbiAqICBlbnYgZGVmYXVsdC4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRG9yYVJldGVudGlvbkRheXMob3ZlcnJpZGU/OiBudW1iZXIgfCBudWxsKTogbnVtYmVyIHtcbiAgaWYgKG92ZXJyaWRlID09PSAtMSkgcmV0dXJuIC0xO1xuICByZXR1cm4gb3ZlcnJpZGUgIT0gbnVsbCAmJiBOdW1iZXIuaXNJbnRlZ2VyKG92ZXJyaWRlKSAmJiBvdmVycmlkZSA+PSBSRVRFTlRJT05fTUlOX0RBWVNcbiAgICA/IE1hdGgubWluKG92ZXJyaWRlLCBSRVRFTlRJT05fTUFYX0RBWVMpIDogUkVQT1JUSU5HX0RPUkFfUkVURU5USU9OX0RBWVM7XG59XG5cbi8qKiBUaGUgY3V0b2ZmIGluc3RhbnQgZm9yIGEgcmV0ZW50aW9uIHdpbmRvdzogcm93cyB3aXRoIGBjcmVhdGVkX2F0YCBzdHJpY3RseVxuICogIGJlZm9yZSB0aGlzIChgPCBjdXRvZmZgKSBhcmUgZXhwaXJlZC4gRGV0ZXJtaW5pc3RpYyBmb3IgYSBmaXhlZCBgbm93YC4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXRlbnRpb25DdXRvZmYobm93OiBEYXRlLCBkYXlzOiBudW1iZXIpOiBEYXRlIHtcbiAgcmV0dXJuIG5ldyBEYXRlKG5vdy5nZXRUaW1lKCkgLSBkYXlzICogODZfNDAwXzAwMCk7XG59XG5cbi8qKiBQZXItb3JnIHJldGVudGlvbiBvdmVycmlkZSByb3cuIEJvdGggbnVsbGFibGUg4oeSIGdsb2JhbCBkZWZhdWx0cy4gKi9cbiJdfQ==
|
|
@@ -7,11 +7,8 @@ export declare class ReportingService {
|
|
|
7
7
|
/**
|
|
8
8
|
* Resolve incoming events against the pipeline registry, batch-insert the
|
|
9
9
|
* matched ones, and invalidate reporting caches for affected orgs.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* events for pipelines that haven't called POST /pipelines/registry yet).
|
|
13
|
-
*
|
|
14
|
-
* Returns counts + a sample of unregistered pipeline ids for observability.
|
|
10
|
+
* See ./reporting/ingest.ts — the caller must already hold a sysadmin
|
|
11
|
+
* tenant scope, because a batch spans whatever orgs the registry resolves to.
|
|
15
12
|
*/
|
|
16
13
|
ingestEvents(events: IngestEvent[], onMetric?: (m: IngestMetric) => void): Promise<IngestResult>;
|
|
17
14
|
/** Execution count per pipeline with status breakdown. */
|
|
@@ -189,19 +186,9 @@ export declare class ReportingService {
|
|
|
189
186
|
*/
|
|
190
187
|
getIngestHealth(orgId: string): Promise<IngestHealthStatus | null>;
|
|
191
188
|
/**
|
|
192
|
-
* Reporting retention sweep
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* low-volume DORA source:
|
|
196
|
-
* - `pipeline_events WHERE environment IS NULL` → standard-event window.
|
|
197
|
-
* - `pipeline_events WHERE environment IS NOT NULL` (deploy stages),
|
|
198
|
-
* `deployment_outcomes`, and `incidents` → DORA-source window.
|
|
199
|
-
* Each org's windows come from its `dora_settings` override, else the global
|
|
200
|
-
* env defaults (see {@link resolveEventRetentionDays} / {@link resolveDoraRetentionDays}).
|
|
201
|
-
* `ingest_health` and `dora_settings` are never purged. One `now` for the whole
|
|
202
|
-
* tick (rows crossing the boundary mid-sweep wait for the next). Establishes a
|
|
203
|
-
* sysadmin tenant scope so the deletes span all orgs / bypass RLS — this is a
|
|
204
|
-
* cross-tenant housekeeping job. Returns (and logs) per-window purge tallies.
|
|
189
|
+
* Reporting retention sweep: hard-deletes rows past their per-org retention
|
|
190
|
+
* window (split standard-event vs DORA-source). Cross-tenant housekeeping —
|
|
191
|
+
* it establishes its own sysadmin scope. See ./reporting/retention-sweep.ts.
|
|
205
192
|
*/
|
|
206
193
|
purgeExpiredReportingData(opts?: ReportingRetentionOptions): Promise<ReportingRetentionCounts>;
|
|
207
194
|
}
|