@pipeline-builder/pipeline-data 4.1.24 → 4.1.25
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/alert-destinations.d.ts +17 -0
- package/lib/api/alert-destinations.js +36 -0
- package/lib/api/reporting/analytics-queries.d.ts +274 -0
- package/lib/api/reporting/analytics-queries.js +598 -0
- package/lib/api/reporting/daily-rollup.d.ts +41 -0
- package/lib/api/reporting/daily-rollup.js +173 -0
- package/lib/api/reporting/deploy-rollup.d.ts +78 -0
- package/lib/api/reporting/deploy-rollup.js +203 -0
- package/lib/api/reporting/dora-scoring.d.ts +14 -0
- package/lib/api/reporting/dora-scoring.js +25 -1
- package/lib/api/reporting/dora.js +30 -6
- package/lib/api/reporting/failure-classifier.d.ts +54 -0
- package/lib/api/reporting/failure-classifier.js +179 -0
- package/lib/api/reporting/ingest.d.ts +2 -2
- package/lib/api/reporting/ingest.js +129 -4
- package/lib/api/reporting/last-deploy.d.ts +31 -0
- package/lib/api/reporting/last-deploy.js +93 -0
- package/lib/api/reporting/needs-attention.d.ts +93 -0
- package/lib/api/reporting/needs-attention.js +152 -0
- package/lib/api/reporting/plugin-exposure.d.ts +129 -0
- package/lib/api/reporting/plugin-exposure.js +312 -0
- package/lib/api/reporting/retention-sweep.js +9 -1
- package/lib/api/reporting/sql-helpers.d.ts +35 -2
- package/lib/api/reporting/sql-helpers.js +39 -1
- package/lib/api/reporting/stakeholder/compose.d.ts +115 -0
- package/lib/api/reporting/stakeholder/compose.js +234 -0
- package/lib/api/reporting/stakeholder/index.d.ts +14 -0
- package/lib/api/reporting/stakeholder/index.js +17 -0
- package/lib/api/reporting/stakeholder/period.d.ts +102 -0
- package/lib/api/reporting/stakeholder/period.js +273 -0
- package/lib/api/reporting/stakeholder/report-html.d.ts +93 -0
- package/lib/api/reporting/stakeholder/report-html.js +208 -0
- package/lib/api/reporting/stakeholder/sections.d.ts +117 -0
- package/lib/api/reporting/stakeholder/sections.js +249 -0
- package/lib/api/reporting/stakeholder/store.d.ts +450 -0
- package/lib/api/reporting/stakeholder/store.js +981 -0
- package/lib/api/reporting/stakeholder/templates.d.ts +21 -0
- package/lib/api/reporting/stakeholder/templates.js +104 -0
- package/lib/api/reporting/types.d.ts +73 -0
- package/lib/api/reporting/types.js +1 -1
- package/lib/api/reporting-service.d.ts +94 -5
- package/lib/api/reporting-service.js +169 -10
- package/lib/api/soft-delete-sweep.d.ts +11 -2
- package/lib/api/soft-delete-sweep.js +17 -6
- package/lib/database/drizzle-schema.d.ts +2263 -385
- package/lib/database/drizzle-schema.js +18 -7
- package/lib/database/index.d.ts +1 -1
- package/lib/database/index.js +4 -2
- package/lib/database/schema/compliance.d.ts +0 -682
- package/lib/database/schema/compliance.js +6 -66
- package/lib/database/schema/ecosystem.d.ts +20 -157
- package/lib/database/schema/ecosystem.js +23 -16
- package/lib/database/schema/pipeline.d.ts +200 -0
- package/lib/database/schema/pipeline.js +100 -1
- package/lib/database/schema/plugin.d.ts +20 -1
- package/lib/database/schema/plugin.js +13 -1
- package/lib/database/schema/reporting-analytics.d.ts +1212 -0
- package/lib/database/schema/reporting-analytics.js +250 -0
- package/lib/database/schema/reporting-stakeholder.d.ts +1405 -0
- package/lib/database/schema/reporting-stakeholder.js +251 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +15 -1
- package/lib/testing/exports.json +67 -4
- package/package.json +4 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* The last commit this pipeline deployed to an environment — the exclusive lower
|
|
5
|
+
* bound the events Lambda needs to ask an SCM "what shipped since?".
|
|
6
|
+
*
|
|
7
|
+
* The Lambda keeps that bound in a warm-container map (`lastShaByPipeline` in
|
|
8
|
+
* `scm.ts`). A cold start empties it, so the next deploy resolved as a SINGLE
|
|
9
|
+
* commit: lead time came out as the age of the newest commit alone and
|
|
10
|
+
* `commitCount` as 1, both far too small, and nothing in the data marked the
|
|
11
|
+
* number as degraded. Lambda containers recycle constantly, so this was the
|
|
12
|
+
* common case, not an edge one.
|
|
13
|
+
*
|
|
14
|
+
* Reading it back from `commit_sha` — already persisted for every event that
|
|
15
|
+
* carried one — makes the bound survive a cold start without any new state.
|
|
16
|
+
*/
|
|
17
|
+
import { createLogger } from '@pipeline-builder/api-core';
|
|
18
|
+
import { eq, sql } from 'drizzle-orm';
|
|
19
|
+
import { schema } from '../../database/drizzle-schema.js';
|
|
20
|
+
import { withTenantTx } from '../../database/tenancy.js';
|
|
21
|
+
import { drizzleRows } from '../crud-service.js';
|
|
22
|
+
const logger = createLogger('reporting-last-deploy');
|
|
23
|
+
const NONE = { commitSha: null, deployedAt: null };
|
|
24
|
+
/**
|
|
25
|
+
* The commit sha of the most recent SUCCEEDED deploy-stage execution for this
|
|
26
|
+
* pipeline — narrowed to one `environment` when given.
|
|
27
|
+
*
|
|
28
|
+
* Two steps, because the two facts live on different rows: `environment` is
|
|
29
|
+
* stamped on the deploy STAGE event, while `commit_sha` rides the source
|
|
30
|
+
* ACTION/PIPELINE event of the same execution. They are joined by `execution_id`,
|
|
31
|
+
* exactly as the DORA lead-time query does it.
|
|
32
|
+
*
|
|
33
|
+
* `environment` is OPTIONAL because of who asks. The events Lambda resolves a
|
|
34
|
+
* commit while handling the SOURCE event, and a source stage carries no
|
|
35
|
+
* environment — the execution has not reached a deploy stage yet, so at that
|
|
36
|
+
* moment the environment is genuinely unknown. Omitting it answers "what did this
|
|
37
|
+
* pipeline last ship anywhere", which is the right lower bound for an SCM range;
|
|
38
|
+
* passing it answers per-environment, for callers that know.
|
|
39
|
+
*
|
|
40
|
+
* TENANCY. `caller` is enforced the same way as {@link ingestEvents}: the org
|
|
41
|
+
* comes from the pipeline REGISTRY, never from the caller, and a pipeline outside
|
|
42
|
+
* the caller's allow-list returns nothing. Without this the endpoint would hand
|
|
43
|
+
* any `reporting:ingest` holder another tenant's commit history — the same hole
|
|
44
|
+
* the ingest tenancy check closes on the write side.
|
|
45
|
+
*/
|
|
46
|
+
export async function getLastDeployedCommit(pipelineId, environment, caller) {
|
|
47
|
+
const unrestricted = caller === undefined || caller.crossTenant === true;
|
|
48
|
+
const allowed = new Set(caller?.allowedOrgIds ?? []);
|
|
49
|
+
return withTenantTx(async (tx) => {
|
|
50
|
+
const [registry] = await tx
|
|
51
|
+
.select({ orgId: schema.pipelineRegistry.orgId })
|
|
52
|
+
.from(schema.pipelineRegistry)
|
|
53
|
+
.where(eq(schema.pipelineRegistry.pipelineId, pipelineId));
|
|
54
|
+
// Unregistered pipeline: indistinguishable from "no deploys yet" to the
|
|
55
|
+
// caller, deliberately — an unregistered id must not confirm or deny existence.
|
|
56
|
+
if (!registry)
|
|
57
|
+
return NONE;
|
|
58
|
+
if (!unrestricted && !allowed.has(registry.orgId)) {
|
|
59
|
+
logger.warn('Refused last-deploy-commit lookup outside the caller org scope', {
|
|
60
|
+
pipelineId, foreignOrgId: registry.orgId, allowedOrgIds: [...allowed],
|
|
61
|
+
});
|
|
62
|
+
return NONE;
|
|
63
|
+
}
|
|
64
|
+
const rows = drizzleRows((await tx.execute(sql `
|
|
65
|
+
WITH last_deploy AS (
|
|
66
|
+
SELECT e.execution_id AS execution_id, e.completed_at AS completed_at
|
|
67
|
+
FROM ${schema.pipelineEvent} e
|
|
68
|
+
WHERE e.pipeline_id = ${pipelineId}
|
|
69
|
+
AND e.org_id = ${registry.orgId}
|
|
70
|
+
${environment ? sql `AND e.environment = ${environment}` : sql `AND e.environment IS NOT NULL`}
|
|
71
|
+
AND e.event_type = 'STAGE'
|
|
72
|
+
AND e.status = 'SUCCEEDED'
|
|
73
|
+
AND e.completed_at IS NOT NULL
|
|
74
|
+
ORDER BY e.completed_at DESC
|
|
75
|
+
LIMIT 1
|
|
76
|
+
)
|
|
77
|
+
SELECT c.commit_sha AS commit_sha, ld.completed_at::text AS completed_at
|
|
78
|
+
FROM last_deploy ld
|
|
79
|
+
JOIN ${schema.pipelineEvent} c
|
|
80
|
+
ON c.execution_id = ld.execution_id
|
|
81
|
+
AND c.pipeline_id = ${pipelineId}
|
|
82
|
+
AND c.org_id = ${registry.orgId}
|
|
83
|
+
WHERE c.commit_sha IS NOT NULL
|
|
84
|
+
ORDER BY c.created_at DESC
|
|
85
|
+
LIMIT 1
|
|
86
|
+
`)).rows);
|
|
87
|
+
const row = rows[0];
|
|
88
|
+
if (!row)
|
|
89
|
+
return NONE;
|
|
90
|
+
return { commitSha: row.commit_sha ?? null, deployedAt: row.completed_at ?? null };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGFzdC1kZXBsb3kuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBpL3JlcG9ydGluZy9sYXN0LWRlcGxveS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDMUQsT0FBTyxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFdEMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQzFELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFakQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLHVCQUF1QixDQUFDLENBQUM7QUFVckQsTUFBTSxJQUFJLEdBQXVCLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFFdkU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXFCRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUscUJBQXFCLENBQ3pDLFVBQWtCLEVBQ2xCLFdBQW9CLEVBQ3BCLE1BQXFCO0lBRXJCLE1BQU0sWUFBWSxHQUFHLE1BQU0sS0FBSyxTQUFTLElBQUksTUFBTSxDQUFDLFdBQVcsS0FBSyxJQUFJLENBQUM7SUFDekUsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxFQUFFLGFBQWEsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUVyRCxPQUFPLFlBQVksQ0FBQyxLQUFLLEVBQUUsRUFBRSxFQUFFLEVBQUU7UUFDL0IsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLE1BQU0sRUFBRTthQUN4QixNQUFNLENBQUMsRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLGdCQUFnQixDQUFDLEtBQUssRUFBRSxDQUFDO2FBQ2hELElBQUksQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUM7YUFDN0IsS0FBSyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsVUFBVSxFQUFFLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFDN0Qsd0VBQXdFO1FBQ3hFLGdGQUFnRjtRQUNoRixJQUFJLENBQUMsUUFBUTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBRTNCLElBQUksQ0FBQyxZQUFZLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ2xELE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0VBQWdFLEVBQUU7Z0JBQzVFLFVBQVUsRUFBRSxZQUFZLEVBQUUsUUFBUSxDQUFDLEtBQUssRUFBRSxhQUFhLEVBQUUsQ0FBQyxHQUFHLE9BQU8sQ0FBQzthQUN0RSxDQUFDLENBQUM7WUFDSCxPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFFRCxNQUFNLElBQUksR0FBRyxXQUFXLENBQTZELENBQUMsTUFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQTs7O2VBRy9GLE1BQU0sQ0FBQyxhQUFhO2dDQUNILFVBQVU7MkJBQ2YsUUFBUSxDQUFDLEtBQUs7WUFDN0IsV0FBVyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUEsdUJBQXVCLFdBQVcsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUEsK0JBQStCOzs7Ozs7Ozs7YUFTekYsTUFBTSxDQUFDLGFBQWE7OzZCQUVKLFVBQVU7d0JBQ2YsUUFBUSxDQUFDLEtBQUs7Ozs7S0FJakMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFVixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDcEIsSUFBSSxDQUFDLEdBQUc7WUFBRSxPQUFPLElBQUksQ0FBQztRQUN0QixPQUFPLEVBQUUsU0FBUyxFQUFFLEdBQUcsQ0FBQyxVQUFVLElBQUksSUFBSSxFQUFFLFVBQVUsRUFBRSxHQUFHLENBQUMsWUFBWSxJQUFJLElBQUksRUFBRSxDQUFDO0lBQ3JGLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRoZSBsYXN0IGNvbW1pdCB0aGlzIHBpcGVsaW5lIGRlcGxveWVkIHRvIGFuIGVudmlyb25tZW50IOKAlCB0aGUgZXhjbHVzaXZlIGxvd2VyXG4gKiBib3VuZCB0aGUgZXZlbnRzIExhbWJkYSBuZWVkcyB0byBhc2sgYW4gU0NNIFwid2hhdCBzaGlwcGVkIHNpbmNlP1wiLlxuICpcbiAqIFRoZSBMYW1iZGEga2VlcHMgdGhhdCBib3VuZCBpbiBhIHdhcm0tY29udGFpbmVyIG1hcCAoYGxhc3RTaGFCeVBpcGVsaW5lYCBpblxuICogYHNjbS50c2ApLiBBIGNvbGQgc3RhcnQgZW1wdGllcyBpdCwgc28gdGhlIG5leHQgZGVwbG95IHJlc29sdmVkIGFzIGEgU0lOR0xFXG4gKiBjb21taXQ6IGxlYWQgdGltZSBjYW1lIG91dCBhcyB0aGUgYWdlIG9mIHRoZSBuZXdlc3QgY29tbWl0IGFsb25lIGFuZFxuICogYGNvbW1pdENvdW50YCBhcyAxLCBib3RoIGZhciB0b28gc21hbGwsIGFuZCBub3RoaW5nIGluIHRoZSBkYXRhIG1hcmtlZCB0aGVcbiAqIG51bWJlciBhcyBkZWdyYWRlZC4gTGFtYmRhIGNvbnRhaW5lcnMgcmVjeWNsZSBjb25zdGFudGx5LCBzbyB0aGlzIHdhcyB0aGVcbiAqIGNvbW1vbiBjYXNlLCBub3QgYW4gZWRnZSBvbmUuXG4gKlxuICogUmVhZGluZyBpdCBiYWNrIGZyb20gYGNvbW1pdF9zaGFgIOKAlCBhbHJlYWR5IHBlcnNpc3RlZCBmb3IgZXZlcnkgZXZlbnQgdGhhdFxuICogY2FycmllZCBvbmUg4oCUIG1ha2VzIHRoZSBib3VuZCBzdXJ2aXZlIGEgY29sZCBzdGFydCB3aXRob3V0IGFueSBuZXcgc3RhdGUuXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgZXEsIHNxbCB9IGZyb20gJ2RyaXp6bGUtb3JtJztcbmltcG9ydCB0eXBlIHsgSW5nZXN0Q2FsbGVyIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBzY2hlbWEgfSBmcm9tICcuLi8uLi9kYXRhYmFzZS9kcml6emxlLXNjaGVtYS5qcyc7XG5pbXBvcnQgeyB3aXRoVGVuYW50VHggfSBmcm9tICcuLi8uLi9kYXRhYmFzZS90ZW5hbmN5LmpzJztcbmltcG9ydCB7IGRyaXp6bGVSb3dzIH0gZnJvbSAnLi4vY3J1ZC1zZXJ2aWNlLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdyZXBvcnRpbmctbGFzdC1kZXBsb3knKTtcblxuLyoqIFRoZSBib3VuZCwgb3IgbnVsbHMgd2hlbiB0aGlzIHBpcGVsaW5lIGhhcyBubyByZWNvcmRlZCBkZXBsb3kgY2FycnlpbmcgYSBjb21taXQuICovXG5leHBvcnQgaW50ZXJmYWNlIExhc3REZXBsb3llZENvbW1pdCB7XG4gIC8qKiBDb21taXQgc2hhIG9mIHRoZSBtb3N0IHJlY2VudCBzdWNjZXNzZnVsIGRlcGxveSB0byB0aGUgZW52aXJvbm1lbnQuICovXG4gIGNvbW1pdFNoYTogc3RyaW5nIHwgbnVsbDtcbiAgLyoqIFdoZW4gdGhhdCBkZXBsb3kgY29tcGxldGVkLCBJU08gdGV4dCDigJQgZm9yIHRoZSBjYWxsZXIncyBvd24gbG9nZ2luZy4gKi9cbiAgZGVwbG95ZWRBdDogc3RyaW5nIHwgbnVsbDtcbn1cblxuY29uc3QgTk9ORTogTGFzdERlcGxveWVkQ29tbWl0ID0geyBjb21taXRTaGE6IG51bGwsIGRlcGxveWVkQXQ6IG51bGwgfTtcblxuLyoqXG4gKiBUaGUgY29tbWl0IHNoYSBvZiB0aGUgbW9zdCByZWNlbnQgU1VDQ0VFREVEIGRlcGxveS1zdGFnZSBleGVjdXRpb24gZm9yIHRoaXNcbiAqIHBpcGVsaW5lIOKAlCBuYXJyb3dlZCB0byBvbmUgYGVudmlyb25tZW50YCB3aGVuIGdpdmVuLlxuICpcbiAqIFR3byBzdGVwcywgYmVjYXVzZSB0aGUgdHdvIGZhY3RzIGxpdmUgb24gZGlmZmVyZW50IHJvd3M6IGBlbnZpcm9ubWVudGAgaXNcbiAqIHN0YW1wZWQgb24gdGhlIGRlcGxveSBTVEFHRSBldmVudCwgd2hpbGUgYGNvbW1pdF9zaGFgIHJpZGVzIHRoZSBzb3VyY2VcbiAqIEFDVElPTi9QSVBFTElORSBldmVudCBvZiB0aGUgc2FtZSBleGVjdXRpb24uIFRoZXkgYXJlIGpvaW5lZCBieSBgZXhlY3V0aW9uX2lkYCxcbiAqIGV4YWN0bHkgYXMgdGhlIERPUkEgbGVhZC10aW1lIHF1ZXJ5IGRvZXMgaXQuXG4gKlxuICogYGVudmlyb25tZW50YCBpcyBPUFRJT05BTCBiZWNhdXNlIG9mIHdobyBhc2tzLiBUaGUgZXZlbnRzIExhbWJkYSByZXNvbHZlcyBhXG4gKiBjb21taXQgd2hpbGUgaGFuZGxpbmcgdGhlIFNPVVJDRSBldmVudCwgYW5kIGEgc291cmNlIHN0YWdlIGNhcnJpZXMgbm9cbiAqIGVudmlyb25tZW50IOKAlCB0aGUgZXhlY3V0aW9uIGhhcyBub3QgcmVhY2hlZCBhIGRlcGxveSBzdGFnZSB5ZXQsIHNvIGF0IHRoYXRcbiAqIG1vbWVudCB0aGUgZW52aXJvbm1lbnQgaXMgZ2VudWluZWx5IHVua25vd24uIE9taXR0aW5nIGl0IGFuc3dlcnMgXCJ3aGF0IGRpZCB0aGlzXG4gKiBwaXBlbGluZSBsYXN0IHNoaXAgYW55d2hlcmVcIiwgd2hpY2ggaXMgdGhlIHJpZ2h0IGxvd2VyIGJvdW5kIGZvciBhbiBTQ00gcmFuZ2U7XG4gKiBwYXNzaW5nIGl0IGFuc3dlcnMgcGVyLWVudmlyb25tZW50LCBmb3IgY2FsbGVycyB0aGF0IGtub3cuXG4gKlxuICogVEVOQU5DWS4gYGNhbGxlcmAgaXMgZW5mb3JjZWQgdGhlIHNhbWUgd2F5IGFzIHtAbGluayBpbmdlc3RFdmVudHN9OiB0aGUgb3JnXG4gKiBjb21lcyBmcm9tIHRoZSBwaXBlbGluZSBSRUdJU1RSWSwgbmV2ZXIgZnJvbSB0aGUgY2FsbGVyLCBhbmQgYSBwaXBlbGluZSBvdXRzaWRlXG4gKiB0aGUgY2FsbGVyJ3MgYWxsb3ctbGlzdCByZXR1cm5zIG5vdGhpbmcuIFdpdGhvdXQgdGhpcyB0aGUgZW5kcG9pbnQgd291bGQgaGFuZFxuICogYW55IGByZXBvcnRpbmc6aW5nZXN0YCBob2xkZXIgYW5vdGhlciB0ZW5hbnQncyBjb21taXQgaGlzdG9yeSDigJQgdGhlIHNhbWUgaG9sZVxuICogdGhlIGluZ2VzdCB0ZW5hbmN5IGNoZWNrIGNsb3NlcyBvbiB0aGUgd3JpdGUgc2lkZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldExhc3REZXBsb3llZENvbW1pdChcbiAgcGlwZWxpbmVJZDogc3RyaW5nLFxuICBlbnZpcm9ubWVudD86IHN0cmluZyxcbiAgY2FsbGVyPzogSW5nZXN0Q2FsbGVyLFxuKTogUHJvbWlzZTxMYXN0RGVwbG95ZWRDb21taXQ+IHtcbiAgY29uc3QgdW5yZXN0cmljdGVkID0gY2FsbGVyID09PSB1bmRlZmluZWQgfHwgY2FsbGVyLmNyb3NzVGVuYW50ID09PSB0cnVlO1xuICBjb25zdCBhbGxvd2VkID0gbmV3IFNldChjYWxsZXI/LmFsbG93ZWRPcmdJZHMgPz8gW10pO1xuXG4gIHJldHVybiB3aXRoVGVuYW50VHgoYXN5bmMgKHR4KSA9PiB7XG4gICAgY29uc3QgW3JlZ2lzdHJ5XSA9IGF3YWl0IHR4XG4gICAgICAuc2VsZWN0KHsgb3JnSWQ6IHNjaGVtYS5waXBlbGluZVJlZ2lzdHJ5Lm9yZ0lkIH0pXG4gICAgICAuZnJvbShzY2hlbWEucGlwZWxpbmVSZWdpc3RyeSlcbiAgICAgIC53aGVyZShlcShzY2hlbWEucGlwZWxpbmVSZWdpc3RyeS5waXBlbGluZUlkLCBwaXBlbGluZUlkKSk7XG4gICAgLy8gVW5yZWdpc3RlcmVkIHBpcGVsaW5lOiBpbmRpc3Rpbmd1aXNoYWJsZSBmcm9tIFwibm8gZGVwbG95cyB5ZXRcIiB0byB0aGVcbiAgICAvLyBjYWxsZXIsIGRlbGliZXJhdGVseSDigJQgYW4gdW5yZWdpc3RlcmVkIGlkIG11c3Qgbm90IGNvbmZpcm0gb3IgZGVueSBleGlzdGVuY2UuXG4gICAgaWYgKCFyZWdpc3RyeSkgcmV0dXJuIE5PTkU7XG5cbiAgICBpZiAoIXVucmVzdHJpY3RlZCAmJiAhYWxsb3dlZC5oYXMocmVnaXN0cnkub3JnSWQpKSB7XG4gICAgICBsb2dnZXIud2FybignUmVmdXNlZCBsYXN0LWRlcGxveS1jb21taXQgbG9va3VwIG91dHNpZGUgdGhlIGNhbGxlciBvcmcgc2NvcGUnLCB7XG4gICAgICAgIHBpcGVsaW5lSWQsIGZvcmVpZ25PcmdJZDogcmVnaXN0cnkub3JnSWQsIGFsbG93ZWRPcmdJZHM6IFsuLi5hbGxvd2VkXSxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIE5PTkU7XG4gICAgfVxuXG4gICAgY29uc3Qgcm93cyA9IGRyaXp6bGVSb3dzPHsgY29tbWl0X3NoYTogc3RyaW5nIHwgbnVsbDsgY29tcGxldGVkX2F0OiBzdHJpbmcgfCBudWxsIH0+KChhd2FpdCB0eC5leGVjdXRlKHNxbGBcbiAgICAgIFdJVEggbGFzdF9kZXBsb3kgQVMgKFxuICAgICAgICBTRUxFQ1QgZS5leGVjdXRpb25faWQgQVMgZXhlY3V0aW9uX2lkLCBlLmNvbXBsZXRlZF9hdCBBUyBjb21wbGV0ZWRfYXRcbiAgICAgICAgRlJPTSAke3NjaGVtYS5waXBlbGluZUV2ZW50fSBlXG4gICAgICAgIFdIRVJFIGUucGlwZWxpbmVfaWQgPSAke3BpcGVsaW5lSWR9XG4gICAgICAgICAgQU5EIGUub3JnX2lkID0gJHtyZWdpc3RyeS5vcmdJZH1cbiAgICAgICAgICAke2Vudmlyb25tZW50ID8gc3FsYEFORCBlLmVudmlyb25tZW50ID0gJHtlbnZpcm9ubWVudH1gIDogc3FsYEFORCBlLmVudmlyb25tZW50IElTIE5PVCBOVUxMYH1cbiAgICAgICAgICBBTkQgZS5ldmVudF90eXBlID0gJ1NUQUdFJ1xuICAgICAgICAgIEFORCBlLnN0YXR1cyA9ICdTVUNDRUVERUQnXG4gICAgICAgICAgQU5EIGUuY29tcGxldGVkX2F0IElTIE5PVCBOVUxMXG4gICAgICAgIE9SREVSIEJZIGUuY29tcGxldGVkX2F0IERFU0NcbiAgICAgICAgTElNSVQgMVxuICAgICAgKVxuICAgICAgU0VMRUNUIGMuY29tbWl0X3NoYSBBUyBjb21taXRfc2hhLCBsZC5jb21wbGV0ZWRfYXQ6OnRleHQgQVMgY29tcGxldGVkX2F0XG4gICAgICBGUk9NIGxhc3RfZGVwbG95IGxkXG4gICAgICBKT0lOICR7c2NoZW1hLnBpcGVsaW5lRXZlbnR9IGNcbiAgICAgICAgT04gYy5leGVjdXRpb25faWQgPSBsZC5leGVjdXRpb25faWRcbiAgICAgICBBTkQgYy5waXBlbGluZV9pZCA9ICR7cGlwZWxpbmVJZH1cbiAgICAgICBBTkQgYy5vcmdfaWQgPSAke3JlZ2lzdHJ5Lm9yZ0lkfVxuICAgICAgV0hFUkUgYy5jb21taXRfc2hhIElTIE5PVCBOVUxMXG4gICAgICBPUkRFUiBCWSBjLmNyZWF0ZWRfYXQgREVTQ1xuICAgICAgTElNSVQgMVxuICAgIGApKS5yb3dzKTtcblxuICAgIGNvbnN0IHJvdyA9IHJvd3NbMF07XG4gICAgaWYgKCFyb3cpIHJldHVybiBOT05FO1xuICAgIHJldHVybiB7IGNvbW1pdFNoYTogcm93LmNvbW1pdF9zaGEgPz8gbnVsbCwgZGVwbG95ZWRBdDogcm93LmNvbXBsZXRlZF9hdCA/PyBudWxsIH07XG4gIH0pO1xufVxuIl19
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which pipelines need somebody to look at them, and WHY.
|
|
3
|
+
*
|
|
4
|
+
* The rest of the report describes; this decides. That is a meaningful difference,
|
|
5
|
+
* and it is why every finding carries its EVIDENCE — "p95 up 31%, 4m12s → 5m31s",
|
|
6
|
+
* not "performance degraded". A flag without the number behind it is an opinion the
|
|
7
|
+
* reader has to take on trust, and the first time one is wrong the whole section
|
|
8
|
+
* stops being read.
|
|
9
|
+
*
|
|
10
|
+
* THE THRESHOLDS ARE ORG-CONFIGURABLE, with defaults stated below. They have to be:
|
|
11
|
+
* 90% success is a crisis for a deploy pipeline and unremarkable for a flaky
|
|
12
|
+
* integration suite somebody is already rewriting, and a platform-imposed number
|
|
13
|
+
* would make the section wrong for one of them by construction.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IS DELIBERATELY NOT A RULE: anything per-person, anything about volume
|
|
16
|
+
* ("this team shipped less than that team"), and anything comparing one team to
|
|
17
|
+
* another. Those are the flags that turn a delivery report into a performance
|
|
18
|
+
* review, which is the failure mode this whole feature is trying to avoid.
|
|
19
|
+
*/
|
|
20
|
+
import type { PipelineBreakdownRow } from './analytics-queries.js';
|
|
21
|
+
/** The thresholds a rule trips at. Every one is overridable per org. */
|
|
22
|
+
export interface NeedsAttentionThresholds {
|
|
23
|
+
/** Success rate below this, as a percentage. */
|
|
24
|
+
successRatePct: number;
|
|
25
|
+
/** p95 duration up by more than this, as a percentage of the previous period. */
|
|
26
|
+
p95IncreasePct: number;
|
|
27
|
+
/** This many consecutive failures. */
|
|
28
|
+
consecutiveFailures: number;
|
|
29
|
+
/** No successful run in this many days. */
|
|
30
|
+
staleDays: number;
|
|
31
|
+
/** A failure within this many hours of a pipeline configuration change. */
|
|
32
|
+
failureAfterChangeHours: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The defaults.
|
|
36
|
+
*
|
|
37
|
+
* Each is the point at which a reasonable lead would want to know, not the point at
|
|
38
|
+
* which something is definitely wrong — the section's job is to raise attention,
|
|
39
|
+
* and a threshold set where the problem is already undeniable raises it too late.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_THRESHOLDS: Readonly<NeedsAttentionThresholds>;
|
|
42
|
+
/** Why a pipeline was flagged. The id is stable; the evidence is human. */
|
|
43
|
+
export type AttentionRuleId = 'low_success_rate' | 'slower_p95' | 'consecutive_failures' | 'no_recent_success' | 'failed_after_config_change' | 'vulnerable_plugin' | 'no_deploy_tracking';
|
|
44
|
+
/** One finding about one pipeline. */
|
|
45
|
+
export interface AttentionFinding {
|
|
46
|
+
rule: AttentionRuleId;
|
|
47
|
+
/** The sentence a lead reads, with the number in it. */
|
|
48
|
+
evidence: string;
|
|
49
|
+
/** `high` means "this is broken now"; `medium` means "this is drifting". */
|
|
50
|
+
severity: 'high' | 'medium';
|
|
51
|
+
}
|
|
52
|
+
/** A flagged pipeline and everything that tripped. */
|
|
53
|
+
export interface AttentionItem {
|
|
54
|
+
pipelineId: string;
|
|
55
|
+
pipelineName: string | null;
|
|
56
|
+
findings: AttentionFinding[];
|
|
57
|
+
/** The worst severity among the findings — what the list sorts on. */
|
|
58
|
+
severity: 'high' | 'medium';
|
|
59
|
+
}
|
|
60
|
+
/** What the evaluator needs beyond the current period's breakdown. */
|
|
61
|
+
export interface AttentionInput {
|
|
62
|
+
current: readonly PipelineBreakdownRow[];
|
|
63
|
+
/** The previous period, for the p95 comparison. Absent ⇒ that rule is skipped. */
|
|
64
|
+
previous?: readonly PipelineBreakdownRow[];
|
|
65
|
+
/** Per pipeline: consecutive failures ending the period, and last success. */
|
|
66
|
+
streaks?: ReadonlyMap<string, {
|
|
67
|
+
consecutiveFailures: number;
|
|
68
|
+
lastSuccessAt: Date | null;
|
|
69
|
+
}>;
|
|
70
|
+
/** Per pipeline: the most recent config change, from the audit trail. */
|
|
71
|
+
configChanges?: ReadonlyMap<string, Date>;
|
|
72
|
+
/** Pipelines with an open Critical plugin exposure. */
|
|
73
|
+
vulnerablePipelines?: ReadonlySet<string>;
|
|
74
|
+
/** Pipelines that ran but produced no deploy rows — DORA cannot see them. */
|
|
75
|
+
untrackedPipelines?: ReadonlySet<string>;
|
|
76
|
+
thresholds?: Partial<NeedsAttentionThresholds>;
|
|
77
|
+
now?: Date;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Evaluate every rule against the period.
|
|
81
|
+
*
|
|
82
|
+
* Returns only the pipelines that tripped something, worst first, so the section is
|
|
83
|
+
* a short list of things to do rather than a table of everything.
|
|
84
|
+
*/
|
|
85
|
+
export declare function evaluateNeedsAttention(input: AttentionInput): AttentionItem[];
|
|
86
|
+
/**
|
|
87
|
+
* Read an org's thresholds out of stored settings, falling back per field.
|
|
88
|
+
*
|
|
89
|
+
* Per FIELD, not per object: an org that set only `successRatePct` keeps the
|
|
90
|
+
* defaults for everything else, rather than silently disabling four rules by
|
|
91
|
+
* configuring one.
|
|
92
|
+
*/
|
|
93
|
+
export declare function thresholdsFrom(stored: Partial<NeedsAttentionThresholds> | null | undefined): NeedsAttentionThresholds;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* The defaults.
|
|
5
|
+
*
|
|
6
|
+
* Each is the point at which a reasonable lead would want to know, not the point at
|
|
7
|
+
* which something is definitely wrong — the section's job is to raise attention,
|
|
8
|
+
* and a threshold set where the problem is already undeniable raises it too late.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_THRESHOLDS = Object.freeze({
|
|
11
|
+
successRatePct: 90,
|
|
12
|
+
p95IncreasePct: 25,
|
|
13
|
+
consecutiveFailures: 3,
|
|
14
|
+
staleDays: 7,
|
|
15
|
+
failureAfterChangeHours: 24,
|
|
16
|
+
});
|
|
17
|
+
/** `272000` → `4m32s`, for evidence a person reads. */
|
|
18
|
+
function duration(ms) {
|
|
19
|
+
const total = Math.round(ms / 1000);
|
|
20
|
+
const m = Math.floor(total / 60);
|
|
21
|
+
const s = total % 60;
|
|
22
|
+
return m > 0 ? `${m}m${String(s).padStart(2, '0')}s` : `${s}s`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Evaluate every rule against the period.
|
|
26
|
+
*
|
|
27
|
+
* Returns only the pipelines that tripped something, worst first, so the section is
|
|
28
|
+
* a short list of things to do rather than a table of everything.
|
|
29
|
+
*/
|
|
30
|
+
export function evaluateNeedsAttention(input) {
|
|
31
|
+
const t = { ...DEFAULT_THRESHOLDS, ...input.thresholds };
|
|
32
|
+
const now = input.now ?? new Date();
|
|
33
|
+
const priorByPipeline = new Map((input.previous ?? []).map((p) => [p.pipelineId, p]));
|
|
34
|
+
const items = [];
|
|
35
|
+
for (const row of input.current) {
|
|
36
|
+
const findings = [];
|
|
37
|
+
// 1. Success rate. Skipped entirely for a pipeline with no runs: a pipeline
|
|
38
|
+
// nobody ran has no success rate, and flagging it as 0% would put every
|
|
39
|
+
// dormant pipeline at the top of the list forever.
|
|
40
|
+
if (row.runs > 0 && row.successPct !== null && row.successPct < t.successRatePct) {
|
|
41
|
+
findings.push({
|
|
42
|
+
rule: 'low_success_rate',
|
|
43
|
+
severity: row.successPct < t.successRatePct / 2 ? 'high' : 'medium',
|
|
44
|
+
evidence: `${row.successPct}% of ${row.runs} runs succeeded (below ${t.successRatePct}%)`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// 2. Getting slower. Needs both periods AND a real baseline — a jump from 2s
|
|
48
|
+
// to 3s is 50% and means nothing, so a tiny baseline is not evidence.
|
|
49
|
+
const prior = priorByPipeline.get(row.pipelineId);
|
|
50
|
+
if (row.p95Ms !== null && prior?.p95Ms && prior.p95Ms > 10_000) {
|
|
51
|
+
const deltaPct = ((row.p95Ms - prior.p95Ms) / prior.p95Ms) * 100;
|
|
52
|
+
if (deltaPct > t.p95IncreasePct) {
|
|
53
|
+
findings.push({
|
|
54
|
+
rule: 'slower_p95',
|
|
55
|
+
severity: deltaPct > t.p95IncreasePct * 2 ? 'high' : 'medium',
|
|
56
|
+
evidence: `p95 up ${deltaPct.toFixed(0)}%, ${duration(prior.p95Ms)} → ${duration(row.p95Ms)}`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const streak = input.streaks?.get(row.pipelineId);
|
|
61
|
+
// 3. Consecutive failures. The clearest "this is broken now" signal there is:
|
|
62
|
+
// a 70% success rate over a month and three failures in a row are very
|
|
63
|
+
// different situations, and only the second needs somebody today.
|
|
64
|
+
if (streak && streak.consecutiveFailures >= t.consecutiveFailures) {
|
|
65
|
+
findings.push({
|
|
66
|
+
rule: 'consecutive_failures',
|
|
67
|
+
severity: 'high',
|
|
68
|
+
evidence: `${streak.consecutiveFailures} consecutive failures`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// 4. Nothing has worked lately. Distinct from the rate: a pipeline that
|
|
72
|
+
// succeeded 90% of the time and then stopped succeeding entirely looks fine
|
|
73
|
+
// by rate and is completely broken.
|
|
74
|
+
if (streak && row.runs > 0) {
|
|
75
|
+
const days = streak.lastSuccessAt
|
|
76
|
+
? Math.floor((now.getTime() - streak.lastSuccessAt.getTime()) / 86_400_000)
|
|
77
|
+
: null;
|
|
78
|
+
if (days === null) {
|
|
79
|
+
findings.push({
|
|
80
|
+
rule: 'no_recent_success',
|
|
81
|
+
severity: 'high',
|
|
82
|
+
evidence: `no successful run in this period (${row.runs} attempts)`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (days >= t.staleDays) {
|
|
86
|
+
findings.push({
|
|
87
|
+
rule: 'no_recent_success',
|
|
88
|
+
severity: days >= t.staleDays * 2 ? 'high' : 'medium',
|
|
89
|
+
evidence: `last succeeded ${days} days ago`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// 5. Broke shortly after somebody changed it. The most useful correlation in
|
|
94
|
+
// the whole section, because it names a likely cause rather than a symptom —
|
|
95
|
+
// and it is the one a human would work out by hand from two screens.
|
|
96
|
+
const changedAt = input.configChanges?.get(row.pipelineId);
|
|
97
|
+
if (changedAt && row.lastFailureAt) {
|
|
98
|
+
const gapHours = (new Date(row.lastFailureAt).getTime() - changedAt.getTime()) / 3600_000;
|
|
99
|
+
if (gapHours >= 0 && gapHours <= t.failureAfterChangeHours) {
|
|
100
|
+
findings.push({
|
|
101
|
+
rule: 'failed_after_config_change',
|
|
102
|
+
severity: 'high',
|
|
103
|
+
evidence: `failed ${gapHours < 1 ? 'less than an hour' : `${Math.round(gapHours)}h`} after a configuration change`
|
|
104
|
+
+ (row.lastFailureStage ? ` (${row.lastFailureStage})` : ''),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 6. Running a plugin version with open Critical findings.
|
|
109
|
+
if (input.vulnerablePipelines?.has(row.pipelineId)) {
|
|
110
|
+
findings.push({
|
|
111
|
+
rule: 'vulnerable_plugin',
|
|
112
|
+
severity: 'high',
|
|
113
|
+
evidence: 'runs a plugin version with open Critical vulnerabilities',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// 7. Invisible to DORA. Not a failure — a MEASUREMENT gap, and flagged
|
|
117
|
+
// because a pipeline nobody can see deploying is the reason a deployment-
|
|
118
|
+
// frequency number is wrong, and nothing else in the report would say so.
|
|
119
|
+
if (input.untrackedPipelines?.has(row.pipelineId) && row.runs > 0) {
|
|
120
|
+
findings.push({
|
|
121
|
+
rule: 'no_deploy_tracking',
|
|
122
|
+
severity: 'medium',
|
|
123
|
+
evidence: 'ran, but produced no deploy data — re-synth to include deploy tags',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (findings.length > 0) {
|
|
127
|
+
items.push({
|
|
128
|
+
pipelineId: row.pipelineId,
|
|
129
|
+
pipelineName: row.pipelineName,
|
|
130
|
+
findings,
|
|
131
|
+
severity: findings.some((f) => f.severity === 'high') ? 'high' : 'medium',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// High first, then the pipelines with the most wrong with them.
|
|
136
|
+
return items.sort((a, b) => {
|
|
137
|
+
if (a.severity !== b.severity)
|
|
138
|
+
return a.severity === 'high' ? -1 : 1;
|
|
139
|
+
return b.findings.length - a.findings.length;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Read an org's thresholds out of stored settings, falling back per field.
|
|
144
|
+
*
|
|
145
|
+
* Per FIELD, not per object: an org that set only `successRatePct` keeps the
|
|
146
|
+
* defaults for everything else, rather than silently disabling four rules by
|
|
147
|
+
* configuring one.
|
|
148
|
+
*/
|
|
149
|
+
export function thresholdsFrom(stored) {
|
|
150
|
+
return { ...DEFAULT_THRESHOLDS, ...(stored ?? {}) };
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmVlZHMtYXR0ZW50aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwaS9yZXBvcnRpbmcvbmVlZHMtYXR0ZW50aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFzQ3RDOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUF1QyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ2xGLGNBQWMsRUFBRSxFQUFFO0lBQ2xCLGNBQWMsRUFBRSxFQUFFO0lBQ2xCLG1CQUFtQixFQUFFLENBQUM7SUFDdEIsU0FBUyxFQUFFLENBQUM7SUFDWix1QkFBdUIsRUFBRSxFQUFFO0NBQzVCLENBQUMsQ0FBQztBQStDSCx1REFBdUQ7QUFDdkQsU0FBUyxRQUFRLENBQUMsRUFBVTtJQUMxQixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztJQUNwQyxNQUFNLENBQUMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNqQyxNQUFNLENBQUMsR0FBRyxLQUFLLEdBQUcsRUFBRSxDQUFDO0lBQ3JCLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQztBQUNqRSxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsc0JBQXNCLENBQUMsS0FBcUI7SUFDMUQsTUFBTSxDQUFDLEdBQUcsRUFBRSxHQUFHLGtCQUFrQixFQUFFLEdBQUcsS0FBSyxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQ3pELE1BQU0sR0FBRyxHQUFHLEtBQUssQ0FBQyxHQUFHLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQztJQUNwQyxNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBQyxRQUFRLElBQUksRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3RGLE1BQU0sS0FBSyxHQUFvQixFQUFFLENBQUM7SUFFbEMsS0FBSyxNQUFNLEdBQUcsSUFBSSxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDaEMsTUFBTSxRQUFRLEdBQXVCLEVBQUUsQ0FBQztRQUV4Qyw0RUFBNEU7UUFDNUUsd0VBQXdFO1FBQ3hFLG1EQUFtRDtRQUNuRCxJQUFJLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxVQUFVLEtBQUssSUFBSSxJQUFJLEdBQUcsQ0FBQyxVQUFVLEdBQUcsQ0FBQyxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQ2pGLFFBQVEsQ0FBQyxJQUFJLENBQUM7Z0JBQ1osSUFBSSxFQUFFLGtCQUFrQjtnQkFDeEIsUUFBUSxFQUFFLEdBQUcsQ0FBQyxVQUFVLEdBQUcsQ0FBQyxDQUFDLGNBQWMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUTtnQkFDbkUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDLFVBQVUsUUFBUSxHQUFHLENBQUMsSUFBSSwwQkFBMEIsQ0FBQyxDQUFDLGNBQWMsSUFBSTthQUMxRixDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsNkVBQTZFO1FBQzdFLHNFQUFzRTtRQUN0RSxNQUFNLEtBQUssR0FBRyxlQUFlLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNsRCxJQUFJLEdBQUcsQ0FBQyxLQUFLLEtBQUssSUFBSSxJQUFJLEtBQUssRUFBRSxLQUFLLElBQUksS0FBSyxDQUFDLEtBQUssR0FBRyxNQUFNLEVBQUUsQ0FBQztZQUMvRCxNQUFNLFFBQVEsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLEdBQUcsQ0FBQztZQUNqRSxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUMsY0FBYyxFQUFFLENBQUM7Z0JBQ2hDLFFBQVEsQ0FBQyxJQUFJLENBQUM7b0JBQ1osSUFBSSxFQUFFLFlBQVk7b0JBQ2xCLFFBQVEsRUFBRSxRQUFRLEdBQUcsQ0FBQyxDQUFDLGNBQWMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUTtvQkFDN0QsUUFBUSxFQUFFLFVBQVUsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxRQUFRLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxNQUFNLFFBQVEsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUU7aUJBQzlGLENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO1FBRUQsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBRWxELDhFQUE4RTtRQUM5RSx1RUFBdUU7UUFDdkUsa0VBQWtFO1FBQ2xFLElBQUksTUFBTSxJQUFJLE1BQU0sQ0FBQyxtQkFBbUIsSUFBSSxDQUFDLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztZQUNsRSxRQUFRLENBQUMsSUFBSSxDQUFDO2dCQUNaLElBQUksRUFBRSxzQkFBc0I7Z0JBQzVCLFFBQVEsRUFBRSxNQUFNO2dCQUNoQixRQUFRLEVBQUUsR0FBRyxNQUFNLENBQUMsbUJBQW1CLHVCQUF1QjthQUMvRCxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsd0VBQXdFO1FBQ3hFLDRFQUE0RTtRQUM1RSxvQ0FBb0M7UUFDcEMsSUFBSSxNQUFNLElBQUksR0FBRyxDQUFDLElBQUksR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsYUFBYTtnQkFDL0IsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEdBQUcsTUFBTSxDQUFDLGFBQWEsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLFVBQVUsQ0FBQztnQkFDM0UsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNULElBQUksSUFBSSxLQUFLLElBQUksRUFBRSxDQUFDO2dCQUNsQixRQUFRLENBQUMsSUFBSSxDQUFDO29CQUNaLElBQUksRUFBRSxtQkFBbUI7b0JBQ3pCLFFBQVEsRUFBRSxNQUFNO29CQUNoQixRQUFRLEVBQUUscUNBQXFDLEdBQUcsQ0FBQyxJQUFJLFlBQVk7aUJBQ3BFLENBQUMsQ0FBQztZQUNMLENBQUM7aUJBQU0sSUFBSSxJQUFJLElBQUksQ0FBQyxDQUFDLFNBQVMsRUFBRSxDQUFDO2dCQUMvQixRQUFRLENBQUMsSUFBSSxDQUFDO29CQUNaLElBQUksRUFBRSxtQkFBbUI7b0JBQ3pCLFFBQVEsRUFBRSxJQUFJLElBQUksQ0FBQyxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUTtvQkFDckQsUUFBUSxFQUFFLGtCQUFrQixJQUFJLFdBQVc7aUJBQzVDLENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO1FBRUQsNkVBQTZFO1FBQzdFLDZFQUE2RTtRQUM3RSxxRUFBcUU7UUFDckUsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLGFBQWEsRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQzNELElBQUksU0FBUyxJQUFJLEdBQUcsQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUNuQyxNQUFNLFFBQVEsR0FBRyxDQUFDLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxTQUFTLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBRyxRQUFRLENBQUM7WUFDMUYsSUFBSSxRQUFRLElBQUksQ0FBQyxJQUFJLFFBQVEsSUFBSSxDQUFDLENBQUMsdUJBQXVCLEVBQUUsQ0FBQztnQkFDM0QsUUFBUSxDQUFDLElBQUksQ0FBQztvQkFDWixJQUFJLEVBQUUsNEJBQTRCO29CQUNsQyxRQUFRLEVBQUUsTUFBTTtvQkFDaEIsUUFBUSxFQUFFLFVBQVUsUUFBUSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsR0FBRywrQkFBK0I7MEJBQzlHLENBQUMsR0FBRyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxnQkFBZ0IsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7aUJBQy9ELENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO1FBRUQsMkRBQTJEO1FBQzNELElBQUksS0FBSyxDQUFDLG1CQUFtQixFQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztZQUNuRCxRQUFRLENBQUMsSUFBSSxDQUFDO2dCQUNaLElBQUksRUFBRSxtQkFBbUI7Z0JBQ3pCLFFBQVEsRUFBRSxNQUFNO2dCQUNoQixRQUFRLEVBQUUsMERBQTBEO2FBQ3JFLENBQUMsQ0FBQztRQUNMLENBQUM7UUFFRCx1RUFBdUU7UUFDdkUsMEVBQTBFO1FBQzFFLDBFQUEwRTtRQUMxRSxJQUFJLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDbEUsUUFBUSxDQUFDLElBQUksQ0FBQztnQkFDWixJQUFJLEVBQUUsb0JBQW9CO2dCQUMxQixRQUFRLEVBQUUsUUFBUTtnQkFDbEIsUUFBUSxFQUFFLG9FQUFvRTthQUMvRSxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsSUFBSSxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3hCLEtBQUssQ0FBQyxJQUFJLENBQUM7Z0JBQ1QsVUFBVSxFQUFFLEdBQUcsQ0FBQyxVQUFVO2dCQUMxQixZQUFZLEVBQUUsR0FBRyxDQUFDLFlBQVk7Z0JBQzlCLFFBQVE7Z0JBQ1IsUUFBUSxFQUFFLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUTthQUMxRSxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUVELGdFQUFnRTtJQUNoRSxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUU7UUFDekIsSUFBSSxDQUFDLENBQUMsUUFBUSxLQUFLLENBQUMsQ0FBQyxRQUFRO1lBQUUsT0FBTyxDQUFDLENBQUMsUUFBUSxLQUFLLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNyRSxPQUFPLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDO0lBQy9DLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxjQUFjLENBQUMsTUFBNEQ7SUFDekYsT0FBTyxFQUFFLEdBQUcsa0JBQWtCLEVBQUUsR0FBRyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUMsRUFBRSxDQUFDO0FBQ3RELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBXaGljaCBwaXBlbGluZXMgbmVlZCBzb21lYm9keSB0byBsb29rIGF0IHRoZW0sIGFuZCBXSFkuXG4gKlxuICogVGhlIHJlc3Qgb2YgdGhlIHJlcG9ydCBkZXNjcmliZXM7IHRoaXMgZGVjaWRlcy4gVGhhdCBpcyBhIG1lYW5pbmdmdWwgZGlmZmVyZW5jZSxcbiAqIGFuZCBpdCBpcyB3aHkgZXZlcnkgZmluZGluZyBjYXJyaWVzIGl0cyBFVklERU5DRSDigJQgXCJwOTUgdXAgMzElLCA0bTEycyDihpIgNW0zMXNcIixcbiAqIG5vdCBcInBlcmZvcm1hbmNlIGRlZ3JhZGVkXCIuIEEgZmxhZyB3aXRob3V0IHRoZSBudW1iZXIgYmVoaW5kIGl0IGlzIGFuIG9waW5pb24gdGhlXG4gKiByZWFkZXIgaGFzIHRvIHRha2Ugb24gdHJ1c3QsIGFuZCB0aGUgZmlyc3QgdGltZSBvbmUgaXMgd3JvbmcgdGhlIHdob2xlIHNlY3Rpb25cbiAqIHN0b3BzIGJlaW5nIHJlYWQuXG4gKlxuICogVEhFIFRIUkVTSE9MRFMgQVJFIE9SRy1DT05GSUdVUkFCTEUsIHdpdGggZGVmYXVsdHMgc3RhdGVkIGJlbG93LiBUaGV5IGhhdmUgdG8gYmU6XG4gKiA5MCUgc3VjY2VzcyBpcyBhIGNyaXNpcyBmb3IgYSBkZXBsb3kgcGlwZWxpbmUgYW5kIHVucmVtYXJrYWJsZSBmb3IgYSBmbGFreVxuICogaW50ZWdyYXRpb24gc3VpdGUgc29tZWJvZHkgaXMgYWxyZWFkeSByZXdyaXRpbmcsIGFuZCBhIHBsYXRmb3JtLWltcG9zZWQgbnVtYmVyXG4gKiB3b3VsZCBtYWtlIHRoZSBzZWN0aW9uIHdyb25nIGZvciBvbmUgb2YgdGhlbSBieSBjb25zdHJ1Y3Rpb24uXG4gKlxuICogV0hBVCBJUyBERUxJQkVSQVRFTFkgTk9UIEEgUlVMRTogYW55dGhpbmcgcGVyLXBlcnNvbiwgYW55dGhpbmcgYWJvdXQgdm9sdW1lXG4gKiAoXCJ0aGlzIHRlYW0gc2hpcHBlZCBsZXNzIHRoYW4gdGhhdCB0ZWFtXCIpLCBhbmQgYW55dGhpbmcgY29tcGFyaW5nIG9uZSB0ZWFtIHRvXG4gKiBhbm90aGVyLiBUaG9zZSBhcmUgdGhlIGZsYWdzIHRoYXQgdHVybiBhIGRlbGl2ZXJ5IHJlcG9ydCBpbnRvIGEgcGVyZm9ybWFuY2VcbiAqIHJldmlldywgd2hpY2ggaXMgdGhlIGZhaWx1cmUgbW9kZSB0aGlzIHdob2xlIGZlYXR1cmUgaXMgdHJ5aW5nIHRvIGF2b2lkLlxuICovXG5cbmltcG9ydCB0eXBlIHsgUGlwZWxpbmVCcmVha2Rvd25Sb3cgfSBmcm9tICcuL2FuYWx5dGljcy1xdWVyaWVzLmpzJztcblxuLyoqIFRoZSB0aHJlc2hvbGRzIGEgcnVsZSB0cmlwcyBhdC4gRXZlcnkgb25lIGlzIG92ZXJyaWRhYmxlIHBlciBvcmcuICovXG5leHBvcnQgaW50ZXJmYWNlIE5lZWRzQXR0ZW50aW9uVGhyZXNob2xkcyB7XG4gIC8qKiBTdWNjZXNzIHJhdGUgYmVsb3cgdGhpcywgYXMgYSBwZXJjZW50YWdlLiAqL1xuICBzdWNjZXNzUmF0ZVBjdDogbnVtYmVyO1xuICAvKiogcDk1IGR1cmF0aW9uIHVwIGJ5IG1vcmUgdGhhbiB0aGlzLCBhcyBhIHBlcmNlbnRhZ2Ugb2YgdGhlIHByZXZpb3VzIHBlcmlvZC4gKi9cbiAgcDk1SW5jcmVhc2VQY3Q6IG51bWJlcjtcbiAgLyoqIFRoaXMgbWFueSBjb25zZWN1dGl2ZSBmYWlsdXJlcy4gKi9cbiAgY29uc2VjdXRpdmVGYWlsdXJlczogbnVtYmVyO1xuICAvKiogTm8gc3VjY2Vzc2Z1bCBydW4gaW4gdGhpcyBtYW55IGRheXMuICovXG4gIHN0YWxlRGF5czogbnVtYmVyO1xuICAvKiogQSBmYWlsdXJlIHdpdGhpbiB0aGlzIG1hbnkgaG91cnMgb2YgYSBwaXBlbGluZSBjb25maWd1cmF0aW9uIGNoYW5nZS4gKi9cbiAgZmFpbHVyZUFmdGVyQ2hhbmdlSG91cnM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBUaGUgZGVmYXVsdHMuXG4gKlxuICogRWFjaCBpcyB0aGUgcG9pbnQgYXQgd2hpY2ggYSByZWFzb25hYmxlIGxlYWQgd291bGQgd2FudCB0byBrbm93LCBub3QgdGhlIHBvaW50IGF0XG4gKiB3aGljaCBzb21ldGhpbmcgaXMgZGVmaW5pdGVseSB3cm9uZyDigJQgdGhlIHNlY3Rpb24ncyBqb2IgaXMgdG8gcmFpc2UgYXR0ZW50aW9uLFxuICogYW5kIGEgdGhyZXNob2xkIHNldCB3aGVyZSB0aGUgcHJvYmxlbSBpcyBhbHJlYWR5IHVuZGVuaWFibGUgcmFpc2VzIGl0IHRvbyBsYXRlLlxuICovXG5leHBvcnQgY29uc3QgREVGQVVMVF9USFJFU0hPTERTOiBSZWFkb25seTxOZWVkc0F0dGVudGlvblRocmVzaG9sZHM+ID0gT2JqZWN0LmZyZWV6ZSh7XG4gIHN1Y2Nlc3NSYXRlUGN0OiA5MCxcbiAgcDk1SW5jcmVhc2VQY3Q6IDI1LFxuICBjb25zZWN1dGl2ZUZhaWx1cmVzOiAzLFxuICBzdGFsZURheXM6IDcsXG4gIGZhaWx1cmVBZnRlckNoYW5nZUhvdXJzOiAyNCxcbn0pO1xuXG4vKiogV2h5IGEgcGlwZWxpbmUgd2FzIGZsYWdnZWQuIFRoZSBpZCBpcyBzdGFibGU7IHRoZSBldmlkZW5jZSBpcyBodW1hbi4gKi9cbmV4cG9ydCB0eXBlIEF0dGVudGlvblJ1bGVJZCA9XG4gIHwgJ2xvd19zdWNjZXNzX3JhdGUnXG4gIHwgJ3Nsb3dlcl9wOTUnXG4gIHwgJ2NvbnNlY3V0aXZlX2ZhaWx1cmVzJ1xuICB8ICdub19yZWNlbnRfc3VjY2VzcydcbiAgfCAnZmFpbGVkX2FmdGVyX2NvbmZpZ19jaGFuZ2UnXG4gIHwgJ3Z1bG5lcmFibGVfcGx1Z2luJ1xuICB8ICdub19kZXBsb3lfdHJhY2tpbmcnO1xuXG4vKiogT25lIGZpbmRpbmcgYWJvdXQgb25lIHBpcGVsaW5lLiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdHRlbnRpb25GaW5kaW5nIHtcbiAgcnVsZTogQXR0ZW50aW9uUnVsZUlkO1xuICAvKiogVGhlIHNlbnRlbmNlIGEgbGVhZCByZWFkcywgd2l0aCB0aGUgbnVtYmVyIGluIGl0LiAqL1xuICBldmlkZW5jZTogc3RyaW5nO1xuICAvKiogYGhpZ2hgIG1lYW5zIFwidGhpcyBpcyBicm9rZW4gbm93XCI7IGBtZWRpdW1gIG1lYW5zIFwidGhpcyBpcyBkcmlmdGluZ1wiLiAqL1xuICBzZXZlcml0eTogJ2hpZ2gnIHwgJ21lZGl1bSc7XG59XG5cbi8qKiBBIGZsYWdnZWQgcGlwZWxpbmUgYW5kIGV2ZXJ5dGhpbmcgdGhhdCB0cmlwcGVkLiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdHRlbnRpb25JdGVtIHtcbiAgcGlwZWxpbmVJZDogc3RyaW5nO1xuICBwaXBlbGluZU5hbWU6IHN0cmluZyB8IG51bGw7XG4gIGZpbmRpbmdzOiBBdHRlbnRpb25GaW5kaW5nW107XG4gIC8qKiBUaGUgd29yc3Qgc2V2ZXJpdHkgYW1vbmcgdGhlIGZpbmRpbmdzIOKAlCB3aGF0IHRoZSBsaXN0IHNvcnRzIG9uLiAqL1xuICBzZXZlcml0eTogJ2hpZ2gnIHwgJ21lZGl1bSc7XG59XG5cbi8qKiBXaGF0IHRoZSBldmFsdWF0b3IgbmVlZHMgYmV5b25kIHRoZSBjdXJyZW50IHBlcmlvZCdzIGJyZWFrZG93bi4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXR0ZW50aW9uSW5wdXQge1xuICBjdXJyZW50OiByZWFkb25seSBQaXBlbGluZUJyZWFrZG93blJvd1tdO1xuICAvKiogVGhlIHByZXZpb3VzIHBlcmlvZCwgZm9yIHRoZSBwOTUgY29tcGFyaXNvbi4gQWJzZW50IOKHkiB0aGF0IHJ1bGUgaXMgc2tpcHBlZC4gKi9cbiAgcHJldmlvdXM/OiByZWFkb25seSBQaXBlbGluZUJyZWFrZG93blJvd1tdO1xuICAvKiogUGVyIHBpcGVsaW5lOiBjb25zZWN1dGl2ZSBmYWlsdXJlcyBlbmRpbmcgdGhlIHBlcmlvZCwgYW5kIGxhc3Qgc3VjY2Vzcy4gKi9cbiAgc3RyZWFrcz86IFJlYWRvbmx5TWFwPHN0cmluZywgeyBjb25zZWN1dGl2ZUZhaWx1cmVzOiBudW1iZXI7IGxhc3RTdWNjZXNzQXQ6IERhdGUgfCBudWxsIH0+O1xuICAvKiogUGVyIHBpcGVsaW5lOiB0aGUgbW9zdCByZWNlbnQgY29uZmlnIGNoYW5nZSwgZnJvbSB0aGUgYXVkaXQgdHJhaWwuICovXG4gIGNvbmZpZ0NoYW5nZXM/OiBSZWFkb25seU1hcDxzdHJpbmcsIERhdGU+O1xuICAvKiogUGlwZWxpbmVzIHdpdGggYW4gb3BlbiBDcml0aWNhbCBwbHVnaW4gZXhwb3N1cmUuICovXG4gIHZ1bG5lcmFibGVQaXBlbGluZXM/OiBSZWFkb25seVNldDxzdHJpbmc+O1xuICAvKiogUGlwZWxpbmVzIHRoYXQgcmFuIGJ1dCBwcm9kdWNlZCBubyBkZXBsb3kgcm93cyDigJQgRE9SQSBjYW5ub3Qgc2VlIHRoZW0uICovXG4gIHVudHJhY2tlZFBpcGVsaW5lcz86IFJlYWRvbmx5U2V0PHN0cmluZz47XG4gIHRocmVzaG9sZHM/OiBQYXJ0aWFsPE5lZWRzQXR0ZW50aW9uVGhyZXNob2xkcz47XG4gIG5vdz86IERhdGU7XG59XG5cbi8qKiBgMjcyMDAwYCDihpIgYDRtMzJzYCwgZm9yIGV2aWRlbmNlIGEgcGVyc29uIHJlYWRzLiAqL1xuZnVuY3Rpb24gZHVyYXRpb24obXM6IG51bWJlcik6IHN0cmluZyB7XG4gIGNvbnN0IHRvdGFsID0gTWF0aC5yb3VuZChtcyAvIDEwMDApO1xuICBjb25zdCBtID0gTWF0aC5mbG9vcih0b3RhbCAvIDYwKTtcbiAgY29uc3QgcyA9IHRvdGFsICUgNjA7XG4gIHJldHVybiBtID4gMCA/IGAke219bSR7U3RyaW5nKHMpLnBhZFN0YXJ0KDIsICcwJyl9c2AgOiBgJHtzfXNgO1xufVxuXG4vKipcbiAqIEV2YWx1YXRlIGV2ZXJ5IHJ1bGUgYWdhaW5zdCB0aGUgcGVyaW9kLlxuICpcbiAqIFJldHVybnMgb25seSB0aGUgcGlwZWxpbmVzIHRoYXQgdHJpcHBlZCBzb21ldGhpbmcsIHdvcnN0IGZpcnN0LCBzbyB0aGUgc2VjdGlvbiBpc1xuICogYSBzaG9ydCBsaXN0IG9mIHRoaW5ncyB0byBkbyByYXRoZXIgdGhhbiBhIHRhYmxlIG9mIGV2ZXJ5dGhpbmcuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBldmFsdWF0ZU5lZWRzQXR0ZW50aW9uKGlucHV0OiBBdHRlbnRpb25JbnB1dCk6IEF0dGVudGlvbkl0ZW1bXSB7XG4gIGNvbnN0IHQgPSB7IC4uLkRFRkFVTFRfVEhSRVNIT0xEUywgLi4uaW5wdXQudGhyZXNob2xkcyB9O1xuICBjb25zdCBub3cgPSBpbnB1dC5ub3cgPz8gbmV3IERhdGUoKTtcbiAgY29uc3QgcHJpb3JCeVBpcGVsaW5lID0gbmV3IE1hcCgoaW5wdXQucHJldmlvdXMgPz8gW10pLm1hcCgocCkgPT4gW3AucGlwZWxpbmVJZCwgcF0pKTtcbiAgY29uc3QgaXRlbXM6IEF0dGVudGlvbkl0ZW1bXSA9IFtdO1xuXG4gIGZvciAoY29uc3Qgcm93IG9mIGlucHV0LmN1cnJlbnQpIHtcbiAgICBjb25zdCBmaW5kaW5nczogQXR0ZW50aW9uRmluZGluZ1tdID0gW107XG5cbiAgICAvLyAxLiBTdWNjZXNzIHJhdGUuIFNraXBwZWQgZW50aXJlbHkgZm9yIGEgcGlwZWxpbmUgd2l0aCBubyBydW5zOiBhIHBpcGVsaW5lXG4gICAgLy8gbm9ib2R5IHJhbiBoYXMgbm8gc3VjY2VzcyByYXRlLCBhbmQgZmxhZ2dpbmcgaXQgYXMgMCUgd291bGQgcHV0IGV2ZXJ5XG4gICAgLy8gZG9ybWFudCBwaXBlbGluZSBhdCB0aGUgdG9wIG9mIHRoZSBsaXN0IGZvcmV2ZXIuXG4gICAgaWYgKHJvdy5ydW5zID4gMCAmJiByb3cuc3VjY2Vzc1BjdCAhPT0gbnVsbCAmJiByb3cuc3VjY2Vzc1BjdCA8IHQuc3VjY2Vzc1JhdGVQY3QpIHtcbiAgICAgIGZpbmRpbmdzLnB1c2goe1xuICAgICAgICBydWxlOiAnbG93X3N1Y2Nlc3NfcmF0ZScsXG4gICAgICAgIHNldmVyaXR5OiByb3cuc3VjY2Vzc1BjdCA8IHQuc3VjY2Vzc1JhdGVQY3QgLyAyID8gJ2hpZ2gnIDogJ21lZGl1bScsXG4gICAgICAgIGV2aWRlbmNlOiBgJHtyb3cuc3VjY2Vzc1BjdH0lIG9mICR7cm93LnJ1bnN9IHJ1bnMgc3VjY2VlZGVkIChiZWxvdyAke3Quc3VjY2Vzc1JhdGVQY3R9JSlgLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gMi4gR2V0dGluZyBzbG93ZXIuIE5lZWRzIGJvdGggcGVyaW9kcyBBTkQgYSByZWFsIGJhc2VsaW5lIOKAlCBhIGp1bXAgZnJvbSAyc1xuICAgIC8vIHRvIDNzIGlzIDUwJSBhbmQgbWVhbnMgbm90aGluZywgc28gYSB0aW55IGJhc2VsaW5lIGlzIG5vdCBldmlkZW5jZS5cbiAgICBjb25zdCBwcmlvciA9IHByaW9yQnlQaXBlbGluZS5nZXQocm93LnBpcGVsaW5lSWQpO1xuICAgIGlmIChyb3cucDk1TXMgIT09IG51bGwgJiYgcHJpb3I/LnA5NU1zICYmIHByaW9yLnA5NU1zID4gMTBfMDAwKSB7XG4gICAgICBjb25zdCBkZWx0YVBjdCA9ICgocm93LnA5NU1zIC0gcHJpb3IucDk1TXMpIC8gcHJpb3IucDk1TXMpICogMTAwO1xuICAgICAgaWYgKGRlbHRhUGN0ID4gdC5wOTVJbmNyZWFzZVBjdCkge1xuICAgICAgICBmaW5kaW5ncy5wdXNoKHtcbiAgICAgICAgICBydWxlOiAnc2xvd2VyX3A5NScsXG4gICAgICAgICAgc2V2ZXJpdHk6IGRlbHRhUGN0ID4gdC5wOTVJbmNyZWFzZVBjdCAqIDIgPyAnaGlnaCcgOiAnbWVkaXVtJyxcbiAgICAgICAgICBldmlkZW5jZTogYHA5NSB1cCAke2RlbHRhUGN0LnRvRml4ZWQoMCl9JSwgJHtkdXJhdGlvbihwcmlvci5wOTVNcyl9IOKGkiAke2R1cmF0aW9uKHJvdy5wOTVNcyl9YCxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgY29uc3Qgc3RyZWFrID0gaW5wdXQuc3RyZWFrcz8uZ2V0KHJvdy5waXBlbGluZUlkKTtcblxuICAgIC8vIDMuIENvbnNlY3V0aXZlIGZhaWx1cmVzLiBUaGUgY2xlYXJlc3QgXCJ0aGlzIGlzIGJyb2tlbiBub3dcIiBzaWduYWwgdGhlcmUgaXM6XG4gICAgLy8gYSA3MCUgc3VjY2VzcyByYXRlIG92ZXIgYSBtb250aCBhbmQgdGhyZWUgZmFpbHVyZXMgaW4gYSByb3cgYXJlIHZlcnlcbiAgICAvLyBkaWZmZXJlbnQgc2l0dWF0aW9ucywgYW5kIG9ubHkgdGhlIHNlY29uZCBuZWVkcyBzb21lYm9keSB0b2RheS5cbiAgICBpZiAoc3RyZWFrICYmIHN0cmVhay5jb25zZWN1dGl2ZUZhaWx1cmVzID49IHQuY29uc2VjdXRpdmVGYWlsdXJlcykge1xuICAgICAgZmluZGluZ3MucHVzaCh7XG4gICAgICAgIHJ1bGU6ICdjb25zZWN1dGl2ZV9mYWlsdXJlcycsXG4gICAgICAgIHNldmVyaXR5OiAnaGlnaCcsXG4gICAgICAgIGV2aWRlbmNlOiBgJHtzdHJlYWsuY29uc2VjdXRpdmVGYWlsdXJlc30gY29uc2VjdXRpdmUgZmFpbHVyZXNgLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gNC4gTm90aGluZyBoYXMgd29ya2VkIGxhdGVseS4gRGlzdGluY3QgZnJvbSB0aGUgcmF0ZTogYSBwaXBlbGluZSB0aGF0XG4gICAgLy8gc3VjY2VlZGVkIDkwJSBvZiB0aGUgdGltZSBhbmQgdGhlbiBzdG9wcGVkIHN1Y2NlZWRpbmcgZW50aXJlbHkgbG9va3MgZmluZVxuICAgIC8vIGJ5IHJhdGUgYW5kIGlzIGNvbXBsZXRlbHkgYnJva2VuLlxuICAgIGlmIChzdHJlYWsgJiYgcm93LnJ1bnMgPiAwKSB7XG4gICAgICBjb25zdCBkYXlzID0gc3RyZWFrLmxhc3RTdWNjZXNzQXRcbiAgICAgICAgPyBNYXRoLmZsb29yKChub3cuZ2V0VGltZSgpIC0gc3RyZWFrLmxhc3RTdWNjZXNzQXQuZ2V0VGltZSgpKSAvIDg2XzQwMF8wMDApXG4gICAgICAgIDogbnVsbDtcbiAgICAgIGlmIChkYXlzID09PSBudWxsKSB7XG4gICAgICAgIGZpbmRpbmdzLnB1c2goe1xuICAgICAgICAgIHJ1bGU6ICdub19yZWNlbnRfc3VjY2VzcycsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdoaWdoJyxcbiAgICAgICAgICBldmlkZW5jZTogYG5vIHN1Y2Nlc3NmdWwgcnVuIGluIHRoaXMgcGVyaW9kICgke3Jvdy5ydW5zfSBhdHRlbXB0cylgLFxuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSBpZiAoZGF5cyA+PSB0LnN0YWxlRGF5cykge1xuICAgICAgICBmaW5kaW5ncy5wdXNoKHtcbiAgICAgICAgICBydWxlOiAnbm9fcmVjZW50X3N1Y2Nlc3MnLFxuICAgICAgICAgIHNldmVyaXR5OiBkYXlzID49IHQuc3RhbGVEYXlzICogMiA/ICdoaWdoJyA6ICdtZWRpdW0nLFxuICAgICAgICAgIGV2aWRlbmNlOiBgbGFzdCBzdWNjZWVkZWQgJHtkYXlzfSBkYXlzIGFnb2AsXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIDUuIEJyb2tlIHNob3J0bHkgYWZ0ZXIgc29tZWJvZHkgY2hhbmdlZCBpdC4gVGhlIG1vc3QgdXNlZnVsIGNvcnJlbGF0aW9uIGluXG4gICAgLy8gdGhlIHdob2xlIHNlY3Rpb24sIGJlY2F1c2UgaXQgbmFtZXMgYSBsaWtlbHkgY2F1c2UgcmF0aGVyIHRoYW4gYSBzeW1wdG9tIOKAlFxuICAgIC8vIGFuZCBpdCBpcyB0aGUgb25lIGEgaHVtYW4gd291bGQgd29yayBvdXQgYnkgaGFuZCBmcm9tIHR3byBzY3JlZW5zLlxuICAgIGNvbnN0IGNoYW5nZWRBdCA9IGlucHV0LmNvbmZpZ0NoYW5nZXM/LmdldChyb3cucGlwZWxpbmVJZCk7XG4gICAgaWYgKGNoYW5nZWRBdCAmJiByb3cubGFzdEZhaWx1cmVBdCkge1xuICAgICAgY29uc3QgZ2FwSG91cnMgPSAobmV3IERhdGUocm93Lmxhc3RGYWlsdXJlQXQpLmdldFRpbWUoKSAtIGNoYW5nZWRBdC5nZXRUaW1lKCkpIC8gMzYwMF8wMDA7XG4gICAgICBpZiAoZ2FwSG91cnMgPj0gMCAmJiBnYXBIb3VycyA8PSB0LmZhaWx1cmVBZnRlckNoYW5nZUhvdXJzKSB7XG4gICAgICAgIGZpbmRpbmdzLnB1c2goe1xuICAgICAgICAgIHJ1bGU6ICdmYWlsZWRfYWZ0ZXJfY29uZmlnX2NoYW5nZScsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdoaWdoJyxcbiAgICAgICAgICBldmlkZW5jZTogYGZhaWxlZCAke2dhcEhvdXJzIDwgMSA/ICdsZXNzIHRoYW4gYW4gaG91cicgOiBgJHtNYXRoLnJvdW5kKGdhcEhvdXJzKX1oYH0gYWZ0ZXIgYSBjb25maWd1cmF0aW9uIGNoYW5nZWBcbiAgICAgICAgICAgICsgKHJvdy5sYXN0RmFpbHVyZVN0YWdlID8gYCAoJHtyb3cubGFzdEZhaWx1cmVTdGFnZX0pYCA6ICcnKSxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gNi4gUnVubmluZyBhIHBsdWdpbiB2ZXJzaW9uIHdpdGggb3BlbiBDcml0aWNhbCBmaW5kaW5ncy5cbiAgICBpZiAoaW5wdXQudnVsbmVyYWJsZVBpcGVsaW5lcz8uaGFzKHJvdy5waXBlbGluZUlkKSkge1xuICAgICAgZmluZGluZ3MucHVzaCh7XG4gICAgICAgIHJ1bGU6ICd2dWxuZXJhYmxlX3BsdWdpbicsXG4gICAgICAgIHNldmVyaXR5OiAnaGlnaCcsXG4gICAgICAgIGV2aWRlbmNlOiAncnVucyBhIHBsdWdpbiB2ZXJzaW9uIHdpdGggb3BlbiBDcml0aWNhbCB2dWxuZXJhYmlsaXRpZXMnLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgLy8gNy4gSW52aXNpYmxlIHRvIERPUkEuIE5vdCBhIGZhaWx1cmUg4oCUIGEgTUVBU1VSRU1FTlQgZ2FwLCBhbmQgZmxhZ2dlZFxuICAgIC8vIGJlY2F1c2UgYSBwaXBlbGluZSBub2JvZHkgY2FuIHNlZSBkZXBsb3lpbmcgaXMgdGhlIHJlYXNvbiBhIGRlcGxveW1lbnQtXG4gICAgLy8gZnJlcXVlbmN5IG51bWJlciBpcyB3cm9uZywgYW5kIG5vdGhpbmcgZWxzZSBpbiB0aGUgcmVwb3J0IHdvdWxkIHNheSBzby5cbiAgICBpZiAoaW5wdXQudW50cmFja2VkUGlwZWxpbmVzPy5oYXMocm93LnBpcGVsaW5lSWQpICYmIHJvdy5ydW5zID4gMCkge1xuICAgICAgZmluZGluZ3MucHVzaCh7XG4gICAgICAgIHJ1bGU6ICdub19kZXBsb3lfdHJhY2tpbmcnLFxuICAgICAgICBzZXZlcml0eTogJ21lZGl1bScsXG4gICAgICAgIGV2aWRlbmNlOiAncmFuLCBidXQgcHJvZHVjZWQgbm8gZGVwbG95IGRhdGEg4oCUIHJlLXN5bnRoIHRvIGluY2x1ZGUgZGVwbG95IHRhZ3MnLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgaWYgKGZpbmRpbmdzLmxlbmd0aCA+IDApIHtcbiAgICAgIGl0ZW1zLnB1c2goe1xuICAgICAgICBwaXBlbGluZUlkOiByb3cucGlwZWxpbmVJZCxcbiAgICAgICAgcGlwZWxpbmVOYW1lOiByb3cucGlwZWxpbmVOYW1lLFxuICAgICAgICBmaW5kaW5ncyxcbiAgICAgICAgc2V2ZXJpdHk6IGZpbmRpbmdzLnNvbWUoKGYpID0+IGYuc2V2ZXJpdHkgPT09ICdoaWdoJykgPyAnaGlnaCcgOiAnbWVkaXVtJyxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuXG4gIC8vIEhpZ2ggZmlyc3QsIHRoZW4gdGhlIHBpcGVsaW5lcyB3aXRoIHRoZSBtb3N0IHdyb25nIHdpdGggdGhlbS5cbiAgcmV0dXJuIGl0ZW1zLnNvcnQoKGEsIGIpID0+IHtcbiAgICBpZiAoYS5zZXZlcml0eSAhPT0gYi5zZXZlcml0eSkgcmV0dXJuIGEuc2V2ZXJpdHkgPT09ICdoaWdoJyA/IC0xIDogMTtcbiAgICByZXR1cm4gYi5maW5kaW5ncy5sZW5ndGggLSBhLmZpbmRpbmdzLmxlbmd0aDtcbiAgfSk7XG59XG5cbi8qKlxuICogUmVhZCBhbiBvcmcncyB0aHJlc2hvbGRzIG91dCBvZiBzdG9yZWQgc2V0dGluZ3MsIGZhbGxpbmcgYmFjayBwZXIgZmllbGQuXG4gKlxuICogUGVyIEZJRUxELCBub3QgcGVyIG9iamVjdDogYW4gb3JnIHRoYXQgc2V0IG9ubHkgYHN1Y2Nlc3NSYXRlUGN0YCBrZWVwcyB0aGVcbiAqIGRlZmF1bHRzIGZvciBldmVyeXRoaW5nIGVsc2UsIHJhdGhlciB0aGFuIHNpbGVudGx5IGRpc2FibGluZyBmb3VyIHJ1bGVzIGJ5XG4gKiBjb25maWd1cmluZyBvbmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0aHJlc2hvbGRzRnJvbShzdG9yZWQ6IFBhcnRpYWw8TmVlZHNBdHRlbnRpb25UaHJlc2hvbGRzPiB8IG51bGwgfCB1bmRlZmluZWQpOiBOZWVkc0F0dGVudGlvblRocmVzaG9sZHMge1xuICByZXR1cm4geyAuLi5ERUZBVUxUX1RIUkVTSE9MRFMsIC4uLihzdG9yZWQgPz8ge30pIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ExposureSource } from '../../database/schema/reporting-analytics.js';
|
|
2
|
+
/** How far behind a resolved version is from the newest one available. */
|
|
3
|
+
export type VersionGap = 'major' | 'minor' | 'patch' | 'none';
|
|
4
|
+
/** One resolved step, as the writer takes it. */
|
|
5
|
+
export interface ResolvedStep {
|
|
6
|
+
orgId: string;
|
|
7
|
+
pipelineId: string;
|
|
8
|
+
stageName: string;
|
|
9
|
+
stepName: string;
|
|
10
|
+
pluginPublisher?: string | null;
|
|
11
|
+
pluginName: string;
|
|
12
|
+
declaredVersion?: string | null;
|
|
13
|
+
resolvedVersion?: string | null;
|
|
14
|
+
latestVersion?: string | null;
|
|
15
|
+
withinPolicy?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* How far `resolved` is behind `latest`.
|
|
19
|
+
*
|
|
20
|
+
* `null` when either side is unparseable, NOT `'none'`. "We could not tell" and
|
|
21
|
+
* "it is up to date" are opposite answers, and reporting the second for the first
|
|
22
|
+
* is how a pipeline on an ancient pre-release version gets a clean bill of health.
|
|
23
|
+
*/
|
|
24
|
+
export declare function versionGap(resolved: string | null | undefined, latest: string | null | undefined): VersionGap | null;
|
|
25
|
+
/**
|
|
26
|
+
* Replace a pipeline's resolution rows.
|
|
27
|
+
*
|
|
28
|
+
* DELETE-then-INSERT for the pipeline, in one transaction, because a step can be
|
|
29
|
+
* REMOVED from a config: an upsert-only writer would leave the deleted step's row
|
|
30
|
+
* behind forever, and the outdated-plugin section would keep reporting a step the
|
|
31
|
+
* pipeline no longer has.
|
|
32
|
+
*/
|
|
33
|
+
export declare function replacePipelineResolution(pipelineId: string, steps: readonly ResolvedStep[]): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Refresh the `latest`/gap columns for one plugin across every pipeline that uses
|
|
36
|
+
* it, without re-resolving anything else.
|
|
37
|
+
*
|
|
38
|
+
* Called on publish. A publish changes what "latest" means for every pipeline in
|
|
39
|
+
* the instance that declares the plugin, and re-resolving each of those pipelines
|
|
40
|
+
* from scratch would make publishing cost O(pipelines) work — so only the two
|
|
41
|
+
* columns a publish can change are touched.
|
|
42
|
+
*/
|
|
43
|
+
export declare function refreshLatestForPlugin(pluginName: string, pluginPublisher: string | null, latestVersion: string): Promise<number>;
|
|
44
|
+
/** One flagged version, as the rescan reports it. */
|
|
45
|
+
export interface ExposureInput {
|
|
46
|
+
orgId: string;
|
|
47
|
+
pipelineId: string;
|
|
48
|
+
pluginPublisher?: string | null;
|
|
49
|
+
pluginName: string;
|
|
50
|
+
pluginVersion: string;
|
|
51
|
+
imageDigest?: string | null;
|
|
52
|
+
source: ExposureSource;
|
|
53
|
+
criticalCount: number;
|
|
54
|
+
highCount: number;
|
|
55
|
+
topFindings?: Array<Record<string, unknown>>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Open or refresh exposures.
|
|
59
|
+
*
|
|
60
|
+
* Upserts on (pipeline, plugin, version, source) so a nightly rescan REFRESHES the
|
|
61
|
+
* counts rather than opening a new row every night — otherwise the report would
|
|
62
|
+
* show one exposure per night of the same problem, which reads as an escalating
|
|
63
|
+
* situation when nothing changed.
|
|
64
|
+
*
|
|
65
|
+
* A refresh deliberately does NOT reset `triage_state`: an org that accepted a
|
|
66
|
+
* finding with a reason and a deadline has made a decision, and re-raising it every
|
|
67
|
+
* night is how a team learns to ignore the report. It DOES clear a stale
|
|
68
|
+
* acceptance, because that is what the deadline was for.
|
|
69
|
+
*/
|
|
70
|
+
export declare function upsertExposures(inputs: readonly ExposureInput[], now?: Date): Promise<number>;
|
|
71
|
+
/**
|
|
72
|
+
* Close the exposures a pipeline has moved off.
|
|
73
|
+
*
|
|
74
|
+
* Called when a deploy records a new step manifest: anything flagged for that
|
|
75
|
+
* pipeline whose version is NOT in the set it now runs is fixed. Closing by
|
|
76
|
+
* absence rather than waiting for a rescan to notice is what makes "we fixed it"
|
|
77
|
+
* show up in the report on the next deploy instead of the next night.
|
|
78
|
+
*/
|
|
79
|
+
export declare function closeFixedExposures(pipelineId: string, liveVersions: ReadonlyArray<{
|
|
80
|
+
pluginName: string;
|
|
81
|
+
pluginVersion: string;
|
|
82
|
+
}>, now?: Date): Promise<number>;
|
|
83
|
+
/**
|
|
84
|
+
* The org's live exposures, for the report section.
|
|
85
|
+
*
|
|
86
|
+
* `accepted` rows whose deadline has passed come back as OPEN even if no rescan
|
|
87
|
+
* has refreshed them yet: a read must not present an expired acceptance as a
|
|
88
|
+
* current one, and the report is read far more often than the rescan runs.
|
|
89
|
+
*/
|
|
90
|
+
export declare function openExposures(orgId: string): Promise<Array<{
|
|
91
|
+
pipelineId: string;
|
|
92
|
+
pluginName: string;
|
|
93
|
+
pluginVersion: string;
|
|
94
|
+
pluginPublisher: string | null;
|
|
95
|
+
source: ExposureSource;
|
|
96
|
+
criticalCount: number;
|
|
97
|
+
highCount: number;
|
|
98
|
+
topFindings: Array<Record<string, unknown>>;
|
|
99
|
+
triageState: string;
|
|
100
|
+
flaggedAt: Date;
|
|
101
|
+
}>>;
|
|
102
|
+
/**
|
|
103
|
+
* Record a triage decision.
|
|
104
|
+
*
|
|
105
|
+
* `accepted` REQUIRES a deadline and a reason. A permanent, unexplained mute is
|
|
106
|
+
* how a finding stops being anybody's problem, and there is no good reason to
|
|
107
|
+
* support it — the caller gets an error rather than a silently unbounded
|
|
108
|
+
* acceptance.
|
|
109
|
+
*/
|
|
110
|
+
export declare function triageExposure(orgId: string, id: string, decision: {
|
|
111
|
+
state: 'open' | 'accepted' | 'false_positive';
|
|
112
|
+
reason?: string;
|
|
113
|
+
acceptedUntil?: Date;
|
|
114
|
+
actor: string;
|
|
115
|
+
}): Promise<boolean>;
|
|
116
|
+
/**
|
|
117
|
+
* Exposures whose acceptance has lapsed, for the org's security notifications.
|
|
118
|
+
*
|
|
119
|
+
* Surfaced rather than silently reopened: somebody decided to accept this until a
|
|
120
|
+
* date, that date has passed, and the person who decided is the one who should
|
|
121
|
+
* hear about it.
|
|
122
|
+
*/
|
|
123
|
+
export declare function lapsedAcceptances(orgId: string, now?: Date): Promise<Array<{
|
|
124
|
+
id: string;
|
|
125
|
+
pluginName: string;
|
|
126
|
+
pluginVersion: string;
|
|
127
|
+
triagedBy: string | null;
|
|
128
|
+
acceptedUntil: Date | null;
|
|
129
|
+
}>>;
|