@pipeline-builder/pipeline-events 4.0.10 → 4.0.12
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/auth.d.ts +14 -0
- package/lib/auth.js +99 -0
- package/lib/health.d.ts +13 -0
- package/lib/health.js +191 -0
- package/lib/index.d.ts +25 -0
- package/lib/index.js +678 -930
- package/lib/parse.d.ts +40 -0
- package/lib/parse.js +150 -0
- package/lib/scm.d.ts +21 -0
- package/lib/scm.js +286 -0
- package/lib/tags.d.ts +18 -0
- package/lib/tags.js +125 -0
- package/lib/testing/exports.json +8 -0
- package/lib/util.d.ts +21 -0
- package/lib/util.js +43 -0
- package/package.json +16 -6
package/lib/auth.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trade the stored key for a short-lived platform token, cached per warm
|
|
3
|
+
* container until it is 80% through its life. Throws on any failure, which fails
|
|
4
|
+
* the batch so SQS retries it — an unauthenticated ship is never an option.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getAuthToken(): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Drop everything the container holds about its credential after a 401/403 from
|
|
9
|
+
* the API: the token, and the KEY — the token-renew Lambda rotates the secret
|
|
10
|
+
* underneath us, so a warm container that only re-exchanged would keep
|
|
11
|
+
* presenting the retired key forever. An env-provided key cannot change without
|
|
12
|
+
* a new container, so it is kept.
|
|
13
|
+
*/
|
|
14
|
+
export declare function invalidateCredential(): void;
|
package/lib/auth.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Reporting-API authentication. The stored credential is an OPAQUE
|
|
5
|
+
* service-account key (`pb_sa_…`), traded at platform for a short-lived JWT.
|
|
6
|
+
* Nothing is verified here: an opaque key has no claims and no signature, and
|
|
7
|
+
* the exchange re-reads the account, its org and the key's own state every time,
|
|
8
|
+
* which is what makes a revocation take effect within one token lifetime.
|
|
9
|
+
*
|
|
10
|
+
* Contract: cache the token in the warm container, refresh EARLY (before
|
|
11
|
+
* expiry) so a batch never presents a token that dies mid-flight, and fail the
|
|
12
|
+
* batch on an exchange failure rather than shipping events unauthenticated.
|
|
13
|
+
*/
|
|
14
|
+
import { createPlatformCredential } from '@pipeline-builder/pipeline-core/lib/handlers/platform-credential.js';
|
|
15
|
+
import { log } from './util.js';
|
|
16
|
+
/** Refresh at this fraction of the token's life — never at the last moment. */
|
|
17
|
+
const TOKEN_REFRESH_FRACTION = 0.8;
|
|
18
|
+
/** Floor on the cached lifetime, so a surprisingly short TTL can't cause a
|
|
19
|
+
* refresh storm (one exchange per batch at worst). */
|
|
20
|
+
const MIN_TOKEN_CACHE_MS = 30_000;
|
|
21
|
+
/** Per-request timeout for the exchange call. */
|
|
22
|
+
const EXCHANGE_TIMEOUT_MS = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* The service-account key: `PLATFORM_ACCESS_KEY` if set, otherwise the
|
|
25
|
+
* `password` field of the Secrets Manager secret named by `PLATFORM_SECRET_NAME`.
|
|
26
|
+
* A stored JWT is refused loudly, naming the fix, instead of failing later as an
|
|
27
|
+
* opaque 401 from the reporting API.
|
|
28
|
+
*/
|
|
29
|
+
const credential = createPlatformCredential({
|
|
30
|
+
secretName: () => process.env.PLATFORM_SECRET_NAME,
|
|
31
|
+
envKeyVar: 'PLATFORM_ACCESS_KEY',
|
|
32
|
+
});
|
|
33
|
+
let cachedToken = null;
|
|
34
|
+
let cachedTokenExpiresAt = 0;
|
|
35
|
+
/**
|
|
36
|
+
* Trade the stored key for a short-lived platform token, cached per warm
|
|
37
|
+
* container until it is 80% through its life. Throws on any failure, which fails
|
|
38
|
+
* the batch so SQS retries it — an unauthenticated ship is never an option.
|
|
39
|
+
*/
|
|
40
|
+
export async function getAuthToken() {
|
|
41
|
+
if (cachedToken && Date.now() < cachedTokenExpiresAt)
|
|
42
|
+
return cachedToken;
|
|
43
|
+
const baseUrl = (process.env.PLATFORM_BASE_URL || '').replace(/\/+$/, '');
|
|
44
|
+
if (!baseUrl)
|
|
45
|
+
throw new Error('PLATFORM_BASE_URL environment variable is required');
|
|
46
|
+
const key = await credential.getKey();
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const timer = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);
|
|
49
|
+
let res;
|
|
50
|
+
try {
|
|
51
|
+
res = await fetch(`${baseUrl}/api/auth/token/exchange`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ key }),
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
}
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
// A rejected key is the one failure an operator has to act on, and platform
|
|
63
|
+
// deliberately answers every refusal the same way — so say what to check
|
|
64
|
+
// rather than echoing an undifferentiated 401.
|
|
65
|
+
if (res.status === 401 || res.status === 403) {
|
|
66
|
+
throw new Error(`Access-key exchange refused (${res.status}) — the stored key is unknown, revoked, expired, `
|
|
67
|
+
+ 'its service account is disabled, or this address is outside the key’s IP allowlist');
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Access-key exchange failed: ${res.status}`);
|
|
70
|
+
}
|
|
71
|
+
const body = await res.json().catch(() => ({}));
|
|
72
|
+
const accessToken = body.data?.accessToken;
|
|
73
|
+
if (!accessToken)
|
|
74
|
+
throw new Error('Access-key exchange returned no access token');
|
|
75
|
+
const ttlMs = (typeof body.data?.expiresIn === 'number' && body.data.expiresIn > 0 ? body.data.expiresIn : 300) * 1000;
|
|
76
|
+
cachedToken = accessToken;
|
|
77
|
+
cachedTokenExpiresAt = Date.now() + Math.max(MIN_TOKEN_CACHE_MS, ttlMs * TOKEN_REFRESH_FRACTION);
|
|
78
|
+
log.info('Exchanged the stored service-account key for a platform token');
|
|
79
|
+
return accessToken;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Drop everything the container holds about its credential after a 401/403 from
|
|
83
|
+
* the API: the token, and the KEY — the token-renew Lambda rotates the secret
|
|
84
|
+
* underneath us, so a warm container that only re-exchanged would keep
|
|
85
|
+
* presenting the retired key forever. An env-provided key cannot change without
|
|
86
|
+
* a new container, so it is kept.
|
|
87
|
+
*/
|
|
88
|
+
export function invalidateCredential() {
|
|
89
|
+
cachedToken = null;
|
|
90
|
+
cachedTokenExpiresAt = 0;
|
|
91
|
+
credential.invalidate();
|
|
92
|
+
}
|
|
93
|
+
/** @internal Test-only: forget every cached credential. */
|
|
94
|
+
export function _resetAuthForTests() {
|
|
95
|
+
cachedToken = null;
|
|
96
|
+
cachedTokenExpiresAt = 0;
|
|
97
|
+
credential.reset();
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9hdXRoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7R0FVRztBQUVILE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLHFFQUFxRSxDQUFDO0FBQy9HLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFFaEMsK0VBQStFO0FBQy9FLE1BQU0sc0JBQXNCLEdBQUcsR0FBRyxDQUFDO0FBQ25DO3VEQUN1RDtBQUN2RCxNQUFNLGtCQUFrQixHQUFHLE1BQU0sQ0FBQztBQUNsQyxpREFBaUQ7QUFDakQsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUM7QUFFakM7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsR0FBRyx3QkFBd0IsQ0FBQztJQUMxQyxVQUFVLEVBQUUsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvQkFBb0I7SUFDbEQsU0FBUyxFQUFFLHFCQUFxQjtDQUNqQyxDQUFDLENBQUM7QUFFSCxJQUFJLFdBQVcsR0FBa0IsSUFBSSxDQUFDO0FBQ3RDLElBQUksb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO0FBRTdCOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVk7SUFDaEMsSUFBSSxXQUFXLElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLG9CQUFvQjtRQUFFLE9BQU8sV0FBVyxDQUFDO0lBRXpFLE1BQU0sT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQzFFLElBQUksQ0FBQyxPQUFPO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxvREFBb0QsQ0FBQyxDQUFDO0lBQ3BGLE1BQU0sR0FBRyxHQUFHLE1BQU0sVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBRXRDLE1BQU0sVUFBVSxHQUFHLElBQUksZUFBZSxFQUFFLENBQUM7SUFDekMsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLEdBQUcsRUFBRSxDQUFDLFVBQVUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksR0FBYSxDQUFDO0lBQ2xCLElBQUksQ0FBQztRQUNILEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLE9BQU8sMEJBQTBCLEVBQUU7WUFDdEQsTUFBTSxFQUFFLE1BQU07WUFDZCxPQUFPLEVBQUUsRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsUUFBUSxFQUFFLGtCQUFrQixFQUFFO1lBQzdFLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUM7WUFDN0IsTUFBTSxFQUFFLFVBQVUsQ0FBQyxNQUFNO1NBQzFCLENBQUMsQ0FBQztJQUNMLENBQUM7WUFBUyxDQUFDO1FBQ1QsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3RCLENBQUM7SUFFRCxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ1osNEVBQTRFO1FBQzVFLHlFQUF5RTtRQUN6RSwrQ0FBK0M7UUFDL0MsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDO1lBQzdDLE1BQU0sSUFBSSxLQUFLLENBQ2IsZ0NBQWdDLEdBQUcsQ0FBQyxNQUFNLG1EQUFtRDtrQkFDM0Ysb0ZBQW9GLENBQ3ZGLENBQUM7UUFDSixDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FBQywrQkFBK0IsR0FBRyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUVELE1BQU0sSUFBSSxHQUFHLE1BQU0sR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUE0RCxDQUFDO0lBQzNHLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLEVBQUUsV0FBVyxDQUFDO0lBQzNDLElBQUksQ0FBQyxXQUFXO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyw4Q0FBOEMsQ0FBQyxDQUFDO0lBRWxGLE1BQU0sS0FBSyxHQUFHLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxFQUFFLFNBQVMsS0FBSyxRQUFRLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQ3ZILFdBQVcsR0FBRyxXQUFXLENBQUM7SUFDMUIsb0JBQW9CLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsa0JBQWtCLEVBQUUsS0FBSyxHQUFHLHNCQUFzQixDQUFDLENBQUM7SUFDakcsR0FBRyxDQUFDLElBQUksQ0FBQywrREFBK0QsQ0FBQyxDQUFDO0lBQzFFLE9BQU8sV0FBVyxDQUFDO0FBQ3JCLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsb0JBQW9CO0lBQ2xDLFdBQVcsR0FBRyxJQUFJLENBQUM7SUFDbkIsb0JBQW9CLEdBQUcsQ0FBQyxDQUFDO0lBQ3pCLFVBQVUsQ0FBQyxVQUFVLEVBQUUsQ0FBQztBQUMxQixDQUFDO0FBRUQsMkRBQTJEO0FBQzNELE1BQU0sVUFBVSxrQkFBa0I7SUFDaEMsV0FBVyxHQUFHLElBQUksQ0FBQztJQUNuQixvQkFBb0IsR0FBRyxDQUFDLENBQUM7SUFDekIsVUFBVSxDQUFDLEtBQUssRUFBRSxDQUFDO0FBQ3JCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBSZXBvcnRpbmctQVBJIGF1dGhlbnRpY2F0aW9uLiBUaGUgc3RvcmVkIGNyZWRlbnRpYWwgaXMgYW4gT1BBUVVFXG4gKiBzZXJ2aWNlLWFjY291bnQga2V5IChgcGJfc2Ff4oCmYCksIHRyYWRlZCBhdCBwbGF0Zm9ybSBmb3IgYSBzaG9ydC1saXZlZCBKV1QuXG4gKiBOb3RoaW5nIGlzIHZlcmlmaWVkIGhlcmU6IGFuIG9wYXF1ZSBrZXkgaGFzIG5vIGNsYWltcyBhbmQgbm8gc2lnbmF0dXJlLCBhbmRcbiAqIHRoZSBleGNoYW5nZSByZS1yZWFkcyB0aGUgYWNjb3VudCwgaXRzIG9yZyBhbmQgdGhlIGtleSdzIG93biBzdGF0ZSBldmVyeSB0aW1lLFxuICogd2hpY2ggaXMgd2hhdCBtYWtlcyBhIHJldm9jYXRpb24gdGFrZSBlZmZlY3Qgd2l0aGluIG9uZSB0b2tlbiBsaWZldGltZS5cbiAqXG4gKiBDb250cmFjdDogY2FjaGUgdGhlIHRva2VuIGluIHRoZSB3YXJtIGNvbnRhaW5lciwgcmVmcmVzaCBFQVJMWSAoYmVmb3JlXG4gKiBleHBpcnkpIHNvIGEgYmF0Y2ggbmV2ZXIgcHJlc2VudHMgYSB0b2tlbiB0aGF0IGRpZXMgbWlkLWZsaWdodCwgYW5kIGZhaWwgdGhlXG4gKiBiYXRjaCBvbiBhbiBleGNoYW5nZSBmYWlsdXJlIHJhdGhlciB0aGFuIHNoaXBwaW5nIGV2ZW50cyB1bmF1dGhlbnRpY2F0ZWQuXG4gKi9cblxuaW1wb3J0IHsgY3JlYXRlUGxhdGZvcm1DcmVkZW50aWFsIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvcGlwZWxpbmUtY29yZS9saWIvaGFuZGxlcnMvcGxhdGZvcm0tY3JlZGVudGlhbC5qcyc7XG5pbXBvcnQgeyBsb2cgfSBmcm9tICcuL3V0aWwuanMnO1xuXG4vKiogUmVmcmVzaCBhdCB0aGlzIGZyYWN0aW9uIG9mIHRoZSB0b2tlbidzIGxpZmUg4oCUIG5ldmVyIGF0IHRoZSBsYXN0IG1vbWVudC4gKi9cbmNvbnN0IFRPS0VOX1JFRlJFU0hfRlJBQ1RJT04gPSAwLjg7XG4vKiogRmxvb3Igb24gdGhlIGNhY2hlZCBsaWZldGltZSwgc28gYSBzdXJwcmlzaW5nbHkgc2hvcnQgVFRMIGNhbid0IGNhdXNlIGFcbiAqICByZWZyZXNoIHN0b3JtIChvbmUgZXhjaGFuZ2UgcGVyIGJhdGNoIGF0IHdvcnN0KS4gKi9cbmNvbnN0IE1JTl9UT0tFTl9DQUNIRV9NUyA9IDMwXzAwMDtcbi8qKiBQZXItcmVxdWVzdCB0aW1lb3V0IGZvciB0aGUgZXhjaGFuZ2UgY2FsbC4gKi9cbmNvbnN0IEVYQ0hBTkdFX1RJTUVPVVRfTVMgPSA1MDAwO1xuXG4vKipcbiAqIFRoZSBzZXJ2aWNlLWFjY291bnQga2V5OiBgUExBVEZPUk1fQUNDRVNTX0tFWWAgaWYgc2V0LCBvdGhlcndpc2UgdGhlXG4gKiBgcGFzc3dvcmRgIGZpZWxkIG9mIHRoZSBTZWNyZXRzIE1hbmFnZXIgc2VjcmV0IG5hbWVkIGJ5IGBQTEFURk9STV9TRUNSRVRfTkFNRWAuXG4gKiBBIHN0b3JlZCBKV1QgaXMgcmVmdXNlZCBsb3VkbHksIG5hbWluZyB0aGUgZml4LCBpbnN0ZWFkIG9mIGZhaWxpbmcgbGF0ZXIgYXMgYW5cbiAqIG9wYXF1ZSA0MDEgZnJvbSB0aGUgcmVwb3J0aW5nIEFQSS5cbiAqL1xuY29uc3QgY3JlZGVudGlhbCA9IGNyZWF0ZVBsYXRmb3JtQ3JlZGVudGlhbCh7XG4gIHNlY3JldE5hbWU6ICgpID0+IHByb2Nlc3MuZW52LlBMQVRGT1JNX1NFQ1JFVF9OQU1FLFxuICBlbnZLZXlWYXI6ICdQTEFURk9STV9BQ0NFU1NfS0VZJyxcbn0pO1xuXG5sZXQgY2FjaGVkVG9rZW46IHN0cmluZyB8IG51bGwgPSBudWxsO1xubGV0IGNhY2hlZFRva2VuRXhwaXJlc0F0ID0gMDtcblxuLyoqXG4gKiBUcmFkZSB0aGUgc3RvcmVkIGtleSBmb3IgYSBzaG9ydC1saXZlZCBwbGF0Zm9ybSB0b2tlbiwgY2FjaGVkIHBlciB3YXJtXG4gKiBjb250YWluZXIgdW50aWwgaXQgaXMgODAlIHRocm91Z2ggaXRzIGxpZmUuIFRocm93cyBvbiBhbnkgZmFpbHVyZSwgd2hpY2ggZmFpbHNcbiAqIHRoZSBiYXRjaCBzbyBTUVMgcmV0cmllcyBpdCDigJQgYW4gdW5hdXRoZW50aWNhdGVkIHNoaXAgaXMgbmV2ZXIgYW4gb3B0aW9uLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZ2V0QXV0aFRva2VuKCk6IFByb21pc2U8c3RyaW5nPiB7XG4gIGlmIChjYWNoZWRUb2tlbiAmJiBEYXRlLm5vdygpIDwgY2FjaGVkVG9rZW5FeHBpcmVzQXQpIHJldHVybiBjYWNoZWRUb2tlbjtcblxuICBjb25zdCBiYXNlVXJsID0gKHByb2Nlc3MuZW52LlBMQVRGT1JNX0JBU0VfVVJMIHx8ICcnKS5yZXBsYWNlKC9cXC8rJC8sICcnKTtcbiAgaWYgKCFiYXNlVXJsKSB0aHJvdyBuZXcgRXJyb3IoJ1BMQVRGT1JNX0JBU0VfVVJMIGVudmlyb25tZW50IHZhcmlhYmxlIGlzIHJlcXVpcmVkJyk7XG4gIGNvbnN0IGtleSA9IGF3YWl0IGNyZWRlbnRpYWwuZ2V0S2V5KCk7XG5cbiAgY29uc3QgY29udHJvbGxlciA9IG5ldyBBYm9ydENvbnRyb2xsZXIoKTtcbiAgY29uc3QgdGltZXIgPSBzZXRUaW1lb3V0KCgpID0+IGNvbnRyb2xsZXIuYWJvcnQoKSwgRVhDSEFOR0VfVElNRU9VVF9NUyk7XG4gIGxldCByZXM6IFJlc3BvbnNlO1xuICB0cnkge1xuICAgIHJlcyA9IGF3YWl0IGZldGNoKGAke2Jhc2VVcmx9L2FwaS9hdXRoL3Rva2VuL2V4Y2hhbmdlYCwge1xuICAgICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicsICdBY2NlcHQnOiAnYXBwbGljYXRpb24vanNvbicgfSxcbiAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHsga2V5IH0pLFxuICAgICAgc2lnbmFsOiBjb250cm9sbGVyLnNpZ25hbCxcbiAgICB9KTtcbiAgfSBmaW5hbGx5IHtcbiAgICBjbGVhclRpbWVvdXQodGltZXIpO1xuICB9XG5cbiAgaWYgKCFyZXMub2spIHtcbiAgICAvLyBBIHJlamVjdGVkIGtleSBpcyB0aGUgb25lIGZhaWx1cmUgYW4gb3BlcmF0b3IgaGFzIHRvIGFjdCBvbiwgYW5kIHBsYXRmb3JtXG4gICAgLy8gZGVsaWJlcmF0ZWx5IGFuc3dlcnMgZXZlcnkgcmVmdXNhbCB0aGUgc2FtZSB3YXkg4oCUIHNvIHNheSB3aGF0IHRvIGNoZWNrXG4gICAgLy8gcmF0aGVyIHRoYW4gZWNob2luZyBhbiB1bmRpZmZlcmVudGlhdGVkIDQwMS5cbiAgICBpZiAocmVzLnN0YXR1cyA9PT0gNDAxIHx8IHJlcy5zdGF0dXMgPT09IDQwMykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgQWNjZXNzLWtleSBleGNoYW5nZSByZWZ1c2VkICgke3Jlcy5zdGF0dXN9KSDigJQgdGhlIHN0b3JlZCBrZXkgaXMgdW5rbm93biwgcmV2b2tlZCwgZXhwaXJlZCwgYFxuICAgICAgICArICdpdHMgc2VydmljZSBhY2NvdW50IGlzIGRpc2FibGVkLCBvciB0aGlzIGFkZHJlc3MgaXMgb3V0c2lkZSB0aGUga2V54oCZcyBJUCBhbGxvd2xpc3QnLFxuICAgICAgKTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKGBBY2Nlc3Mta2V5IGV4Y2hhbmdlIGZhaWxlZDogJHtyZXMuc3RhdHVzfWApO1xuICB9XG5cbiAgY29uc3QgYm9keSA9IGF3YWl0IHJlcy5qc29uKCkuY2F0Y2goKCkgPT4gKHt9KSkgYXMgeyBkYXRhPzogeyBhY2Nlc3NUb2tlbj86IHN0cmluZzsgZXhwaXJlc0luPzogbnVtYmVyIH0gfTtcbiAgY29uc3QgYWNjZXNzVG9rZW4gPSBib2R5LmRhdGE/LmFjY2Vzc1Rva2VuO1xuICBpZiAoIWFjY2Vzc1Rva2VuKSB0aHJvdyBuZXcgRXJyb3IoJ0FjY2Vzcy1rZXkgZXhjaGFuZ2UgcmV0dXJuZWQgbm8gYWNjZXNzIHRva2VuJyk7XG5cbiAgY29uc3QgdHRsTXMgPSAodHlwZW9mIGJvZHkuZGF0YT8uZXhwaXJlc0luID09PSAnbnVtYmVyJyAmJiBib2R5LmRhdGEuZXhwaXJlc0luID4gMCA/IGJvZHkuZGF0YS5leHBpcmVzSW4gOiAzMDApICogMTAwMDtcbiAgY2FjaGVkVG9rZW4gPSBhY2Nlc3NUb2tlbjtcbiAgY2FjaGVkVG9rZW5FeHBpcmVzQXQgPSBEYXRlLm5vdygpICsgTWF0aC5tYXgoTUlOX1RPS0VOX0NBQ0hFX01TLCB0dGxNcyAqIFRPS0VOX1JFRlJFU0hfRlJBQ1RJT04pO1xuICBsb2cuaW5mbygnRXhjaGFuZ2VkIHRoZSBzdG9yZWQgc2VydmljZS1hY2NvdW50IGtleSBmb3IgYSBwbGF0Zm9ybSB0b2tlbicpO1xuICByZXR1cm4gYWNjZXNzVG9rZW47XG59XG5cbi8qKlxuICogRHJvcCBldmVyeXRoaW5nIHRoZSBjb250YWluZXIgaG9sZHMgYWJvdXQgaXRzIGNyZWRlbnRpYWwgYWZ0ZXIgYSA0MDEvNDAzIGZyb21cbiAqIHRoZSBBUEk6IHRoZSB0b2tlbiwgYW5kIHRoZSBLRVkg4oCUIHRoZSB0b2tlbi1yZW5ldyBMYW1iZGEgcm90YXRlcyB0aGUgc2VjcmV0XG4gKiB1bmRlcm5lYXRoIHVzLCBzbyBhIHdhcm0gY29udGFpbmVyIHRoYXQgb25seSByZS1leGNoYW5nZWQgd291bGQga2VlcFxuICogcHJlc2VudGluZyB0aGUgcmV0aXJlZCBrZXkgZm9yZXZlci4gQW4gZW52LXByb3ZpZGVkIGtleSBjYW5ub3QgY2hhbmdlIHdpdGhvdXRcbiAqIGEgbmV3IGNvbnRhaW5lciwgc28gaXQgaXMga2VwdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGludmFsaWRhdGVDcmVkZW50aWFsKCk6IHZvaWQge1xuICBjYWNoZWRUb2tlbiA9IG51bGw7XG4gIGNhY2hlZFRva2VuRXhwaXJlc0F0ID0gMDtcbiAgY3JlZGVudGlhbC5pbnZhbGlkYXRlKCk7XG59XG5cbi8qKiBAaW50ZXJuYWwgVGVzdC1vbmx5OiBmb3JnZXQgZXZlcnkgY2FjaGVkIGNyZWRlbnRpYWwuICovXG5leHBvcnQgZnVuY3Rpb24gX3Jlc2V0QXV0aEZvclRlc3RzKCk6IHZvaWQge1xuICBjYWNoZWRUb2tlbiA9IG51bGw7XG4gIGNhY2hlZFRva2VuRXhwaXJlc0F0ID0gMDtcbiAgY3JlZGVudGlhbC5yZXNldCgpO1xufVxuIl19
|
package/lib/health.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throttled, best-effort post-success work: emit per-org ingest-health and self-heal
|
|
3
|
+
* the DLQ. Never throws. `forwarded`/`lastEventAt` accumulate PER ORG across batches
|
|
4
|
+
* between health posts; `dropped` is the shared fleet DLQ depth snapshot. A per-org
|
|
5
|
+
* counter is zeroed only after that org's health POST is confirmed 2xx.
|
|
6
|
+
*/
|
|
7
|
+
export declare function reportHealthAndRedrive(baseUrl: string, mainQueueArn: string | undefined): Promise<void>;
|
|
8
|
+
/** Count a forwarded batch toward each resolved org's delivery-health report. */
|
|
9
|
+
export declare function recordForwarded(events: ReadonlyArray<{
|
|
10
|
+
orgId: string | null;
|
|
11
|
+
completedAt?: string;
|
|
12
|
+
startedAt?: string;
|
|
13
|
+
}>): void;
|
package/lib/health.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { getAuthToken } from './auth.js';
|
|
4
|
+
import { BoundedMap, CACHE_MAX_ENTRIES, loadSdk, log } from './util.js';
|
|
5
|
+
let sqsMod;
|
|
6
|
+
let sqsClientsByRegion;
|
|
7
|
+
async function sqsClient(region) {
|
|
8
|
+
if (!sqsMod)
|
|
9
|
+
sqsMod = await loadSdk('@aws-sdk/client-sqs');
|
|
10
|
+
if (!sqsClientsByRegion)
|
|
11
|
+
sqsClientsByRegion = new Map();
|
|
12
|
+
let client = sqsClientsByRegion.get(region);
|
|
13
|
+
if (!client) {
|
|
14
|
+
client = new sqsMod.SQSClient({ region });
|
|
15
|
+
sqsClientsByRegion.set(region, client);
|
|
16
|
+
}
|
|
17
|
+
return client;
|
|
18
|
+
}
|
|
19
|
+
// Delivery-health counters are attributed PER RESOLVED ORG (the forwarder is a fleet
|
|
20
|
+
// Lambda serving many orgs; a single global counter would mis-attribute one org's
|
|
21
|
+
// throughput to another). Keyed by orgId ('' bucket = pipeline with no OrgId tag).
|
|
22
|
+
// The DLQ depth (`dropped`) is genuinely fleet-global — one shared DLQ — so it is a
|
|
23
|
+
// single snapshot posted alongside each org's health, documented as the shared depth.
|
|
24
|
+
const forwardedByOrg = new BoundedMap(CACHE_MAX_ENTRIES);
|
|
25
|
+
const lastEventAtByOrg = new BoundedMap(CACHE_MAX_ENTRIES);
|
|
26
|
+
let lastHealthAt = 0;
|
|
27
|
+
let lastRedriveAt = 0;
|
|
28
|
+
const HEALTH_THROTTLE_MS = 60 * 1000;
|
|
29
|
+
const REDRIVE_THROTTLE_MS = 5 * 60 * 1000;
|
|
30
|
+
// Poison-message ceiling: if the oldest DLQ message is older than this, we STOP
|
|
31
|
+
// redriving it (DLQ→main→fail→DLQ would loop forever). Such messages are left for an
|
|
32
|
+
// alarm / manual inspection instead of churning the fleet indefinitely.
|
|
33
|
+
const MAX_DLQ_REDRIVE_AGE_SEC = 6 * 60 * 60; // 6 hours
|
|
34
|
+
/** Parse the SQS trigger ARN and derive the main + DLQ ARNs and the DLQ URL. */
|
|
35
|
+
function deriveQueues(mainQueueArn) {
|
|
36
|
+
if (!mainQueueArn)
|
|
37
|
+
return undefined;
|
|
38
|
+
// arn:aws:sqs:{region}:{account}:{name}
|
|
39
|
+
const parts = mainQueueArn.split(':');
|
|
40
|
+
if (parts.length < 6 || parts[2] !== 'sqs')
|
|
41
|
+
return undefined;
|
|
42
|
+
const [, , , region, account, name] = parts;
|
|
43
|
+
const dlqArn = process.env.EVENT_DLQ_ARN || `arn:aws:sqs:${region}:${account}:${name}-dlq`;
|
|
44
|
+
const dlqName = dlqArn.split(':').pop() ?? `${name}-dlq`;
|
|
45
|
+
const dlqUrl = `https://sqs.${region}.amazonaws.com/${account}/${dlqName}`;
|
|
46
|
+
return { region, mainArn: mainQueueArn, dlqArn, dlqUrl };
|
|
47
|
+
}
|
|
48
|
+
/** Snapshot the DLQ depth + the age (seconds) of its oldest message (for the
|
|
49
|
+
* poison-message redrive ceiling). Missing/NaN attributes fall back to 0. */
|
|
50
|
+
async function dlqStats(region, dlqUrl) {
|
|
51
|
+
if (!sqsMod)
|
|
52
|
+
sqsMod = await loadSdk('@aws-sdk/client-sqs');
|
|
53
|
+
const client = await sqsClient(region);
|
|
54
|
+
const out = await client.send(new sqsMod.GetQueueAttributesCommand({
|
|
55
|
+
QueueUrl: dlqUrl,
|
|
56
|
+
AttributeNames: ['ApproximateNumberOfMessages', 'ApproximateAgeOfOldestMessage'],
|
|
57
|
+
}));
|
|
58
|
+
const n = Number(out.Attributes?.ApproximateNumberOfMessages ?? '0');
|
|
59
|
+
const age = Number(out.Attributes?.ApproximateAgeOfOldestMessage ?? '0');
|
|
60
|
+
return {
|
|
61
|
+
depth: Number.isFinite(n) ? n : 0,
|
|
62
|
+
oldestAgeSec: Number.isFinite(age) ? age : 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function hasActiveMoveTask(region, dlqArn) {
|
|
66
|
+
if (!sqsMod)
|
|
67
|
+
sqsMod = await loadSdk('@aws-sdk/client-sqs');
|
|
68
|
+
const client = await sqsClient(region);
|
|
69
|
+
const out = await client.send(new sqsMod.ListMessageMoveTasksCommand({ SourceArn: dlqArn }));
|
|
70
|
+
return (out.Results ?? []).some(t => t.Status === 'RUNNING');
|
|
71
|
+
}
|
|
72
|
+
async function startMoveTask(region, dlqArn, mainArn) {
|
|
73
|
+
if (!sqsMod)
|
|
74
|
+
sqsMod = await loadSdk('@aws-sdk/client-sqs');
|
|
75
|
+
const client = await sqsClient(region);
|
|
76
|
+
await client.send(new sqsMod.StartMessageMoveTaskCommand({ SourceArn: dlqArn, DestinationArn: mainArn }));
|
|
77
|
+
}
|
|
78
|
+
/** POST one org's ingest-health signal. Returns true only on a confirmed 2xx so the
|
|
79
|
+
* caller resets that org's `forwarded` counter ONLY when the signal was accepted
|
|
80
|
+
* (a dropped/failed POST must not silently zero the accumulated count). */
|
|
81
|
+
async function postIngestHealth(baseUrl, body) {
|
|
82
|
+
try {
|
|
83
|
+
const token = await getAuthToken();
|
|
84
|
+
const res = await fetch(`${baseUrl}/api/reports/ingest-health`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
});
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
log.warn('ingest-health POST returned non-2xx (ignored)', { status: res.status, orgId: body.orgId });
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
log.warn('ingest-health POST failed (ignored)', { error: String(err), orgId: body.orgId });
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Throttled, best-effort post-success work: emit per-org ingest-health and self-heal
|
|
102
|
+
* the DLQ. Never throws. `forwarded`/`lastEventAt` accumulate PER ORG across batches
|
|
103
|
+
* between health posts; `dropped` is the shared fleet DLQ depth snapshot. A per-org
|
|
104
|
+
* counter is zeroed only after that org's health POST is confirmed 2xx.
|
|
105
|
+
*/
|
|
106
|
+
export async function reportHealthAndRedrive(baseUrl, mainQueueArn) {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
if (now - lastHealthAt < HEALTH_THROTTLE_MS)
|
|
109
|
+
return;
|
|
110
|
+
lastHealthAt = now;
|
|
111
|
+
try {
|
|
112
|
+
const q = deriveQueues(mainQueueArn);
|
|
113
|
+
let depth = 0;
|
|
114
|
+
let oldestAgeSec = 0;
|
|
115
|
+
if (q) {
|
|
116
|
+
try {
|
|
117
|
+
const stats = await dlqStats(q.region, q.dlqUrl);
|
|
118
|
+
depth = stats.depth;
|
|
119
|
+
oldestAgeSec = stats.oldestAgeSec;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
log.warn('DLQ stats check failed (ignored)', { error: err?.name ?? String(err) });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Post one health signal per resolved org, attributing its own forwarded count +
|
|
126
|
+
// last event time. `dropped` (the shared fleet DLQ depth) rides along on each.
|
|
127
|
+
// Snapshot the keys first so we don't mutate the map while iterating.
|
|
128
|
+
const orgKeys = new Set([...forwardedByOrg.keys(), ...lastEventAtByOrg.keys()]);
|
|
129
|
+
if (orgKeys.size === 0)
|
|
130
|
+
orgKeys.add(''); // nothing forwarded yet → still report depth
|
|
131
|
+
for (const key of orgKeys) {
|
|
132
|
+
const forwarded = forwardedByOrg.get(key) ?? 0;
|
|
133
|
+
const lastEventAt = lastEventAtByOrg.get(key);
|
|
134
|
+
const ok = await postIngestHealth(baseUrl, {
|
|
135
|
+
...(key ? { orgId: key } : {}),
|
|
136
|
+
forwarded,
|
|
137
|
+
dropped: depth,
|
|
138
|
+
lastEventAt,
|
|
139
|
+
});
|
|
140
|
+
// Reset the forwarded counter ONLY on a confirmed 2xx — a failed/dropped POST
|
|
141
|
+
// keeps the count so the next tick re-reports it instead of losing it.
|
|
142
|
+
if (ok)
|
|
143
|
+
forwardedByOrg.set(key, 0);
|
|
144
|
+
}
|
|
145
|
+
if (q && depth > 0 && now - lastRedriveAt >= REDRIVE_THROTTLE_MS) {
|
|
146
|
+
// Poison-message ceiling: never redrive a DLQ whose oldest message has aged
|
|
147
|
+
// past the ceiling — that message keeps failing and would loop DLQ→main→DLQ.
|
|
148
|
+
if (oldestAgeSec > MAX_DLQ_REDRIVE_AGE_SEC) {
|
|
149
|
+
log.warn('DLQ oldest message exceeds poison-age ceiling — skipping redrive (needs manual attention)', {
|
|
150
|
+
dlqArn: q.dlqArn, dropped: depth, oldestAgeSec, ceilingSec: MAX_DLQ_REDRIVE_AGE_SEC,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
try {
|
|
155
|
+
if (!(await hasActiveMoveTask(q.region, q.dlqArn))) {
|
|
156
|
+
lastRedriveAt = now;
|
|
157
|
+
await startMoveTask(q.region, q.dlqArn, q.mainArn);
|
|
158
|
+
log.info('Started self-healing DLQ→main redrive', { dlqArn: q.dlqArn, dropped: depth, oldestAgeSec });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
log.warn('DLQ redrive failed (ignored)', { error: err?.name ?? String(err) });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
log.warn('health/redrive step failed (ignored)', { error: String(err) });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Count a forwarded batch toward each resolved org's delivery-health report. */
|
|
172
|
+
export function recordForwarded(events) {
|
|
173
|
+
for (const e of events) {
|
|
174
|
+
const key = e.orgId ?? '';
|
|
175
|
+
forwardedByOrg.set(key, (forwardedByOrg.get(key) ?? 0) + 1);
|
|
176
|
+
const at = e.completedAt || e.startedAt;
|
|
177
|
+
if (at) {
|
|
178
|
+
const prev = lastEventAtByOrg.get(key);
|
|
179
|
+
if (!prev || at > prev)
|
|
180
|
+
lastEventAtByOrg.set(key, at);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** @internal Test-only: forget health counters and throttles. */
|
|
185
|
+
export function _resetHealthForTests() {
|
|
186
|
+
forwardedByOrg.clear();
|
|
187
|
+
lastEventAtByOrg.clear();
|
|
188
|
+
lastHealthAt = 0;
|
|
189
|
+
lastRedriveAt = 0;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVhbHRoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2hlYWx0aC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDekMsT0FBTyxFQUFFLFVBQVUsRUFBRSxpQkFBaUIsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBeUJ4RSxJQUFJLE1BQTZCLENBQUM7QUFDbEMsSUFBSSxrQkFBMEQsQ0FBQztBQUMvRCxLQUFLLFVBQVUsU0FBUyxDQUFDLE1BQWM7SUFDckMsSUFBSSxDQUFDLE1BQU07UUFBRSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQVkscUJBQXFCLENBQUMsQ0FBQztJQUN0RSxJQUFJLENBQUMsa0JBQWtCO1FBQUUsa0JBQWtCLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUN4RCxJQUFJLE1BQU0sR0FBRyxrQkFBa0IsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDNUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDMUMsa0JBQWtCLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQztBQUVELHFGQUFxRjtBQUNyRixrRkFBa0Y7QUFDbEYsbUZBQW1GO0FBQ25GLG9GQUFvRjtBQUNwRixzRkFBc0Y7QUFDdEYsTUFBTSxjQUFjLEdBQUcsSUFBSSxVQUFVLENBQWlCLGlCQUFpQixDQUFDLENBQUM7QUFDekUsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLFVBQVUsQ0FBaUIsaUJBQWlCLENBQUMsQ0FBQztBQUMzRSxJQUFJLFlBQVksR0FBRyxDQUFDLENBQUM7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Q0FBQyxJQUFJLE1BQU0sRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sR0FBRyxHQUFHLENBQUMsQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO1FBQzFCLGNBQWMsQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUM1RCxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUMsV0FBVyxJQUFJLENBQUMsQ0FBQyxTQUFTLENBQUM7UUFDeEMsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUNQLE1BQU0sSUFBSSxHQUFHLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUN2QyxJQUFJLENBQUMsSUFBSSxJQUFJLEVBQUUsR0FBRyxJQUFJO2dCQUFFLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDeEQsQ0FBQztJQUNILENBQUM7QUFDSCxDQUFDO0FBRUQsaUVBQWlFO0FBQ2pFLE1BQU0sVUFBVSxvQkFBb0I7SUFDbEMsY0FBYyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ3ZCLGdCQUFnQixDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ3pCLFlBQVksR0FBRyxDQUFDLENBQUM7SUFDakIsYUFBYSxHQUFHLENBQUMsQ0FBQztBQUNwQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IGdldEF1dGhUb2tlbiB9IGZyb20gJy4vYXV0aC5qcyc7XG5pbXBvcnQgeyBCb3VuZGVkTWFwLCBDQUNIRV9NQVhfRU5UUklFUywgbG9hZFNkaywgbG9nIH0gZnJvbSAnLi91dGlsLmpzJztcblxuLy8gRGVsaXZlcnkgaGVhbHRoICsgc2VsZi1oZWFsaW5nIERMUSByZWRyaXZlLlxuLy8gQWZ0ZXIgYSBTVUNDRVNTRlVMIGJhdGNoIFBPU1Qgd2UgKHRocm90dGxlZCwgYmVzdC1lZmZvcnQpOlxuLy8gICAxLiBQT1NUIGFuIGluZ2VzdC1oZWFsdGggc2lnbmFsIHRvIC9hcGkvcmVwb3J0cy9pbmdlc3QtaGVhbHRoIHNvIHRoZSBSZXBvcnRzIFVJXG4vLyAgICAgIGNhbiBzaG93IGZsb3dpbmcgLyBzdGFsZSAvIGRyb3BwaW5nLlxuLy8gICAyLiBJZiB0aGUgRExRIGlzIG5vbi1lbXB0eSwgbm8gbW92ZSB0YXNrIGlzIGFscmVhZHkgcnVubmluZywgQU5EIHRoZSBvbGRlc3QgRExRXG4vLyAgICAgIG1lc3NhZ2UgaXMgeW91bmdlciB0aGFuIHRoZSBwb2lzb24tYWdlIGNlaWxpbmcsIGtpY2sgb2ZmIGFuIFNRU1xuLy8gICAgICBNZXNzYWdlTW92ZVRhc2sgKERMUSDihpIgbWFpbiBxdWV1ZSkgc28gcmV0cnlhYmxlIGZhaWx1cmVzIHNlbGYtaGVhbCB3aXRob3V0IGFcbi8vICAgICAgbmV3IEFXUyBzZXJ2aWNlLiBUaGUgcmVwb3J0aW5nIGluZ2VzdCdzIHBhcnRpYWwtdW5pcXVlIGluZGV4IGRlZHVwZXMgYW55XG4vLyAgICAgIHJlZGVsaXZlcmVkIGV2ZW50LCBzbyBhIHJlZHJpdmUgY2FuJ3QgZG91YmxlLWNvdW50LlxuLy8gR3VhcmRzOiBvbmx5IG9uIHN1Y2Nlc3MsIG9uZS1hdC1hLXRpbWUgKExpc3RNZXNzYWdlTW92ZVRhc2tzKSwgYSBwb2lzb24tYWdlIGNlaWxpbmdcbi8vIChzbyBhIG1lc3NhZ2UgdGhhdCBhbHdheXMgZmFpbHMgY2FuJ3QgbG9vcCBETFHihpJtYWlu4oaSRExRIGZvcmV2ZXIpLCBhbmQgdGhyb3R0bGVkIHZpYVxuLy8gbW9kdWxlIHRpbWVzdGFtcHMuIE5FVkVSIHRocm93cyDigJQgYSBmYWlsdXJlIGhlcmUgbXVzdCBub3QgZmFpbCB0aGUgYmF0Y2guXG4vL1xuLy8gSUFNIHJlcXVpcmVkIChkb2N1bWVudCBvbmx5IOKAlCBncmFudGVkIG9uIHRoZSBldmVudHMgc3RhY2spOlxuLy8gICBzcXM6R2V0UXVldWVBdHRyaWJ1dGVzLCBzcXM6TGlzdE1lc3NhZ2VNb3ZlVGFza3MsIHNxczpTdGFydE1lc3NhZ2VNb3ZlVGFzayAob24gdGhlIERMUSlcblxuaW50ZXJmYWNlIFNxc0NsaWVudExpa2UgeyBzZW5kKGNvbW1hbmQ6IHVua25vd24pOiBQcm9taXNlPHVua25vd24+IH1cbmludGVyZmFjZSBTcXNNb2R1bGUge1xuICBTUVNDbGllbnQ6IG5ldyAoY29uZmlnOiB7IHJlZ2lvbjogc3RyaW5nIH0pID0+IFNxc0NsaWVudExpa2U7XG4gIEdldFF1ZXVlQXR0cmlidXRlc0NvbW1hbmQ6IG5ldyAoaW5wdXQ6IHsgUXVldWVVcmw6IHN0cmluZzsgQXR0cmlidXRlTmFtZXM6IHN0cmluZ1tdIH0pID0+IHVua25vd247XG4gIExpc3RNZXNzYWdlTW92ZVRhc2tzQ29tbWFuZDogbmV3IChpbnB1dDogeyBTb3VyY2VBcm46IHN0cmluZyB9KSA9PiB1bmtub3duO1xuICBTdGFydE1lc3NhZ2VNb3ZlVGFza0NvbW1hbmQ6IG5ldyAoaW5wdXQ6IHsgU291cmNlQXJuOiBzdHJpbmc7IERlc3RpbmF0aW9uQXJuPzogc3RyaW5nIH0pID0+IHVua25vd247XG59XG5sZXQgc3FzTW9kOiBTcXNNb2R1bGUgfCB1bmRlZmluZWQ7XG5sZXQgc3FzQ2xpZW50c0J5UmVnaW9uOiBNYXA8c3RyaW5nLCBTcXNDbGllbnRMaWtlPiB8IHVuZGVmaW5lZDtcbmFzeW5jIGZ1bmN0aW9uIHNxc0NsaWVudChyZWdpb246IHN0cmluZyk6IFByb21pc2U8U3FzQ2xpZW50TGlrZT4ge1xuICBpZiAoIXNxc01vZCkgc3FzTW9kID0gYXdhaXQgbG9hZFNkazxTcXNNb2R1bGU+KCdAYXdzLXNkay9jbGllbnQtc3FzJyk7XG4gIGlmICghc3FzQ2xpZW50c0J5UmVnaW9uKSBzcXNDbGllbnRzQnlSZWdpb24gPSBuZXcgTWFwKCk7XG4gIGxldCBjbGllbnQgPSBzcXNDbGllbnRzQnlSZWdpb24uZ2V0KHJlZ2lvbik7XG4gIGlmICghY2xpZW50KSB7XG4gICAgY2xpZW50ID0gbmV3IHNxc01vZC5TUVNDbGllbnQoeyByZWdpb24gfSk7XG4gICAgc3FzQ2xpZW50c0J5UmVnaW9uLnNldChyZWdpb24sIGNsaWVudCk7XG4gIH1cbiAgcmV0dXJuIGNsaWVudDtcbn1cblxuLy8gRGVsaXZlcnktaGVhbHRoIGNvdW50ZXJzIGFyZSBhdHRyaWJ1dGVkIFBFUiBSRVNPTFZFRCBPUkcgKHRoZSBmb3J3YXJkZXIgaXMgYSBmbGVldFxuLy8gTGFtYmRhIHNlcnZpbmcgbWFueSBvcmdzOyBhIHNpbmdsZSBnbG9iYWwgY291bnRlciB3b3VsZCBtaXMtYXR0cmlidXRlIG9uZSBvcmcnc1xuLy8gdGhyb3VnaHB1dCB0byBhbm90aGVyKS4gS2V5ZWQgYnkgb3JnSWQgKCcnIGJ1Y2tldCA9IHBpcGVsaW5lIHdpdGggbm8gT3JnSWQgdGFnKS5cbi8vIFRoZSBETFEgZGVwdGggKGBkcm9wcGVkYCkgaXMgZ2VudWluZWx5IGZsZWV0LWdsb2JhbCDigJQgb25lIHNoYXJlZCBETFEg4oCUIHNvIGl0IGlzIGFcbi8vIHNpbmdsZSBzbmFwc2hvdCBwb3N0ZWQgYWxvbmdzaWRlIGVhY2ggb3JnJ3MgaGVhbHRoLCBkb2N1bWVudGVkIGFzIHRoZSBzaGFyZWQgZGVwdGguXG5jb25zdCBmb3J3YXJkZWRCeU9yZyA9IG5ldyBCb3VuZGVkTWFwPHN0cmluZywgbnVtYmVyPihDQUNIRV9NQVhfRU5UUklFUyk7XG5jb25zdCBsYXN0RXZlbnRBdEJ5T3JnID0gbmV3IEJvdW5kZWRNYXA8c3RyaW5nLCBzdHJpbmc+KENBQ0hFX01BWF9FTlRSSUVTKTtcbmxldCBsYXN0SGVhbHRoQXQgPSAwO1xubGV0IGxhc3RSZWRyaXZlQXQgPSAwO1xuY29uc3QgSEVBTFRIX1RIUk9UVExFX01TID0gNjAgKiAxMDAwO1xuY29uc3QgUkVEUklWRV9USFJPVFRMRV9NUyA9IDUgKiA2MCAqIDEwMDA7XG4vLyBQb2lzb24tbWVzc2FnZSBjZWlsaW5nOiBpZiB0aGUgb2xkZXN0IERMUSBtZXNzYWdlIGlzIG9sZGVyIHRoYW4gdGhpcywgd2UgU1RPUFxuLy8gcmVkcml2aW5nIGl0IChETFHihpJtYWlu4oaSZmFpbOKGkkRMUSB3b3VsZCBsb29wIGZvcmV2ZXIpLiBTdWNoIG1lc3NhZ2VzIGFyZSBsZWZ0IGZvciBhblxuLy8gYWxhcm0gLyBtYW51YWwgaW5zcGVjdGlvbiBpbnN0ZWFkIG9mIGNodXJuaW5nIHRoZSBmbGVldCBpbmRlZmluaXRlbHkuXG5jb25zdCBNQVhfRExRX1JFRFJJVkVfQUdFX1NFQyA9IDYgKiA2MCAqIDYwOyAvLyA2IGhvdXJzXG5cbi8qKiBQYXJzZSB0aGUgU1FTIHRyaWdnZXIgQVJOIGFuZCBkZXJpdmUgdGhlIG1haW4gKyBETFEgQVJOcyBhbmQgdGhlIERMUSBVUkwuICovXG5mdW5jdGlvbiBkZXJpdmVRdWV1ZXMobWFpblF1ZXVlQXJuOiBzdHJpbmcgfCB1bmRlZmluZWQpOiB7IHJlZ2lvbjogc3RyaW5nOyBtYWluQXJuOiBzdHJpbmc7IGRscUFybjogc3RyaW5nOyBkbHFVcmw6IHN0cmluZyB9IHwgdW5kZWZpbmVkIHtcbiAgaWYgKCFtYWluUXVldWVBcm4pIHJldHVybiB1bmRlZmluZWQ7XG4gIC8vIGFybjphd3M6c3FzOntyZWdpb259OnthY2NvdW50fTp7bmFtZX1cbiAgY29uc3QgcGFydHMgPSBtYWluUXVldWVBcm4uc3BsaXQoJzonKTtcbiAgaWYgKHBhcnRzLmxlbmd0aCA8IDYgfHwgcGFydHNbMl0gIT09ICdzcXMnKSByZXR1cm4gdW5kZWZpbmVkO1xuICBjb25zdCBbLCAsICwgcmVnaW9uLCBhY2NvdW50LCBuYW1lXSA9IHBhcnRzO1xuICBjb25zdCBkbHFBcm4gPSBwcm9jZXNzLmVudi5FVkVOVF9ETFFfQVJOIHx8IGBhcm46YXdzOnNxczoke3JlZ2lvbn06JHthY2NvdW50fToke25hbWV9LWRscWA7XG4gIGNvbnN0IGRscU5hbWUgPSBkbHFBcm4uc3BsaXQoJzonKS5wb3AoKSA/PyBgJHtuYW1lfS1kbHFgO1xuICBjb25zdCBkbHFVcmwgPSBgaHR0cHM6Ly9zcXMuJHtyZWdpb259LmFtYXpvbmF3cy5jb20vJHthY2NvdW50fS8ke2RscU5hbWV9YDtcbiAgcmV0dXJuIHsgcmVnaW9uLCBtYWluQXJuOiBtYWluUXVldWVBcm4sIGRscUFybiwgZGxxVXJsIH07XG59XG5cbi8qKiBTbmFwc2hvdCB0aGUgRExRIGRlcHRoICsgdGhlIGFnZSAoc2Vjb25kcykgb2YgaXRzIG9sZGVzdCBtZXNzYWdlIChmb3IgdGhlXG4gKiAgcG9pc29uLW1lc3NhZ2UgcmVkcml2ZSBjZWlsaW5nKS4gTWlzc2luZy9OYU4gYXR0cmlidXRlcyBmYWxsIGJhY2sgdG8gMC4gKi9cbmFzeW5jIGZ1bmN0aW9uIGRscVN0YXRzKHJlZ2lvbjogc3RyaW5nLCBkbHFVcmw6IHN0cmluZyk6IFByb21pc2U8eyBkZXB0aDogbnVtYmVyOyBvbGRlc3RBZ2VTZWM6IG51bWJlciB9PiB7XG4gIGlmICghc3FzTW9kKSBzcXNNb2QgPSBhd2FpdCBsb2FkU2RrPFNxc01vZHVsZT4oJ0Bhd3Mtc2RrL2NsaWVudC1zcXMnKTtcbiAgY29uc3QgY2xpZW50ID0gYXdhaXQgc3FzQ2xpZW50KHJlZ2lvbik7XG4gIGNvbnN0IG91dCA9IGF3YWl0IGNsaWVudC5zZW5kKG5ldyBzcXNNb2QuR2V0UXVldWVBdHRyaWJ1dGVzQ29tbWFuZCh7XG4gICAgUXVldWVVcmw6IGRscVVybCxcbiAgICBBdHRyaWJ1dGVOYW1lczogWydBcHByb3hpbWF0ZU51bWJlck9mTWVzc2FnZXMnLCAnQXBwcm94aW1hdGVBZ2VPZk9sZGVzdE1lc3NhZ2UnXSxcbiAgfSkpIGFzIHsgQXR0cmlidXRlcz86IHsgQXBwcm94aW1hdGVOdW1iZXJPZk1lc3NhZ2VzPzogc3RyaW5nOyBBcHByb3hpbWF0ZUFnZU9mT2xkZXN0TWVzc2FnZT86IHN0cmluZyB9IH07XG4gIGNvbnN0IG4gPSBOdW1iZXIob3V0LkF0dHJpYnV0ZXM/LkFwcHJveGltYXRlTnVtYmVyT2ZNZXNzYWdlcyA/PyAnMCcpO1xuICBjb25zdCBhZ2UgPSBOdW1iZXIob3V0LkF0dHJpYnV0ZXM/LkFwcHJveGltYXRlQWdlT2ZPbGRlc3RNZXNzYWdlID8/ICcwJyk7XG4gIHJldHVybiB7XG4gICAgZGVwdGg6IE51bWJlci5pc0Zpbml0ZShuKSA/IG4gOiAwLFxuICAgIG9sZGVzdEFnZVNlYzogTnVtYmVyLmlzRmluaXRlKGFnZSkgPyBhZ2UgOiAwLFxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBoYXNBY3RpdmVNb3ZlVGFzayhyZWdpb246IHN0cmluZywgZGxxQXJuOiBzdHJpbmcpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgaWYgKCFzcXNNb2QpIHNxc01vZCA9IGF3YWl0IGxvYWRTZGs8U3FzTW9kdWxlPignQGF3cy1zZGsvY2xpZW50LXNxcycpO1xuICBjb25zdCBjbGllbnQgPSBhd2FpdCBzcXNDbGllbnQocmVnaW9uKTtcbiAgY29uc3Qgb3V0ID0gYXdhaXQgY2xpZW50LnNlbmQobmV3IHNxc01vZC5MaXN0TWVzc2FnZU1vdmVUYXNrc0NvbW1hbmQoeyBTb3VyY2VBcm46IGRscUFybiB9KSkgYXMge1xuICAgIFJlc3VsdHM/OiBBcnJheTx7IFN0YXR1cz86IHN0cmluZyB9PjtcbiAgfTtcbiAgcmV0dXJuIChvdXQuUmVzdWx0cyA/PyBbXSkuc29tZSh0ID0+IHQuU3RhdHVzID09PSAnUlVOTklORycpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBzdGFydE1vdmVUYXNrKHJlZ2lvbjogc3RyaW5nLCBkbHFBcm46IHN0cmluZywgbWFpbkFybjogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gIGlmICghc3FzTW9kKSBzcXNNb2QgPSBhd2FpdCBsb2FkU2RrPFNxc01vZHVsZT4oJ0Bhd3Mtc2RrL2NsaWVudC1zcXMnKTtcbiAgY29uc3QgY2xpZW50ID0gYXdhaXQgc3FzQ2xpZW50KHJlZ2lvbik7XG4gIGF3YWl0IGNsaWVudC5zZW5kKG5ldyBzcXNNb2QuU3RhcnRNZXNzYWdlTW92ZVRhc2tDb21tYW5kKHsgU291cmNlQXJuOiBkbHFBcm4sIERlc3RpbmF0aW9uQXJuOiBtYWluQXJuIH0pKTtcbn1cblxuLyoqIFBPU1Qgb25lIG9yZydzIGluZ2VzdC1oZWFsdGggc2lnbmFsLiBSZXR1cm5zIHRydWUgb25seSBvbiBhIGNvbmZpcm1lZCAyeHggc28gdGhlXG4gKiAgY2FsbGVyIHJlc2V0cyB0aGF0IG9yZydzIGBmb3J3YXJkZWRgIGNvdW50ZXIgT05MWSB3aGVuIHRoZSBzaWduYWwgd2FzIGFjY2VwdGVkXG4gKiAgKGEgZHJvcHBlZC9mYWlsZWQgUE9TVCBtdXN0IG5vdCBzaWxlbnRseSB6ZXJvIHRoZSBhY2N1bXVsYXRlZCBjb3VudCkuICovXG5hc3luYyBmdW5jdGlvbiBwb3N0SW5nZXN0SGVhbHRoKGJhc2VVcmw6IHN0cmluZywgYm9keTogeyBvcmdJZD86IHN0cmluZzsgZm9yd2FyZGVkOiBudW1iZXI7IGRyb3BwZWQ6IG51bWJlcjsgbGFzdEV2ZW50QXQ/OiBzdHJpbmcgfSk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICB0cnkge1xuICAgIGNvbnN0IHRva2VuID0gYXdhaXQgZ2V0QXV0aFRva2VuKCk7XG4gICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7YmFzZVVybH0vYXBpL3JlcG9ydHMvaW5nZXN0LWhlYWx0aGAsIHtcbiAgICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgICAgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nLCAnQXV0aG9yaXphdGlvbic6IGBCZWFyZXIgJHt0b2tlbn1gIH0sXG4gICAgICBib2R5OiBKU09OLnN0cmluZ2lmeShib2R5KSxcbiAgICB9KTtcbiAgICBpZiAoIXJlcy5vaykge1xuICAgICAgbG9nLndhcm4oJ2luZ2VzdC1oZWFsdGggUE9TVCByZXR1cm5lZCBub24tMnh4IChpZ25vcmVkKScsIHsgc3RhdHVzOiByZXMuc3RhdHVzLCBvcmdJZDogYm9keS5vcmdJZCB9KTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZy53YXJuKCdpbmdlc3QtaGVhbHRoIFBPU1QgZmFpbGVkIChpZ25vcmVkKScsIHsgZXJyb3I6IFN0cmluZyhlcnIpLCBvcmdJZDogYm9keS5vcmdJZCB9KTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuLyoqXG4gKiBUaHJvdHRsZWQsIGJlc3QtZWZmb3J0IHBvc3Qtc3VjY2VzcyB3b3JrOiBlbWl0IHBlci1vcmcgaW5nZXN0LWhlYWx0aCBhbmQgc2VsZi1oZWFsXG4gKiB0aGUgRExRLiBOZXZlciB0aHJvd3MuIGBmb3J3YXJkZWRgL2BsYXN0RXZlbnRBdGAgYWNjdW11bGF0ZSBQRVIgT1JHIGFjcm9zcyBiYXRjaGVzXG4gKiBiZXR3ZWVuIGhlYWx0aCBwb3N0czsgYGRyb3BwZWRgIGlzIHRoZSBzaGFyZWQgZmxlZXQgRExRIGRlcHRoIHNuYXBzaG90LiBBIHBlci1vcmdcbiAqIGNvdW50ZXIgaXMgemVyb2VkIG9ubHkgYWZ0ZXIgdGhhdCBvcmcncyBoZWFsdGggUE9TVCBpcyBjb25maXJtZWQgMnh4LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVwb3J0SGVhbHRoQW5kUmVkcml2ZShiYXNlVXJsOiBzdHJpbmcsIG1haW5RdWV1ZUFybjogc3RyaW5nIHwgdW5kZWZpbmVkKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gIGlmIChub3cgLSBsYXN0SGVhbHRoQXQgPCBIRUFMVEhfVEhST1RUTEVfTVMpIHJldHVybjtcbiAgbGFzdEhlYWx0aEF0ID0gbm93O1xuICB0cnkge1xuICAgIGNvbnN0IHEgPSBkZXJpdmVRdWV1ZXMobWFpblF1ZXVlQXJuKTtcbiAgICBsZXQgZGVwdGggPSAwO1xuICAgIGxldCBvbGRlc3RBZ2VTZWMgPSAwO1xuICAgIGlmIChxKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGRscVN0YXRzKHEucmVnaW9uLCBxLmRscVVybCk7XG4gICAgICAgIGRlcHRoID0gc3RhdHMuZGVwdGg7XG4gICAgICAgIG9sZGVzdEFnZVNlYyA9IHN0YXRzLm9sZGVzdEFnZVNlYztcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2cud2FybignRExRIHN0YXRzIGNoZWNrIGZhaWxlZCAoaWdub3JlZCknLCB7IGVycm9yOiAoZXJyIGFzIHsgbmFtZT86IHN0cmluZyB9KT8ubmFtZSA/PyBTdHJpbmcoZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBQb3N0IG9uZSBoZWFsdGggc2lnbmFsIHBlciByZXNvbHZlZCBvcmcsIGF0dHJpYnV0aW5nIGl0cyBvd24gZm9yd2FyZGVkIGNvdW50ICtcbiAgICAvLyBsYXN0IGV2ZW50IHRpbWUuIGBkcm9wcGVkYCAodGhlIHNoYXJlZCBmbGVldCBETFEgZGVwdGgpIHJpZGVzIGFsb25nIG9uIGVhY2guXG4gICAgLy8gU25hcHNob3QgdGhlIGtleXMgZmlyc3Qgc28gd2UgZG9uJ3QgbXV0YXRlIHRoZSBtYXAgd2hpbGUgaXRlcmF0aW5nLlxuICAgIGNvbnN0IG9yZ0tleXMgPSBuZXcgU2V0KFsuLi5mb3J3YXJkZWRCeU9yZy5rZXlzKCksIC4uLmxhc3RFdmVudEF0QnlPcmcua2V5cygpXSk7XG4gICAgaWYgKG9yZ0tleXMuc2l6ZSA9PT0gMCkgb3JnS2V5cy5hZGQoJycpOyAvLyBub3RoaW5nIGZvcndhcmRlZCB5ZXQg4oaSIHN0aWxsIHJlcG9ydCBkZXB0aFxuICAgIGZvciAoY29uc3Qga2V5IG9mIG9yZ0tleXMpIHtcbiAgICAgIGNvbnN0IGZvcndhcmRlZCA9IGZvcndhcmRlZEJ5T3JnLmdldChrZXkpID8/IDA7XG4gICAgICBjb25zdCBsYXN0RXZlbnRBdCA9IGxhc3RFdmVudEF0QnlPcmcuZ2V0KGtleSk7XG4gICAgICBjb25zdCBvayA9IGF3YWl0IHBvc3RJbmdlc3RIZWFsdGgoYmFzZVVybCwge1xuICAgICAgICAuLi4oa2V5ID8geyBvcmdJZDoga2V5IH0gOiB7fSksXG4gICAgICAgIGZvcndhcmRlZCxcbiAgICAgICAgZHJvcHBlZDogZGVwdGgsXG4gICAgICAgIGxhc3RFdmVudEF0LFxuICAgICAgfSk7XG4gICAgICAvLyBSZXNldCB0aGUgZm9yd2FyZGVkIGNvdW50ZXIgT05MWSBvbiBhIGNvbmZpcm1lZCAyeHgg4oCUIGEgZmFpbGVkL2Ryb3BwZWQgUE9TVFxuICAgICAgLy8ga2VlcHMgdGhlIGNvdW50IHNvIHRoZSBuZXh0IHRpY2sgcmUtcmVwb3J0cyBpdCBpbnN0ZWFkIG9mIGxvc2luZyBpdC5cbiAgICAgIGlmIChvaykgZm9yd2FyZGVkQnlPcmcuc2V0KGtleSwgMCk7XG4gICAgfVxuXG4gICAgaWYgKHEgJiYgZGVwdGggPiAwICYmIG5vdyAtIGxhc3RSZWRyaXZlQXQgPj0gUkVEUklWRV9USFJPVFRMRV9NUykge1xuICAgICAgLy8gUG9pc29uLW1lc3NhZ2UgY2VpbGluZzogbmV2ZXIgcmVkcml2ZSBhIERMUSB3aG9zZSBvbGRlc3QgbWVzc2FnZSBoYXMgYWdlZFxuICAgICAgLy8gcGFzdCB0aGUgY2VpbGluZyDigJQgdGhhdCBtZXNzYWdlIGtlZXBzIGZhaWxpbmcgYW5kIHdvdWxkIGxvb3AgRExR4oaSbWFpbuKGkkRMUS5cbiAgICAgIGlmIChvbGRlc3RBZ2VTZWMgPiBNQVhfRExRX1JFRFJJVkVfQUdFX1NFQykge1xuICAgICAgICBsb2cud2FybignRExRIG9sZGVzdCBtZXNzYWdlIGV4Y2VlZHMgcG9pc29uLWFnZSBjZWlsaW5nIOKAlCBza2lwcGluZyByZWRyaXZlIChuZWVkcyBtYW51YWwgYXR0ZW50aW9uKScsIHtcbiAgICAgICAgICBkbHFBcm46IHEuZGxxQXJuLCBkcm9wcGVkOiBkZXB0aCwgb2xkZXN0QWdlU2VjLCBjZWlsaW5nU2VjOiBNQVhfRExRX1JFRFJJVkVfQUdFX1NFQyxcbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGlmICghKGF3YWl0IGhhc0FjdGl2ZU1vdmVUYXNrKHEucmVnaW9uLCBxLmRscUFybikpKSB7XG4gICAgICAgICAgICBsYXN0UmVkcml2ZUF0ID0gbm93O1xuICAgICAgICAgICAgYXdhaXQgc3RhcnRNb3ZlVGFzayhxLnJlZ2lvbiwgcS5kbHFBcm4sIHEubWFpbkFybik7XG4gICAgICAgICAgICBsb2cuaW5mbygnU3RhcnRlZCBzZWxmLWhlYWxpbmcgRExR4oaSbWFpbiByZWRyaXZlJywgeyBkbHFBcm46IHEuZGxxQXJuLCBkcm9wcGVkOiBkZXB0aCwgb2xkZXN0QWdlU2VjIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgbG9nLndhcm4oJ0RMUSByZWRyaXZlIGZhaWxlZCAoaWdub3JlZCknLCB7IGVycm9yOiAoZXJyIGFzIHsgbmFtZT86IHN0cmluZyB9KT8ubmFtZSA/PyBTdHJpbmcoZXJyKSB9KTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgbG9nLndhcm4oJ2hlYWx0aC9yZWRyaXZlIHN0ZXAgZmFpbGVkIChpZ25vcmVkKScsIHsgZXJyb3I6IFN0cmluZyhlcnIpIH0pO1xuICB9XG59XG5cbi8qKiBDb3VudCBhIGZvcndhcmRlZCBiYXRjaCB0b3dhcmQgZWFjaCByZXNvbHZlZCBvcmcncyBkZWxpdmVyeS1oZWFsdGggcmVwb3J0LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlY29yZEZvcndhcmRlZChldmVudHM6IFJlYWRvbmx5QXJyYXk8eyBvcmdJZDogc3RyaW5nIHwgbnVsbDsgY29tcGxldGVkQXQ/OiBzdHJpbmc7IHN0YXJ0ZWRBdD86IHN0cmluZyB9Pik6IHZvaWQge1xuICBmb3IgKGNvbnN0IGUgb2YgZXZlbnRzKSB7XG4gICAgY29uc3Qga2V5ID0gZS5vcmdJZCA/PyAnJztcbiAgICBmb3J3YXJkZWRCeU9yZy5zZXQoa2V5LCAoZm9yd2FyZGVkQnlPcmcuZ2V0KGtleSkgPz8gMCkgKyAxKTtcbiAgICBjb25zdCBhdCA9IGUuY29tcGxldGVkQXQgfHwgZS5zdGFydGVkQXQ7XG4gICAgaWYgKGF0KSB7XG4gICAgICBjb25zdCBwcmV2ID0gbGFzdEV2ZW50QXRCeU9yZy5nZXQoa2V5KTtcbiAgICAgIGlmICghcHJldiB8fCBhdCA+IHByZXYpIGxhc3RFdmVudEF0QnlPcmcuc2V0KGtleSwgYXQpO1xuICAgIH1cbiAgfVxufVxuXG4vKiogQGludGVybmFsIFRlc3Qtb25seTogZm9yZ2V0IGhlYWx0aCBjb3VudGVycyBhbmQgdGhyb3R0bGVzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIF9yZXNldEhlYWx0aEZvclRlc3RzKCk6IHZvaWQge1xuICBmb3J3YXJkZWRCeU9yZy5jbGVhcigpO1xuICBsYXN0RXZlbnRBdEJ5T3JnLmNsZWFyKCk7XG4gIGxhc3RIZWFsdGhBdCA9IDA7XG4gIGxhc3RSZWRyaXZlQXQgPSAwO1xufVxuIl19
|
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
import type { SQSEvent } from 'aws-lambda';
|
|
2
|
+
/**
|
|
3
|
+
* Pipeline event ingestion Lambda handler.
|
|
4
|
+
*
|
|
5
|
+
* Receives CodePipeline/CodeBuild events from SQS (sourced by EventBridge),
|
|
6
|
+
* parses them into a normalized format, and POSTs them to the reporting service
|
|
7
|
+
* via PLATFORM_BASE_URL.
|
|
8
|
+
*
|
|
9
|
+
* Authentication (service-account keys):
|
|
10
|
+
* The Lambda holds an opaque SERVICE-ACCOUNT KEY (`pb_sa_…`), never a JWT, and
|
|
11
|
+
* trades it at platform's `/auth/token/exchange` for a 5-minute token per batch.
|
|
12
|
+
* It verifies NOTHING locally: an opaque key carries no claims to check, and the
|
|
13
|
+
* exchange is the authority (a revoked key stops working within one token
|
|
14
|
+
* lifetime, which is the entire point of the shape). The key is minted by
|
|
15
|
+
* `pipeline-manager infra store-token` and rotated in place by the token-renew
|
|
16
|
+
* Lambda, so the secret's value changes underneath a warm container — which is
|
|
17
|
+
* why an auth failure re-reads the SECRET, not just the token.
|
|
18
|
+
*
|
|
19
|
+
* Environment variables:
|
|
20
|
+
* - PLATFORM_BASE_URL — Base URL of the platform
|
|
21
|
+
* - PLATFORM_ACCESS_KEY — the `pb_sa_…` key set directly (no Secrets Manager call), or
|
|
22
|
+
* - PLATFORM_SECRET_NAME — Secrets Manager secret containing { password: <pb_sa_ key> }
|
|
23
|
+
* - EVENT_DLQ_ARN — (optional) override for the dead-letter queue ARN used by the
|
|
24
|
+
* self-healing redrive; defaults to `<main-queue-arn>-dlq` derived from the SQS
|
|
25
|
+
* trigger's eventSourceARN.
|
|
26
|
+
*/
|
|
2
27
|
export declare const handler: (event: SQSEvent) => Promise<void>;
|