@pipeline-builder/api-core 5.2.2 → 5.2.4

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.
Files changed (91) hide show
  1. package/lib/constants/ai-providers.d.ts +3 -22
  2. package/lib/constants/ai-providers.js +6 -56
  3. package/lib/errors/app-errors.d.ts +14 -0
  4. package/lib/errors/app-errors.js +21 -1
  5. package/lib/helpers/index.d.ts +1 -0
  6. package/lib/helpers/index.js +2 -1
  7. package/lib/helpers/sse-helpers.d.ts +33 -3
  8. package/lib/helpers/sse-helpers.js +91 -19
  9. package/lib/helpers/support-calendar.d.ts +69 -0
  10. package/lib/helpers/support-calendar.js +179 -0
  11. package/lib/middleware/auth.js +6 -3
  12. package/lib/middleware/deferred-quota-check.d.ts +5 -0
  13. package/lib/middleware/deferred-quota-check.js +33 -0
  14. package/lib/middleware/ecosystem-guard.d.ts +3 -3
  15. package/lib/middleware/ecosystem-guard.js +5 -5
  16. package/lib/middleware/index.d.ts +5 -2
  17. package/lib/middleware/index.js +6 -3
  18. package/lib/middleware/on-behalf.d.ts +37 -0
  19. package/lib/middleware/on-behalf.js +143 -0
  20. package/lib/middleware/permission-gates.d.ts +11 -0
  21. package/lib/middleware/permission-gates.js +7 -1
  22. package/lib/middleware/quota-exempt-routes.d.ts +36 -0
  23. package/lib/middleware/quota-exempt-routes.js +47 -0
  24. package/lib/middleware/route-table.d.ts +104 -0
  25. package/lib/middleware/route-table.js +72 -2
  26. package/lib/middleware/service-tokens.d.ts +8 -0
  27. package/lib/middleware/service-tokens.js +5 -3
  28. package/lib/middleware/step-up.js +11 -1
  29. package/lib/services/ai-call-weight.d.ts +24 -0
  30. package/lib/services/ai-call-weight.js +77 -0
  31. package/lib/services/alert-delivery.d.ts +99 -0
  32. package/lib/services/alert-delivery.js +132 -0
  33. package/lib/services/audit-context.d.ts +105 -0
  34. package/lib/services/audit-context.js +134 -0
  35. package/lib/services/index.d.ts +10 -2
  36. package/lib/services/index.js +10 -2
  37. package/lib/services/org-ai-client.d.ts +61 -0
  38. package/lib/services/org-ai-client.js +112 -0
  39. package/lib/services/pagerduty-inbound.d.ts +101 -0
  40. package/lib/services/pagerduty-inbound.js +195 -0
  41. package/lib/services/pagerduty.d.ts +109 -0
  42. package/lib/services/pagerduty.js +186 -0
  43. package/lib/services/quota-increment-batcher.d.ts +81 -0
  44. package/lib/services/quota-increment-batcher.js +166 -0
  45. package/lib/services/quota.d.ts +158 -14
  46. package/lib/services/quota.js +252 -22
  47. package/lib/services/remote-audit-client.d.ts +44 -1
  48. package/lib/services/remote-audit-client.js +106 -20
  49. package/lib/services/version-vuln-policy.d.ts +174 -0
  50. package/lib/services/version-vuln-policy.js +242 -0
  51. package/lib/types/ai-catalog.d.ts +63 -0
  52. package/lib/types/ai-catalog.js +82 -0
  53. package/lib/types/ask-proposals.js +6 -6
  54. package/lib/types/build-catalog.d.ts +154 -0
  55. package/lib/types/build-catalog.js +158 -0
  56. package/lib/types/common.d.ts +37 -3
  57. package/lib/types/common.js +20 -2
  58. package/lib/types/ecosystem-notifications.d.ts +5 -5
  59. package/lib/types/ecosystem-notifications.js +9 -3
  60. package/lib/types/error-codes.d.ts +13 -6
  61. package/lib/types/error-codes.js +18 -8
  62. package/lib/types/feature-flags.d.ts +71 -1
  63. package/lib/types/feature-flags.js +131 -11
  64. package/lib/types/index.d.ts +6 -4
  65. package/lib/types/index.js +12 -5
  66. package/lib/types/metadata-keys.d.ts +6 -0
  67. package/lib/types/metadata-keys.js +4 -1
  68. package/lib/types/permissions.d.ts +351 -3
  69. package/lib/types/permissions.js +610 -21
  70. package/lib/types/pipeline.d.ts +39 -0
  71. package/lib/types/pipeline.js +57 -1
  72. package/lib/types/plugin-health.d.ts +0 -2
  73. package/lib/types/plugin-health.js +1 -11
  74. package/lib/types/plugin-scan.d.ts +192 -42
  75. package/lib/types/plugin-scan.js +159 -43
  76. package/lib/types/quota-catalog.d.ts +247 -0
  77. package/lib/types/quota-catalog.js +122 -0
  78. package/lib/types/quota-tiers.d.ts +150 -0
  79. package/lib/types/quota-tiers.js +167 -2
  80. package/lib/types/remote-audit-actions.d.ts +1 -1
  81. package/lib/types/remote-audit-actions.js +217 -4
  82. package/lib/types/wire-vocabulary.js +8 -1
  83. package/lib/utils/index.d.ts +1 -1
  84. package/lib/utils/index.js +2 -2
  85. package/lib/utils/response.d.ts +35 -2
  86. package/lib/utils/response.js +42 -3
  87. package/lib/validation/ai-schemas.d.ts +27 -0
  88. package/lib/validation/ai-schemas.js +31 -5
  89. package/lib/validation/plugin-schemas.d.ts +15 -0
  90. package/lib/validation/plugin-schemas.js +28 -1
  91. package/package.json +31 -2
@@ -0,0 +1,77 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { OPENAI_COMPATIBLE_PROVIDER_ID } from '../constants/ai-providers.js';
4
+ import { createLogger } from '../utils/logger.js';
5
+ const logger = createLogger('ai-call-weight');
6
+ /** Largest weight one model may carry — one call can never reserve more than this many units. */
7
+ export const MAX_AI_CALL_WEIGHT = 100;
8
+ /**
9
+ * Built-in weights, under whatever `AI_MODEL_WEIGHTS` sets. The self-hosted
10
+ * OpenAI-compatible provider (the in-cluster `ask-model`) runs on hardware the
11
+ * operator already pays for, so a call on it spends NO `aiCalls` unit by default;
12
+ * set `{"openai-compatible": 1}` to meter it like any other model.
13
+ */
14
+ const BUILT_IN_WEIGHTS = { [OPENAI_COMPATIBLE_PROVIDER_ID]: 0 };
15
+ /**
16
+ * Parse `AI_MODEL_WEIGHTS` — a JSON object of `key → integer weight` — into
17
+ * lower-cased entries. A malformed value is IGNORED (logged), never fatal: the
18
+ * weights are an optional pricing lever and every call still meters at 1.
19
+ */
20
+ export function parseAiModelWeights(raw) {
21
+ const out = { ...BUILT_IN_WEIGHTS };
22
+ if (!raw || raw.trim() === '')
23
+ return out;
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(raw);
27
+ }
28
+ catch {
29
+ logger.warn('AI_MODEL_WEIGHTS is not valid JSON; every model weighs 1');
30
+ return out;
31
+ }
32
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
33
+ logger.warn('AI_MODEL_WEIGHTS must be a JSON object; every model weighs 1');
34
+ return out;
35
+ }
36
+ for (const [key, value] of Object.entries(parsed)) {
37
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > MAX_AI_CALL_WEIGHT) {
38
+ logger.warn('AI_MODEL_WEIGHTS entry ignored (weight must be an integer 0..100)', { key });
39
+ continue;
40
+ }
41
+ out[key.trim().toLowerCase()] = value;
42
+ }
43
+ return out;
44
+ }
45
+ let cached;
46
+ function currentWeights() {
47
+ const raw = process.env.AI_MODEL_WEIGHTS;
48
+ if (!cached || cached.raw !== raw)
49
+ cached = { raw, weights: parseAiModelWeights(raw) };
50
+ return cached.weights;
51
+ }
52
+ /**
53
+ * How many `aiCalls` units one call on `{ provider, modelId }` reserves.
54
+ *
55
+ * MODEL-CLASS WEIGHTING is OFF by default: with `AI_MODEL_WEIGHTS` unset every
56
+ * model weighs 1 — except the self-hosted provider, which weighs 0 (see
57
+ * {@link BUILT_IN_WEIGHTS}). Matching, first hit wins:
58
+ * 1. a key equal to the PROVIDER id (`"openai-compatible": 0`, `"bedrock": 2`);
59
+ * 2. the LONGEST key contained in the model id (`"opus": 3` matches
60
+ * `claude-opus-4-1`; `"sonnet": 1`, `"haiku": 1`).
61
+ * No match → 1. A weight of 0 means the call reserves nothing (it is still
62
+ * recorded in the usage ledger with its tokens).
63
+ */
64
+ export function aiCallWeight(selection) {
65
+ const weights = currentWeights();
66
+ const provider = selection.provider.toLowerCase();
67
+ if (provider in weights)
68
+ return weights[provider];
69
+ const model = selection.modelId.toLowerCase();
70
+ let best;
71
+ for (const [key, weight] of Object.entries(weights)) {
72
+ if (key.length > 0 && model.includes(key) && (!best || key.length > best.key.length))
73
+ best = { key, weight };
74
+ }
75
+ return best ? best.weight : 1;
76
+ }
77
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWktY2FsbC13ZWlnaHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvYWktY2FsbC13ZWlnaHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUM3RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbEQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGdCQUFnQixDQUFDLENBQUM7QUFFOUMsaUdBQWlHO0FBQ2pHLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLEdBQUcsQ0FBQztBQUV0Qzs7Ozs7R0FLRztBQUNILE1BQU0sZ0JBQWdCLEdBQXFDLEVBQUUsQ0FBQyw2QkFBNkIsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDO0FBRWxHOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CLENBQUMsR0FBdUI7SUFDekQsTUFBTSxHQUFHLEdBQTJCLEVBQUUsR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO0lBQzVELElBQUksQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUU7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUMxQyxJQUFJLE1BQWUsQ0FBQztJQUNwQixJQUFJLENBQUM7UUFDSCxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsTUFBTSxDQUFDLElBQUksQ0FBQywwREFBMEQsQ0FBQyxDQUFDO1FBQ3hFLE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUNELElBQUksTUFBTSxLQUFLLElBQUksSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQzNFLE1BQU0sQ0FBQyxJQUFJLENBQUMsOERBQThELENBQUMsQ0FBQztRQUM1RSxPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7SUFDRCxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFpQyxDQUFDLEVBQUUsQ0FBQztRQUM3RSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsSUFBSSxLQUFLLEdBQUcsa0JBQWtCLEVBQUUsQ0FBQztZQUNyRyxNQUFNLENBQUMsSUFBSSxDQUFDLG1FQUFtRSxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztZQUMxRixTQUFTO1FBQ1gsQ0FBQztRQUNELEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDeEMsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVELElBQUksTUFBZ0YsQ0FBQztBQUVyRixTQUFTLGNBQWM7SUFDckIsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQztJQUN6QyxJQUFJLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQyxHQUFHLEtBQUssR0FBRztRQUFFLE1BQU0sR0FBRyxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsbUJBQW1CLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztJQUN2RixPQUFPLE1BQU0sQ0FBQyxPQUFPLENBQUM7QUFDeEIsQ0FBQztBQUVEOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxVQUFVLFlBQVksQ0FBQyxTQUFnRDtJQUMzRSxNQUFNLE9BQU8sR0FBRyxjQUFjLEVBQUUsQ0FBQztJQUNqQyxNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ2xELElBQUksUUFBUSxJQUFJLE9BQU87UUFBRSxPQUFPLE9BQU8sQ0FBQyxRQUFRLENBQUUsQ0FBQztJQUNuRCxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQzlDLElBQUksSUFBaUQsQ0FBQztJQUN0RCxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ3BELElBQUksR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxJQUFJLEdBQUcsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUM7WUFBRSxJQUFJLEdBQUcsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLENBQUM7SUFDL0csQ0FBQztJQUNELE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDaEMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBPUEVOQUlfQ09NUEFUSUJMRV9QUk9WSURFUl9JRCB9IGZyb20gJy4uL2NvbnN0YW50cy9haS1wcm92aWRlcnMuanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdhaS1jYWxsLXdlaWdodCcpO1xuXG4vKiogTGFyZ2VzdCB3ZWlnaHQgb25lIG1vZGVsIG1heSBjYXJyeSDigJQgb25lIGNhbGwgY2FuIG5ldmVyIHJlc2VydmUgbW9yZSB0aGFuIHRoaXMgbWFueSB1bml0cy4gKi9cbmV4cG9ydCBjb25zdCBNQVhfQUlfQ0FMTF9XRUlHSFQgPSAxMDA7XG5cbi8qKlxuICogQnVpbHQtaW4gd2VpZ2h0cywgdW5kZXIgd2hhdGV2ZXIgYEFJX01PREVMX1dFSUdIVFNgIHNldHMuIFRoZSBzZWxmLWhvc3RlZFxuICogT3BlbkFJLWNvbXBhdGlibGUgcHJvdmlkZXIgKHRoZSBpbi1jbHVzdGVyIGBhc2stbW9kZWxgKSBydW5zIG9uIGhhcmR3YXJlIHRoZVxuICogb3BlcmF0b3IgYWxyZWFkeSBwYXlzIGZvciwgc28gYSBjYWxsIG9uIGl0IHNwZW5kcyBOTyBgYWlDYWxsc2AgdW5pdCBieSBkZWZhdWx0O1xuICogc2V0IGB7XCJvcGVuYWktY29tcGF0aWJsZVwiOiAxfWAgdG8gbWV0ZXIgaXQgbGlrZSBhbnkgb3RoZXIgbW9kZWwuXG4gKi9cbmNvbnN0IEJVSUxUX0lOX1dFSUdIVFM6IFJlYWRvbmx5PFJlY29yZDxzdHJpbmcsIG51bWJlcj4+ID0geyBbT1BFTkFJX0NPTVBBVElCTEVfUFJPVklERVJfSURdOiAwIH07XG5cbi8qKlxuICogUGFyc2UgYEFJX01PREVMX1dFSUdIVFNgIOKAlCBhIEpTT04gb2JqZWN0IG9mIGBrZXkg4oaSIGludGVnZXIgd2VpZ2h0YCDigJQgaW50b1xuICogbG93ZXItY2FzZWQgZW50cmllcy4gQSBtYWxmb3JtZWQgdmFsdWUgaXMgSUdOT1JFRCAobG9nZ2VkKSwgbmV2ZXIgZmF0YWw6IHRoZVxuICogd2VpZ2h0cyBhcmUgYW4gb3B0aW9uYWwgcHJpY2luZyBsZXZlciBhbmQgZXZlcnkgY2FsbCBzdGlsbCBtZXRlcnMgYXQgMS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlQWlNb2RlbFdlaWdodHMocmF3OiBzdHJpbmcgfCB1bmRlZmluZWQpOiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+IHtcbiAgY29uc3Qgb3V0OiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+ID0geyAuLi5CVUlMVF9JTl9XRUlHSFRTIH07XG4gIGlmICghcmF3IHx8IHJhdy50cmltKCkgPT09ICcnKSByZXR1cm4gb3V0O1xuICBsZXQgcGFyc2VkOiB1bmtub3duO1xuICB0cnkge1xuICAgIHBhcnNlZCA9IEpTT04ucGFyc2UocmF3KTtcbiAgfSBjYXRjaCB7XG4gICAgbG9nZ2VyLndhcm4oJ0FJX01PREVMX1dFSUdIVFMgaXMgbm90IHZhbGlkIEpTT047IGV2ZXJ5IG1vZGVsIHdlaWdocyAxJyk7XG4gICAgcmV0dXJuIG91dDtcbiAgfVxuICBpZiAocGFyc2VkID09PSBudWxsIHx8IHR5cGVvZiBwYXJzZWQgIT09ICdvYmplY3QnIHx8IEFycmF5LmlzQXJyYXkocGFyc2VkKSkge1xuICAgIGxvZ2dlci53YXJuKCdBSV9NT0RFTF9XRUlHSFRTIG11c3QgYmUgYSBKU09OIG9iamVjdDsgZXZlcnkgbW9kZWwgd2VpZ2hzIDEnKTtcbiAgICByZXR1cm4gb3V0O1xuICB9XG4gIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKHBhcnNlZCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikpIHtcbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnbnVtYmVyJyB8fCAhTnVtYmVyLmlzSW50ZWdlcih2YWx1ZSkgfHwgdmFsdWUgPCAwIHx8IHZhbHVlID4gTUFYX0FJX0NBTExfV0VJR0hUKSB7XG4gICAgICBsb2dnZXIud2FybignQUlfTU9ERUxfV0VJR0hUUyBlbnRyeSBpZ25vcmVkICh3ZWlnaHQgbXVzdCBiZSBhbiBpbnRlZ2VyIDAuLjEwMCknLCB7IGtleSB9KTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICBvdXRba2V5LnRyaW0oKS50b0xvd2VyQ2FzZSgpXSA9IHZhbHVlO1xuICB9XG4gIHJldHVybiBvdXQ7XG59XG5cbmxldCBjYWNoZWQ6IHsgcmF3OiBzdHJpbmcgfCB1bmRlZmluZWQ7IHdlaWdodHM6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gfSB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gY3VycmVudFdlaWdodHMoKTogUmVjb3JkPHN0cmluZywgbnVtYmVyPiB7XG4gIGNvbnN0IHJhdyA9IHByb2Nlc3MuZW52LkFJX01PREVMX1dFSUdIVFM7XG4gIGlmICghY2FjaGVkIHx8IGNhY2hlZC5yYXcgIT09IHJhdykgY2FjaGVkID0geyByYXcsIHdlaWdodHM6IHBhcnNlQWlNb2RlbFdlaWdodHMocmF3KSB9O1xuICByZXR1cm4gY2FjaGVkLndlaWdodHM7XG59XG5cbi8qKlxuICogSG93IG1hbnkgYGFpQ2FsbHNgIHVuaXRzIG9uZSBjYWxsIG9uIGB7IHByb3ZpZGVyLCBtb2RlbElkIH1gIHJlc2VydmVzLlxuICpcbiAqIE1PREVMLUNMQVNTIFdFSUdIVElORyBpcyBPRkYgYnkgZGVmYXVsdDogd2l0aCBgQUlfTU9ERUxfV0VJR0hUU2AgdW5zZXQgZXZlcnlcbiAqIG1vZGVsIHdlaWdocyAxIOKAlCBleGNlcHQgdGhlIHNlbGYtaG9zdGVkIHByb3ZpZGVyLCB3aGljaCB3ZWlnaHMgMCAoc2VlXG4gKiB7QGxpbmsgQlVJTFRfSU5fV0VJR0hUU30pLiBNYXRjaGluZywgZmlyc3QgaGl0IHdpbnM6XG4gKiAgMS4gYSBrZXkgZXF1YWwgdG8gdGhlIFBST1ZJREVSIGlkIChgXCJvcGVuYWktY29tcGF0aWJsZVwiOiAwYCwgYFwiYmVkcm9ja1wiOiAyYCk7XG4gKiAgMi4gdGhlIExPTkdFU1Qga2V5IGNvbnRhaW5lZCBpbiB0aGUgbW9kZWwgaWQgKGBcIm9wdXNcIjogM2AgbWF0Y2hlc1xuICogICAgIGBjbGF1ZGUtb3B1cy00LTFgOyBgXCJzb25uZXRcIjogMWAsIGBcImhhaWt1XCI6IDFgKS5cbiAqIE5vIG1hdGNoIOKGkiAxLiBBIHdlaWdodCBvZiAwIG1lYW5zIHRoZSBjYWxsIHJlc2VydmVzIG5vdGhpbmcgKGl0IGlzIHN0aWxsXG4gKiByZWNvcmRlZCBpbiB0aGUgdXNhZ2UgbGVkZ2VyIHdpdGggaXRzIHRva2VucykuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhaUNhbGxXZWlnaHQoc2VsZWN0aW9uOiB7IHByb3ZpZGVyOiBzdHJpbmc7IG1vZGVsSWQ6IHN0cmluZyB9KTogbnVtYmVyIHtcbiAgY29uc3Qgd2VpZ2h0cyA9IGN1cnJlbnRXZWlnaHRzKCk7XG4gIGNvbnN0IHByb3ZpZGVyID0gc2VsZWN0aW9uLnByb3ZpZGVyLnRvTG93ZXJDYXNlKCk7XG4gIGlmIChwcm92aWRlciBpbiB3ZWlnaHRzKSByZXR1cm4gd2VpZ2h0c1twcm92aWRlcl0hO1xuICBjb25zdCBtb2RlbCA9IHNlbGVjdGlvbi5tb2RlbElkLnRvTG93ZXJDYXNlKCk7XG4gIGxldCBiZXN0OiB7IGtleTogc3RyaW5nOyB3ZWlnaHQ6IG51bWJlciB9IHwgdW5kZWZpbmVkO1xuICBmb3IgKGNvbnN0IFtrZXksIHdlaWdodF0gb2YgT2JqZWN0LmVudHJpZXMod2VpZ2h0cykpIHtcbiAgICBpZiAoa2V5Lmxlbmd0aCA+IDAgJiYgbW9kZWwuaW5jbHVkZXMoa2V5KSAmJiAoIWJlc3QgfHwga2V5Lmxlbmd0aCA+IGJlc3Qua2V5Lmxlbmd0aCkpIGJlc3QgPSB7IGtleSwgd2VpZ2h0IH07XG4gIH1cbiAgcmV0dXJuIGJlc3QgPyBiZXN0LndlaWdodCA6IDE7XG59XG4iXX0=
@@ -0,0 +1,99 @@
1
+ export interface SlackAlertsConfig {
2
+ /**
3
+ * Whether ops-team Slack delivery is DECLARED on.
4
+ *
5
+ * Defaults FALSE, matching `ONCALL_ENABLED`: both destinations are opt-in, so
6
+ * a deployment states what it wants rather than inheriting a channel it may
7
+ * not have. Note what this flag does NOT do — it cannot remove the Slack
8
+ * receivers, because `alertmanager.yml` is asserted byte-identical across
9
+ * targets and Alertmanager has no templating. A deployment with the flag off
10
+ * and real URLs in its Secret still delivers to Slack.
11
+ */
12
+ enabled: boolean;
13
+ /**
14
+ * Whether Slack delivery CAN happen — i.e. the webhook URLs are non-empty.
15
+ *
16
+ * Derived at deploy time (`pb_check_alert_delivery` writes it) because the
17
+ * URLs live in Alertmanager's Secret and never enter this process: a Slack
18
+ * webhook URL is a bearer credential, read per notification via
19
+ * `api_url_file`. Platform therefore cannot inspect them and must be told.
20
+ *
21
+ * SEPARATE FROM `enabled` for the same reason the pager splits the two, and
22
+ * the distinction is what keeps `AlertDeliveryDisabled` honest. Keying that
23
+ * alert on the DECLARATION would page every deployment that left the flag at
24
+ * its default while Slack was quietly working — a false alarm on a healthy
25
+ * system, which is the fastest way to teach people to ignore an alert.
26
+ */
27
+ configured: boolean;
28
+ }
29
+ export declare function slackAlertsConfig(): SlackAlertsConfig;
30
+ /** Both destinations, resolved together — what `reportAlertDeliveryPosture` reads. */
31
+ export interface AlertDeliveryConfig {
32
+ slack: SlackAlertsConfig;
33
+ pager: {
34
+ enabled: boolean;
35
+ configured: boolean;
36
+ };
37
+ }
38
+ /** Whether ANY destination could actually deliver — reality, not declaration. */
39
+ export declare function canDeliverAnywhere(cfg: AlertDeliveryConfig): boolean;
40
+ export declare function alertDeliveryConfig(): AlertDeliveryConfig;
41
+ /**
42
+ * Publish the delivery posture as gauges, and shout if it is incoherent.
43
+ *
44
+ * Called once at boot. Gauges rather than counters because these are STATES of
45
+ * the deployment — an operator wants "will anyone hear an alert right now", not
46
+ * how many times a process started and wondered.
47
+ *
48
+ * The value of having this at all: before it, answering "does this deployment
49
+ * page anyone?" meant reading `.env`, two Secrets and four copies of
50
+ * `alertmanager.yml`. A deployment can now alert on its own inability to alert.
51
+ */
52
+ export declare function reportAlertDeliveryPosture(cfg?: AlertDeliveryConfig): void;
53
+ /**
54
+ * The dead-man switch: a heartbeat whose ABSENCE is the alert.
55
+ *
56
+ * Every other alert in this system depends on something inside it still working.
57
+ * This one inverts that — an external service expects a POST on a schedule and
58
+ * raises an incident when one does not arrive, so a platform that is wedged,
59
+ * crashed, partitioned or never started is reported BY ITS SILENCE. Nothing
60
+ * inside a broken deployment has to notice its own brokenness.
61
+ *
62
+ * WHY IT IS BUILT HERE RATHER THAN BOUGHT, when the rest of on-call deliberately
63
+ * is not: PagerDuty's heartbeat monitoring is an Event Orchestration pattern and
64
+ * documented as AIOps-powered, so it may sit behind a tier and is not part of
65
+ * Events v2. More importantly a dead-man switch is a CLOCK, not a telecom
66
+ * problem — the thing that made the last mile worth buying (carrier retries,
67
+ * delivery receipts, a 3am failure budget) simply does not apply to sending one
68
+ * request on an interval. `ONCALL_DEADMAN_URL` therefore points wherever the
69
+ * operator likes: PagerDuty's orchestration endpoint, a purpose-built dead-man
70
+ * service, or an uptime monitor.
71
+ *
72
+ * TWO PROPERTIES WORTH STATING, because they are the reason this is safe:
73
+ *
74
+ * - It is NOT leader-locked. A heartbeat is per-process on purpose: with three
75
+ * replicas, the external service hears from whichever are alive, and silence
76
+ * means ALL of them stopped. Leader-locking it would make the switch report
77
+ * the leader's health rather than the deployment's — and would make a Redis
78
+ * outage look identical to a total outage.
79
+ * - It fails SILENTLY on its own errors, deliberately. A heartbeat that logged
80
+ * an error every interval during a network blip would be its own noise
81
+ * source, and the consequence of a missed beat is already handled: the
82
+ * external service alerts. The only thing it must never do is report success
83
+ * it did not achieve, which is why nothing here swallows a non-2xx.
84
+ */
85
+ export interface DeadmanConfig {
86
+ url: string;
87
+ intervalMs: number;
88
+ }
89
+ export declare function deadmanConfig(): DeadmanConfig;
90
+ /**
91
+ * Start beating. No-op when `ONCALL_DEADMAN_URL` is unset, which is the default:
92
+ * an unconfigured dead-man switch must not beat against nothing, and must not
93
+ * pretend it is covering the deployment.
94
+ *
95
+ * Returns true when a timer was started, so a caller can log the posture.
96
+ */
97
+ export declare function startDeadmanHeartbeat(cfg?: DeadmanConfig): boolean;
98
+ /** Stop beating — called from the unified shutdown path, not a signal handler. */
99
+ export declare function stopDeadmanHeartbeat(): void;
@@ -0,0 +1,132 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Where a platform-wide alert is meant to GO, as declared configuration.
5
+ *
6
+ * Two destinations, with different jobs. The ops-team Slack channels are the
7
+ * baseline: everything critical or warning lands there and that is where the
8
+ * thread, the context and the handover live. The pager is additive and narrow —
9
+ * only `severity: critical` + `component: support`, because that is the one
10
+ * alert backing a contractual out-of-hours promise.
11
+ *
12
+ * WHAT THIS MODULE CAN AND CANNOT DO, because the limit is not obvious.
13
+ * `config/alertmanager/alertmanager.yml` is in the deploy contract's
14
+ * REQUIRED_IDENTICAL set and asserted byte-identical across targets, and
15
+ * Alertmanager has no templating. So NOTHING here removes a receiver or a route
16
+ * — the routes are always present. What these flags do is:
17
+ *
18
+ * - declare INTENT, so "we do not use Slack" is a stated decision rather than
19
+ * something inferred from two empty URLs that look like an oversight;
20
+ * - gate the deploy-time check (`pb_check_alert_delivery`), which is what turns
21
+ * a half-configured channel into a refused deploy instead of a 3am surprise;
22
+ * - drive the posture gauges below, so a dashboard can answer "is anyone
23
+ * actually going to hear this" without reading four YAML files.
24
+ *
25
+ * The URLS THEMSELVES ARE NOT HERE, and that is deliberate. A Slack webhook URL
26
+ * is a bearer credential: anyone holding it can post to the channel. Alertmanager
27
+ * reads them from a mounted Secret via `api_url_file`, per notification, so they
28
+ * never enter a ConfigMap and never enter this process. Platform knows WHETHER
29
+ * Slack delivery is declared on; it does not know, and does not need, where to.
30
+ * The pager's routing key is different — that one IS consumed by this process, so
31
+ * it lives in `pagerDutyConfig()`.
32
+ */
33
+ import { pagerDutyConfig } from './pagerduty.js';
34
+ import { envBool, envInt, envStr } from '../utils/env.js';
35
+ import { createLogger } from '../utils/logger.js';
36
+ import { emitCounter, emitGauge } from '../utils/metric-emitter.js';
37
+ import { safeFetch } from '../utils/ssrf.js';
38
+ const logger = createLogger('alert-delivery');
39
+ export function slackAlertsConfig() {
40
+ return {
41
+ enabled: envBool('SLACK_ALERTS_ENABLED', false),
42
+ configured: envBool('SLACK_ALERTS_CONFIGURED', false),
43
+ };
44
+ }
45
+ /** Whether ANY destination could actually deliver — reality, not declaration. */
46
+ export function canDeliverAnywhere(cfg) {
47
+ return cfg.slack.configured || (cfg.pager.enabled && cfg.pager.configured);
48
+ }
49
+ export function alertDeliveryConfig() {
50
+ const pd = pagerDutyConfig();
51
+ return {
52
+ slack: slackAlertsConfig(),
53
+ // `configured` separately from `enabled`: enabled-without-a-key is the
54
+ // half-configured state worth seeing, and it is invisible if the two are
55
+ // collapsed into one boolean.
56
+ pager: { enabled: pd.enabled, configured: pd.routingKey.length > 0 },
57
+ };
58
+ }
59
+ /**
60
+ * Publish the delivery posture as gauges, and shout if it is incoherent.
61
+ *
62
+ * Called once at boot. Gauges rather than counters because these are STATES of
63
+ * the deployment — an operator wants "will anyone hear an alert right now", not
64
+ * how many times a process started and wondered.
65
+ *
66
+ * The value of having this at all: before it, answering "does this deployment
67
+ * page anyone?" meant reading `.env`, two Secrets and four copies of
68
+ * `alertmanager.yml`. A deployment can now alert on its own inability to alert.
69
+ */
70
+ export function reportAlertDeliveryPosture(cfg = alertDeliveryConfig()) {
71
+ emitGauge('alert_delivery_enabled', { channel: 'slack' }, cfg.slack.enabled ? 1 : 0);
72
+ emitGauge('alert_delivery_enabled', { channel: 'pager' }, cfg.pager.enabled ? 1 : 0);
73
+ emitGauge('alert_delivery_configured', { channel: 'slack' }, cfg.slack.configured ? 1 : 0);
74
+ emitGauge('alert_delivery_configured', { channel: 'pager' }, cfg.pager.configured ? 1 : 0);
75
+ // Enabled with no credential: somebody asked for paging and is not getting it.
76
+ // The deploy check refuses this combination, so reaching here means it was
77
+ // changed after provisioning — which is exactly when nobody is looking.
78
+ if (cfg.pager.enabled && !cfg.pager.configured) {
79
+ logger.error('ONCALL_ENABLED is true but no PAGERDUTY_ROUTING_KEY is set — support criticals will not page');
80
+ }
81
+ // Nothing CAN deliver. Keyed on reachability rather than on the two flags,
82
+ // because the flags are declarations: a deployment that left both at their
83
+ // (false) defaults while real Slack URLs sit in its Secret is delivering
84
+ // fine, and warning it would be a lie.
85
+ if (!canDeliverAnywhere(cfg)) {
86
+ logger.warn('No platform alert destination is enabled — alerts will reach Alertmanager and go no further', {
87
+ hint: 'SLACK_ALERTS_ENABLED / ONCALL_ENABLED',
88
+ });
89
+ }
90
+ }
91
+ export function deadmanConfig() {
92
+ return {
93
+ url: envStr('ONCALL_DEADMAN_URL', ''),
94
+ intervalMs: envInt('ONCALL_DEADMAN_INTERVAL_MS', 60_000, { min: 1000 }),
95
+ };
96
+ }
97
+ let deadmanTimer;
98
+ /**
99
+ * Start beating. No-op when `ONCALL_DEADMAN_URL` is unset, which is the default:
100
+ * an unconfigured dead-man switch must not beat against nothing, and must not
101
+ * pretend it is covering the deployment.
102
+ *
103
+ * Returns true when a timer was started, so a caller can log the posture.
104
+ */
105
+ export function startDeadmanHeartbeat(cfg = deadmanConfig()) {
106
+ if (deadmanTimer || !cfg.url)
107
+ return false;
108
+ const beat = async () => {
109
+ try {
110
+ const resp = await safeFetch(cfg.url, { method: 'POST', timeoutMs: 5000, body: '{}', headers: { 'Content-Type': 'application/json' } });
111
+ emitCounter('oncall_deadman_total', { outcome: resp.ok && !resp.redirected ? 'sent' : 'failed' });
112
+ }
113
+ catch {
114
+ // Counted, never logged per-beat — see the module note on noise.
115
+ emitCounter('oncall_deadman_total', { outcome: 'failed' });
116
+ }
117
+ };
118
+ // `unref` so a pending beat cannot hold the process open during shutdown: the
119
+ // switch exists to report that the process STOPPED, and delaying that is
120
+ // precisely backwards.
121
+ deadmanTimer = setInterval(() => void beat(), cfg.intervalMs);
122
+ deadmanTimer.unref();
123
+ void beat();
124
+ return true;
125
+ }
126
+ /** Stop beating — called from the unified shutdown path, not a signal handler. */
127
+ export function stopDeadmanHeartbeat() {
128
+ if (deadmanTimer)
129
+ clearInterval(deadmanTimer);
130
+ deadmanTimer = undefined;
131
+ }
132
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxlcnQtZGVsaXZlcnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvYWxlcnQtZGVsaXZlcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Qkc7QUFFSCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDakQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDMUQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDcEUsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRTdDLE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBK0I5QyxNQUFNLFVBQVUsaUJBQWlCO0lBQy9CLE9BQU87UUFDTCxPQUFPLEVBQUUsT0FBTyxDQUFDLHNCQUFzQixFQUFFLEtBQUssQ0FBQztRQUMvQyxVQUFVLEVBQUUsT0FBTyxDQUFDLHlCQUF5QixFQUFFLEtBQUssQ0FBQztLQUN0RCxDQUFDO0FBQ0osQ0FBQztBQVFELGlGQUFpRjtBQUNqRixNQUFNLFVBQVUsa0JBQWtCLENBQUMsR0FBd0I7SUFDekQsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDLFVBQVUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7QUFDN0UsQ0FBQztBQUVELE1BQU0sVUFBVSxtQkFBbUI7SUFDakMsTUFBTSxFQUFFLEdBQUcsZUFBZSxFQUFFLENBQUM7SUFDN0IsT0FBTztRQUNMLEtBQUssRUFBRSxpQkFBaUIsRUFB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LFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDcEcsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLGlFQUFpRTtZQUNqRSxXQUFXLENBQUMsc0JBQXNCLEVBQUUsRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUM3RCxDQUFDO0lBQ0gsQ0FBQyxDQUFDO0lBQ0YsOEVBQThFO0lBQzlFLHlFQUF5RTtJQUN6RSx1QkFBdUI7SUFDdkIsWUFBWSxHQUFHLFdBQVcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLElBQUksRUFBRSxFQUFFLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM5RCxZQUFZLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDckIsS0FBSyxJQUFJLEVBQUUsQ0FBQztJQUNaLE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztBQUVELGtGQUFrRjtBQUNsRixNQUFNLFVBQVUsb0JBQW9CO0lBQ2xDLElBQUksWUFBWTtRQUFFLGFBQWEsQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUM5QyxZQUFZLEdBQUcsU0FBUyxDQUFDO0FBQzNCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBXaGVyZSBhIHBsYXRmb3JtLXdpZGUgYWxlcnQgaXMgbWVhbnQgdG8gR08sIGFzIGRlY2xhcmVkIGNvbmZpZ3VyYXRpb24uXG4gKlxuICogVHdvIGRlc3RpbmF0aW9ucywgd2l0aCBkaWZmZXJlbnQgam9icy4gVGhlIG9wcy10ZWFtIFNsYWNrIGNoYW5uZWxzIGFyZSB0aGVcbiAqIGJhc2VsaW5lOiBldmVyeXRoaW5nIGNyaXRpY2FsIG9yIHdhcm5pbmcgbGFuZHMgdGhlcmUgYW5kIHRoYXQgaXMgd2hlcmUgdGhlXG4gKiB0aHJlYWQsIHRoZSBjb250ZXh0IGFuZCB0aGUgaGFuZG92ZXIgbGl2ZS4gVGhlIHBhZ2VyIGlzIGFkZGl0aXZlIGFuZCBuYXJyb3cg4oCUXG4gKiBvbmx5IGBzZXZlcml0eTogY3JpdGljYWxgICsgYGNvbXBvbmVudDogc3VwcG9ydGAsIGJlY2F1c2UgdGhhdCBpcyB0aGUgb25lXG4gKiBhbGVydCBiYWNraW5nIGEgY29udHJhY3R1YWwgb3V0LW9mLWhvdXJzIHByb21pc2UuXG4gKlxuICogV0hBVCBUSElTIE1PRFVMRSBDQU4gQU5EIENBTk5PVCBETywgYmVjYXVzZSB0aGUgbGltaXQgaXMgbm90IG9idmlvdXMuXG4gKiBgY29uZmlnL2FsZXJ0bWFuYWdlci9hbGVydG1hbmFnZXIueW1sYCBpcyBpbiB0aGUgZGVwbG95IGNvbnRyYWN0J3NcbiAqIFJFUVVJUkVEX0lERU5USUNBTCBzZXQgYW5kIGFzc2VydGVkIGJ5dGUtaWRlbnRpY2FsIGFjcm9zcyB0YXJnZXRzLCBhbmRcbiAqIEFsZXJ0bWFuYWdlciBoYXMgbm8gdGVtcGxhdGluZy4gU28gTk9USElORyBoZXJlIHJlbW92ZXMgYSByZWNlaXZlciBvciBhIHJvdXRlXG4gKiDigJQgdGhlIHJvdXRlcyBhcmUgYWx3YXlzIHByZXNlbnQuIFdoYXQgdGhlc2UgZmxhZ3MgZG8gaXM6XG4gKlxuICogIC0gZGVjbGFyZSBJTlRFTlQsIHNvIFwid2UgZG8gbm90IHVzZSBTbGFja1wiIGlzIGEgc3RhdGVkIGRlY2lzaW9uIHJhdGhlciB0aGFuXG4gKiAgICBzb21ldGhpbmcgaW5mZXJyZWQgZnJvbSB0d28gZW1wdHkgVVJMcyB0aGF0IGxvb2sgbGlrZSBhbiBvdmVyc2lnaHQ7XG4gKiAgLSBnYXRlIHRoZSBkZXBsb3ktdGltZSBjaGVjayAoYHBiX2NoZWNrX2FsZXJ0X2RlbGl2ZXJ5YCksIHdoaWNoIGlzIHdoYXQgdHVybnNcbiAqICAgIGEgaGFsZi1jb25maWd1cmVkIGNoYW5uZWwgaW50byBhIHJlZnVzZWQgZGVwbG95IGluc3RlYWQgb2YgYSAzYW0gc3VycHJpc2U7XG4gKiAgLSBkcml2ZSB0aGUgcG9zdHVyZSBnYXVnZXMgYmVsb3csIHNvIGEgZGFzaGJvYXJkIGNhbiBhbnN3ZXIgXCJpcyBhbnlvbmVcbiAqICAgIGFjdHVhbGx5IGdvaW5nIHRvIGhlYXIgdGhpc1wiIHdpdGhvdXQgcmVhZGluZyBmb3VyIFlBTUwgZmlsZXMuXG4gKlxuICogVGhlIFVSTFMgVEhFTVNFTFZFUyBBUkUgTk9UIEhFUkUsIGFuZCB0aGF0IGlzIGRlbGliZXJhdGUuIEEgU2xhY2sgd2ViaG9vayBVUkxcbiAqIGlzIGEgYmVhcmVyIGNyZWRlbnRpYWw6IGFueW9uZSBob2xkaW5nIGl0IGNhbiBwb3N0IHRvIHRoZSBjaGFubmVsLiBBbGVydG1hbmFnZXJcbiAqIHJlYWRzIHRoZW0gZnJvbSBhIG1vdW50ZWQgU2VjcmV0IHZpYSBgYXBpX3VybF9maWxlYCwgcGVyIG5vdGlmaWNhdGlvbiwgc28gdGhleVxuICogbmV2ZXIgZW50ZXIgYSBDb25maWdNYXAgYW5kIG5ldmVyIGVudGVyIHRoaXMgcHJvY2Vzcy4gUGxhdGZvcm0ga25vd3MgV0hFVEhFUlxuICogU2xhY2sgZGVsaXZlcnkgaXMgZGVjbGFyZWQgb247IGl0IGRvZXMgbm90IGtub3csIGFuZCBkb2VzIG5vdCBuZWVkLCB3aGVyZSB0by5cbiAqIFRoZSBwYWdlcidzIHJvdXRpbmcga2V5IGlzIGRpZmZlcmVudCDigJQgdGhhdCBvbmUgSVMgY29uc3VtZWQgYnkgdGhpcyBwcm9jZXNzLCBzb1xuICogaXQgbGl2ZXMgaW4gYHBhZ2VyRHV0eUNvbmZpZygpYC5cbiAqL1xuXG5pbXBvcnQgeyBwYWdlckR1dHlDb25maWcgfSBmcm9tICcuL3BhZ2VyZHV0eS5qcyc7XG5pbXBvcnQgeyBlbnZCb29sLCBlbnZJbnQsIGVudlN0ciB9IGZyb20gJy4uL3V0aWxzL2Vudi5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIsIGVtaXRHYXVnZSB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcbmltcG9ydCB7IHNhZmVGZXRjaCB9IGZyb20gJy4uL3V0aWxzL3NzcmYuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ2FsZXJ0LWRlbGl2ZXJ5Jyk7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU2xhY2tBbGVydHNDb25maWcge1xuICAvKipcbiAgICogV2hldGhlciBvcHMtdGVhbSBTbGFjayBkZWxpdmVyeSBpcyBERUNMQVJFRCBvbi5cbiAgICpcbiAgICogRGVmYXVsdHMgRkFMU0UsIG1hdGNoaW5nIGBPTkNBTExfRU5BQkxFRGA6IGJvdGggZGVzdGluYXRpb25zIGFyZSBvcHQtaW4sIHNvXG4gICAqIGEgZGVwbG95bWVudCBzdGF0ZXMgd2hhdCBpdCB3YW50cyByYXRoZXIgdGhhbiBpbmhlcml0aW5nIGEgY2hhbm5lbCBpdCBtYXlcbiAgICogbm90IGhhdmUuIE5vdGUgd2hhdCB0aGlzIGZsYWcgZG9lcyBOT1QgZG8g4oCUIGl0IGNhbm5vdCByZW1vdmUgdGhlIFNsYWNrXG4gICAqIHJlY2VpdmVycywgYmVjYXVzZSBgYWxlcnRtYW5hZ2VyLnltbGAgaXMgYXNzZXJ0ZWQgYnl0ZS1pZGVudGljYWwgYWNyb3NzXG4gICAqIHRhcmdldHMgYW5kIEFsZXJ0bWFuYWdlciBoYXMgbm8gdGVtcGxhdGluZy4gQSBkZXBsb3ltZW50IHdpdGggdGhlIGZsYWcgb2ZmXG4gICAqIGFuZCByZWFsIFVSTHMgaW4gaXRzIFNlY3JldCBzdGlsbCBkZWxpdmVycyB0byBTbGFjay5cbiAgICovXG4gIGVuYWJsZWQ6IGJvb2xlYW47XG4gIC8qKlxuICAgKiBXaGV0aGVyIFNsYWNrIGRlbGl2ZXJ5IENBTiBoYXBwZW4g4oCUIGkuZS4gdGhlIHdlYmhvb2sgVVJMcyBhcmUgbm9uLWVtcHR5LlxuICAgKlxuICAgKiBEZXJpdmVkIGF0IGRlcGxveSB0aW1lIChgcGJfY2hlY2tfYWxlcnRfZGVsaXZlcnlgIHdyaXRlcyBpdCkgYmVjYXVzZSB0aGVcbiAgICogVVJMcyBsaXZlIGluIEFsZXJ0bWFuYWdlcidzIFNlY3JldCBhbmQgbmV2ZXIgZW50ZXIgdGhpcyBwcm9jZXNzOiBhIFNsYWNrXG4gICAqIHdlYmhvb2sgVVJMIGlzIGEgYmVhcmVyIGNyZWRlbnRpYWwsIHJlYWQgcGVyIG5vdGlmaWNhdGlvbiB2aWFcbiAgICogYGFwaV91cmxfZmlsZWAuIFBsYXRmb3JtIHRoZXJlZm9yZSBjYW5ub3QgaW5zcGVjdCB0aGVtIGFuZCBtdXN0IGJlIHRvbGQuXG4gICAqXG4gICAqIFNFUEFSQVRFIEZST00gYGVuYWJsZWRgIGZvciB0aGUgc2FtZSByZWFzb24gdGhlIHBhZ2VyIHNwbGl0cyB0aGUgdHdvLCBhbmRcbiAgICogdGhlIGRpc3RpbmN0aW9uIGlzIHdoYXQga2VlcHMgYEFsZXJ0RGVsaXZlcnlEaXNhYmxlZGAgaG9uZXN0LiBLZXlpbmcgdGhhdFxuICAgKiBhbGVydCBvbiB0aGUgREVDTEFSQVRJT04gd291bGQgcGFnZSBldmVyeSBkZXBsb3ltZW50IHRoYXQgbGVmdCB0aGUgZmxhZyBhdFxuICAgKiBpdHMgZGVmYXVsdCB3aGlsZSBTbGFjayB3YXMgcXVpZXRseSB3b3JraW5nIOKAlCBhIGZhbHNlIGFsYXJtIG9uIGEgaGVhbHRoeVxuICAgKiBzeXN0ZW0sIHdoaWNoIGlzIHRoZSBmYXN0ZXN0IHdheSB0byB0ZWFjaCBwZW9wbGUgdG8gaWdub3JlIGFuIGFsZXJ0LlxuICAgKi9cbiAgY29uZmlndXJlZDogYm9vbGVhbjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNsYWNrQWxlcnRzQ29uZmlnKCk6IFNsYWNrQWxlcnRzQ29uZmlnIHtcbiAgcmV0dXJuIHtcbiAgICBlbmFibGVkOiBlbnZCb29sKCdTTEFDS19BTEVSVFNfRU5BQkxFRCcsIGZhbHNlKSxcbiAgICBjb25maWd1cmVkOiBlbnZCb29sKCdTTEFDS19BTEVSVFNfQ09ORklHVVJFRCcsIGZhbHNlKSxcbiAgfTtcbn1cblxuLyoqIEJvdGggZGVzdGluYXRpb25zLCByZXNvbHZlZCB0b2dldGhlciDigJQgd2hhdCBgcmVwb3J0QWxlcnREZWxpdmVyeVBvc3R1cmVgIHJlYWRzLiAqL1xuZXhwb3J0IGludGVyZmFjZSBBbGVydERlbGl2ZXJ5Q29uZmlnIHtcbiAgc2xhY2s6IFNsYWNrQWxlcnRzQ29uZmlnO1xuICBwYWdlcjogeyBlbmFibGVkOiBib29sZWFuOyBjb25maWd1cmVkOiBib29sZWFuIH07XG59XG5cbi8qKiBXaGV0aGVyIEFOWSBkZXN0aW5hdGlvbiBjb3VsZCBhY3R1YWxseSBkZWxpdmVyIOKAlCByZWFsaXR5LCBub3QgZGVjbGFyYXRpb24uICovXG5leHBvcnQgZnVuY3Rpb24gY2FuRGVsaXZlckFueXdoZXJlKGNmZzogQWxlcnREZWxpdmVyeUNvbmZpZyk6IGJvb2xlYW4ge1xuICByZXR1cm4gY2ZnLnNsYWNrLmNvbmZpZ3VyZWQgfHwgKGNmZy5wYWdlci5lbmFibGVkICYmIGNmZy5wYWdlci5jb25maWd1cmVkKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGFsZXJ0RGVsaXZlcnlDb25maWcoKTogQWxlcnREZWxpdmVyeUNvbmZpZyB7XG4gIGNvbnN0IHBkID0gcGFnZXJEdXR5Q29uZmlnKCk7XG4gIHJldHVybiB7XG4gICAgc2xhY2s6IHNsYWNrQWxlcnRzQ29uZmlnKCksXG4gICAgLy8gYGNvbmZpZ3VyZWRgIHNlcGFyYXRlbHkgZnJvbSBgZW5hYmxlZGA6IGVuYWJsZWQtd2l0aG91dC1hLWtleSBpcyB0aGVcbiAgICAvLyBoYWxmLWNvbmZpZ3VyZWQgc3RhdGUgd29ydGggc2VlaW5nLCBhbmQgaXQgaXMgaW52aXNpYmxlIGlmIHRoZSB0d28gYXJlXG4gICAgLy8gY29sbGFwc2VkIGludG8gb25lIGJvb2xlYW4uXG4gICAgcGFnZXI6IHsgZW5hYmxlZDogcGQuZW5hYmxlZCwgY29uZmlndXJlZDogcGQucm91dGluZ0tleS5sZW5ndGggPiAwIH0sXG4gIH07XG59XG5cbi8qKlxuICogUHVibGlzaCB0aGUgZGVsaXZlcnkgcG9zdHVyZSBhcyBnYXVnZXMsIGFuZCBzaG91dCBpZiBpdCBpcyBpbmNvaGVyZW50LlxuICpcbiAqIENhbGxlZCBvbmNlIGF0IGJvb3QuIEdhdWdlcyByYXRoZXIgdGhhbiBjb3VudGVycyBiZWNhdXNlIHRoZXNlIGFyZSBTVEFURVMgb2ZcbiAqIHRoZSBkZXBsb3ltZW50IOKAlCBhbiBvcGVyYXRvciB3YW50cyBcIndpbGwgYW55b25lIGhlYXIgYW4gYWxlcnQgcmlnaHQgbm93XCIsIG5vdFxuICogaG93IG1hbnkgdGltZXMgYSBwcm9jZXNzIHN0YXJ0ZWQgYW5kIHdvbmRlcmVkLlxuICpcbiAqIFRoZSB2YWx1ZSBvZiBoYXZpbmcgdGhpcyBhdCBhbGw6IGJlZm9yZSBpdCwgYW5zd2VyaW5nIFwiZG9lcyB0aGlzIGRlcGxveW1lbnRcbiAqIHBhZ2UgYW55b25lP1wiIG1lYW50IHJlYWRpbmcgYC5lbnZgLCB0d28gU2VjcmV0cyBhbmQgZm91ciBjb3BpZXMgb2ZcbiAqIGBhbGVydG1hbmFnZXIueW1sYC4gQSBkZXBsb3ltZW50IGNhbiBub3cgYWxlcnQgb24gaXRzIG93biBpbmFiaWxpdHkgdG8gYWxlcnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXBvcnRBbGVydERlbGl2ZXJ5UG9zdHVyZShjZmc6IEFsZXJ0RGVsaXZlcnlDb25maWcgPSBhbGVydERlbGl2ZXJ5Q29uZmlnKCkpOiB2b2lkIHtcbiAgZW1pdEdhdWdlKCdhbGVydF9kZWxpdmVyeV9lbmFibGVkJywgeyBjaGFubmVsOiAnc2xhY2snIH0sIGNmZy5zbGFjay5lbmFibGVkID8gMSA6IDApO1xuICBlbWl0R2F1Z2UoJ2FsZXJ0X2RlbGl2ZXJ5X2VuYWJsZWQnLCB7IGNoYW5uZWw6ICdwYWdlcicgfSwgY2ZnLnBhZ2VyLmVuYWJsZWQgPyAxIDogMCk7XG4gIGVtaXRHYXVnZSgnYWxlcnRfZGVsaXZlcnlfY29uZmlndXJlZCcsIHsgY2hhbm5lbDogJ3NsYWNrJyB9LCBjZmcuc2xhY2suY29uZmlndXJlZCA/IDEgOiAwKTtcbiAgZW1pdEdhdWdlKCdhbGVydF9kZWxpdmVyeV9jb25maWd1cmVkJywgeyBjaGFubmVsOiAncGFnZXInIH0sIGNmZy5wYWdlci5jb25maWd1cmVkID8gMSA6IDApO1xuXG4gIC8vIEVuYWJsZWQgd2l0aCBubyBjcmVkZW50aWFsOiBzb21lYm9keSBhc2tlZCBmb3IgcGFnaW5nIGFuZCBpcyBub3QgZ2V0dGluZyBpdC5cbiAgLy8gVGhlIGRlcGxveSBjaGVjayByZWZ1c2VzIHRoaXMgY29tYmluYXRpb24sIHNvIHJlYWNoaW5nIGhlcmUgbWVhbnMgaXQgd2FzXG4gIC8vIGNoYW5nZWQgYWZ0ZXIgcHJvdmlzaW9uaW5nIOKAlCB3aGljaCBpcyBleGFjdGx5IHdoZW4gbm9ib2R5IGlzIGxvb2tpbmcuXG4gIGlmIChjZmcucGFnZXIuZW5hYmxlZCAmJiAhY2ZnLnBhZ2VyLmNvbmZpZ3VyZWQpIHtcbiAgICBsb2dnZXIuZXJyb3IoJ09OQ0FMTF9FTkFCTEVEIGlzIHRydWUgYnV0IG5vIFBBR0VSRFVUWV9ST1VUSU5HX0tFWSBpcyBzZXQg4oCUIHN1cHBvcnQgY3JpdGljYWxzIHdpbGwgbm90IHBhZ2UnKTtcbiAgfVxuICAvLyBOb3RoaW5nIENBTiBkZWxpdmVyLiBLZXllZCBvbiByZWFjaGFiaWxpdHkgcmF0aGVyIHRoYW4gb24gdGhlIHR3byBmbGFncyxcbiAgLy8gYmVjYXVzZSB0aGUgZmxhZ3MgYXJlIGRlY2xhcmF0aW9uczogYSBkZXBsb3ltZW50IHRoYXQgbGVmdCBib3RoIGF0IHRoZWlyXG4gIC8vIChmYWxzZSkgZGVmYXVsdHMgd2hpbGUgcmVhbCBTbGFjayBVUkxzIHNpdCBpbiBpdHMgU2VjcmV0IGlzIGRlbGl2ZXJpbmdcbiAgLy8gZmluZSwgYW5kIHdhcm5pbmcgaXQgd291bGQgYmUgYSBsaWUuXG4gIGlmICghY2FuRGVsaXZlckFueXdoZXJlKGNmZykpIHtcbiAgICBsb2dnZXIud2FybignTm8gcGxhdGZvcm0gYWxlcnQgZGVzdGluYXRpb24gaXMgZW5hYmxlZCDigJQgYWxlcnRzIHdpbGwgcmVhY2ggQWxlcnRtYW5hZ2VyIGFuZCBnbyBubyBmdXJ0aGVyJywge1xuICAgICAgaGludDogJ1NMQUNLX0FMRVJUU19FTkFCTEVEIC8gT05DQUxMX0VOQUJMRUQnLFxuICAgIH0pO1xuICB9XG59XG5cbi8qKlxuICogVGhlIGRlYWQtbWFuIHN3aXRjaDogYSBoZWFydGJlYXQgd2hvc2UgQUJTRU5DRSBpcyB0aGUgYWxlcnQuXG4gKlxuICogRXZlcnkgb3RoZXIgYWxlcnQgaW4gdGhpcyBzeXN0ZW0gZGVwZW5kcyBvbiBzb21ldGhpbmcgaW5zaWRlIGl0IHN0aWxsIHdvcmtpbmcuXG4gKiBUaGlzIG9uZSBpbnZlcnRzIHRoYXQg4oCUIGFuIGV4dGVybmFsIHNlcnZpY2UgZXhwZWN0cyBhIFBPU1Qgb24gYSBzY2hlZHVsZSBhbmRcbiAqIHJhaXNlcyBhbiBpbmNpZGVudCB3aGVuIG9uZSBkb2VzIG5vdCBhcnJpdmUsIHNvIGEgcGxhdGZvcm0gdGhhdCBpcyB3ZWRnZWQsXG4gKiBjcmFzaGVkLCBwYXJ0aXRpb25lZCBvciBuZXZlciBzdGFydGVkIGlzIHJlcG9ydGVkIEJZIElUUyBTSUxFTkNFLiBOb3RoaW5nXG4gKiBpbnNpZGUgYSBicm9rZW4gZGVwbG95bWVudCBoYXMgdG8gbm90aWNlIGl0cyBvd24gYnJva2VubmVzcy5cbiAqXG4gKiBXSFkgSVQgSVMgQlVJTFQgSEVSRSBSQVRIRVIgVEhBTiBCT1VHSFQsIHdoZW4gdGhlIHJlc3Qgb2Ygb24tY2FsbCBkZWxpYmVyYXRlbHlcbiAqIGlzIG5vdDogUGFnZXJEdXR5J3MgaGVhcnRiZWF0IG1vbml0b3JpbmcgaXMgYW4gRXZlbnQgT3JjaGVzdHJhdGlvbiBwYXR0ZXJuIGFuZFxuICogZG9jdW1lbnRlZCBhcyBBSU9wcy1wb3dlcmVkLCBzbyBpdCBtYXkgc2l0IGJlaGluZCBhIHRpZXIgYW5kIGlzIG5vdCBwYXJ0IG9mXG4gKiBFdmVudHMgdjIuIE1vcmUgaW1wb3J0YW50bHkgYSBkZWFkLW1hbiBzd2l0Y2ggaXMgYSBDTE9DSywgbm90IGEgdGVsZWNvbVxuICogcHJvYmxlbSDigJQgdGhlIHRoaW5nIHRoYXQgbWFkZSB0aGUgbGFzdCBtaWxlIHdvcnRoIGJ1eWluZyAoY2FycmllciByZXRyaWVzLFxuICogZGVsaXZlcnkgcmVjZWlwdHMsIGEgM2FtIGZhaWx1cmUgYnVkZ2V0KSBzaW1wbHkgZG9lcyBub3QgYXBwbHkgdG8gc2VuZGluZyBvbmVcbiAqIHJlcXVlc3Qgb24gYW4gaW50ZXJ2YWwuIGBPTkNBTExfREVBRE1BTl9VUkxgIHRoZXJlZm9yZSBwb2ludHMgd2hlcmV2ZXIgdGhlXG4gKiBvcGVyYXRvciBsaWtlczogUGFnZXJEdXR5J3Mgb3JjaGVzdHJhdGlvbiBlbmRwb2ludCwgYSBwdXJwb3NlLWJ1aWx0IGRlYWQtbWFuXG4gKiBzZXJ2aWNlLCBvciBhbiB1cHRpbWUgbW9uaXRvci5cbiAqXG4gKiBUV08gUFJPUEVSVElFUyBXT1JUSCBTVEFUSU5HLCBiZWNhdXNlIHRoZXkgYXJlIHRoZSByZWFzb24gdGhpcyBpcyBzYWZlOlxuICpcbiAqICAtIEl0IGlzIE5PVCBsZWFkZXItbG9ja2VkLiBBIGhlYXJ0YmVhdCBpcyBwZXItcHJvY2VzcyBvbiBwdXJwb3NlOiB3aXRoIHRocmVlXG4gKiAgICByZXBsaWNhcywgdGhlIGV4dGVybmFsIHNlcnZpY2UgaGVhcnMgZnJvbSB3aGljaGV2ZXIgYXJlIGFsaXZlLCBhbmQgc2lsZW5jZVxuICogICAgbWVhbnMgQUxMIG9mIHRoZW0gc3RvcHBlZC4gTGVhZGVyLWxvY2tpbmcgaXQgd291bGQgbWFrZSB0aGUgc3dpdGNoIHJlcG9ydFxuICogICAgdGhlIGxlYWRlcidzIGhlYWx0aCByYXRoZXIgdGhhbiB0aGUgZGVwbG95bWVudCdzIOKAlCBhbmQgd291bGQgbWFrZSBhIFJlZGlzXG4gKiAgICBvdXRhZ2UgbG9vayBpZGVudGljYWwgdG8gYSB0b3RhbCBvdXRhZ2UuXG4gKiAgLSBJdCBmYWlscyBTSUxFTlRMWSBvbiBpdHMgb3duIGVycm9ycywgZGVsaWJlcmF0ZWx5LiBBIGhlYXJ0YmVhdCB0aGF0IGxvZ2dlZFxuICogICAgYW4gZXJyb3IgZXZlcnkgaW50ZXJ2YWwgZHVyaW5nIGEgbmV0d29yayBibGlwIHdvdWxkIGJlIGl0cyBvd24gbm9pc2VcbiAqICAgIHNvdXJjZSwgYW5kIHRoZSBjb25zZXF1ZW5jZSBvZiBhIG1pc3NlZCBiZWF0IGlzIGFscmVhZHkgaGFuZGxlZDogdGhlXG4gKiAgICBleHRlcm5hbCBzZXJ2aWNlIGFsZXJ0cy4gVGhlIG9ubHkgdGhpbmcgaXQgbXVzdCBuZXZlciBkbyBpcyByZXBvcnQgc3VjY2Vzc1xuICogICAgaXQgZGlkIG5vdCBhY2hpZXZlLCB3aGljaCBpcyB3aHkgbm90aGluZyBoZXJlIHN3YWxsb3dzIGEgbm9uLTJ4eC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBEZWFkbWFuQ29uZmlnIHtcbiAgdXJsOiBzdHJpbmc7XG4gIGludGVydmFsTXM6IG51bWJlcjtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGRlYWRtYW5Db25maWcoKTogRGVhZG1hbkNvbmZpZyB7XG4gIHJldHVybiB7XG4gICAgdXJsOiBlbnZTdHIoJ09OQ0FMTF9ERUFETUFOX1VSTCcsICcnKSxcbiAgICBpbnRlcnZhbE1zOiBlbnZJbnQoJ09OQ0FMTF9ERUFETUFOX0lOVEVSVkFMX01TJywgNjBfMDAwLCB7IG1pbjogMTAwMCB9KSxcbiAgfTtcbn1cblxubGV0IGRlYWRtYW5UaW1lcjogTm9kZUpTLlRpbWVvdXQgfCB1bmRlZmluZWQ7XG5cbi8qKlxuICogU3RhcnQgYmVhdGluZy4gTm8tb3Agd2hlbiBgT05DQUxMX0RFQURNQU5fVVJMYCBpcyB1bnNldCwgd2hpY2ggaXMgdGhlIGRlZmF1bHQ6XG4gKiBhbiB1bmNvbmZpZ3VyZWQgZGVhZC1tYW4gc3dpdGNoIG11c3Qgbm90IGJlYXQgYWdhaW5zdCBub3RoaW5nLCBhbmQgbXVzdCBub3RcbiAqIHByZXRlbmQgaXQgaXMgY292ZXJpbmcgdGhlIGRlcGxveW1lbnQuXG4gKlxuICogUmV0dXJucyB0cnVlIHdoZW4gYSB0aW1lciB3YXMgc3RhcnRlZCwgc28gYSBjYWxsZXIgY2FuIGxvZyB0aGUgcG9zdHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0YXJ0RGVhZG1hbkhlYXJ0YmVhdChjZmc6IERlYWRtYW5Db25maWcgPSBkZWFkbWFuQ29uZmlnKCkpOiBib29sZWFuIHtcbiAgaWYgKGRlYWRtYW5UaW1lciB8fCAhY2ZnLnVybCkgcmV0dXJuIGZhbHNlO1xuICBjb25zdCBiZWF0ID0gYXN5bmMgKCk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXNwID0gYXdhaXQgc2FmZUZldGNoKGNmZy51cmwsIHsgbWV0aG9kOiAnUE9TVCcsIHRpbWVvdXRNczogNTAwMCwgYm9keTogJ3t9JywgaGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0gfSk7XG4gICAgICBlbWl0Q291bnRlcignb25jYWxsX2RlYWRtYW5fdG90YWwnLCB7IG91dGNvbWU6IHJlc3Aub2sgJiYgIXJlc3AucmVkaXJlY3RlZCA/ICdzZW50JyA6ICdmYWlsZWQnIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gQ291bnRlZCwgbmV2ZXIgbG9nZ2VkIHBlci1iZWF0IOKAlCBzZWUgdGhlIG1vZHVsZSBub3RlIG9uIG5vaXNlLlxuICAgICAgZW1pdENvdW50ZXIoJ29uY2FsbF9kZWFkbWFuX3RvdGFsJywgeyBvdXRjb21lOiAnZmFpbGVkJyB9KTtcbiAgICB9XG4gIH07XG4gIC8vIGB1bnJlZmAgc28gYSBwZW5kaW5nIGJlYXQgY2Fubm90IGhvbGQgdGhlIHByb2Nlc3Mgb3BlbiBkdXJpbmcgc2h1dGRvd246IHRoZVxuICAvLyBzd2l0Y2ggZXhpc3RzIHRvIHJlcG9ydCB0aGF0IHRoZSBwcm9jZXNzIFNUT1BQRUQsIGFuZCBkZWxheWluZyB0aGF0IGlzXG4gIC8vIHByZWNpc2VseSBiYWNrd2FyZHMuXG4gIGRlYWRtYW5UaW1lciA9IHNldEludGVydmFsKCgpID0+IHZvaWQgYmVhdCgpLCBjZmcuaW50ZXJ2YWxNcyk7XG4gIGRlYWRtYW5UaW1lci51bnJlZigpO1xuICB2b2lkIGJlYXQoKTtcbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKiBTdG9wIGJlYXRpbmcg4oCUIGNhbGxlZCBmcm9tIHRoZSB1bmlmaWVkIHNodXRkb3duIHBhdGgsIG5vdCBhIHNpZ25hbCBoYW5kbGVyLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEZWFkbWFuSGVhcnRiZWF0KCk6IHZvaWQge1xuICBpZiAoZGVhZG1hblRpbWVyKSBjbGVhckludGVydmFsKGRlYWRtYW5UaW1lcik7XG4gIGRlYWRtYW5UaW1lciA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
@@ -0,0 +1,105 @@
1
+ import type { Request } from 'express';
2
+ import type { JwtPayload } from '../types/common.js';
3
+ /** Inbound correlation ids a caller (CLI, Lambda, CodeBuild) may forward. */
4
+ export interface AuditCorrelation {
5
+ /** `X-Pipeline-Execution-Id` — the CodePipeline execution that made the call. */
6
+ pipelineExecutionId?: string;
7
+ /** `X-CodeBuild-Build-Id` — the CodeBuild build that made the call. */
8
+ codebuildBuildId?: string;
9
+ /** `X-Source-Event-Id` — the upstream event (EventBridge / webhook) being handled. */
10
+ sourceEventId?: string;
11
+ }
12
+ /** Background-work identity for events recorded outside any request. */
13
+ export interface AuditJobContext {
14
+ /** Stable job name (e.g. `registry-gc`, `vuln-rescan`). */
15
+ jobId?: string;
16
+ /** This run's id (a BullMQ job id, a sweep's uuid). */
17
+ runId?: string;
18
+ /** What started it: `schedule`, `queue`, `startup`, `manual`, … */
19
+ trigger?: string;
20
+ }
21
+ export interface AuditContext {
22
+ requestId?: string;
23
+ traceId?: string;
24
+ ip?: string;
25
+ userAgent?: string;
26
+ correlation?: AuditCorrelation;
27
+ job?: AuditJobContext;
28
+ /**
29
+ * The VERIFIED principal of the inbound request. A getter, not a value: the
30
+ * scope opens before `requireAuth` runs, so `req.user` is read at the moment
31
+ * an event is recorded.
32
+ */
33
+ principal?: () => JwtPayload | undefined;
34
+ }
35
+ /**
36
+ * The credential behind an audited action — reserved `details._auth`, so it is
37
+ * covered by the audit hash chain (which hashes `details`). Built only from a
38
+ * verified token, never from caller input; the remote-audit client overwrites
39
+ * any `_auth` a call site put in `details`.
40
+ */
41
+ export interface AuditAuthBlock {
42
+ principalType?: string;
43
+ /** The internal service, when the principal is a service token. */
44
+ service?: string;
45
+ /**
46
+ * The revocable credential: the access key (PAT / service-account key) the
47
+ * token derives from (`parentKeyId`), else the token's own `jti` (an exchanged
48
+ * key token's key id, an impersonation session id, a service mint id).
49
+ */
50
+ keyId?: string;
51
+ /** Refresh-session slot the token was minted on. */
52
+ sid?: string;
53
+ scope?: string;
54
+ tokenUse?: string;
55
+ /** The token's permissions were narrowed to a subset at credential creation. */
56
+ permissionsRestricted?: true;
57
+ /** The principal is a platform superadmin. */
58
+ superAdmin?: true;
59
+ /**
60
+ * The principal's active org is the system org (vendor staff acting through a
61
+ * system-org job Role). With `superAdmin`, this is what lets the remote-audit
62
+ * client claim a cross-org `affectedOrgId` (see `auditCrossOrg`).
63
+ */
64
+ systemOrg?: true;
65
+ }
66
+ /**
67
+ * Run `fn` inside an audit-context scope. Nested scopes MERGE onto the outer
68
+ * one (a job started inside a request keeps the request's id), with the inner
69
+ * fields winning.
70
+ */
71
+ export declare function runWithAuditContext<T>(ctx: AuditContext, fn: () => T): T;
72
+ /** The active audit context, or undefined outside any scope. */
73
+ export declare function currentAuditContext(): AuditContext | undefined;
74
+ /** Inbound correlation headers recorded into the audit context. */
75
+ export declare const AUDIT_CORRELATION_HEADERS: {
76
+ readonly pipelineExecutionId: 'x-pipeline-execution-id';
77
+ readonly codebuildBuildId: 'x-codebuild-build-id';
78
+ readonly sourceEventId: 'x-source-event-id';
79
+ };
80
+ /**
81
+ * Validate an inbound correlation id (`X-Request-Id`, `X-CodeBuild-Build-Id`, …):
82
+ * a bounded opaque token, else `undefined` — an invalid value is dropped, never
83
+ * truncated into something that looks like a different id.
84
+ *
85
+ * No AWS account id is ever stored: an ARN (a CodeBuild build ARN, say) has its
86
+ * account segment emptied, and a bare 12-digit value is refused. The generic
87
+ * 12-digit-run scrub is NOT used here — it would redact the all-digit final
88
+ * group of roughly one uuid in three hundred, corrupting the id it exists to
89
+ * correlate.
90
+ */
91
+ export declare function sanitizeCorrelationId(raw: unknown): string | undefined;
92
+ /** Strip control characters from a User-Agent and cap its length; undefined when empty. */
93
+ export declare function sanitizeUserAgent(raw: unknown): string | undefined;
94
+ /**
95
+ * The audit context for an inbound request. `requestId` is the id the
96
+ * request-id middleware resolved (validated inbound `X-Request-Id`, else a fresh
97
+ * uuid); the principal is read lazily from `req.user` (auth runs later in the
98
+ * chain).
99
+ */
100
+ export declare function auditContextFromRequest(req: Request, ids?: {
101
+ requestId?: string;
102
+ traceId?: string;
103
+ }): AuditContext;
104
+ /** The `_auth` block for a verified principal (undefined when there is none). */
105
+ export declare function auditAuthBlock(user: JwtPayload | undefined): AuditAuthBlock | undefined;
@@ -0,0 +1,134 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Ambient audit context: the request/job facts every audit event should carry
5
+ * (request id, trace id, client ip / user agent, the verified principal's
6
+ * credential, inbound correlation ids) without each of the ~150 `recordAudit`
7
+ * call sites threading them through by hand.
8
+ *
9
+ * api-server's request pipeline opens a scope per inbound request
10
+ * (`auditContextFromRequest` + `runWithAuditContext`); background work opens its
11
+ * own with `runWithAuditContext({ job: { jobId, runId, trigger } }, fn)`. The
12
+ * remote-audit client reads the scope when an event is RECORDED and stamps the
13
+ * result onto the event itself, so a spooled re-delivery minutes later still
14
+ * carries the context of the moment it happened.
15
+ */
16
+ import { AsyncLocalStorage } from 'async_hooks';
17
+ import { serviceNameOf } from '../middleware/service-tokens.js';
18
+ import { isSystemOrgId } from '../middleware/system-org.js';
19
+ const storage = new AsyncLocalStorage();
20
+ /**
21
+ * Run `fn` inside an audit-context scope. Nested scopes MERGE onto the outer
22
+ * one (a job started inside a request keeps the request's id), with the inner
23
+ * fields winning.
24
+ */
25
+ export function runWithAuditContext(ctx, fn) {
26
+ const outer = storage.getStore();
27
+ const merged = outer
28
+ ? { ...outer, ...ctx, correlation: ctx.correlation ?? outer.correlation, job: ctx.job ? { ...outer.job, ...ctx.job } : outer.job }
29
+ : ctx;
30
+ return storage.run(merged, fn);
31
+ }
32
+ /** The active audit context, or undefined outside any scope. */
33
+ export function currentAuditContext() {
34
+ return storage.getStore();
35
+ }
36
+ /** Inbound correlation headers recorded into the audit context. */
37
+ export const AUDIT_CORRELATION_HEADERS = {
38
+ pipelineExecutionId: 'x-pipeline-execution-id',
39
+ codebuildBuildId: 'x-codebuild-build-id',
40
+ sourceEventId: 'x-source-event-id',
41
+ };
42
+ /** Max length of a stored correlation id / request id. */
43
+ const MAX_CORRELATION_LEN = 128;
44
+ /** Ids are opaque tokens: uuids, `project:uuid` build ids, ULIDs. Nothing else is stored. */
45
+ const CORRELATION_ID = /^[A-Za-z0-9._:/@+=-]+$/;
46
+ /** A bare AWS account id. */
47
+ const BARE_ACCOUNT_ID = /^\d{12}$/;
48
+ /** Max stored User-Agent length — a hostile header must not bloat audit rows. */
49
+ const MAX_USER_AGENT_LEN = 512;
50
+ /** C0 + DEL + C1 control characters, replaced so a UA can't inject into the UI/CSV export. */
51
+ const CONTROL_CHARS = new RegExp('[\\u0000-\\u001f\\u007f-\\u009f]', 'g');
52
+ function firstHeader(raw) {
53
+ return Array.isArray(raw) ? raw[0] : raw;
54
+ }
55
+ /**
56
+ * Validate an inbound correlation id (`X-Request-Id`, `X-CodeBuild-Build-Id`, …):
57
+ * a bounded opaque token, else `undefined` — an invalid value is dropped, never
58
+ * truncated into something that looks like a different id.
59
+ *
60
+ * No AWS account id is ever stored: an ARN (a CodeBuild build ARN, say) has its
61
+ * account segment emptied, and a bare 12-digit value is refused. The generic
62
+ * 12-digit-run scrub is NOT used here — it would redact the all-digit final
63
+ * group of roughly one uuid in three hundred, corrupting the id it exists to
64
+ * correlate.
65
+ */
66
+ export function sanitizeCorrelationId(raw) {
67
+ if (typeof raw !== 'string')
68
+ return undefined;
69
+ const value = raw.trim();
70
+ if (value.length === 0 || value.length > MAX_CORRELATION_LEN || !CORRELATION_ID.test(value))
71
+ return undefined;
72
+ if (BARE_ACCOUNT_ID.test(value))
73
+ return undefined;
74
+ if (/^arn:/i.test(value)) {
75
+ const parts = value.split(':');
76
+ if (parts.length > 4)
77
+ parts[4] = '';
78
+ return parts.join(':');
79
+ }
80
+ return value;
81
+ }
82
+ /** Strip control characters from a User-Agent and cap its length; undefined when empty. */
83
+ export function sanitizeUserAgent(raw) {
84
+ if (typeof raw !== 'string' || raw.length === 0)
85
+ return undefined;
86
+ const cleaned = raw.replace(CONTROL_CHARS, ' ').trim();
87
+ return cleaned.length === 0 ? undefined : cleaned.slice(0, MAX_USER_AGENT_LEN);
88
+ }
89
+ /**
90
+ * The audit context for an inbound request. `requestId` is the id the
91
+ * request-id middleware resolved (validated inbound `X-Request-Id`, else a fresh
92
+ * uuid); the principal is read lazily from `req.user` (auth runs later in the
93
+ * chain).
94
+ */
95
+ export function auditContextFromRequest(req, ids = {}) {
96
+ const correlation = {};
97
+ for (const [field, header] of Object.entries(AUDIT_CORRELATION_HEADERS)) {
98
+ const value = sanitizeCorrelationId(firstHeader(req.headers[header]));
99
+ if (value)
100
+ correlation[field] = value;
101
+ }
102
+ return {
103
+ requestId: ids.requestId,
104
+ traceId: ids.traceId,
105
+ ip: req.ip,
106
+ userAgent: sanitizeUserAgent(firstHeader(req.headers['user-agent'])),
107
+ correlation: Object.keys(correlation).length > 0 ? correlation : undefined,
108
+ principal: () => req.user,
109
+ };
110
+ }
111
+ /** The `_auth` block for a verified principal (undefined when there is none). */
112
+ export function auditAuthBlock(user) {
113
+ if (!user)
114
+ return undefined;
115
+ const block = {
116
+ principalType: user.principalType,
117
+ service: serviceNameOf(user),
118
+ keyId: user.parentKeyId ?? user.jti,
119
+ sid: user.sid,
120
+ scope: user.scope,
121
+ tokenUse: user.token_use,
122
+ ...(user.permissionsRestricted === true ? { permissionsRestricted: true } : {}),
123
+ ...(user.isSuperAdmin === true ? { superAdmin: true } : {}),
124
+ // A SERVICE token in the system org is the platform's own plumbing, not staff
125
+ // acting on a tenant; only human / service-account principals count.
126
+ ...(user.principalType !== 'service' && isSystemOrgId(user.organizationId) ? { systemOrg: true } : {}),
127
+ };
128
+ for (const key of Object.keys(block)) {
129
+ if (block[key] === undefined)
130
+ delete block[key];
131
+ }
132
+ return Object.keys(block).length > 0 ? block : undefined;
133
+ }
134
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXVkaXQtY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9hdWRpdC1jb250ZXh0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7OztHQVlHO0FBRUgsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRWhELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNoRSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFzRTVELE1BQU0sT0FBTyxHQUFHLElBQUksaUJBQWlCLEVBQWdCLENBQUM7QUFFdEQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBSSxHQUFpQixFQUFFLEVBQVc7SUFDbkUsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ2pDLE1BQU0sTUFBTSxHQUFpQixLQUFLO1FBQ2hDLENBQUMsQ0FBQyxFQUFFLEdBQUcsS0FBSyxFQUFFLEdBQUcsR0FBRyxFQUFFLFdBQVcsRUFBRSxHQUFHLENBQUMsV0FBVyxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxLQUFLLENBQUMsR0FBRyxFQUFFLEdBQUcsR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFO1FBQ2xJLENBQUMsQ0FBQyxHQUFHLENBQUM7SUFDUixPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ2pDLENBQUM7QUFFRCxnRUFBZ0U7QUFDaEUsTUFBTSxVQUFVLG1CQUFtQjtJQUNqQyxPQUFPLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQUM1QixDQUFDO0FBRUQsbUVBQW1FO0FBQ25F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QUFxRTtRQUNyRSxHQUFHLENBQUMsSUFBSSxDQUFDLGFBQWEsS0FBSyxTQUFTLElBQUksYUFBYSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxTQUFTLEVBQUUsSUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUNoSCxDQUFDO0lBQ0YsS0FBSyxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBZ0MsRUFBRSxDQUFDO1FBQ3BFLElBQUksS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVM7WUFBRSxPQUFPLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNsRCxDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQzNELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBBbWJpZW50IGF1ZGl0IGNvbnRleHQ6IHRoZSByZXF1ZXN0L2pvYiBmYWN0cyBldmVyeSBhdWRpdCBldmVudCBzaG91bGQgY2FycnlcbiAqIChyZXF1ZXN0IGlkLCB0cmFjZSBpZCwgY2xpZW50IGlwIC8gdXNlciBhZ2VudCwgdGhlIHZlcmlmaWVkIHByaW5jaXBhbCdzXG4gKiBjcmVkZW50aWFsLCBpbmJvdW5kIGNvcnJlbGF0aW9uIGlkcykgd2l0aG91dCBlYWNoIG9mIHRoZSB+MTUwIGByZWNvcmRBdWRpdGBcbiAqIGNhbGwgc2l0ZXMgdGhyZWFkaW5nIHRoZW0gdGhyb3VnaCBieSBoYW5kLlxuICpcbiAqIGFwaS1zZXJ2ZXIncyByZXF1ZXN0IHBpcGVsaW5lIG9wZW5zIGEgc2NvcGUgcGVyIGluYm91bmQgcmVxdWVzdFxuICogKGBhdWRpdENvbnRleHRGcm9tUmVxdWVzdGAgKyBgcnVuV2l0aEF1ZGl0Q29udGV4dGApOyBiYWNrZ3JvdW5kIHdvcmsgb3BlbnMgaXRzXG4gKiBvd24gd2l0aCBgcnVuV2l0aEF1ZGl0Q29udGV4dCh7IGpvYjogeyBqb2JJZCwgcnVuSWQsIHRyaWdnZXIgfSB9LCBmbilgLiBUaGVcbiAqIHJlbW90ZS1hdWRpdCBjbGllbnQgcmVhZHMgdGhlIHNjb3BlIHdoZW4gYW4gZXZlbnQgaXMgUkVDT1JERUQgYW5kIHN0YW1wcyB0aGVcbiAqIHJlc3VsdCBvbnRvIHRoZSBldmVudCBpdHNlbGYsIHNvIGEgc3Bvb2xlZCByZS1kZWxpdmVyeSBtaW51dGVzIGxhdGVyIHN0aWxsXG4gKiBjYXJyaWVzIHRoZSBjb250ZXh0IG9mIHRoZSBtb21lbnQgaXQgaGFwcGVuZWQuXG4gKi9cblxuaW1wb3J0IHsgQXN5bmNMb2NhbFN0b3JhZ2UgfSBmcm9tICdhc3luY19ob29rcyc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IHNlcnZpY2VOYW1lT2YgfSBmcm9tICcuLi9taWRkbGV3YXJlL3NlcnZpY2UtdG9rZW5zLmpzJztcbmltcG9ydCB7IGlzU3lzdGVtT3JnSWQgfSBmcm9tICcuLi9taWRkbGV3YXJlL3N5c3RlbS1vcmcuanMnO1xuaW1wb3J0IHR5cGUgeyBKd3RQYXlsb2FkIH0gZnJvbSAnLi4vdHlwZXMvY29tbW9uLmpzJztcblxuLyoqIEluYm91bmQgY29ycmVsYXRpb24gaWRzIGEgY2FsbGVyIChDTEksIExhbWJkYSwgQ29kZUJ1aWxkKSBtYXkgZm9yd2FyZC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXVkaXRDb3JyZWxhdGlvbiB7XG4gIC8qKiBgWC1QaXBlbGluZS1FeGVjdXRpb24tSWRgIOKAlCB0aGUgQ29kZVBpcGVsaW5lIGV4ZWN1dGlvbiB0aGF0IG1hZGUgdGhlIGNhbGwuICovXG4gIHBpcGVsaW5lRXhlY3V0aW9uSWQ/OiBzdHJpbmc7XG4gIC8qKiBgWC1Db2RlQnVpbGQtQnVpbGQtSWRgIOKAlCB0aGUgQ29kZUJ1aWxkIGJ1aWxkIHRoYXQgbWFkZSB0aGUgY2FsbC4gKi9cbiAgY29kZWJ1aWxkQnVpbGRJZD86IHN0cmluZztcbiAgLyoqIGBYLVNvdXJjZS1FdmVudC1JZGAg4oCUIHRoZSB1cHN0cmVhbSBldmVudCAoRXZlbnRCcmlkZ2UgLyB3ZWJob29rKSBiZWluZyBoYW5kbGVkLiAqL1xuICBzb3VyY2VFdmVudElkPzogc3RyaW5nO1xufVxuXG4vKiogQmFja2dyb3VuZC13b3JrIGlkZW50aXR5IGZvciBldmVudHMgcmVjb3JkZWQgb3V0c2lkZSBhbnkgcmVxdWVzdC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXVkaXRKb2JDb250ZXh0IHtcbiAgLyoqIFN0YWJsZSBqb2IgbmFtZSAoZS5nLiBgcmVnaXN0cnktZ2NgLCBgdnVsbi1yZXNjYW5gKS4gKi9cbiAgam9iSWQ/OiBzdHJpbmc7XG4gIC8qKiBUaGlzIHJ1bidzIGlkIChhIEJ1bGxNUSBqb2IgaWQsIGEgc3dlZXAncyB1dWlkKS4gKi9cbiAgcnVuSWQ/OiBzdHJpbmc7XG4gIC8qKiBXaGF0IHN0YXJ0ZWQgaXQ6IGBzY2hlZHVsZWAsIGBxdWV1ZWAsIGBzdGFydHVwYCwgYG1hbnVhbGAsIOKApiAqL1xuICB0cmlnZ2VyPzogc3RyaW5nO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0Q29udGV4dCB7XG4gIHJlcXVlc3RJZD86IHN0cmluZztcbiAgdHJhY2VJZD86IHN0cmluZztcbiAgaXA/OiBzdHJpbmc7XG4gIHVzZXJBZ2VudD86IHN0cmluZztcbiAgY29ycmVsYXRpb24/OiBBdWRpdENvcnJlbGF0aW9uO1xuICBqb2I/OiBBdWRpdEpvYkNvbnRleHQ7XG4gIC8qKlxuICAgKiBUaGUgVkVSSUZJRUQgcHJpbmNpcGFsIG9mIHRoZSBpbmJvdW5kIHJlcXVlc3QuIEEgZ2V0dGVyLCBub3QgYSB2YWx1ZTogdGhlXG4gICAqIHNjb3BlIG9wZW5zIGJlZm9yZSBgcmVxdWlyZUF1dGhgIHJ1bnMsIHNvIGByZXEudXNlcmAgaXMgcmVhZCBhdCB0aGUgbW9tZW50XG4gICAqIGFuIGV2ZW50IGlzIHJlY29yZGVkLlxuICAgKi9cbiAgcHJpbmNpcGFsPzogKCkgPT4gSnd0UGF5bG9hZCB8IHVuZGVmaW5lZDtcbn1cblxuLyoqXG4gKiBUaGUgY3JlZGVudGlhbCBiZWhpbmQgYW4gYXVkaXRlZCBhY3Rpb24g4oCUIHJlc2VydmVkIGBkZXRhaWxzLl9hdXRoYCwgc28gaXQgaXNcbiAqIGNvdmVyZWQgYnkgdGhlIGF1ZGl0IGhhc2ggY2hhaW4gKHdoaWNoIGhhc2hlcyBgZGV0YWlsc2ApLiBCdWlsdCBvbmx5IGZyb20gYVxuICogdmVyaWZpZWQgdG9rZW4sIG5ldmVyIGZyb20gY2FsbGVyIGlucHV0OyB0aGUgcmVtb3RlLWF1ZGl0IGNsaWVudCBvdmVyd3JpdGVzXG4gKiBhbnkgYF9hdXRoYCBhIGNhbGwgc2l0ZSBwdXQgaW4gYGRldGFpbHNgLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0QXV0aEJsb2NrIHtcbiAgcHJpbmNpcGFsVHlwZT86IHN0cmluZztcbiAgLyoqIFRoZSBpbnRlcm5hbCBzZXJ2aWNlLCB3aGVuIHRoZSBwcmluY2lwYWwgaXMgYSBzZXJ2aWNlIHRva2VuLiAqL1xuICBzZXJ2aWNlPzogc3RyaW5nO1xuICAvKipcbiAgICogVGhlIHJldm9jYWJsZSBjcmVkZW50aWFsOiB0aGUgYWNjZXNzIGtleSAoUEFUIC8gc2VydmljZS1hY2NvdW50IGtleSkgdGhlXG4gICAqIHRva2VuIGRlcml2ZXMgZnJvbSAoYHBhcmVudEtleUlkYCksIGVsc2UgdGhlIHRva2VuJ3Mgb3duIGBqdGlgIChhbiBleGNoYW5nZWRcbiAgICoga2V5IHRva2VuJ3Mga2V5IGlkLCBhbiBpbXBlcnNvbmF0aW9uIHNlc3Npb24gaWQsIGEgc2VydmljZSBtaW50IGlkKS5cbiAgICovXG4gIGtleUlkPzogc3RyaW5nO1xuICAvKiogUmVmcmVzaC1zZXNzaW9uIHNsb3QgdGhlIHRva2VuIHdhcyBtaW50ZWQgb24uICovXG4gIHNpZD86IHN0cmluZztcbiAgc2NvcGU/OiBzdHJpbmc7XG4gIHRva2VuVXNlPzogc3RyaW5nO1xuICAvKiogVGhlIHRva2VuJ3MgcGVybWlzc2lvbnMgd2VyZSBuYXJyb3dlZCB0byBhIHN1YnNldCBhdCBjcmVkZW50aWFsIGNyZWF0aW9uLiAqL1xuICBwZXJtaXNzaW9uc1Jlc3RyaWN0ZWQ/OiB0cnVlO1xuICAvKiogVGhlIHByaW5jaXBhbCBpcyBhIHBsYXRmb3JtIHN1cGVyYWRtaW4uICovXG4gIHN1cGVyQWRtaW4/OiB0cnVlO1xuICAvKipcbiAgICogVGhlIHByaW5jaXBhbCdzIGFjdGl2ZSBvcmcgaXMgdGhlIHN5c3RlbSBvcmcgKHZlbmRvciBzdGFmZiBhY3RpbmcgdGhyb3VnaCBhXG4gICAqIHN5c3RlbS1vcmcgam9iIFJvbGUpLiBXaXRoIGBzdXBlckFkbWluYCwgdGhpcyBpcyB3aGF0IGxldHMgdGhlIHJlbW90ZS1hdWRpdFxuICAgKiBjbGllbnQgY2xhaW0gYSBjcm9zcy1vcmcgYGFmZmVjdGVkT3JnSWRgIChzZWUgYGF1ZGl0Q3Jvc3NPcmdgKS5cbiAgICovXG4gIHN5c3RlbU9yZz86IHRydWU7XG59XG5cbmNvbnN0IHN0b3JhZ2UgPSBuZXcgQXN5bmNMb2NhbFN0b3JhZ2U8QXVkaXRDb250ZXh0PigpO1xuXG4vKipcbiAqIFJ1biBgZm5gIGluc2lkZSBhbiBhdWRpdC1jb250ZXh0IHNjb3BlLiBOZXN0ZWQgc2NvcGVzIE1FUkdFIG9udG8gdGhlIG91dGVyXG4gKiBvbmUgKGEgam9iIHN0YXJ0ZWQgaW5zaWRlIGEgcmVxdWVzdCBrZWVwcyB0aGUgcmVxdWVzdCdzIGlkKSwgd2l0aCB0aGUgaW5uZXJcbiAqIGZpZWxkcyB3aW5uaW5nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcnVuV2l0aEF1ZGl0Q29udGV4dDxUPihjdHg6IEF1ZGl0Q29udGV4dCwgZm46ICgpID0+IFQpOiBUIHtcbiAgY29uc3Qgb3V0ZXIgPSBzdG9yYWdlLmdldFN0b3JlKCk7XG4gIGNvbnN0IG1lcmdlZDogQXVkaXRDb250ZXh0ID0gb3V0ZXJcbiAgICA/IHsgLi4ub3V0ZXIsIC4uLmN0eCwgY29ycmVsYXRpb246IGN0eC5jb3JyZWxhdGlvbiA/PyBvdXRlci5jb3JyZWxhdGlvbiwgam9iOiBjdHguam9iID8geyAuLi5vdXRlci5qb2IsIC4uLmN0eC5qb2IgfSA6IG91dGVyLmpvYiB9XG4gICAgOiBjdHg7XG4gIHJldHVybiBzdG9yYWdlLnJ1bihtZXJnZWQsIGZuKTtcbn1cblxuLyoqIFRoZSBhY3RpdmUgYXVkaXQgY29udGV4dCwgb3IgdW5kZWZpbmVkIG91dHNpZGUgYW55IHNjb3BlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGN1cnJlbnRBdWRpdENvbnRleHQoKTogQXVkaXRDb250ZXh0IHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIHN0b3JhZ2UuZ2V0U3RvcmUoKTtcbn1cblxuLyoqIEluYm91bmQgY29ycmVsYXRpb24gaGVhZGVycyByZWNvcmRlZCBpbnRvIHRoZSBhdWRpdCBjb250ZXh0LiAqL1xuZXhwb3J0IGNvbnN0IEFVRElUX0NPUlJFTEFUSU9OX0hFQURFUlMgPSB7XG4gIHBpcGVsaW5lRXhlY3V0aW9uSWQ6ICd4LXBpcGVsaW5lLWV4ZWN1dGlvbi1pZCcsXG4gIGNvZGVidWlsZEJ1aWxkSWQ6ICd4LWNvZGVidWlsZC1idWlsZC1pZCcsXG4gIHNvdXJjZUV2ZW50SWQ6ICd4LXNvdXJjZS1ldmVudC1pZCcsXG59IGFzIGNvbnN0O1xuXG4vKiogTWF4IGxlbmd0aCBvZiBhIHN0b3JlZCBjb3JyZWxhdGlvbiBpZCAvIHJlcXVlc3QgaWQuICovXG5jb25zdCBNQVhfQ09SUkVMQVRJT05fTEVOID0gMTI4O1xuLyoqIElkcyBhcmUgb3BhcXVlIHRva2VuczogdXVpZHMsIGBwcm9qZWN0OnV1aWRgIGJ1aWxkIGlkcywgVUxJRHMuIE5vdGhpbmcgZWxzZSBpcyBzdG9yZWQuICovXG5jb25zdCBDT1JSRUxBVElPTl9JRCA9IC9eW0EtWmEtejAtOS5fOi9AKz0tXSskLztcbi8qKiBBIGJhcmUgQVdTIGFjY291bnQgaWQuICovXG5jb25zdCBCQVJFX0FDQ09VTlRfSUQgPSAvXlxcZHsxMn0kLztcblxuLyoqIE1heCBzdG9yZWQgVXNlci1BZ2VudCBsZW5ndGgg4oCUIGEgaG9zdGlsZSBoZWFkZXIgbXVzdCBub3QgYmxvYXQgYXVkaXQgcm93cy4gKi9cbmNvbnN0IE1BWF9VU0VSX0FHRU5UX0xFTiA9IDUxMjtcbi8qKiBDMCArIERFTCArIEMxIGNvbnRyb2wgY2hhcmFjdGVycywgcmVwbGFjZWQgc28gYSBVQSBjYW4ndCBpbmplY3QgaW50byB0aGUgVUkvQ1NWIGV4cG9ydC4gKi9cbmNvbnN0IENPTlRST0xfQ0hBUlMgPSBuZXcgUmVnRXhwKCdbXFxcXHUwMDAwLVxcXFx1MDAxZlxcXFx1MDA3Zi1cXFxcdTAwOWZdJywgJ2cnKTtcblxuZnVuY3Rpb24gZmlyc3RIZWFkZXIocmF3OiBzdHJpbmcgfCBzdHJpbmdbXSB8IHVuZGVmaW5lZCk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHJhdykgPyByYXdbMF0gOiByYXc7XG59XG5cbi8qKlxuICogVmFsaWRhdGUgYW4gaW5ib3VuZCBjb3JyZWxhdGlvbiBpZCAoYFgtUmVxdWVzdC1JZGAsIGBYLUNvZGVCdWlsZC1CdWlsZC1JZGAsIOKApik6XG4gKiBhIGJvdW5kZWQgb3BhcXVlIHRva2VuLCBlbHNlIGB1bmRlZmluZWRgIOKAlCBhbiBpbnZhbGlkIHZhbHVlIGlzIGRyb3BwZWQsIG5ldmVyXG4gKiB0cnVuY2F0ZWQgaW50byBzb21ldGhpbmcgdGhhdCBsb29rcyBsaWtlIGEgZGlmZmVyZW50IGlkLlxuICpcbiAqIE5vIEFXUyBhY2NvdW50IGlkIGlzIGV2ZXIgc3RvcmVkOiBhbiBBUk4gKGEgQ29kZUJ1aWxkIGJ1aWxkIEFSTiwgc2F5KSBoYXMgaXRzXG4gKiBhY2NvdW50IHNlZ21lbnQgZW1wdGllZCwgYW5kIGEgYmFyZSAxMi1kaWdpdCB2YWx1ZSBpcyByZWZ1c2VkLiBUaGUgZ2VuZXJpY1xuICogMTItZGlnaXQtcnVuIHNjcnViIGlzIE5PVCB1c2VkIGhlcmUg4oCUIGl0IHdvdWxkIHJlZGFjdCB0aGUgYWxsLWRpZ2l0IGZpbmFsXG4gKiBncm91cCBvZiByb3VnaGx5IG9uZSB1dWlkIGluIHRocmVlIGh1bmRyZWQsIGNvcnJ1cHRpbmcgdGhlIGlkIGl0IGV4aXN0cyB0b1xuICogY29ycmVsYXRlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2FuaXRpemVDb3JyZWxhdGlvbklkKHJhdzogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICh0eXBlb2YgcmF3ICE9PSAnc3RyaW5nJykgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgdmFsdWUgPSByYXcudHJpbSgpO1xuICBpZiAodmFsdWUubGVuZ3RoID09PSAwIHx8IHZhbHVlLmxlbmd0aCA+IE1BWF9DT1JSRUxBVElPTl9MRU4gfHwgIUNPUlJFTEFUSU9OX0lELnRlc3QodmFsdWUpKSByZXR1cm4gdW5kZWZpbmVkO1xuICBpZiAoQkFSRV9BQ0NPVU5UX0lELnRlc3QodmFsdWUpKSByZXR1cm4gdW5kZWZpbmVkO1xuICBpZiAoL15hcm46L2kudGVzdCh2YWx1ZSkpIHtcbiAgICBjb25zdCBwYXJ0cyA9IHZhbHVlLnNwbGl0KCc6Jyk7XG4gICAgaWYgKHBhcnRzLmxlbmd0aCA+IDQpIHBhcnRzWzRdID0gJyc7XG4gICAgcmV0dXJuIHBhcnRzLmpvaW4oJzonKTtcbiAgfVxuICByZXR1cm4gdmFsdWU7XG59XG5cbi8qKiBTdHJpcCBjb250cm9sIGNoYXJhY3RlcnMgZnJvbSBhIFVzZXItQWdlbnQgYW5kIGNhcCBpdHMgbGVuZ3RoOyB1bmRlZmluZWQgd2hlbiBlbXB0eS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzYW5pdGl6ZVVzZXJBZ2VudChyYXc6IHVua25vd24pOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBpZiAodHlwZW9mIHJhdyAhPT0gJ3N0cmluZycgfHwgcmF3Lmxlbmd0aCA9PT0gMCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgY2xlYW5lZCA9IHJhdy5yZXBsYWNlKENPTlRST0xfQ0hBUlMsICcgJykudHJpbSgpO1xuICByZXR1cm4gY2xlYW5lZC5sZW5ndGggPT09IDAgPyB1bmRlZmluZWQgOiBjbGVhbmVkLnNsaWNlKDAsIE1BWF9VU0VSX0FHRU5UX0xFTik7XG59XG5cbi8qKlxuICogVGhlIGF1ZGl0IGNvbnRleHQgZm9yIGFuIGluYm91bmQgcmVxdWVzdC4gYHJlcXVlc3RJZGAgaXMgdGhlIGlkIHRoZVxuICogcmVxdWVzdC1pZCBtaWRkbGV3YXJlIHJlc29sdmVkICh2YWxpZGF0ZWQgaW5ib3VuZCBgWC1SZXF1ZXN0LUlkYCwgZWxzZSBhIGZyZXNoXG4gKiB1dWlkKTsgdGhlIHByaW5jaXBhbCBpcyByZWFkIGxhemlseSBmcm9tIGByZXEudXNlcmAgKGF1dGggcnVucyBsYXRlciBpbiB0aGVcbiAqIGNoYWluKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0Q29udGV4dEZyb21SZXF1ZXN0KHJlcTogUmVxdWVzdCwgaWRzOiB7IHJlcXVlc3RJZD86IHN0cmluZzsgdHJhY2VJZD86IHN0cmluZyB9ID0ge30pOiBBdWRpdENvbnRleHQge1xuICBjb25zdCBjb3JyZWxhdGlvbjogQXVkaXRDb3JyZWxhdGlvbiA9IHt9O1xuICBmb3IgKGNvbnN0IFtmaWVsZCwgaGVhZGVyXSBvZiBPYmplY3QuZW50cmllcyhBVURJVF9DT1JSRUxBVElPTl9IRUFERVJTKSBhcyBBcnJheTxba2V5b2YgQXVkaXRDb3JyZWxhdGlvbiwgc3RyaW5nXT4pIHtcbiAgICBjb25zdCB2YWx1ZSA9IHNhbml0aXplQ29ycmVsYXRpb25JZChmaXJzdEhlYWRlcihyZXEuaGVhZGVyc1toZWFkZXJdKSk7XG4gICAgaWYgKHZhbHVlKSBjb3JyZWxhdGlvbltmaWVsZF0gPSB2YWx1ZTtcbiAgfVxuICByZXR1cm4ge1xuICAgIHJlcXVlc3RJZDogaWRzLnJlcXVlc3RJZCxcbiAgICB0cmFjZUlkOiBpZHMudHJhY2VJZCxcbiAgICBpcDogcmVxLmlwLFxuICAgIHVzZXJBZ2VudDogc2FuaXRpemVVc2VyQWdlbnQoZmlyc3RIZWFkZXIocmVxLmhlYWRlcnNbJ3VzZXItYWdlbnQnXSkpLFxuICAgIGNvcnJlbGF0aW9uOiBPYmplY3Qua2V5cyhjb3JyZWxhdGlvbikubGVuZ3RoID4gMCA/IGNvcnJlbGF0aW9uIDogdW5kZWZpbmVkLFxuICAgIHByaW5jaXBhbDogKCkgPT4gcmVxLnVzZXIsXG4gIH07XG59XG5cbi8qKiBUaGUgYF9hdXRoYCBibG9jayBmb3IgYSB2ZXJpZmllZCBwcmluY2lwYWwgKHVuZGVmaW5lZCB3aGVuIHRoZXJlIGlzIG5vbmUpLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGF1ZGl0QXV0aEJsb2NrKHVzZXI6IEp3dFBheWxvYWQgfCB1bmRlZmluZWQpOiBBdWRpdEF1dGhCbG9jayB8IHVuZGVmaW5lZCB7XG4gIGlmICghdXNlcikgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgYmxvY2s6IEF1ZGl0QXV0aEJsb2NrID0ge1xuICAgIHByaW5jaXBhbFR5cGU6IHVzZXIucHJpbmNpcGFsVHlwZSxcbiAgICBzZXJ2aWNlOiBzZXJ2aWNlTmFtZU9mKHVzZXIpLFxuICAgIGtleUlkOiB1c2VyLnBhcmVudEtleUlkID8/IHVzZXIuanRpLFxuICAgIHNpZDogdXNlci5zaWQsXG4gICAgc2NvcGU6IHVzZXIuc2NvcGUsXG4gICAgdG9rZW5Vc2U6IHVzZXIudG9rZW5fdXNlLFxuICAgIC4uLih1c2VyLnBlcm1pc3Npb25zUmVzdHJpY3RlZCA9PT0gdHJ1ZSA/IHsgcGVybWlzc2lvbnNSZXN0cmljdGVkOiB0cnVlIGFzIGNvbnN0IH0gOiB7fSksXG4gICAgLi4uKHVzZXIuaXNTdXBlckFkbWluID09PSB0cnVlID8geyBzdXBlckFkbWluOiB0cnVlIGFzIGNvbnN0IH0gOiB7fSksXG4gICAgLy8gQSBTRVJWSUNFIHRva2VuIGluIHRoZSBzeXN0ZW0gb3JnIGlzIHRoZSBwbGF0Zm9ybSdzIG93biBwbHVtYmluZywgbm90IHN0YWZmXG4gICAgLy8gYWN0aW5nIG9uIGEgdGVuYW50OyBvbmx5IGh1bWFuIC8gc2VydmljZS1hY2NvdW50IHByaW5jaXBhbHMgY291bnQuXG4gICAgLi4uKHVzZXIucHJpbmNpcGFsVHlwZSAhPT0gJ3NlcnZpY2UnICYmIGlzU3lzdGVtT3JnSWQodXNlci5vcmdhbml6YXRpb25JZCkgPyB7IHN5c3RlbU9yZzogdHJ1ZSBhcyBjb25zdCB9IDoge30pLFxuICB9O1xuICBmb3IgKGNvbnN0IGtleSBvZiBPYmplY3Qua2V5cyhibG9jaykgYXMgQXJyYXk8a2V5b2YgQXVkaXRBdXRoQmxvY2s+KSB7XG4gICAgaWYgKGJsb2NrW2tleV0gPT09IHVuZGVmaW5lZCkgZGVsZXRlIGJsb2NrW2tleV07XG4gIH1cbiAgcmV0dXJuIE9iamVjdC5rZXlzKGJsb2NrKS5sZW5ndGggPiAwID8gYmxvY2sgOiB1bmRlZmluZWQ7XG59XG4iXX0=