@pipeline-builder/pipeline-data 4.1.1 → 4.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Tenancy scoping and cache/context policy for reporting reads.
5
+ *
6
+ * These were private methods on ReportingService. They hold no instance state —
7
+ * only the module-level logger and timeseries cache — so they are plain
8
+ * functions here, which is what lets the per-domain query modules (dora.ts)
9
+ * use the identical scoping rules without reaching back into the class.
10
+ */
11
+ import { createLogger } from '@pipeline-builder/api-core';
12
+ import { sql } from 'drizzle-orm';
13
+ import { timeseriesCache } from './caches.js';
14
+ import { runWithTenantContext } from '../../database/tenancy.js';
15
+ const logger = createLogger('reporting-service');
16
+ /**
17
+ * Build the org-scope predicate for a report query. With `orgIds` (the
18
+ * org → team rollup — a parent's `[self, ...descendants]`) it becomes an
19
+ * `IN (...)` over the subtree; otherwise the single-org `= $org`. Returns a
20
+ * `multi` flag so callers can run multi-org reads under sysadmin context
21
+ * (the subtree spans orgs outside the request's RLS scope) and salt the
22
+ * cache key.
23
+ */
24
+ export function orgScope(orgId, orgIds) {
25
+ // Defense-in-depth: the multi-org rollup runs under sysadmin context (RLS
26
+ // OFF, see runReport), so `orgIds` is the ONLY tenancy gate on the read. The
27
+ // route resolves it from the platform's authoritative descendants endpoint as
28
+ // `[self, ...descendants]`, but validate the subtree defensively here before
29
+ // the bypass: the requesting (anchor) org MUST appear in the set. A set that
30
+ // omits the caller's own org is a malformed/spoofed rollup — collapse to the
31
+ // safe single-org scope rather than reading an arbitrary org under sysadmin.
32
+ // (Full descendant-membership validation needs the org tree, which this
33
+ // service does not hold — see the report FLAG.)
34
+ const anchor = orgId.toLowerCase();
35
+ const provided = (orgIds ?? []).filter((id) => typeof id === 'string' && id.length > 0);
36
+ const anchored = provided.some((id) => id.toLowerCase() === anchor);
37
+ if (provided.length > 0 && !anchored) {
38
+ logger.warn('Reporting rollup rejected: requesting org absent from resolved subtree; using single-org scope', {
39
+ orgId: anchor, subtreeSize: provided.length,
40
+ });
41
+ }
42
+ const ids = provided.length > 0 && anchored ? provided : [orgId];
43
+ const multi = ids.length > 1;
44
+ const pred = multi
45
+ ? sql `IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`
46
+ : sql `= ${ids[0]}`;
47
+ return { pred, multi };
48
+ }
49
+ /**
50
+ * Run a report read. Single-org reads use the per-org cache (invalidated on
51
+ * that org's event ingest). Rollup (multi-org) reads **bypass the cache** and
52
+ * run under sysadmin context: they're admin-only and lower-frequency, and a
53
+ * child org's event ingest can't invalidate a parent's rollup entry (reporting
54
+ * has no org tree), so caching them would serve stale aggregates. Always fresh.
55
+ */
56
+ export function runReport(cacheKey, multi, exec) {
57
+ return multi
58
+ ? runWithTenantContext({ isSuperAdmin: true }, exec)
59
+ : timeseriesCache.getOrSet(cacheKey, exec);
60
+ }
61
+ /** 1.1 Execution count per pipeline with status breakdown. */
62
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcnktc2NvcGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBpL3JlcG9ydGluZy9xdWVyeS1zY29wZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7O0dBT0c7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDMUQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQzlDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRWpFLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0FBRWpEOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFVBQVUsUUFBUSxDQUFDLEtBQWEsRUFBRSxNQUFpQjtJQUN2RCwwRUFBMEU7SUFDMUUsNkVBQTZFO0lBQzdFLDhFQUE4RTtJQUM5RSw2RUFBNkU7SUFDN0UsNkVBQTZFO0lBQzdFLDZFQUE2RTtJQUM3RSw2RUFBNkU7SUFDN0Usd0VBQXdFO0lBQ3hFLGdEQUFnRDtJQUNoRCxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDbkMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxNQUFNLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxPQUFPLEVBQUUsS0FBSyxRQUFRLElBQUksRUFBRSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RixNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUMsV0FBVyxFQUFFLEtBQUssTUFBTSxDQUFDLENBQUM7SUFDcEUsSUFBSSxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3JDLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0dBQWdHLEVBQUU7WUFDNUcsS0FBSyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsUUFBUSxDQUFDLE1BQU07U0FDNUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUNELE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pFLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQzdCLE1BQU0sSUFBSSxHQUFHLEtBQUs7UUFDaEIsQ0FBQyxDQUFDLEdBQUcsQ0FBQSxPQUFPLEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFBLEdBQUcsRUFBRSxFQUFFLENBQUMsRUFBRSxHQUFHLENBQUEsSUFBSSxDQUFDLEdBQUc7UUFDN0QsQ0FBQyxDQUFDLEdBQUcsQ0FBQSxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQ3JCLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUM7QUFDekIsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxTQUFTLENBQUksUUFBZ0IsRUFBRSxLQUFjLEVBQUUsSUFBc0I7SUFDbkYsT0FBTyxLQUFLO1FBQ1YsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsWUFBWSxFQUFFLElBQUksRUFBRSxFQUFFLElBQUksQ0FBQztRQUNwRCxDQUFDLENBQUMsZUFBZSxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDL0MsQ0FBQztBQUVELDhEQUE4RCIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRlbmFuY3kgc2NvcGluZyBhbmQgY2FjaGUvY29udGV4dCBwb2xpY3kgZm9yIHJlcG9ydGluZyByZWFkcy5cbiAqXG4gKiBUaGVzZSB3ZXJlIHByaXZhdGUgbWV0aG9kcyBvbiBSZXBvcnRpbmdTZXJ2aWNlLiBUaGV5IGhvbGQgbm8gaW5zdGFuY2Ugc3RhdGUg4oCUXG4gKiBvbmx5IHRoZSBtb2R1bGUtbGV2ZWwgbG9nZ2VyIGFuZCB0aW1lc2VyaWVzIGNhY2hlIOKAlCBzbyB0aGV5IGFyZSBwbGFpblxuICogZnVuY3Rpb25zIGhlcmUsIHdoaWNoIGlzIHdoYXQgbGV0cyB0aGUgcGVyLWRvbWFpbiBxdWVyeSBtb2R1bGVzIChkb3JhLnRzKVxuICogdXNlIHRoZSBpZGVudGljYWwgc2NvcGluZyBydWxlcyB3aXRob3V0IHJlYWNoaW5nIGJhY2sgaW50byB0aGUgY2xhc3MuXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgc3FsIH0gZnJvbSAnZHJpenpsZS1vcm0nO1xuaW1wb3J0IHsgdGltZXNlcmllc0NhY2hlIH0gZnJvbSAnLi9jYWNoZXMuanMnO1xuaW1wb3J0IHsgcnVuV2l0aFRlbmFudENvbnRleHQgfSBmcm9tICcuLi8uLi9kYXRhYmFzZS90ZW5hbmN5LmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdyZXBvcnRpbmctc2VydmljZScpO1xuXG4vKipcbiAqIEJ1aWxkIHRoZSBvcmctc2NvcGUgcHJlZGljYXRlIGZvciBhIHJlcG9ydCBxdWVyeS4gV2l0aCBgb3JnSWRzYCAodGhlXG4gKiBvcmcg4oaSIHRlYW0gcm9sbHVwIOKAlCBhIHBhcmVudCdzIGBbc2VsZiwgLi4uZGVzY2VuZGFudHNdYCkgaXQgYmVjb21lcyBhblxuICogYElOICguLi4pYCBvdmVyIHRoZSBzdWJ0cmVlOyBvdGhlcndpc2UgdGhlIHNpbmdsZS1vcmcgYD0gJG9yZ2AuIFJldHVybnMgYVxuICogYG11bHRpYCBmbGFnIHNvIGNhbGxlcnMgY2FuIHJ1biBtdWx0aS1vcmcgcmVhZHMgdW5kZXIgc3lzYWRtaW4gY29udGV4dFxuICogKHRoZSBzdWJ0cmVlIHNwYW5zIG9yZ3Mgb3V0c2lkZSB0aGUgcmVxdWVzdCdzIFJMUyBzY29wZSkgYW5kIHNhbHQgdGhlXG4gKiBjYWNoZSBrZXkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBvcmdTY29wZShvcmdJZDogc3RyaW5nLCBvcmdJZHM/OiBzdHJpbmdbXSkge1xuICAvLyBEZWZlbnNlLWluLWRlcHRoOiB0aGUgbXVsdGktb3JnIHJvbGx1cCBydW5zIHVuZGVyIHN5c2FkbWluIGNvbnRleHQgKFJMU1xuICAvLyBPRkYsIHNlZSBydW5SZXBvcnQpLCBzbyBgb3JnSWRzYCBpcyB0aGUgT05MWSB0ZW5hbmN5IGdhdGUgb24gdGhlIHJlYWQuIFRoZVxuICAvLyByb3V0ZSByZXNvbHZlcyBpdCBmcm9tIHRoZSBwbGF0Zm9ybSdzIGF1dGhvcml0YXRpdmUgZGVzY2VuZGFudHMgZW5kcG9pbnQgYXNcbiAgLy8gYFtzZWxmLCAuLi5kZXNjZW5kYW50c11gLCBidXQgdmFsaWRhdGUgdGhlIHN1YnRyZWUgZGVmZW5zaXZlbHkgaGVyZSBiZWZvcmVcbiAgLy8gdGhlIGJ5cGFzczogdGhlIHJlcXVlc3RpbmcgKGFuY2hvcikgb3JnIE1VU1QgYXBwZWFyIGluIHRoZSBzZXQuIEEgc2V0IHRoYXRcbiAgLy8gb21pdHMgdGhlIGNhbGxlcidzIG93biBvcmcgaXMgYSBtYWxmb3JtZWQvc3Bvb2ZlZCByb2xsdXAg4oCUIGNvbGxhcHNlIHRvIHRoZVxuICAvLyBzYWZlIHNpbmdsZS1vcmcgc2NvcGUgcmF0aGVyIHRoYW4gcmVhZGluZyBhbiBhcmJpdHJhcnkgb3JnIHVuZGVyIHN5c2FkbWluLlxuICAvLyAoRnVsbCBkZXNjZW5kYW50LW1lbWJlcnNoaXAgdmFsaWRhdGlvbiBuZWVkcyB0aGUgb3JnIHRyZWUsIHdoaWNoIHRoaXNcbiAgLy8gc2VydmljZSBkb2VzIG5vdCBob2xkIOKAlCBzZWUgdGhlIHJlcG9ydCBGTEFHLilcbiAgY29uc3QgYW5jaG9yID0gb3JnSWQudG9Mb3dlckNhc2UoKTtcbiAgY29uc3QgcHJvdmlkZWQgPSAob3JnSWRzID8/IFtdKS5maWx0ZXIoKGlkKSA9PiB0eXBlb2YgaWQgPT09ICdzdHJpbmcnICYmIGlkLmxlbmd0aCA+IDApO1xuICBjb25zdCBhbmNob3JlZCA9IHByb3ZpZGVkLnNvbWUoKGlkKSA9PiBpZC50b0xvd2VyQ2FzZSgpID09PSBhbmNob3IpO1xuICBpZiAocHJvdmlkZWQubGVuZ3RoID4gMCAmJiAhYW5jaG9yZWQpIHtcbiAgICBsb2dnZXIud2FybignUmVwb3J0aW5nIHJvbGx1cCByZWplY3RlZDogcmVxdWVzdGluZyBvcmcgYWJzZW50IGZyb20gcmVzb2x2ZWQgc3VidHJlZTsgdXNpbmcgc2luZ2xlLW9yZyBzY29wZScsIHtcbiAgICAgIG9yZ0lkOiBhbmNob3IsIHN1YnRyZWVTaXplOiBwcm92aWRlZC5sZW5ndGgsXG4gICAgfSk7XG4gIH1cbiAgY29uc3QgaWRzID0gcHJvdmlkZWQubGVuZ3RoID4gMCAmJiBhbmNob3JlZCA/IHByb3ZpZGVkIDogW29yZ0lkXTtcbiAgY29uc3QgbXVsdGkgPSBpZHMubGVuZ3RoID4gMTtcbiAgY29uc3QgcHJlZCA9IG11bHRpXG4gICAgPyBzcWxgSU4gKCR7c3FsLmpvaW4oaWRzLm1hcCgoaWQpID0+IHNxbGAke2lkfWApLCBzcWxgLCBgKX0pYFxuICAgIDogc3FsYD0gJHtpZHNbMF19YDtcbiAgcmV0dXJuIHsgcHJlZCwgbXVsdGkgfTtcbn1cblxuLyoqXG4gKiBSdW4gYSByZXBvcnQgcmVhZC4gU2luZ2xlLW9yZyByZWFkcyB1c2UgdGhlIHBlci1vcmcgY2FjaGUgKGludmFsaWRhdGVkIG9uXG4gKiB0aGF0IG9yZydzIGV2ZW50IGluZ2VzdCkuIFJvbGx1cCAobXVsdGktb3JnKSByZWFkcyAqKmJ5cGFzcyB0aGUgY2FjaGUqKiBhbmRcbiAqIHJ1biB1bmRlciBzeXNhZG1pbiBjb250ZXh0OiB0aGV5J3JlIGFkbWluLW9ubHkgYW5kIGxvd2VyLWZyZXF1ZW5jeSwgYW5kIGFcbiAqIGNoaWxkIG9yZydzIGV2ZW50IGluZ2VzdCBjYW4ndCBpbnZhbGlkYXRlIGEgcGFyZW50J3Mgcm9sbHVwIGVudHJ5IChyZXBvcnRpbmdcbiAqIGhhcyBubyBvcmcgdHJlZSksIHNvIGNhY2hpbmcgdGhlbSB3b3VsZCBzZXJ2ZSBzdGFsZSBhZ2dyZWdhdGVzLiBBbHdheXMgZnJlc2guXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBydW5SZXBvcnQ8VD4oY2FjaGVLZXk6IHN0cmluZywgbXVsdGk6IGJvb2xlYW4sIGV4ZWM6ICgpID0+IFByb21pc2U8VD4pOiBQcm9taXNlPFQ+IHtcbiAgcmV0dXJuIG11bHRpXG4gICAgPyBydW5XaXRoVGVuYW50Q29udGV4dCh7IGlzU3VwZXJBZG1pbjogdHJ1ZSB9LCBleGVjKVxuICAgIDogdGltZXNlcmllc0NhY2hlLmdldE9yU2V0KGNhY2hlS2V5LCBleGVjKTtcbn1cblxuLyoqIDEuMSBFeGVjdXRpb24gY291bnQgcGVyIHBpcGVsaW5lIHdpdGggc3RhdHVzIGJyZWFrZG93bi4gKi9cbiJdfQ==
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Retention windows for reporting data: env-configurable, clamped to a sane
3
+ * range, and overridable per-org by the settings row.
4
+ */
5
+ /**
6
+ * Reporting retention windows (Phase 7). Records in `pipeline_events`,
7
+ * `deployment_outcomes`, and `incidents` grow unbounded without a sweep, so a
8
+ * split, per-org retention purge (see {@link ReportingService.purgeExpiredReportingData})
9
+ * hard-deletes rows older than these windows, by `created_at`:
10
+ * - **Standard events** — `pipeline_events` with `environment IS NULL`
11
+ * (non-deploy STAGE/ACTION/build). High volume → short default (30 days).
12
+ * - **DORA source** — `pipeline_events` with `environment IS NOT NULL` (deploy
13
+ * stages) plus all of `deployment_outcomes` and `incidents`. Low volume →
14
+ * longer default (180 days). DORA history is therefore bounded by this window;
15
+ * the report query still hard-caps at 365 days (a longer per-org override only
16
+ * retains raw source rows, never widens a report).
17
+ * `ingest_health` and `dora_settings` are never purged. Env-overridable globals;
18
+ * a per-org override in `dora_settings` wins (see {@link resolveEventRetentionDays}).
19
+ */
20
+ export declare const RETENTION_MIN_DAYS = 1;
21
+ export declare const RETENTION_MAX_DAYS = 730;
22
+ /** Parse a positive-int env day-count, clamped to [MIN, MAX], else `fallback`. */
23
+ export declare function retentionEnvDays(name: string, fallback: number): number;
24
+ export declare const REPORTING_EVENT_RETENTION_DAYS: number;
25
+ export declare const REPORTING_DORA_RETENTION_DAYS: number;
26
+ /** Effective standard-event retention (days): a valid per-org override wins,
27
+ * else the global env default. Shared by the sweep + the settings surface so
28
+ * they can't drift. The `-1` unlimited sentinel (Phase 8) passes straight
29
+ * through — the sweep reads it as "keep forever, skip this org's standard
30
+ * events". A positive override clamps to [1, RETENTION_MAX_DAYS]; a `null`, a
31
+ * non-integer, or any other out-of-range value (e.g. 0, -2) falls back to the
32
+ * env default. */
33
+ export declare function resolveEventRetentionDays(override?: number | null): number;
34
+ /** Effective DORA-source retention (days): a valid per-org override wins, else
35
+ * the global env default. The `-1` unlimited sentinel (Phase 8) passes straight
36
+ * through — the sweep reads it as "keep forever, skip this org's DORA-source
37
+ * windows". A positive override clamps to [1, RETENTION_MAX_DAYS]; a `null`, a
38
+ * non-integer, or any other out-of-range value (e.g. 0, -2) falls back to the
39
+ * env default. */
40
+ export declare function resolveDoraRetentionDays(override?: number | null): number;
41
+ /** The cutoff instant for a retention window: rows with `created_at` strictly
42
+ * before this (`< cutoff`) are expired. Deterministic for a fixed `now`. */
43
+ export declare function retentionCutoff(now: Date, days: number): Date;
44
+ /** Per-org retention override row (Phase 7). Both nullable ⇒ global defaults. */
@@ -0,0 +1,64 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Retention windows for reporting data: env-configurable, clamped to a sane
5
+ * range, and overridable per-org by the settings row.
6
+ */
7
+ /**
8
+ * Reporting retention windows (Phase 7). Records in `pipeline_events`,
9
+ * `deployment_outcomes`, and `incidents` grow unbounded without a sweep, so a
10
+ * split, per-org retention purge (see {@link ReportingService.purgeExpiredReportingData})
11
+ * hard-deletes rows older than these windows, by `created_at`:
12
+ * - **Standard events** — `pipeline_events` with `environment IS NULL`
13
+ * (non-deploy STAGE/ACTION/build). High volume → short default (30 days).
14
+ * - **DORA source** — `pipeline_events` with `environment IS NOT NULL` (deploy
15
+ * stages) plus all of `deployment_outcomes` and `incidents`. Low volume →
16
+ * longer default (180 days). DORA history is therefore bounded by this window;
17
+ * the report query still hard-caps at 365 days (a longer per-org override only
18
+ * retains raw source rows, never widens a report).
19
+ * `ingest_health` and `dora_settings` are never purged. Env-overridable globals;
20
+ * a per-org override in `dora_settings` wins (see {@link resolveEventRetentionDays}).
21
+ */
22
+ export const RETENTION_MIN_DAYS = 1;
23
+ export const RETENTION_MAX_DAYS = 730;
24
+ /** Parse a positive-int env day-count, clamped to [MIN, MAX], else `fallback`. */
25
+ export function retentionEnvDays(name, fallback) {
26
+ const raw = Number(process.env[name]);
27
+ if (!Number.isFinite(raw) || !Number.isInteger(raw) || raw < RETENTION_MIN_DAYS)
28
+ return fallback;
29
+ return Math.min(raw, RETENTION_MAX_DAYS);
30
+ }
31
+ export const REPORTING_EVENT_RETENTION_DAYS = retentionEnvDays('REPORTING_EVENT_RETENTION_DAYS', 30);
32
+ export const REPORTING_DORA_RETENTION_DAYS = retentionEnvDays('REPORTING_DORA_RETENTION_DAYS', 180);
33
+ /** Effective standard-event retention (days): a valid per-org override wins,
34
+ * else the global env default. Shared by the sweep + the settings surface so
35
+ * they can't drift. The `-1` unlimited sentinel (Phase 8) passes straight
36
+ * through — the sweep reads it as "keep forever, skip this org's standard
37
+ * events". A positive override clamps to [1, RETENTION_MAX_DAYS]; a `null`, a
38
+ * non-integer, or any other out-of-range value (e.g. 0, -2) falls back to the
39
+ * env default. */
40
+ export function resolveEventRetentionDays(override) {
41
+ if (override === -1)
42
+ return -1;
43
+ return override != null && Number.isInteger(override) && override >= RETENTION_MIN_DAYS
44
+ ? Math.min(override, RETENTION_MAX_DAYS) : REPORTING_EVENT_RETENTION_DAYS;
45
+ }
46
+ /** Effective DORA-source retention (days): a valid per-org override wins, else
47
+ * the global env default. The `-1` unlimited sentinel (Phase 8) passes straight
48
+ * through — the sweep reads it as "keep forever, skip this org's DORA-source
49
+ * windows". A positive override clamps to [1, RETENTION_MAX_DAYS]; a `null`, a
50
+ * non-integer, or any other out-of-range value (e.g. 0, -2) falls back to the
51
+ * env default. */
52
+ export function resolveDoraRetentionDays(override) {
53
+ if (override === -1)
54
+ return -1;
55
+ return override != null && Number.isInteger(override) && override >= RETENTION_MIN_DAYS
56
+ ? Math.min(override, RETENTION_MAX_DAYS) : REPORTING_DORA_RETENTION_DAYS;
57
+ }
58
+ /** The cutoff instant for a retention window: rows with `created_at` strictly
59
+ * before this (`< cutoff`) are expired. Deterministic for a fixed `now`. */
60
+ export function retentionCutoff(now, days) {
61
+ return new Date(now.getTime() - days * 86_400_000);
62
+ }
63
+ /** Per-org retention override row (Phase 7). Both nullable ⇒ global defaults. */
64
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0ZW50aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwaS9yZXBvcnRpbmcvcmV0ZW50aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7OztHQUdHO0FBRUg7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLENBQUM7QUFDcEMsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxDQUFDO0FBRXRDLGtGQUFrRjtBQUNsRixNQUFNLFVBQVUsZ0JBQWdCLENBQUMsSUFBWSxFQUFFLFFBQWdCO0lBQzdELE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDdEMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEdBQUcsR0FBRyxrQkFBa0I7UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUNqRyxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLGtCQUFrQixDQUFDLENBQUM7QUFDM0MsQ0FBQztBQUVELE1BQU0sQ0FBQyxNQUFNLDhCQUE4QixHQUFHLGdCQUFnQixDQUFDLGdDQUFnQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3JHLE1BQU0sQ0FBQyxNQUFNLDZCQUE2QixHQUFHLGdCQUFnQixDQUFDLCtCQUErQixFQUFFLEdBQUcsQ0FBQyxDQUFDO0FBRXBHOzs7Ozs7bUJBTW1CO0FBQ25CLE1BQU0sVUFBVSx5QkFBeUIsQ0FBQyxRQUF3QjtJQUNoRSxJQUFJLFFBQVEsS0FBSyxDQUFDLENBQUM7UUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQy9CLE9BQU8sUUFBUSxJQUFJLElBQUksSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxJQUFJLFFBQVEsSUFBSSxrQkFBa0I7UUFDckYsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxDQUFDLDhCQUE4QixDQUFDO0FBQzlFLENBQUM7QUFFRDs7Ozs7bUJBS21CO0FBQ25CLE1BQU0sVUFBVSx3QkFBd0IsQ0FBQyxRQUF3QjtJQUMvRCxJQUFJLFFBQVEsS0FBSyxDQUFDLENBQUM7UUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQy9CLE9BQU8sUUFBUSxJQUFJLElBQUksSUFBSSxNQUFNLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxJQUFJLFFBQVEsSUFBSSxrQkFBa0I7UUFDckYsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxDQUFDLDZCQUE2QixDQUFDO0FBQzdFLENBQUM7QUFFRDs2RUFDNkU7QUFDN0UsTUFBTSxVQUFVLGVBQWUsQ0FBQyxHQUFTLEVBQUUsSUFBWTtJQUNyRCxPQUFPLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsR0FBRyxJQUFJLEdBQUcsVUFBVSxDQUFDLENBQUM7QUFDckQsQ0FBQztBQUVELGlGQUFpRiIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFJldGVudGlvbiB3aW5kb3dzIGZvciByZXBvcnRpbmcgZGF0YTogZW52LWNvbmZpZ3VyYWJsZSwgY2xhbXBlZCB0byBhIHNhbmVcbiAqIHJhbmdlLCBhbmQgb3ZlcnJpZGFibGUgcGVyLW9yZyBieSB0aGUgc2V0dGluZ3Mgcm93LlxuICovXG5cbi8qKlxuICogUmVwb3J0aW5nIHJldGVudGlvbiB3aW5kb3dzIChQaGFzZSA3KS4gUmVjb3JkcyBpbiBgcGlwZWxpbmVfZXZlbnRzYCxcbiAqIGBkZXBsb3ltZW50X291dGNvbWVzYCwgYW5kIGBpbmNpZGVudHNgIGdyb3cgdW5ib3VuZGVkIHdpdGhvdXQgYSBzd2VlcCwgc28gYVxuICogc3BsaXQsIHBlci1vcmcgcmV0ZW50aW9uIHB1cmdlIChzZWUge0BsaW5rIFJlcG9ydGluZ1NlcnZpY2UucHVyZ2VFeHBpcmVkUmVwb3J0aW5nRGF0YX0pXG4gKiBoYXJkLWRlbGV0ZXMgcm93cyBvbGRlciB0aGFuIHRoZXNlIHdpbmRvd3MsIGJ5IGBjcmVhdGVkX2F0YDpcbiAqICAtICoqU3RhbmRhcmQgZXZlbnRzKiog4oCUIGBwaXBlbGluZV9ldmVudHNgIHdpdGggYGVudmlyb25tZW50IElTIE5VTExgXG4gKiAgICAobm9uLWRlcGxveSBTVEFHRS9BQ1RJT04vYnVpbGQpLiBIaWdoIHZvbHVtZSDihpIgc2hvcnQgZGVmYXVsdCAoMzAgZGF5cykuXG4gKiAgLSAqKkRPUkEgc291cmNlKiog4oCUIGBwaXBlbGluZV9ldmVudHNgIHdpdGggYGVudmlyb25tZW50IElTIE5PVCBOVUxMYCAoZGVwbG95XG4gKiAgICBzdGFnZXMpIHBsdXMgYWxsIG9mIGBkZXBsb3ltZW50X291dGNvbWVzYCBhbmQgYGluY2lkZW50c2AuIExvdyB2b2x1bWUg4oaSXG4gKiAgICBsb25nZXIgZGVmYXVsdCAoMTgwIGRheXMpLiBET1JBIGhpc3RvcnkgaXMgdGhlcmVmb3JlIGJvdW5kZWQgYnkgdGhpcyB3aW5kb3c7XG4gKiAgICB0aGUgcmVwb3J0IHF1ZXJ5IHN0aWxsIGhhcmQtY2FwcyBhdCAzNjUgZGF5cyAoYSBsb25nZXIgcGVyLW9yZyBvdmVycmlkZSBvbmx5XG4gKiAgICByZXRhaW5zIHJhdyBzb3VyY2Ugcm93cywgbmV2ZXIgd2lkZW5zIGEgcmVwb3J0KS5cbiAqIGBpbmdlc3RfaGVhbHRoYCBhbmQgYGRvcmFfc2V0dGluZ3NgIGFyZSBuZXZlciBwdXJnZWQuIEVudi1vdmVycmlkYWJsZSBnbG9iYWxzO1xuICogYSBwZXItb3JnIG92ZXJyaWRlIGluIGBkb3JhX3NldHRpbmdzYCB3aW5zIChzZWUge0BsaW5rIHJlc29sdmVFdmVudFJldGVudGlvbkRheXN9KS5cbiAqL1xuZXhwb3J0IGNvbnN0IFJFVEVOVElPTl9NSU5fREFZUyA9IDE7XG5leHBvcnQgY29uc3QgUkVURU5USU9OX01BWF9EQVlTID0gNzMwO1xuXG4vKiogUGFyc2UgYSBwb3NpdGl2ZS1pbnQgZW52IGRheS1jb3VudCwgY2xhbXBlZCB0byBbTUlOLCBNQVhdLCBlbHNlIGBmYWxsYmFja2AuICovXG5leHBvcnQgZnVuY3Rpb24gcmV0ZW50aW9uRW52RGF5cyhuYW1lOiBzdHJpbmcsIGZhbGxiYWNrOiBudW1iZXIpOiBudW1iZXIge1xuICBjb25zdCByYXcgPSBOdW1iZXIocHJvY2Vzcy5lbnZbbmFtZV0pO1xuICBpZiAoIU51bWJlci5pc0Zpbml0ZShyYXcpIHx8ICFOdW1iZXIuaXNJbnRlZ2VyKHJhdykgfHwgcmF3IDwgUkVURU5USU9OX01JTl9EQVlTKSByZXR1cm4gZmFsbGJhY2s7XG4gIHJldHVybiBNYXRoLm1pbihyYXcsIFJFVEVOVElPTl9NQVhfREFZUyk7XG59XG5cbmV4cG9ydCBjb25zdCBSRVBPUlRJTkdfRVZFTlRfUkVURU5USU9OX0RBWVMgPSByZXRlbnRpb25FbnZEYXlzKCdSRVBPUlRJTkdfRVZFTlRfUkVURU5USU9OX0RBWVMnLCAzMCk7XG5leHBvcnQgY29uc3QgUkVQT1JUSU5HX0RPUkFfUkVURU5USU9OX0RBWVMgPSByZXRlbnRpb25FbnZEYXlzKCdSRVBPUlRJTkdfRE9SQV9SRVRFTlRJT05fREFZUycsIDE4MCk7XG5cbi8qKiBFZmZlY3RpdmUgc3RhbmRhcmQtZXZlbnQgcmV0ZW50aW9uIChkYXlzKTogYSB2YWxpZCBwZXItb3JnIG92ZXJyaWRlIHdpbnMsXG4gKiAgZWxzZSB0aGUgZ2xvYmFsIGVudiBkZWZhdWx0LiBTaGFyZWQgYnkgdGhlIHN3ZWVwICsgdGhlIHNldHRpbmdzIHN1cmZhY2Ugc29cbiAqICB0aGV5IGNhbid0IGRyaWZ0LiBUaGUgYC0xYCB1bmxpbWl0ZWQgc2VudGluZWwgKFBoYXNlIDgpIHBhc3NlcyBzdHJhaWdodFxuICogIHRocm91Z2gg4oCUIHRoZSBzd2VlcCByZWFkcyBpdCBhcyBcImtlZXAgZm9yZXZlciwgc2tpcCB0aGlzIG9yZydzIHN0YW5kYXJkXG4gKiAgZXZlbnRzXCIuIEEgcG9zaXRpdmUgb3ZlcnJpZGUgY2xhbXBzIHRvIFsxLCBSRVRFTlRJT05fTUFYX0RBWVNdOyBhIGBudWxsYCwgYVxuICogIG5vbi1pbnRlZ2VyLCBvciBhbnkgb3RoZXIgb3V0LW9mLXJhbmdlIHZhbHVlIChlLmcuIDAsIC0yKSBmYWxscyBiYWNrIHRvIHRoZVxuICogIGVudiBkZWZhdWx0LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVFdmVudFJldGVudGlvbkRheXMob3ZlcnJpZGU/OiBudW1iZXIgfCBudWxsKTogbnVtYmVyIHtcbiAgaWYgKG92ZXJyaWRlID09PSAtMSkgcmV0dXJuIC0xO1xuICByZXR1cm4gb3ZlcnJpZGUgIT0gbnVsbCAmJiBOdW1iZXIuaXNJbnRlZ2VyKG92ZXJyaWRlKSAmJiBvdmVycmlkZSA+PSBSRVRFTlRJT05fTUlOX0RBWVNcbiAgICA/IE1hdGgubWluKG92ZXJyaWRlLCBSRVRFTlRJT05fTUFYX0RBWVMpIDogUkVQT1JUSU5HX0VWRU5UX1JFVEVOVElPTl9EQVlTO1xufVxuXG4vKiogRWZmZWN0aXZlIERPUkEtc291cmNlIHJldGVudGlvbiAoZGF5cyk6IGEgdmFsaWQgcGVyLW9yZyBvdmVycmlkZSB3aW5zLCBlbHNlXG4gKiAgdGhlIGdsb2JhbCBlbnYgZGVmYXVsdC4gVGhlIGAtMWAgdW5saW1pdGVkIHNlbnRpbmVsIChQaGFzZSA4KSBwYXNzZXMgc3RyYWlnaHRcbiAqICB0aHJvdWdoIOKAlCB0aGUgc3dlZXAgcmVhZHMgaXQgYXMgXCJrZWVwIGZvcmV2ZXIsIHNraXAgdGhpcyBvcmcncyBET1JBLXNvdXJjZVxuICogIHdpbmRvd3NcIi4gQSBwb3NpdGl2ZSBvdmVycmlkZSBjbGFtcHMgdG8gWzEsIFJFVEVOVElPTl9NQVhfREFZU107IGEgYG51bGxgLCBhXG4gKiAgbm9uLWludGVnZXIsIG9yIGFueSBvdGhlciBvdXQtb2YtcmFuZ2UgdmFsdWUgKGUuZy4gMCwgLTIpIGZhbGxzIGJhY2sgdG8gdGhlXG4gKiAgZW52IGRlZmF1bHQuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZURvcmFSZXRlbnRpb25EYXlzKG92ZXJyaWRlPzogbnVtYmVyIHwgbnVsbCk6IG51bWJlciB7XG4gIGlmIChvdmVycmlkZSA9PT0gLTEpIHJldHVybiAtMTtcbiAgcmV0dXJuIG92ZXJyaWRlICE9IG51bGwgJiYgTnVtYmVyLmlzSW50ZWdlcihvdmVycmlkZSkgJiYgb3ZlcnJpZGUgPj0gUkVURU5USU9OX01JTl9EQVlTXG4gICAgPyBNYXRoLm1pbihvdmVycmlkZSwgUkVURU5USU9OX01BWF9EQVlTKSA6IFJFUE9SVElOR19ET1JBX1JFVEVOVElPTl9EQVlTO1xufVxuXG4vKiogVGhlIGN1dG9mZiBpbnN0YW50IGZvciBhIHJldGVudGlvbiB3aW5kb3c6IHJvd3Mgd2l0aCBgY3JlYXRlZF9hdGAgc3RyaWN0bHlcbiAqICBiZWZvcmUgdGhpcyAoYDwgY3V0b2ZmYCkgYXJlIGV4cGlyZWQuIERldGVybWluaXN0aWMgZm9yIGEgZml4ZWQgYG5vd2AuICovXG5leHBvcnQgZnVuY3Rpb24gcmV0ZW50aW9uQ3V0b2ZmKG5vdzogRGF0ZSwgZGF5czogbnVtYmVyKTogRGF0ZSB7XG4gIHJldHVybiBuZXcgRGF0ZShub3cuZ2V0VGltZSgpIC0gZGF5cyAqIDg2XzQwMF8wMDApO1xufVxuXG4vKiogUGVyLW9yZyByZXRlbnRpb24gb3ZlcnJpZGUgcm93IChQaGFzZSA3KS4gQm90aCBudWxsYWJsZSDih5IgZ2xvYmFsIGRlZmF1bHRzLiAqL1xuIl19
@@ -0,0 +1,47 @@
1
+ import { sql } from 'drizzle-orm';
2
+ /**
3
+ * Defense-in-depth guard for the `DATE_TRUNC(${interval}, …)` bucket argument.
4
+ * The route layer (`parseReportInterval`) is the security boundary and already
5
+ * rejects unknown intervals; this assert makes an invalid value fail fast at the
6
+ * service boundary with a clear error instead of surfacing a raw Postgres error
7
+ * if a caller ever bypasses the route validation. Shares api-core's
8
+ * REPORT_INTERVALS allow-list so the two can't drift.
9
+ */
10
+ export declare function assertReportInterval(interval: string): void;
11
+ /**
12
+ * Scrub AWS identifiers from an optional free-form string, preserving `undefined`
13
+ * (so an absent field stays absent rather than becoming an empty scrubbed string).
14
+ * The DURABLE persistence boundary for the ingest paths — every user/AWS-derived
15
+ * string field is funneled through this before insert.
16
+ */
17
+ export declare function scrubOptional(value: string | undefined): string | undefined;
18
+ /**
19
+ * Cache for reporting aggregations. Two tiers:
20
+ * - Inventory queries (plugin summary/distribution/versions): 5 min TTL — changes on plugin CRUD
21
+ * - Execution/build queries with date ranges: 2 min TTL — new events arrive continuously
22
+ */
23
+ /**
24
+ * Roll a group of deploy/stage events (grouped per execution) up to one terminal
25
+ * status: FAILED wins, then SUCCEEDED, else OTHER (still-running / non-terminal).
26
+ * Shared by the three per-(env|stage, execution) rollup CTEs — DORA metrics, DORA
27
+ * trend, and build health — so the precedence can't drift between them. Assumes the
28
+ * grouped event alias is `e`.
29
+ */
30
+ export declare const terminalStatusRollup: import("drizzle-orm").SQL<unknown>;
31
+ /** Optional `[from,to]` window on `e.started_at` for the execution reports —
32
+ * a no-op `sql` fragment when either bound is absent (all-time). Shared by the
33
+ * execution-count and per-pipeline-execution reports so they narrow identically. */
34
+ export declare function optionalStartedAtRange(range?: {
35
+ from?: string;
36
+ to?: string;
37
+ }): ReturnType<typeof sql>;
38
+ /**
39
+ * Per-execution terminal deploy row (D1 — one row per (environment, execution),
40
+ * NOT per stage). The SQL rolls every deploy STAGE targeting an env within an
41
+ * execution up to a single terminal status; the JS shaping buckets these by
42
+ * environment. `commit_ts` (D2) is the execution's EARLIEST commit time across
43
+ * all its events — commit enrichment rides the PIPELINE/source event, not the
44
+ * deploy STAGE — joined by execution_id (null when unresolved → lead `unknown`).
45
+ * `in_window` is false for a look-back-only row kept solely so incidents opened
46
+ * near `from` can correlate to a deploy that completed just before the window.
47
+ */
@@ -0,0 +1,65 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Small SQL fragments and input guards shared across the reporting queries.
5
+ */
6
+ import { REPORT_INTERVALS, scrubAwsIdentifiersFromString } from '@pipeline-builder/api-core';
7
+ import { sql } from 'drizzle-orm';
8
+ /**
9
+ * Defense-in-depth guard for the `DATE_TRUNC(${interval}, …)` bucket argument.
10
+ * The route layer (`parseReportInterval`) is the security boundary and already
11
+ * rejects unknown intervals; this assert makes an invalid value fail fast at the
12
+ * service boundary with a clear error instead of surfacing a raw Postgres error
13
+ * if a caller ever bypasses the route validation. Shares api-core's
14
+ * REPORT_INTERVALS allow-list so the two can't drift.
15
+ */
16
+ export function assertReportInterval(interval) {
17
+ if (!REPORT_INTERVALS.includes(interval)) {
18
+ throw new Error(`Invalid report interval: ${interval}. Expected one of: ${REPORT_INTERVALS.join(', ')}`);
19
+ }
20
+ }
21
+ /**
22
+ * Scrub AWS identifiers from an optional free-form string, preserving `undefined`
23
+ * (so an absent field stays absent rather than becoming an empty scrubbed string).
24
+ * The DURABLE persistence boundary for the ingest paths — every user/AWS-derived
25
+ * string field is funneled through this before insert.
26
+ */
27
+ export function scrubOptional(value) {
28
+ return value !== undefined ? scrubAwsIdentifiersFromString(value) : undefined;
29
+ }
30
+ /**
31
+ * Cache for reporting aggregations. Two tiers:
32
+ * - Inventory queries (plugin summary/distribution/versions): 5 min TTL — changes on plugin CRUD
33
+ * - Execution/build queries with date ranges: 2 min TTL — new events arrive continuously
34
+ */
35
+ /**
36
+ * Roll a group of deploy/stage events (grouped per execution) up to one terminal
37
+ * status: FAILED wins, then SUCCEEDED, else OTHER (still-running / non-terminal).
38
+ * Shared by the three per-(env|stage, execution) rollup CTEs — DORA metrics, DORA
39
+ * trend, and build health — so the precedence can't drift between them. Assumes the
40
+ * grouped event alias is `e`.
41
+ */
42
+ export const terminalStatusRollup = sql `CASE
43
+ WHEN bool_or(e.status = 'FAILED') THEN 'FAILED'
44
+ WHEN bool_or(e.status = 'SUCCEEDED') THEN 'SUCCEEDED'
45
+ ELSE 'OTHER'
46
+ END`;
47
+ /** Optional `[from,to]` window on `e.started_at` for the execution reports —
48
+ * a no-op `sql` fragment when either bound is absent (all-time). Shared by the
49
+ * execution-count and per-pipeline-execution reports so they narrow identically. */
50
+ export function optionalStartedAtRange(range) {
51
+ return range?.from && range?.to
52
+ ? sql `AND e.started_at >= ${range.from}::timestamptz AND e.started_at <= ${range.to}::timestamptz`
53
+ : sql ``;
54
+ }
55
+ /**
56
+ * Per-execution terminal deploy row (D1 — one row per (environment, execution),
57
+ * NOT per stage). The SQL rolls every deploy STAGE targeting an env within an
58
+ * execution up to a single terminal status; the JS shaping buckets these by
59
+ * environment. `commit_ts` (D2) is the execution's EARLIEST commit time across
60
+ * all its events — commit enrichment rides the PIPELINE/source event, not the
61
+ * deploy STAGE — joined by execution_id (null when unresolved → lead `unknown`).
62
+ * `in_window` is false for a look-back-only row kept solely so incidents opened
63
+ * near `from` can correlate to a deploy that completed just before the window.
64
+ */
65
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3FsLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBpL3JlcG9ydGluZy9zcWwtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOztHQUVHO0FBRUgsT0FBTyxFQUFFLGdCQUFnQixFQUFFLDZCQUE2QixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDN0YsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUVsQzs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFDLFFBQWdCO0lBQ25ELElBQUksQ0FBRSxnQkFBc0MsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUNoRSxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixRQUFRLHNCQUFzQixnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzNHLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsYUFBYSxDQUFDLEtBQXlCO0lBQ3JELE9BQU8sS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsNkJBQTZCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUNoRixDQUFDO0FBRUQ7Ozs7R0FJRztBQUVIOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLG9CQUFvQixHQUFHLEdBQUcsQ0FBQTs7OztnQkFJdkIsQ0FBQztBQUVqQjs7cUZBRXFGO0FBQ3JGLE1BQU0sVUFBVSxzQkFBc0IsQ0FBQyxLQUFzQztJQUMzRSxPQUFPLEtBQUssRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLEVBQUU7UUFDN0IsQ0FBQyxDQUFDLEdBQUcsQ0FBQSx1QkFBdUIsS0FBSyxDQUFDLElBQUkscUNBQXFDLEtBQUssQ0FBQyxFQUFFLGVBQWU7UUFDbEcsQ0FBQyxDQUFDLEdBQUcsQ0FBQSxFQUFFLENBQUM7QUFDWixDQUFDO0FBRUQ7Ozs7Ozs7OztHQVNHIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogU21hbGwgU1FMIGZyYWdtZW50cyBhbmQgaW5wdXQgZ3VhcmRzIHNoYXJlZCBhY3Jvc3MgdGhlIHJlcG9ydGluZyBxdWVyaWVzLlxuICovXG5cbmltcG9ydCB7IFJFUE9SVF9JTlRFUlZBTFMsIHNjcnViQXdzSWRlbnRpZmllcnNGcm9tU3RyaW5nIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgc3FsIH0gZnJvbSAnZHJpenpsZS1vcm0nO1xuXG4vKipcbiAqIERlZmVuc2UtaW4tZGVwdGggZ3VhcmQgZm9yIHRoZSBgREFURV9UUlVOQygke2ludGVydmFsfSwg4oCmKWAgYnVja2V0IGFyZ3VtZW50LlxuICogVGhlIHJvdXRlIGxheWVyIChgcGFyc2VSZXBvcnRJbnRlcnZhbGApIGlzIHRoZSBzZWN1cml0eSBib3VuZGFyeSBhbmQgYWxyZWFkeVxuICogcmVqZWN0cyB1bmtub3duIGludGVydmFsczsgdGhpcyBhc3NlcnQgbWFrZXMgYW4gaW52YWxpZCB2YWx1ZSBmYWlsIGZhc3QgYXQgdGhlXG4gKiBzZXJ2aWNlIGJvdW5kYXJ5IHdpdGggYSBjbGVhciBlcnJvciBpbnN0ZWFkIG9mIHN1cmZhY2luZyBhIHJhdyBQb3N0Z3JlcyBlcnJvclxuICogaWYgYSBjYWxsZXIgZXZlciBieXBhc3NlcyB0aGUgcm91dGUgdmFsaWRhdGlvbi4gU2hhcmVzIGFwaS1jb3JlJ3NcbiAqIFJFUE9SVF9JTlRFUlZBTFMgYWxsb3ctbGlzdCBzbyB0aGUgdHdvIGNhbid0IGRyaWZ0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gYXNzZXJ0UmVwb3J0SW50ZXJ2YWwoaW50ZXJ2YWw6IHN0cmluZyk6IHZvaWQge1xuICBpZiAoIShSRVBPUlRfSU5URVJWQUxTIGFzIHJlYWRvbmx5IHN0cmluZ1tdKS5pbmNsdWRlcyhpbnRlcnZhbCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYEludmFsaWQgcmVwb3J0IGludGVydmFsOiAke2ludGVydmFsfS4gRXhwZWN0ZWQgb25lIG9mOiAke1JFUE9SVF9JTlRFUlZBTFMuam9pbignLCAnKX1gKTtcbiAgfVxufVxuXG4vKipcbiAqIFNjcnViIEFXUyBpZGVudGlmaWVycyBmcm9tIGFuIG9wdGlvbmFsIGZyZWUtZm9ybSBzdHJpbmcsIHByZXNlcnZpbmcgYHVuZGVmaW5lZGBcbiAqIChzbyBhbiBhYnNlbnQgZmllbGQgc3RheXMgYWJzZW50IHJhdGhlciB0aGFuIGJlY29taW5nIGFuIGVtcHR5IHNjcnViYmVkIHN0cmluZykuXG4gKiBUaGUgRFVSQUJMRSBwZXJzaXN0ZW5jZSBib3VuZGFyeSBmb3IgdGhlIGluZ2VzdCBwYXRocyDigJQgZXZlcnkgdXNlci9BV1MtZGVyaXZlZFxuICogc3RyaW5nIGZpZWxkIGlzIGZ1bm5lbGVkIHRocm91Z2ggdGhpcyBiZWZvcmUgaW5zZXJ0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2NydWJPcHRpb25hbCh2YWx1ZTogc3RyaW5nIHwgdW5kZWZpbmVkKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIHZhbHVlICE9PSB1bmRlZmluZWQgPyBzY3J1YkF3c0lkZW50aWZpZXJzRnJvbVN0cmluZyh2YWx1ZSkgOiB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogQ2FjaGUgZm9yIHJlcG9ydGluZyBhZ2dyZWdhdGlvbnMuIFR3byB0aWVyczpcbiAqIC0gSW52ZW50b3J5IHF1ZXJpZXMgKHBsdWdpbiBzdW1tYXJ5L2Rpc3RyaWJ1dGlvbi92ZXJzaW9ucyk6IDUgbWluIFRUTCDigJQgY2hhbmdlcyBvbiBwbHVnaW4gQ1JVRFxuICogLSBFeGVjdXRpb24vYnVpbGQgcXVlcmllcyB3aXRoIGRhdGUgcmFuZ2VzOiAyIG1pbiBUVEwg4oCUIG5ldyBldmVudHMgYXJyaXZlIGNvbnRpbnVvdXNseVxuICovXG5cbi8qKlxuICogUm9sbCBhIGdyb3VwIG9mIGRlcGxveS9zdGFnZSBldmVudHMgKGdyb3VwZWQgcGVyIGV4ZWN1dGlvbikgdXAgdG8gb25lIHRlcm1pbmFsXG4gKiBzdGF0dXM6IEZBSUxFRCB3aW5zLCB0aGVuIFNVQ0NFRURFRCwgZWxzZSBPVEhFUiAoc3RpbGwtcnVubmluZyAvIG5vbi10ZXJtaW5hbCkuXG4gKiBTaGFyZWQgYnkgdGhlIHRocmVlIHBlci0oZW52fHN0YWdlLCBleGVjdXRpb24pIHJvbGx1cCBDVEVzIOKAlCBET1JBIG1ldHJpY3MsIERPUkFcbiAqIHRyZW5kLCBhbmQgYnVpbGQgaGVhbHRoIOKAlCBzbyB0aGUgcHJlY2VkZW5jZSBjYW4ndCBkcmlmdCBiZXR3ZWVuIHRoZW0uIEFzc3VtZXMgdGhlXG4gKiBncm91cGVkIGV2ZW50IGFsaWFzIGlzIGBlYC5cbiAqL1xuZXhwb3J0IGNvbnN0IHRlcm1pbmFsU3RhdHVzUm9sbHVwID0gc3FsYENBU0VcbiAgICAgICAgICAgICAgV0hFTiBib29sX29yKGUuc3RhdHVzID0gJ0ZBSUxFRCcpIFRIRU4gJ0ZBSUxFRCdcbiAgICAgICAgICAgICAgV0hFTiBib29sX29yKGUuc3RhdHVzID0gJ1NVQ0NFRURFRCcpIFRIRU4gJ1NVQ0NFRURFRCdcbiAgICAgICAgICAgICAgRUxTRSAnT1RIRVInXG4gICAgICAgICAgICBFTkRgO1xuXG4vKiogT3B0aW9uYWwgYFtmcm9tLHRvXWAgd2luZG93IG9uIGBlLnN0YXJ0ZWRfYXRgIGZvciB0aGUgZXhlY3V0aW9uIHJlcG9ydHMg4oCUXG4gKiAgYSBuby1vcCBgc3FsYCBmcmFnbWVudCB3aGVuIGVpdGhlciBib3VuZCBpcyBhYnNlbnQgKGFsbC10aW1lKS4gU2hhcmVkIGJ5IHRoZVxuICogIGV4ZWN1dGlvbi1jb3VudCBhbmQgcGVyLXBpcGVsaW5lLWV4ZWN1dGlvbiByZXBvcnRzIHNvIHRoZXkgbmFycm93IGlkZW50aWNhbGx5LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG9wdGlvbmFsU3RhcnRlZEF0UmFuZ2UocmFuZ2U/OiB7IGZyb20/OiBzdHJpbmc7IHRvPzogc3RyaW5nIH0pOiBSZXR1cm5UeXBlPHR5cGVvZiBzcWw+IHtcbiAgcmV0dXJuIHJhbmdlPy5mcm9tICYmIHJhbmdlPy50b1xuICAgID8gc3FsYEFORCBlLnN0YXJ0ZWRfYXQgPj0gJHtyYW5nZS5mcm9tfTo6dGltZXN0YW1wdHogQU5EIGUuc3RhcnRlZF9hdCA8PSAke3JhbmdlLnRvfTo6dGltZXN0YW1wdHpgXG4gICAgOiBzcWxgYDtcbn1cblxuLyoqXG4gKiBQZXItZXhlY3V0aW9uIHRlcm1pbmFsIGRlcGxveSByb3cgKEQxIOKAlCBvbmUgcm93IHBlciAoZW52aXJvbm1lbnQsIGV4ZWN1dGlvbiksXG4gKiBOT1QgcGVyIHN0YWdlKS4gVGhlIFNRTCByb2xscyBldmVyeSBkZXBsb3kgU1RBR0UgdGFyZ2V0aW5nIGFuIGVudiB3aXRoaW4gYW5cbiAqIGV4ZWN1dGlvbiB1cCB0byBhIHNpbmdsZSB0ZXJtaW5hbCBzdGF0dXM7IHRoZSBKUyBzaGFwaW5nIGJ1Y2tldHMgdGhlc2UgYnlcbiAqIGVudmlyb25tZW50LiBgY29tbWl0X3RzYCAoRDIpIGlzIHRoZSBleGVjdXRpb24ncyBFQVJMSUVTVCBjb21taXQgdGltZSBhY3Jvc3NcbiAqIGFsbCBpdHMgZXZlbnRzIOKAlCBjb21taXQgZW5yaWNobWVudCByaWRlcyB0aGUgUElQRUxJTkUvc291cmNlIGV2ZW50LCBub3QgdGhlXG4gKiBkZXBsb3kgU1RBR0Ug4oCUIGpvaW5lZCBieSBleGVjdXRpb25faWQgKG51bGwgd2hlbiB1bnJlc29sdmVkIOKGkiBsZWFkIGB1bmtub3duYCkuXG4gKiBgaW5fd2luZG93YCBpcyBmYWxzZSBmb3IgYSBsb29rLWJhY2stb25seSByb3cga2VwdCBzb2xlbHkgc28gaW5jaWRlbnRzIG9wZW5lZFxuICogbmVhciBgZnJvbWAgY2FuIGNvcnJlbGF0ZSB0byBhIGRlcGxveSB0aGF0IGNvbXBsZXRlZCBqdXN0IGJlZm9yZSB0aGUgd2luZG93LlxuICovXG4iXX0=