@pipeline-builder/api-core 4.1.2 → 4.1.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 (81) hide show
  1. package/README.md +3 -3
  2. package/lib/errors/app-errors.d.ts +0 -4
  3. package/lib/errors/app-errors.js +1 -8
  4. package/lib/helpers/restore-helpers.d.ts +3 -3
  5. package/lib/helpers/restore-helpers.js +4 -4
  6. package/lib/helpers/sse-helpers.d.ts +8 -1
  7. package/lib/helpers/sse-helpers.js +11 -3
  8. package/lib/middleware/auth.d.ts +174 -43
  9. package/lib/middleware/auth.js +384 -179
  10. package/lib/middleware/index.d.ts +1 -0
  11. package/lib/middleware/index.js +2 -1
  12. package/lib/middleware/route-table.d.ts +104 -0
  13. package/lib/middleware/route-table.js +230 -0
  14. package/lib/middleware/step-up.d.ts +48 -16
  15. package/lib/middleware/step-up.js +74 -36
  16. package/lib/services/api-key-exchange.d.ts +25 -0
  17. package/lib/services/api-key-exchange.js +219 -0
  18. package/lib/services/audit-spool.d.ts +2 -3
  19. package/lib/services/audit-spool.js +3 -4
  20. package/lib/services/cache-service.d.ts +65 -21
  21. package/lib/services/cache-service.js +173 -78
  22. package/lib/services/compliance-event-subscriber.d.ts +5 -4
  23. package/lib/services/compliance-event-subscriber.js +8 -6
  24. package/lib/services/durable-event-bus.d.ts +22 -39
  25. package/lib/services/durable-event-bus.js +109 -47
  26. package/lib/services/env-redis.d.ts +114 -35
  27. package/lib/services/env-redis.js +215 -73
  28. package/lib/services/http-client.d.ts +10 -15
  29. package/lib/services/http-client.js +32 -27
  30. package/lib/services/index.d.ts +3 -0
  31. package/lib/services/index.js +4 -1
  32. package/lib/services/jwks-cache.d.ts +102 -0
  33. package/lib/services/jwks-cache.js +222 -0
  34. package/lib/services/leader-lock.d.ts +6 -3
  35. package/lib/services/leader-lock.js +20 -5
  36. package/lib/services/quota.d.ts +44 -10
  37. package/lib/services/quota.js +95 -51
  38. package/lib/services/remote-audit-client.d.ts +1 -1
  39. package/lib/services/remote-audit-client.js +32 -1
  40. package/lib/services/scheduler.d.ts +2 -3
  41. package/lib/services/scheduler.js +2 -2
  42. package/lib/services/service-boot.d.ts +4 -2
  43. package/lib/services/service-boot.js +7 -3
  44. package/lib/services/service-keys.d.ts +83 -0
  45. package/lib/services/service-keys.js +354 -0
  46. package/lib/services/sse-ticket-store.d.ts +55 -22
  47. package/lib/services/sse-ticket-store.js +122 -79
  48. package/lib/services/token-revocation.d.ts +1 -1
  49. package/lib/services/token-revocation.js +31 -19
  50. package/lib/testing/route-coverage.d.ts +64 -0
  51. package/lib/testing/route-coverage.js +183 -0
  52. package/lib/testing/service-tokens.d.ts +66 -0
  53. package/lib/testing/service-tokens.js +137 -0
  54. package/lib/testing/tier-mock.d.ts +2 -2
  55. package/lib/testing/tier-mock.js +3 -3
  56. package/lib/testing/user-tokens.d.ts +75 -0
  57. package/lib/testing/user-tokens.js +115 -0
  58. package/lib/types/catalog-metadata.d.ts +0 -2
  59. package/lib/types/catalog-metadata.js +1 -3
  60. package/lib/types/common.d.ts +103 -4
  61. package/lib/types/common.js +11 -1
  62. package/lib/types/error-codes.d.ts +15 -0
  63. package/lib/types/error-codes.js +22 -1
  64. package/lib/types/permissions.d.ts +35 -1
  65. package/lib/types/permissions.js +70 -2
  66. package/lib/utils/api-key.d.ts +34 -0
  67. package/lib/utils/api-key.js +75 -0
  68. package/lib/utils/env.d.ts +0 -2
  69. package/lib/utils/env.js +1 -6
  70. package/lib/utils/index.d.ts +3 -0
  71. package/lib/utils/index.js +4 -1
  72. package/lib/utils/jwk.d.ts +98 -0
  73. package/lib/utils/jwk.js +139 -0
  74. package/lib/utils/logger.d.ts +4 -1
  75. package/lib/utils/logger.js +1 -22
  76. package/lib/utils/secret-encryption.d.ts +7 -2
  77. package/lib/utils/secret-encryption.js +47 -4
  78. package/lib/utils/secret-rotation.d.ts +17 -0
  79. package/lib/utils/secret-rotation.js +54 -0
  80. package/lib/validation/common-schemas.js +5 -4
  81. package/package.json +20 -1
@@ -0,0 +1,222 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { JWKS_PATH, isJwksDocument, publicKeyFromJwk, } from '../utils/jwk.js';
4
+ import { createLogger } from '../utils/logger.js';
5
+ import { emitCounter } from '../utils/metric-emitter.js';
6
+ const logger = createLogger('jwks-cache');
7
+ /** How long a fetched key set is served before a refresh is due. */
8
+ export const JWKS_REFRESH_INTERVAL_MS = 600_000; // 10 minutes
9
+ /** How long a fetch FAILURE suppresses further attempts. */
10
+ export const JWKS_NEGATIVE_TTL_MS = 10_000;
11
+ /** Minimum gap between unknown-`kid` refetches, so forged kids can't stampede platform. */
12
+ export const UNKNOWN_KID_REFETCH_COOLDOWN_MS = 30_000;
13
+ /** The key set could not be obtained — the token's validity is UNKNOWN (503-shaped). */
14
+ export class JwksUnavailableError extends Error {
15
+ constructor(message = 'Token signing keys are temporarily unavailable') {
16
+ super(message);
17
+ this.name = 'JwksUnavailableError';
18
+ }
19
+ }
20
+ /** The key set was obtained and does NOT contain the token's `kid` (401-shaped). */
21
+ export class UnknownKidError extends Error {
22
+ constructor(kid) {
23
+ super(`No published signing key for kid ${kid}`);
24
+ this.name = 'UnknownKidError';
25
+ }
26
+ }
27
+ /**
28
+ * A key set with the freshness bookkeeping the contract above needs. Deliberately
29
+ * transport-agnostic: the CLI and the Lambda hand it their own fetcher.
30
+ */
31
+ export class JwksCache {
32
+ options;
33
+ keys = new Map();
34
+ fetchedAt = 0;
35
+ failedUntil = 0;
36
+ lastUnknownKidFetchAt = 0;
37
+ inFlight;
38
+ refreshIntervalMs;
39
+ negativeTtlMs;
40
+ unknownKidCooldownMs;
41
+ source;
42
+ constructor(options) {
43
+ this.options = options;
44
+ this.refreshIntervalMs = options.refreshIntervalMs ?? JWKS_REFRESH_INTERVAL_MS;
45
+ this.negativeTtlMs = options.negativeTtlMs ?? JWKS_NEGATIVE_TTL_MS;
46
+ this.unknownKidCooldownMs = options.unknownKidCooldownMs ?? UNKNOWN_KID_REFETCH_COOLDOWN_MS;
47
+ this.source = options.source ?? 'platform';
48
+ }
49
+ /**
50
+ * The verification key for `kid`.
51
+ *
52
+ * @throws {JwksUnavailableError} the key set could not be obtained at all.
53
+ * @throws {UnknownKidError} the key set is in hand but has no such key.
54
+ */
55
+ async getKey(kid) {
56
+ await this.ensureFresh();
57
+ const key = this.keys.get(kid);
58
+ if (key)
59
+ return key;
60
+ // Unknown kid: platform may have JUST rotated. Refetch once (rate-limited),
61
+ // then decide. A cached-but-stale set that still lacks the kid after the
62
+ // refetch is a real rejection, not a freshness problem.
63
+ const now = Date.now();
64
+ if (now - this.lastUnknownKidFetchAt >= this.unknownKidCooldownMs) {
65
+ this.lastUnknownKidFetchAt = now;
66
+ emitCounter('jwks_unknown_kid_refetch_total', { source: this.source });
67
+ await this.refresh({ force: true }).catch(() => { });
68
+ const refreshed = this.keys.get(kid);
69
+ if (refreshed)
70
+ return refreshed;
71
+ }
72
+ if (this.keys.size === 0)
73
+ throw new JwksUnavailableError();
74
+ throw new UnknownKidError(kid);
75
+ }
76
+ /** Every currently-trusted `kid`. Exposed for diagnostics and tests. */
77
+ knownKids() {
78
+ return [...this.keys.keys()];
79
+ }
80
+ /** Drop everything, so the next lookup refetches. */
81
+ reset() {
82
+ this.keys.clear();
83
+ this.fetchedAt = 0;
84
+ this.failedUntil = 0;
85
+ this.lastUnknownKidFetchAt = 0;
86
+ this.inFlight = undefined;
87
+ }
88
+ /** Refetch when the set is missing or past its refresh interval. */
89
+ async ensureFresh() {
90
+ const now = Date.now();
91
+ const stale = now - this.fetchedAt >= this.refreshIntervalMs;
92
+ if (this.keys.size > 0 && !stale)
93
+ return;
94
+ if (now < this.failedUntil) {
95
+ // Inside the negative-cache window. Serve a key set we already hold
96
+ // (public keys don't expire); with nothing in hand, fail closed.
97
+ if (this.keys.size > 0)
98
+ return;
99
+ throw new JwksUnavailableError();
100
+ }
101
+ try {
102
+ await this.refresh({ force: false });
103
+ }
104
+ catch (error) {
105
+ if (this.keys.size > 0) {
106
+ logger.warn('JWKS refresh failed; continuing on the cached key set', {
107
+ source: this.source,
108
+ error: error instanceof Error ? error.message : String(error),
109
+ });
110
+ return;
111
+ }
112
+ throw new JwksUnavailableError();
113
+ }
114
+ }
115
+ /** One real fetch, shared by every concurrent caller. */
116
+ refresh(opts) {
117
+ if (this.inFlight)
118
+ return this.inFlight;
119
+ if (!opts.force && Date.now() < this.failedUntil)
120
+ return Promise.reject(new JwksUnavailableError());
121
+ const promise = this.performFetch().finally(() => { this.inFlight = undefined; });
122
+ this.inFlight = promise;
123
+ return promise;
124
+ }
125
+ async performFetch() {
126
+ let document;
127
+ try {
128
+ document = await this.options.fetch();
129
+ }
130
+ catch (error) {
131
+ this.failedUntil = Date.now() + this.negativeTtlMs;
132
+ emitCounter('jwks_fetch_total', { source: this.source, result: 'error' });
133
+ logger.warn('JWKS fetch failed', {
134
+ source: this.source,
135
+ error: error instanceof Error ? error.message : String(error),
136
+ });
137
+ throw error instanceof Error ? error : new Error(String(error));
138
+ }
139
+ const keys = new Map();
140
+ for (const jwk of document.keys) {
141
+ if (!jwk?.kid)
142
+ continue;
143
+ try {
144
+ keys.set(jwk.kid, publicKeyFromJwk(jwk));
145
+ }
146
+ catch (error) {
147
+ // One unusable entry must not discard the rest of the set.
148
+ logger.warn('Skipping unusable JWKS entry', { kid: jwk.kid, error: error instanceof Error ? error.message : String(error) });
149
+ }
150
+ }
151
+ if (keys.size === 0) {
152
+ this.failedUntil = Date.now() + this.negativeTtlMs;
153
+ emitCounter('jwks_fetch_total', { source: this.source, result: 'empty' });
154
+ throw new Error('JWKS document contained no usable keys');
155
+ }
156
+ this.keys = keys;
157
+ this.fetchedAt = Date.now();
158
+ this.failedUntil = 0;
159
+ emitCounter('jwks_fetch_total', { source: this.source, result: 'success' });
160
+ }
161
+ }
162
+ /**
163
+ * Fetch platform's JWKS over the in-cluster address every other caller uses
164
+ * (`PLATFORM_SERVICE_HOST` / `PLATFORM_SERVICE_PORT`), or the absolute
165
+ * `PLATFORM_JWKS_URL` when one is configured (the CLI and the events Lambda run
166
+ * outside the cluster).
167
+ *
168
+ * Unauthenticated by design: the key set is public, and requiring a credential
169
+ * to fetch the keys that verify credentials is a bootstrap cycle.
170
+ */
171
+ export async function fetchPlatformJwks() {
172
+ const absolute = process.env.PLATFORM_JWKS_URL;
173
+ const timeout = parseInt(process.env.JWKS_FETCH_TIMEOUT_MS || '3000', 10);
174
+ if (absolute)
175
+ return fetchJwksFromUrl(absolute, timeout);
176
+ // Loaded LAZILY, on the first fetch. A static import would put the HTTP client
177
+ // (and platform's address) into the import graph of every module that merely
178
+ // uses `requireAuth` — the same reason `api-key-exchange` is lazy, and what
179
+ // keeps partial `http-client.js` mocks in the service suites working.
180
+ const { InternalHttpClient } = await import('./http-client.js');
181
+ const client = new InternalHttpClient({
182
+ host: process.env.PLATFORM_SERVICE_HOST || 'platform',
183
+ port: parseInt(process.env.PLATFORM_SERVICE_PORT || '3000', 10),
184
+ timeout,
185
+ });
186
+ const res = await client.get(JWKS_PATH, { idempotent: true });
187
+ if (res.statusCode !== 200 || !isJwksDocument(res.body)) {
188
+ throw new Error(`Unexpected JWKS response (status ${res.statusCode})`);
189
+ }
190
+ return res.body;
191
+ }
192
+ /** Fetch a JWKS document from an absolute URL (outside-the-cluster verifiers). */
193
+ export async function fetchJwksFromUrl(url, timeoutMs = 3000) {
194
+ const controller = new AbortController();
195
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
196
+ try {
197
+ const res = await fetch(url, { signal: controller.signal, headers: { accept: 'application/json' } });
198
+ if (!res.ok)
199
+ throw new Error(`JWKS fetch returned ${res.status}`);
200
+ const body = await res.json();
201
+ if (!isJwksDocument(body))
202
+ throw new Error('JWKS response was not a key set');
203
+ return body;
204
+ }
205
+ finally {
206
+ clearTimeout(timer);
207
+ }
208
+ }
209
+ let platformJwks;
210
+ /** The process-wide cache of platform's published signing keys. */
211
+ export function platformJwksCache() {
212
+ platformJwks ??= new JwksCache({ fetch: fetchPlatformJwks, source: 'platform' });
213
+ return platformJwks;
214
+ }
215
+ /**
216
+ * Replace the process-wide cache — used by tests, and by any host that must
217
+ * supply its own fetcher. Pass `undefined` to drop back to the default.
218
+ */
219
+ export function setPlatformJwksCache(cache) {
220
+ platformJwks = cache;
221
+ }
222
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiandrcy1jYWNoZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9qd2tzLWNhY2hlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUE0QnRDLE9BQU8sRUFDTCxTQUFTLEVBQ1QsY0FBYyxFQUNkLGdCQUFnQixHQUdqQixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFekQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBRTFDLG9FQUFvRTtBQUNwRSxNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxPQUFPLENBQUMsQ0FBQyxhQUFhO0FBRTlELDREQUE0RDtBQUM1RCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxNQUFNLENBQUM7QUFFM0MsMkZBQTJGO0FBQzNGLE1BQU0sQ0FBQyxNQUFNLCtCQUErQixHQUFHLE1BQU0sQ0FBQztBQUV0RCx3RkFBd0Y7QUFDeEYsTUFBTSxPQUFPLG9CQUFxQixTQUFRLEtBQUs7SUFDN0MsWUFBWSxPQUFPLEdBQUcsZ0RBQWdEO1FBQ3BFLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNmLElBQUksQ0FBQyxJQUFJLEdBQUcsc0JBQXNCLENBQUM7SUFDckMsQ0FBQztDQUNGO0FBRUQsb0ZBQW9GO0FBQ3BGLE1BQU0sT0FBTyxlQUFnQixTQUFRLEtBQUs7SUFDeEMsWUFBWSxHQUFXO1FBQ3JCLEtBQUssQ0FBQyxvQ0FBb0MsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUNqRCxJQUFJLENBQUMsSUFBSSxHQUFHLGlCQUFpQixDQUFDO0lBQ2hDLENBQUM7Q0FDRjtBQWNEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxTQUFTO0lBWVMsT0FBTztJQVg1QixJQUFJLEdBQUcsSUFB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RyxFQUFFLENBQUMsVUFBVSxDQUFDLEtBQUssRUFBRSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQzlELElBQUksQ0FBQztRQUNILE1BQU0sR0FBRyxHQUFHLE1BQU0sS0FBSyxDQUFDLEdBQUcsRUFBRSxFQUFFLE1BQU0sRUFBRSxVQUFVLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxFQUFFLE1BQU0sRUFBRSxrQkFBa0IsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNyRyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUU7WUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixHQUFHLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUNsRSxNQUFNLElBQUksR0FBWSxNQUFNLEdBQUcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN2QyxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQztZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztRQUM5RSxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7WUFBUyxDQUFDO1FBQ1QsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3RCLENBQUM7QUFDSCxDQUFDO0FBRUQsSUFBSSxZQUFtQyxDQUFDO0FBRXhDLG1FQUFtRTtBQUNuRSxNQUFNLFVBQVUsaUJBQWlCO0lBQy9CLFlBQVksS0FBSyxJQUFJLFNBQVMsQ0FBQyxFQUFFLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUNqRixPQUFPLFlBQVksQ0FBQztBQUN0QixDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFDLEtBQTRCO0lBQy9ELFlBQVksR0FBRyxLQUFLLENBQUM7QUFDdkIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRoZSBKV0tTIGNhY2hlIGV2ZXJ5IHZlcmlmaWVyIGluIHRoZSBmbGVldCBzaGFyZXMuXG4gKlxuICogUGxhdGZvcm0gc2lnbnMgdXNlciB0b2tlbnMgd2l0aCBFUzI1NiBhbmQgcHVibGlzaGVzIHRoZSBwdWJsaWMgaGFsdmVzIGF0XG4gKiBgLy53ZWxsLWtub3duL2p3a3MuanNvbmAuIFZlcmlmaWVycyBtdXN0IG5vdCBmZXRjaCB0aGF0IHBlciByZXF1ZXN0IChwbGF0Zm9ybVxuICogd291bGQgbGFuZCBvbiB0aGUgaG90IHBhdGggb2YgdGhlIHdob2xlIGZsZWV0KSBub3IgY2FjaGUgaXQgZm9yZXZlciAoYSByb3RhdGVkXG4gKiBrZXkgd291bGQgbmV2ZXIgYmUgcGlja2VkIHVwKSwgc28gdGhlIGNvbnRyYWN0IGlzIGZpeGVkIGhlcmUgYW5kIHJldXNlZCBieVxuICogYXBpLWNvcmUncyBgcmVxdWlyZUF1dGhgLCBpbWFnZS1yZWdpc3RyeSdzIGF1dGggcmVzb2x2ZXIgYW5kIHRoZSBDTEk6XG4gKlxuICogLSAqKlJlZnJlc2ggZXZlcnkgMTAgbWludXRlcy4qKiBBIGtleSByb3RhdGVkIGluIGF0IHBsYXRmb3JtIGlzIHRydXN0ZWQgZmxlZXQtXG4gKiAgIHdpZGUgd2l0aGluIHRoYXQgd2luZG93IHdpdGhvdXQgYW55b25lIHJlc3RhcnRpbmcuXG4gKiAtICoqUmVmZXRjaCBvbmNlIG9uIGFuIHVua25vd24gYGtpZGAuKiogQSBicmFuZC1uZXcgc2lnbmluZyBrZXkgaXMgcGlja2VkIHVwXG4gKiAgIGltbWVkaWF0ZWx5IGluc3RlYWQgb2YgNDAxLWluZyBldmVyeSByZXF1ZXN0IHVudGlsIHRoZSBuZXh0IHJlZnJlc2guIFJhdGUtXG4gKiAgIGxpbWl0ZWQgKHtAbGluayBVTktOT1dOX0tJRF9SRUZFVENIX0NPT0xET1dOX01TfSkgc28gYSBmbG9vZCBvZiBmb3JnZWQgYGtpZGBzXG4gKiAgIGNhbid0IHR1cm4gaW50byBhIGZldGNoIHN0b3JtIGFnYWluc3QgcGxhdGZvcm0uXG4gKiAtICoqTmVnYXRpdmUtY2FjaGUgZmFpbHVyZXMgYnJpZWZseS4qKiBBIHBsYXRmb3JtIG91dGFnZSBkb2Vzbid0IGJlY29tZSBhXG4gKiAgIGZldGNoLXBlci1yZXF1ZXN0IGFtcGxpZmllci5cbiAqIC0gKipGYWlsIGNsb3NlZC4qKiBBbiB1bnZlcmlmaWFibGUgdG9rZW4gaXMgbm90IGFuIGlkZW50aXR5OiB3aGVuIHRoZSBrZXkgc2V0XG4gKiAgIGNhbm5vdCBiZSBvYnRhaW5lZCB0aGUgdmVyaWZpZXIgdGhyb3dzIHtAbGluayBKd2tzVW5hdmFpbGFibGVFcnJvcn0gYW5kIHRoZVxuICogICBjYWxsZXIgYW5zd2VycyA0MDEvNTAzIOKAlCBpdCBORVZFUiBmYWxscyB0aHJvdWdoIHRvIFwiYWxsb3dcIi5cbiAqICAgQSBrZXkgc2V0IGFscmVhZHkgaW4gaGFuZCBpcyBzdGlsbCB1c2VkIHdoaWxlIGEgcmVmcmVzaCBpcyBmYWlsaW5nICh0aGUga2V5c1xuICogICBhcmUgcHVibGljIGFuZCBkb24ndCBleHBpcmU7IHJlZnVzaW5nIGV2ZXJ5IHJlcXVlc3QgYmVjYXVzZSBwbGF0Zm9ybSBibGlwcGVkXG4gKiAgIHdvdWxkIGJlIGEgc2VsZi1pbmZsaWN0ZWQgb3V0YWdlKSwgd2hpY2ggaXMgd2h5IHRoZSBuZWdhdGl2ZSBjYWNoZSBleGlzdHMuXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBLZXlPYmplY3QgfSBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHtcbiAgSldLU19QQVRILFxuICBpc0p3a3NEb2N1bWVudCxcbiAgcHVibGljS2V5RnJvbUp3ayxcbiAgdHlwZSBKd2tzRG9jdW1lbnQsXG4gIHR5cGUgUHVibGljSndrLFxufSBmcm9tICcuLi91dGlscy9qd2suanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IGVtaXRDb3VudGVyIH0gZnJvbSAnLi4vdXRpbHMvbWV0cmljLWVtaXR0ZXIuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ2p3a3MtY2FjaGUnKTtcblxuLyoqIEhvdyBsb25nIGEgZmV0Y2hlZCBrZXkgc2V0IGlzIHNlcnZlZCBiZWZvcmUgYSByZWZyZXNoIGlzIGR1ZS4gKi9cbmV4cG9ydCBjb25zdCBKV0tTX1JFRlJFU0hfSU5URVJWQUxfTVMgPSA2MDBfMDAwOyAvLyAxMCBtaW51dGVzXG5cbi8qKiBIb3cgbG9uZyBhIGZldGNoIEZBSUxVUkUgc3VwcHJlc3NlcyBmdXJ0aGVyIGF0dGVtcHRzLiAqL1xuZXhwb3J0IGNvbnN0IEpXS1NfTkVHQVRJVkVfVFRMX01TID0gMTBfMDAwO1xuXG4vKiogTWluaW11bSBnYXAgYmV0d2VlbiB1bmtub3duLWBraWRgIHJlZmV0Y2hlcywgc28gZm9yZ2VkIGtpZHMgY2FuJ3Qgc3RhbXBlZGUgcGxhdGZvcm0uICovXG5leHBvcnQgY29uc3QgVU5LTk9XTl9LSURfUkVGRVRDSF9DT09MRE9XTl9NUyA9IDMwXzAwMDtcblxuLyoqIFRoZSBrZXkgc2V0IGNvdWxkIG5vdCBiZSBvYnRhaW5lZCDigJQgdGhlIHRva2VuJ3MgdmFsaWRpdHkgaXMgVU5LTk9XTiAoNTAzLXNoYXBlZCkuICovXG5leHBvcnQgY2xhc3MgSndrc1VuYXZhaWxhYmxlRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2UgPSAnVG9rZW4gc2lnbmluZyBrZXlzIGFyZSB0ZW1wb3JhcmlseSB1bmF2YWlsYWJsZScpIHtcbiAgICBzdXBlcihtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnSndrc1VuYXZhaWxhYmxlRXJyb3InO1xuICB9XG59XG5cbi8qKiBUaGUga2V5IHNldCB3YXMgb2J0YWluZWQgYW5kIGRvZXMgTk9UIGNvbnRhaW4gdGhlIHRva2VuJ3MgYGtpZGAgKDQwMS1zaGFwZWQpLiAqL1xuZXhwb3J0IGNsYXNzIFVua25vd25LaWRFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3Ioa2lkOiBzdHJpbmcpIHtcbiAgICBzdXBlcihgTm8gcHVibGlzaGVkIHNpZ25pbmcga2V5IGZvciBraWQgJHtraWR9YCk7XG4gICAgdGhpcy5uYW1lID0gJ1Vua25vd25LaWRFcnJvcic7XG4gIH1cbn1cblxuLyoqIEZldGNoZXMgdGhlIHJhdyBKV0tTIGRvY3VtZW50LiBSZWplY3RzIG9uIGFueSB0cmFuc3BvcnQvc2hhcGUgZmFpbHVyZS4gKi9cbmV4cG9ydCB0eXBlIEp3a3NGZXRjaGVyID0gKCkgPT4gUHJvbWlzZTxKd2tzRG9jdW1lbnQ+O1xuXG5leHBvcnQgaW50ZXJmYWNlIEp3a3NDYWNoZU9wdGlvbnMge1xuICBmZXRjaDogSndrc0ZldGNoZXI7XG4gIC8qKiBGb3IgbWV0cmljcy9sb2dnaW5nIChlLmcuIGAncGxhdGZvcm0nYCkuICovXG4gIHNvdXJjZT86IHN0cmluZztcbiAgcmVmcmVzaEludGVydmFsTXM/OiBudW1iZXI7XG4gIG5lZ2F0aXZlVHRsTXM/OiBudW1iZXI7XG4gIHVua25vd25LaWRDb29sZG93bk1zPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIEEga2V5IHNldCB3aXRoIHRoZSBmcmVzaG5lc3MgYm9va2tlZXBpbmcgdGhlIGNvbnRyYWN0IGFib3ZlIG5lZWRzLiBEZWxpYmVyYXRlbHlcbiAqIHRyYW5zcG9ydC1hZ25vc3RpYzogdGhlIENMSSBhbmQgdGhlIExhbWJkYSBoYW5kIGl0IHRoZWlyIG93biBmZXRjaGVyLlxuICovXG5leHBvcnQgY2xhc3MgSndrc0NhY2hlIHtcbiAgcHJpdmF0ZSBrZXlzID0gbmV3IE1hcDxzdHJpbmcsIEtleU9iamVjdD4oKTtcbiAgcHJpdmF0ZSBmZXRjaGVkQXQgPSAwO1xuICBwcml2YXRlIGZhaWxlZFVudGlsID0gMDtcbiAgcHJpdmF0ZSBsYXN0VW5rbm93bktpZEZldGNoQXQgPSAwO1xuICBwcml2YXRlIGluRmxpZ2h0OiBQcm9taXNlPHZvaWQ+IHwgdW5kZWZpbmVkO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgcmVmcmVzaEludGVydmFsTXM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBuZWdhdGl2ZVR0bE1zOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgdW5rbm93bktpZENvb2xkb3duTXM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBzb3VyY2U6IHN0cmluZztcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IG9wdGlvbnM6IEp3a3NDYWNoZU9wdGlvbnMpIHtcbiAgICB0aGlzLnJlZnJlc2hJbnRlcnZhbE1zID0gb3B0aW9ucy5yZWZyZXNoSW50ZXJ2YWxNcyA/PyBKV0tTX1JFRlJFU0hfSU5URVJWQUxfTVM7XG4gICAgdGhpcy5uZWdhdGl2ZVR0bE1zID0gb3B0aW9ucy5uZWdhdGl2ZVR0bE1zID8/IEpXS1NfTkVHQVRJVkVfVFRMX01TO1xuICAgIHRoaXMudW5rbm93bktpZENvb2xkb3duTXMgPSBvcHRpb25zLnVua25vd25LaWRDb29sZG93bk1zID8/IFVOS05PV05fS0lEX1JFRkVUQ0hfQ09PTERPV05fTVM7XG4gICAgdGhpcy5zb3VyY2UgPSBvcHRpb25zLnNvdXJjZSA/PyAncGxhdGZvcm0nO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJpZmljYXRpb24ga2V5IGZvciBga2lkYC5cbiAgICpcbiAgICogQHRocm93cyB7Sndrc1VuYXZhaWxhYmxlRXJyb3J9IHRoZSBrZXkgc2V0IGNvdWxkIG5vdCBiZSBvYnRhaW5lZCBhdCBhbGwuXG4gICAqIEB0aHJvd3Mge1Vua25vd25LaWRFcnJvcn0gdGhlIGtleSBzZXQgaXMgaW4gaGFuZCBidXQgaGFzIG5vIHN1Y2gga2V5LlxuICAgKi9cbiAgYXN5bmMgZ2V0S2V5KGtpZDogc3RyaW5nKTogUHJvbWlzZTxLZXlPYmplY3Q+IHtcbiAgICBhd2FpdCB0aGlzLmVuc3VyZUZyZXNoKCk7XG4gICAgY29uc3Qga2V5ID0gdGhpcy5rZXlzLmdldChraWQpO1xuICAgIGlmIChrZXkpIHJldHVybiBrZXk7XG5cbiAgICAvLyBVbmtub3duIGtpZDogcGxhdGZvcm0gbWF5IGhhdmUgSlVTVCByb3RhdGVkLiBSZWZldGNoIG9uY2UgKHJhdGUtbGltaXRlZCksXG4gICAgLy8gdGhlbiBkZWNpZGUuIEEgY2FjaGVkLWJ1dC1zdGFsZSBzZXQgdGhhdCBzdGlsbCBsYWNrcyB0aGUga2lkIGFmdGVyIHRoZVxuICAgIC8vIHJlZmV0Y2ggaXMgYSByZWFsIHJlamVjdGlvbiwgbm90IGEgZnJlc2huZXNzIHByb2JsZW0uXG4gICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICBpZiAobm93IC0gdGhpcy5sYXN0VW5rbm93bktpZEZldGNoQXQgPj0gdGhpcy51bmtub3duS2lkQ29vbGRvd25Ncykge1xuICAgICAgdGhpcy5sYXN0VW5rbm93bktpZEZldGNoQXQgPSBub3c7XG4gICAgICBlbWl0Q291bnRlcignandrc191bmtub3duX2tpZF9yZWZldGNoX3RvdGFsJywgeyBzb3VyY2U6IHRoaXMuc291cmNlIH0pO1xuICAgICAgYXdhaXQgdGhpcy5yZWZyZXNoKHsgZm9yY2U6IHRydWUgfSkuY2F0Y2goKCkgPT4geyAvKiBoYW5kbGVkIGJlbG93IGJ5IHRoZSBtaXNzICovIH0pO1xuICAgICAgY29uc3QgcmVmcmVzaGVkID0gdGhpcy5rZXlzLmdldChraWQpO1xuICAgICAgaWYgKHJlZnJlc2hlZCkgcmV0dXJuIHJlZnJlc2hlZDtcbiAgICB9XG4gICAgaWYgKHRoaXMua2V5cy5zaXplID09PSAwKSB0aHJvdyBuZXcgSndrc1VuYXZhaWxhYmxlRXJyb3IoKTtcbiAgICB0aHJvdyBuZXcgVW5rbm93bktpZEVycm9yKGtpZCk7XG4gIH1cblxuICAvKiogRXZlcnkgY3VycmVudGx5LXRydXN0ZWQgYGtpZGAuIEV4cG9zZWQgZm9yIGRpYWdub3N0aWNzIGFuZCB0ZXN0cy4gKi9cbiAga25vd25LaWRzKCk6IHN0cmluZ1tdIHtcbiAgICByZXR1cm4gWy4uLnRoaXMua2V5cy5rZXlzKCldO1xuICB9XG5cbiAgLyoqIERyb3AgZXZlcnl0aGluZywgc28gdGhlIG5leHQgbG9va3VwIHJlZmV0Y2hlcy4gKi9cbiAgcmVzZXQoKTogdm9pZCB7XG4gICAgdGhpcy5rZXlzLmNsZWFyKCk7XG4gICAgdGhpcy5mZXRjaGVkQXQgPSAwO1xuICAgIHRoaXMuZmFpbGVkVW50aWwgPSAwO1xuICAgIHRoaXMubGFzdFVua25vd25LaWRGZXRjaEF0ID0gMDtcbiAgICB0aGlzLmluRmxpZ2h0ID0gdW5kZWZpbmVkO1xuICB9XG5cbiAgLyoqIFJlZmV0Y2ggd2hlbiB0aGUgc2V0IGlzIG1pc3Npbmcgb3IgcGFzdCBpdHMgcmVmcmVzaCBpbnRlcnZhbC4gKi9cbiAgcHJpdmF0ZSBhc3luYyBlbnN1cmVGcmVzaCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGNvbnN0IHN0YWxlID0gbm93IC0gdGhpcy5mZXRjaGVkQXQgPj0gdGhpcy5yZWZyZXNoSW50ZXJ2YWxNcztcbiAgICBpZiAodGhpcy5rZXlzLnNpemUgPiAwICYmICFzdGFsZSkgcmV0dXJuO1xuXG4gICAgaWYgKG5vdyA8IHRoaXMuZmFpbGVkVW50aWwpIHtcbiAgICAgIC8vIEluc2lkZSB0aGUgbmVnYXRpdmUtY2FjaGUgd2luZG93LiBTZXJ2ZSBhIGtleSBzZXQgd2UgYWxyZWFkeSBob2xkXG4gICAgICAvLyAocHVibGljIGtleXMgZG9uJ3QgZXhwaXJlKTsgd2l0aCBub3RoaW5nIGluIGhhbmQsIGZhaWwgY2xvc2VkLlxuICAgICAgaWYgKHRoaXMua2V5cy5zaXplID4gMCkgcmV0dXJuO1xuICAgICAgdGhyb3cgbmV3IEp3a3NVbmF2YWlsYWJsZUVycm9yKCk7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHRoaXMucmVmcmVzaCh7IGZvcmNlOiBmYWxzZSB9KTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKHRoaXMua2V5cy5zaXplID4gMCkge1xuICAgICAgICBsb2dnZXIud2FybignSldLUyByZWZyZXNoIGZhaWxlZDsgY29udGludWluZyBvbiB0aGUgY2FjaGVkIGtleSBzZXQnLCB7XG4gICAgICAgICAgc291cmNlOiB0aGlzLnNvdXJjZSxcbiAgICAgICAgICBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpLFxuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhyb3cgbmV3IEp3a3NVbmF2YWlsYWJsZUVycm9yKCk7XG4gICAgfVxuICB9XG5cbiAgLyoqIE9uZSByZWFsIGZldGNoLCBzaGFyZWQgYnkgZXZlcnkgY29uY3VycmVudCBjYWxsZXIuICovXG4gIHByaXZhdGUgcmVmcmVzaChvcHRzOiB7IGZvcmNlOiBib29sZWFuIH0pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5pbkZsaWdodCkgcmV0dXJuIHRoaXMuaW5GbGlnaHQ7XG4gICAgaWYgKCFvcHRzLmZvcmNlICYmIERhdGUubm93KCkgPCB0aGlzLmZhaWxlZFVudGlsKSByZXR1cm4gUHJvbWlzZS5yZWplY3QobmV3IEp3a3NVbmF2YWlsYWJsZUVycm9yKCkpO1xuICAgIGNvbnN0IHByb21pc2UgPSB0aGlzLnBlcmZvcm1GZXRjaCgpLmZpbmFsbHkoKCkgPT4geyB0aGlzLmluRmxpZ2h0ID0gdW5kZWZpbmVkOyB9KTtcbiAgICB0aGlzLmluRmxpZ2h0ID0gcHJvbWlzZTtcbiAgICByZXR1cm4gcHJvbWlzZTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgcGVyZm9ybUZldGNoKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGxldCBkb2N1bWVudDogSndrc0RvY3VtZW50O1xuICAgIHRyeSB7XG4gICAgICBkb2N1bWVudCA9IGF3YWl0IHRoaXMub3B0aW9ucy5mZXRjaCgpO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICB0aGlzLmZhaWxlZFVudGlsID0gRGF0ZS5ub3coKSArIHRoaXMubmVnYXRpdmVUdGxNcztcbiAgICAgIGVtaXRDb3VudGVyKCdqd2tzX2ZldGNoX3RvdGFsJywgeyBzb3VyY2U6IHRoaXMuc291cmNlLCByZXN1bHQ6ICdlcnJvcicgfSk7XG4gICAgICBsb2dnZXIud2FybignSldLUyBmZXRjaCBmYWlsZWQnLCB7XG4gICAgICAgIHNvdXJjZTogdGhpcy5zb3VyY2UsXG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvciksXG4gICAgICB9KTtcbiAgICAgIHRocm93IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvciA6IG5ldyBFcnJvcihTdHJpbmcoZXJyb3IpKTtcbiAgICB9XG5cbiAgICBjb25zdCBrZXlzID0gbmV3IE1hcDxzdHJpbmcsIEtleU9iamVjdD4oKTtcbiAgICBmb3IgKGNvbnN0IGp3ayBvZiBkb2N1bWVudC5rZXlzIGFzIFB1YmxpY0p3a1tdKSB7XG4gICAgICBpZiAoIWp3az8ua2lkKSBjb250aW51ZTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGtleXMuc2V0KGp3ay5raWQsIHB1YmxpY0tleUZyb21Kd2soandrKSk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAvLyBPbmUgdW51c2FibGUgZW50cnkgbXVzdCBub3QgZGlzY2FyZCB0aGUgcmVzdCBvZiB0aGUgc2V0LlxuICAgICAgICBsb2dnZXIud2FybignU2tpcHBpbmcgdW51c2FibGUgSldLUyBlbnRyeScsIHsga2lkOiBqd2sua2lkLCBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpIH0pO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoa2V5cy5zaXplID09PSAwKSB7XG4gICAgICB0aGlzLmZhaWxlZFVudGlsID0gRGF0ZS5ub3coKSArIHRoaXMubmVnYXRpdmVUdGxNcztcbiAgICAgIGVtaXRDb3VudGVyKCdqd2tzX2ZldGNoX3RvdGFsJywgeyBzb3VyY2U6IHRoaXMuc291cmNlLCByZXN1bHQ6ICdlbXB0eScgfSk7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0pXS1MgZG9jdW1lbnQgY29udGFpbmVkIG5vIHVzYWJsZSBrZXlzJyk7XG4gICAgfVxuXG4gICAgdGhpcy5rZXlzID0ga2V5cztcbiAgICB0aGlzLmZldGNoZWRBdCA9IERhdGUubm93KCk7XG4gICAgdGhpcy5mYWlsZWRVbnRpbCA9IDA7XG4gICAgZW1pdENvdW50ZXIoJ2p3a3NfZmV0Y2hfdG90YWwnLCB7IHNvdXJjZTogdGhpcy5zb3VyY2UsIHJlc3VsdDogJ3N1Y2Nlc3MnIH0pO1xuICB9XG59XG5cbi8qKlxuICogRmV0Y2ggcGxhdGZvcm0ncyBKV0tTIG92ZXIgdGhlIGluLWNsdXN0ZXIgYWRkcmVzcyBldmVyeSBvdGhlciBjYWxsZXIgdXNlc1xuICogKGBQTEFURk9STV9TRVJWSUNFX0hPU1RgIC8gYFBMQVRGT1JNX1NFUlZJQ0VfUE9SVGApLCBvciB0aGUgYWJzb2x1dGVcbiAqIGBQTEFURk9STV9KV0tTX1VSTGAgd2hlbiBvbmUgaXMgY29uZmlndXJlZCAodGhlIENMSSBhbmQgdGhlIGV2ZW50cyBMYW1iZGEgcnVuXG4gKiBvdXRzaWRlIHRoZSBjbHVzdGVyKS5cbiAqXG4gKiBVbmF1dGhlbnRpY2F0ZWQgYnkgZGVzaWduOiB0aGUga2V5IHNldCBpcyBwdWJsaWMsIGFuZCByZXF1aXJpbmcgYSBjcmVkZW50aWFsXG4gKiB0byBmZXRjaCB0aGUga2V5cyB0aGF0IHZlcmlmeSBjcmVkZW50aWFscyBpcyBhIGJvb3RzdHJhcCBjeWNsZS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZldGNoUGxhdGZvcm1Kd2tzKCk6IFByb21pc2U8Sndrc0RvY3VtZW50PiB7XG4gIGNvbnN0IGFic29sdXRlID0gcHJvY2Vzcy5lbnYuUExBVEZPUk1fSldLU19VUkw7XG4gIGNvbnN0IHRpbWVvdXQgPSBwYXJzZUludChwcm9jZXNzLmVudi5KV0tTX0ZFVENIX1RJTUVPVVRfTVMgfHwgJzMwMDAnLCAxMCk7XG4gIGlmIChhYnNvbHV0ZSkgcmV0dXJuIGZldGNoSndrc0Zyb21VcmwoYWJzb2x1dGUsIHRpbWVvdXQpO1xuXG4gIC8vIExvYWRlZCBMQVpJTFksIG9uIHRoZSBmaXJzdCBmZXRjaC4gQSBzdGF0aWMgaW1wb3J0IHdvdWxkIHB1dCB0aGUgSFRUUCBjbGllbnRcbiAgLy8gKGFuZCBwbGF0Zm9ybSdzIGFkZHJlc3MpIGludG8gdGhlIGltcG9ydCBncmFwaCBvZiBldmVyeSBtb2R1bGUgdGhhdCBtZXJlbHlcbiAgLy8gdXNlcyBgcmVxdWlyZUF1dGhgIOKAlCB0aGUgc2FtZSByZWFzb24gYGFwaS1rZXktZXhjaGFuZ2VgIGlzIGxhenksIGFuZCB3aGF0XG4gIC8vIGtlZXBzIHBhcnRpYWwgYGh0dHAtY2xpZW50LmpzYCBtb2NrcyBpbiB0aGUgc2VydmljZSBzdWl0ZXMgd29ya2luZy5cbiAgY29uc3QgeyBJbnRlcm5hbEh0dHBDbGllbnQgfSA9IGF3YWl0IGltcG9ydCgnLi9odHRwLWNsaWVudC5qcycpO1xuICBjb25zdCBjbGllbnQgPSBuZXcgSW50ZXJuYWxIdHRwQ2xpZW50KHtcbiAgICBob3N0OiBwcm9jZXNzLmVudi5QTEFURk9STV9TRVJWSUNFX0hPU1QgfHwgJ3BsYXRmb3JtJyxcbiAgICBwb3J0OiBwYXJzZUludChwcm9jZXNzLmVudi5QTEFURk9STV9TRVJWSUNFX1BPUlQgfHwgJzMwMDAnLCAxMCksXG4gICAgdGltZW91dCxcbiAgfSk7XG4gIGNvbnN0IHJlcyA9IGF3YWl0IGNsaWVudC5nZXQ8dW5rbm93bj4oSldLU19QQVRILCB7IGlkZW1wb3RlbnQ6IHRydWUgfSk7XG4gIGlmIChyZXMuc3RhdHVzQ29kZSAhPT0gMjAwIHx8ICFpc0p3a3NEb2N1bWVudChyZXMuYm9keSkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYFVuZXhwZWN0ZWQgSldLUyByZXNwb25zZSAoc3RhdHVzICR7cmVzLnN0YXR1c0NvZGV9KWApO1xuICB9XG4gIHJldHVybiByZXMuYm9keTtcbn1cblxuLyoqIEZldGNoIGEgSldLUyBkb2N1bWVudCBmcm9tIGFuIGFic29sdXRlIFVSTCAob3V0c2lkZS10aGUtY2x1c3RlciB2ZXJpZmllcnMpLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZldGNoSndrc0Zyb21VcmwodXJsOiBzdHJpbmcsIHRpbWVvdXRNcyA9IDMwMDApOiBQcm9taXNlPEp3a3NEb2N1bWVudD4ge1xuICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICBjb25zdCB0aW1lciA9IHNldFRpbWVvdXQoKCkgPT4gY29udHJvbGxlci5hYm9ydCgpLCB0aW1lb3V0TXMpO1xuICB0cnkge1xuICAgIGNvbnN0IHJlcyA9IGF3YWl0IGZldGNoKHVybCwgeyBzaWduYWw6IGNvbnRyb2xsZXIuc2lnbmFsLCBoZWFkZXJzOiB7IGFjY2VwdDogJ2FwcGxpY2F0aW9uL2pzb24nIH0gfSk7XG4gICAgaWYgKCFyZXMub2spIHRocm93IG5ldyBFcnJvcihgSldLUyBmZXRjaCByZXR1cm5lZCAke3Jlcy5zdGF0dXN9YCk7XG4gICAgY29uc3QgYm9keTogdW5rbm93biA9IGF3YWl0IHJlcy5qc29uKCk7XG4gICAgaWYgKCFpc0p3a3NEb2N1bWVudChib2R5KSkgdGhyb3cgbmV3IEVycm9yKCdKV0tTIHJlc3BvbnNlIHdhcyBub3QgYSBrZXkgc2V0Jyk7XG4gICAgcmV0dXJuIGJvZHk7XG4gIH0gZmluYWxseSB7XG4gICAgY2xlYXJUaW1lb3V0KHRpbWVyKTtcbiAgfVxufVxuXG5sZXQgcGxhdGZvcm1Kd2tzOiBKd2tzQ2FjaGUgfCB1bmRlZmluZWQ7XG5cbi8qKiBUaGUgcHJvY2Vzcy13aWRlIGNhY2hlIG9mIHBsYXRmb3JtJ3MgcHVibGlzaGVkIHNpZ25pbmcga2V5cy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwbGF0Zm9ybUp3a3NDYWNoZSgpOiBKd2tzQ2FjaGUge1xuICBwbGF0Zm9ybUp3a3MgPz89IG5ldyBKd2tzQ2FjaGUoeyBmZXRjaDogZmV0Y2hQbGF0Zm9ybUp3a3MsIHNvdXJjZTogJ3BsYXRmb3JtJyB9KTtcbiAgcmV0dXJuIHBsYXRmb3JtSndrcztcbn1cblxuLyoqXG4gKiBSZXBsYWNlIHRoZSBwcm9jZXNzLXdpZGUgY2FjaGUg4oCUIHVzZWQgYnkgdGVzdHMsIGFuZCBieSBhbnkgaG9zdCB0aGF0IG11c3RcbiAqIHN1cHBseSBpdHMgb3duIGZldGNoZXIuIFBhc3MgYHVuZGVmaW5lZGAgdG8gZHJvcCBiYWNrIHRvIHRoZSBkZWZhdWx0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2V0UGxhdGZvcm1Kd2tzQ2FjaGUoY2FjaGU6IEp3a3NDYWNoZSB8IHVuZGVmaW5lZCk6IHZvaWQge1xuICBwbGF0Zm9ybUp3a3MgPSBjYWNoZTtcbn1cbiJdfQ==
@@ -30,12 +30,15 @@ export declare function closeLeaderLock(redis: LockRedis | null | undefined): Pr
30
30
  * released on completion, but only if we still own it — a slower predecessor
31
31
  * never deletes a successor's lock. `ttlMs` should comfortably exceed one run.
32
32
  *
33
- * @returns true if we held the lock and ran `fn`; false if another holder did.
33
+ * If Redis can't be reached the run is skipped (returns false) rather than
34
+ * rejecting — callers are timers, and a Redis blip must not crash the process.
35
+ *
36
+ * @returns true if we held the lock and ran `fn`; false if another holder did or
37
+ * the lock couldn't be taken.
34
38
  */
35
39
  export declare function withLeaderLock(redis: LockRedis, key: string, ttlMs: number, fn: () => Promise<void>): Promise<boolean>;
36
40
  /**
37
- * A `LockRedis` backed by an env-configured ioredis client (`REDIS_URL` or
38
- * `REDIS_HOST`) — the leader-lock counterpart to `createEnvRedisAuditSpool` /
41
+ * A `LockRedis` backed by the shared env Redis client (see env-redis.ts) — the leader-lock counterpart to `createEnvRedisAuditSpool` /
39
42
  * `createEnvRedisTokenRevocationStore`, for a service that has no BullMQ client to
40
43
  * borrow. Returns `null` when Redis isn't configured, so a caller can degrade to
41
44
  * running on every pod (the atomic guards it wraps must still make that safe).
@@ -38,11 +38,27 @@ const RELEASE_IF_OWNER = 'if redis.call("get", KEYS[1]) == ARGV[1] then return r
38
38
  * released on completion, but only if we still own it — a slower predecessor
39
39
  * never deletes a successor's lock. `ttlMs` should comfortably exceed one run.
40
40
  *
41
- * @returns true if we held the lock and ran `fn`; false if another holder did.
41
+ * If Redis can't be reached the run is skipped (returns false) rather than
42
+ * rejecting — callers are timers, and a Redis blip must not crash the process.
43
+ *
44
+ * @returns true if we held the lock and ran `fn`; false if another holder did or
45
+ * the lock couldn't be taken.
42
46
  */
43
47
  export async function withLeaderLock(redis, key, ttlMs, fn) {
44
48
  const token = randomUUID();
45
- const acquired = await redis.set(key, token, 'PX', ttlMs, 'NX');
49
+ let acquired;
50
+ try {
51
+ acquired = await redis.set(key, token, 'PX', ttlMs, 'NX');
52
+ }
53
+ catch (err) {
54
+ // Redis unreachable (including before the first connection completes): no
55
+ // pod can prove it's the leader, so nobody runs this window. Skipping is
56
+ // the safe side — running here could duplicate destructive work across pods.
57
+ lockLogger.warn('Leader lock unavailable; skipping this run', {
58
+ key, error: err instanceof Error ? err.message : String(err),
59
+ });
60
+ return false;
61
+ }
46
62
  if (acquired !== 'OK')
47
63
  return false;
48
64
  try {
@@ -68,8 +84,7 @@ export async function withLeaderLock(redis, key, ttlMs, fn) {
68
84
  return true;
69
85
  }
70
86
  /**
71
- * A `LockRedis` backed by an env-configured ioredis client (`REDIS_URL` or
72
- * `REDIS_HOST`) — the leader-lock counterpart to `createEnvRedisAuditSpool` /
87
+ * A `LockRedis` backed by the shared env Redis client (see env-redis.ts) — the leader-lock counterpart to `createEnvRedisAuditSpool` /
73
88
  * `createEnvRedisTokenRevocationStore`, for a service that has no BullMQ client to
74
89
  * borrow. Returns `null` when Redis isn't configured, so a caller can degrade to
75
90
  * running on every pod (the atomic guards it wraps must still make that safe).
@@ -82,4 +97,4 @@ export function createEnvRedisLock() {
82
97
  lockLogger.info('Redis leader-lock client initialized');
83
98
  return inst;
84
99
  }
85
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGVhZGVyLWxvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvbGVhZGVyLWxvY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVsRCxNQUFNLFVBQVUsR0FBRyxZQUFZLENBQUMsYUFBYSxDQUFDLENBQUM7QUFvQi9DOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLGVBQWUsQ0FBQyxLQUFtQztJQUN2RSxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU87SUFDbkIsSUFBSSxDQUFDO1FBQ0gsSUFBSSxPQUFPLEtBQUssQ0FBQyxJQUFJLEtBQUssVUFBVSxFQUFFLENBQUM7WUFBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFDLE9BQU87UUFBQyxDQUFDO0lBQ3ZFLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCw2QkFBNkI7SUFDL0IsQ0FBQztJQUNELElBQUksQ0FBQztRQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDO0lBQUMsQ0FBQztJQUFDLE1BQU0sQ0FBQyxDQUFDLGlCQUFpQixDQUFDLENBQUM7QUFDM0QsQ0FBQztBQUVEO2lFQUNpRTtBQUNqRSxNQUFNLGdCQUFnQixHQUNwQixtR0FBbUcsQ0FBQztBQUV0Rzs7Ozs7Ozs7OztHQVVHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxjQUFjLENBQ2xDLEtBQWdCLEVBQ2hCLEdBQVcsRUFDWCxLQUFhLEVBQ2IsRUFBdUI7SUFFdkIsTUFBTSxLQUFLLEdBQUcsVUFBVSxFQUFFLENBQUM7SUFDM0IsTUFBTSxRQUFRLEdBQUcsTUFBTSxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNoRSxJQUFJLFFBQVEsS0FBSyxJQUFJO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFFcEMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxFQUFFLEVBQUUsQ0FBQztJQUNiLENBQUM7WUFBUyxDQUFDO1FBQ1QsMEVBQTBFO1FBQzFFLHdFQUF3RTtRQUN4RSw0RUFBNEU7UUFDNUUscURBQXFEO1FBQ3JELElBQUksQ0FBQztZQUNILElBQUksT0FBTyxLQUFLLENBQUMsSUFBSSxLQUFLLFVBQVUsRUFBRSxDQUFDO2dCQUNyQyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQyxFQUFFLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztZQUNwRCxDQUFDO2lCQUFNLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxLQUFLLEVBQUUsQ0FBQztnQkFDNUMsTUFBTSxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3ZCLENBQUM7UUFDSCxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1Asb0RBQW9EO1FBQ3RELENBQUM7SUFDSCxDQUFDO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDO0FBRUQ7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLFVBQVUsa0JBQWtCO0lBQ2hDLE1BQU0sSUFBSSxHQUFHLG9CQUFvQixDQUFZLGFBQWEsQ0FBQyxDQUFDO0lBQzVELElBQUksSUFBSTtRQUFFLFVBQVUsQ0FBQyxJQUFJLENBQUMsc0NBQXNDLENBQUMsQ0FBQztJQUNsRSxPQUFPLElBQUksQ0FBQztBQUNkLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgcmFuZG9tVVVJRCB9IGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgeyBjcmVhdGVFbnZSZWRpc0NsaWVudCB9IGZyb20gJy4vZW52LXJlZGlzLmpzJztcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbmNvbnN0IGxvY2tMb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ2xlYWRlci1sb2NrJyk7XG5cbi8qKlxuICogTWluaW1hbCBSZWRpcyBjbGllbnQgc3VyZmFjZSBuZWVkZWQgZm9yIGEgbGVhZGVyIGxvY2sgKGEgc3Vic2V0IG9mIGlvcmVkaXMpLlxuICogYHNldGAgaXMgdmFyaWFkaWMgc28gY2FsbGVycyBjYW4gcGFzcyBgJ1BYJywgdHRsLCAnTlgnYC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBMb2NrUmVkaXMge1xuICBzZXQoa2V5OiBzdHJpbmcsIHZhbHVlOiBzdHJpbmcsIC4uLmFyZ3M6IHVua25vd25bXSk6IFByb21pc2U8dW5rbm93bj47XG4gIGdldChrZXk6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIGRlbCguLi5rZXlzOiBzdHJpbmdbXSk6IFByb21pc2U8bnVtYmVyPjtcbiAgLyoqIE9wdGlvbmFsIEx1YSBldmFsIChpb3JlZGlzIGhhcyBpdCkuIFdoZW4gcHJlc2VudCwgcmVsZWFzZSB1c2VzIGFuIGF0b21pY1xuICAgKiAgY29tcGFyZS1hbmQtZGVsZXRlIHNvIGEgbGFwc2VkIGhvbGRlciBjYW4ndCBmcmVlIGEgc3VjY2Vzc29yJ3MgbG9jay4gKi9cbiAgZXZhbD8oc2NyaXB0OiBzdHJpbmcsIG51bUtleXM6IG51bWJlciwgLi4uYXJnczogdW5rbm93bltdKTogUHJvbWlzZTx1bmtub3duPjtcbiAgLyoqIEdyYWNlZnVsIGNsb3NlIChpb3JlZGlzIGBxdWl0YCkg4oCUIGNhbGxlZCBvbiBzaHV0ZG93biBzbyB0aGUgY29ubmVjdGlvblxuICAgKiAgZG9lc24ndCBrZWVwIHRoZSBldmVudCBsb29wIGFsaXZlLiAqL1xuICBxdWl0PygpOiBQcm9taXNlPHVua25vd24+O1xuICAvKiogSW1tZWRpYXRlIGNsb3NlIChpb3JlZGlzIGBkaXNjb25uZWN0YCkg4oCUIHRoZSBmYWxsYmFjayBpZiBgcXVpdGAgZmFpbHMuICovXG4gIGRpc2Nvbm5lY3Q/KCk6IHZvaWQ7XG59XG5cbi8qKlxuICogQmVzdC1lZmZvcnQgY2xvc2Ugb2YgYSBsZWFkZXItbG9jayBSZWRpcyBjbGllbnQgb24gc2h1dGRvd24uIFByZWZlcnMgZ3JhY2VmdWxcbiAqIGBxdWl0YDsgZmFsbHMgYmFjayB0byBgZGlzY29ubmVjdGAuIE5vLW9wIGZvciBhIG51bGwgY2xpZW50IC8gb25lIHdpdGhvdXRcbiAqIGVpdGhlciBtZXRob2QuIE5ldmVyIHRocm93cy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsb3NlTGVhZGVyTG9jayhyZWRpczogTG9ja1JlZGlzIHwgbnVsbCB8IHVuZGVmaW5lZCk6IFByb21pc2U8dm9pZD4ge1xuICBpZiAoIXJlZGlzKSByZXR1cm47XG4gIHRyeSB7XG4gICAgaWYgKHR5cGVvZiByZWRpcy5xdWl0ID09PSAnZnVuY3Rpb24nKSB7IGF3YWl0IHJlZGlzLnF1aXQoKTsgcmV0dXJuOyB9XG4gIH0gY2F0Y2gge1xuICAgIC8vIGZhbGwgdGhyb3VnaCB0byBkaXNjb25uZWN0XG4gIH1cbiAgdHJ5IHsgcmVkaXMuZGlzY29ubmVjdD8uKCk7IH0gY2F0Y2ggeyAvKiBiZXN0LWVmZm9ydCAqLyB9XG59XG5cbi8qKiBBdG9taWMgXCJkZWxldGUgb25seSBpZiBJIHN0aWxsIG93biBpdFwiIOKAlCBjbG9zZXMgdGhlIGdldC10aGVuLWRlbCByYWNlIHdoZXJlXG4gKiAgdGhlIGxvY2sgZXhwaXJlcyBhbmQgaXMgcmUtYWNxdWlyZWQgYmV0d2VlbiB0aGUgdHdvIGNhbGxzLiAqL1xuY29uc3QgUkVMRUFTRV9JRl9PV05FUiA9XG4gICdpZiByZWRpcy5jYWxsKFwiZ2V0XCIsIEtFWVNbMV0pID09IEFSR1ZbMV0gdGhlbiByZXR1cm4gcmVkaXMuY2FsbChcImRlbFwiLCBLRVlTWzFdKSBlbHNlIHJldHVybiAwIGVuZCc7XG5cbi8qKlxuICogUnVuIGBmbmAgb25seSBpZiB0aGlzIHByb2Nlc3Mgd2lucyBhIHNob3J0LWxpdmVkIGRpc3RyaWJ1dGVkIGxvY2sg4oCUIHRoZVxuICogYWNyb3NzLXBvZHMgXCJzaW5nbGUgcnVubmVyXCIgZ3VhcmQgZm9yIHBlcmlvZGljIGpvYnMgKGRpZ2VzdCBmbHVzaGVzLCBldGMuKS5cbiAqXG4gKiBVc2VzIGBTRVQga2V5IHRva2VuIE5YIFBYIHR0bGAsIHNvIGV4YWN0bHkgb25lIGhvbGRlciBydW5zIHBlciB3aW5kb3cgYW5kIHRoZVxuICogbG9jayBhdXRvLWV4cGlyZXMgaWYgdGhhdCBob2xkZXIgZGllcyBtaWQtcnVuIChubyBzdHVjayBsb2NrKS4gVGhlIGxvY2sgaXNcbiAqIHJlbGVhc2VkIG9uIGNvbXBsZXRpb24sIGJ1dCBvbmx5IGlmIHdlIHN0aWxsIG93biBpdCDigJQgYSBzbG93ZXIgcHJlZGVjZXNzb3JcbiAqIG5ldmVyIGRlbGV0ZXMgYSBzdWNjZXNzb3IncyBsb2NrLiBgdHRsTXNgIHNob3VsZCBjb21mb3J0YWJseSBleGNlZWQgb25lIHJ1bi5cbiAqXG4gKiBAcmV0dXJucyB0cnVlIGlmIHdlIGhlbGQgdGhlIGxvY2sgYW5kIHJhbiBgZm5gOyBmYWxzZSBpZiBhbm90aGVyIGhvbGRlciBkaWQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3aXRoTGVhZGVyTG9jayhcbiAgcmVkaXM6IExvY2tSZWRpcyxcbiAga2V5OiBzdHJpbmcsXG4gIHR0bE1zOiBudW1iZXIsXG4gIGZuOiAoKSA9PiBQcm9taXNlPHZvaWQ+LFxuKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IHRva2VuID0gcmFuZG9tVVVJRCgpO1xuICBjb25zdCBhY3F1aXJlZCA9IGF3YWl0IHJlZGlzLnNldChrZXksIHRva2VuLCAnUFgnLCB0dGxNcywgJ05YJyk7XG4gIGlmIChhY3F1aXJlZCAhPT0gJ09LJykgcmV0dXJuIGZhbHNlO1xuXG4gIHRyeSB7XG4gICAgYXdhaXQgZm4oKTtcbiAgfSBmaW5hbGx5IHtcbiAgICAvLyBSZWxlYXNlIG9ubHkgaWYgdGhlIGxvY2sgaXMgc3RpbGwgb3VycyDigJQgaWYgb3VyIHJ1biBvdmVycmFuIHRoZSBUVEwgYW5kXG4gICAgLy8gYW5vdGhlciBob2xkZXIgdG9vayBvdmVyLCByZWxlYXNpbmcgaGVyZSB3b3VsZCBmcmVlIHRoZWlyIGxvY2sgZWFybHkuXG4gICAgLy8gUHJlZmVyIGFuIGF0b21pYyBDQVMgKEx1YSk7IGZhbGwgYmFjayB0byBnZXQtdGhlbi1kZWwgZm9yIGNsaWVudHMgd2l0aG91dFxuICAgIC8vIGV2YWwgKHRoZSBzbWFsbCBub24tYXRvbWljIHdpbmRvdyBpcyBUVEwtYm91bmRlZCkuXG4gICAgdHJ5IHtcbiAgICAgIGlmICh0eXBlb2YgcmVkaXMuZXZhbCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBhd2FpdCByZWRpcy5ldmFsKFJFTEVBU0VfSUZfT1dORVIsIDEsIGtleSwgdG9rZW4pO1xuICAgICAgfSBlbHNlIGlmICgoYXdhaXQgcmVkaXMuZ2V0KGtleSkpID09PSB0b2tlbikge1xuICAgICAgICBhd2FpdCByZWRpcy5kZWwoa2V5KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIEJlc3QtZWZmb3J0OiB0aGUgVFRMIGV4cGlyZXMgdGhlIGxvY2sgcmVnYXJkbGVzcy5cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKlxuICogQSBgTG9ja1JlZGlzYCBiYWNrZWQgYnkgYW4gZW52LWNvbmZpZ3VyZWQgaW9yZWRpcyBjbGllbnQgKGBSRURJU19VUkxgIG9yXG4gKiBgUkVESVNfSE9TVGApIOKAlCB0aGUgbGVhZGVyLWxvY2sgY291bnRlcnBhcnQgdG8gYGNyZWF0ZUVudlJlZGlzQXVkaXRTcG9vbGAgL1xuICogYGNyZWF0ZUVudlJlZGlzVG9rZW5SZXZvY2F0aW9uU3RvcmVgLCBmb3IgYSBzZXJ2aWNlIHRoYXQgaGFzIG5vIEJ1bGxNUSBjbGllbnQgdG9cbiAqIGJvcnJvdy4gUmV0dXJucyBgbnVsbGAgd2hlbiBSZWRpcyBpc24ndCBjb25maWd1cmVkLCBzbyBhIGNhbGxlciBjYW4gZGVncmFkZSB0b1xuICogcnVubmluZyBvbiBldmVyeSBwb2QgKHRoZSBhdG9taWMgZ3VhcmRzIGl0IHdyYXBzIG11c3Qgc3RpbGwgbWFrZSB0aGF0IHNhZmUpLlxuICogVGhlIGNsaWVudCBpcyBhIHJlYWwgaW9yZWRpcyBpbnN0YW5jZTsgaW9yZWRpcyBleHBvc2VzIGBzZXRgL2BnZXRgL2BkZWxgL2BldmFsYCxcbiAqIHNhdGlzZnlpbmcgYExvY2tSZWRpc2AgaW5jbHVkaW5nIHRoZSBhdG9taWMgQ0FTIHJlbGVhc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVFbnZSZWRpc0xvY2soKTogTG9ja1JlZGlzIHwgbnVsbCB7XG4gIGNvbnN0IGluc3QgPSBjcmVhdGVFbnZSZWRpc0NsaWVudDxMb2NrUmVkaXM+KCdsZWFkZXItbG9jaycpO1xuICBpZiAoaW5zdCkgbG9ja0xvZ2dlci5pbmZvKCdSZWRpcyBsZWFkZXItbG9jayBjbGllbnQgaW5pdGlhbGl6ZWQnKTtcbiAgcmV0dXJuIGluc3Q7XG59XG4iXX0=
100
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGVhZGVyLWxvY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvbGVhZGVyLWxvY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVsRCxNQUFNLFVBQVUsR0FBRyxZQUFZLENBQUMsYUFBYSxDQUFDLENBQUM7QUFvQi9DOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLGVBQWUsQ0FBQyxLQUFtQztJQUN2RSxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU87SUFDbkIsSUFBSSxDQUFDO1FBQ0gsSUFBSSxPQUFPLEtBQUssQ0FBQyxJQUFJLEtBQUssVUFBVSxFQUFFLENBQUM7WUFBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUFDLE9BQU87UUFBQyxDQUFDO0lBQ3ZFLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCw2QkFBNkI7SUFDL0IsQ0FBQztJQUNELElBQUksQ0FBQztRQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDO0lBQUMsQ0FBQztJQUFDLE1BQU0sQ0FBQyxDQUFDLGlCQUFpQixDQUFDLENBQUM7QUFDM0QsQ0FBQztBQUVEO2lFQUNpRTtBQUNqRSxNQUFNLGdCQUFnQixHQUNwQixtR0FBbUcsQ0FBQztBQUV0Rzs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsY0FBYyxDQUNsQyxLQUFnQixFQUNoQixHQUFXLEVBQ1gsS0FBYSxFQUNiLEVBQXVCO0lBRXZCLE1BQU0sS0FBSyxHQUFHLFVBQVUsRUFBRSxDQUFDO0lBQzNCLElBQUksUUFBaUIsQ0FBQztJQUN0QixJQUFJLENBQUM7UUFDSCxRQUFRLEdBQUcsTUFBTSxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQztJQUM1RCxDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLDBFQUEwRTtRQUMxRSx5RUFBeUU7UUFDekUsNkVBQTZFO1FBQzdFLFVBQVUsQ0FBQyxJQUFJLENBQUMsNENBQTRDLEVBQUU7WUFDNUQsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDO1NBQzdELENBQUMsQ0FBQztRQUNILE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUNELElBQUksUUFBUSxLQUFLLElBQUk7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUVwQyxJQUFJLENBQUM7UUFDSCxNQUFNLEVBQUUsRUFBRSxDQUFDO0lBQ2IsQ0FBQztZQUFTLENBQUM7UUFDVCwwRUFBMEU7UUFDMUUsd0VBQXdFO1FBQ3hFLDRFQUE0RTtRQUM1RSxxREFBcUQ7UUFDckQsSUFBSSxDQUFDO1lBQ0gsSUFBSSxPQUFPLEtBQUssQ0FBQyxJQUFJLEtBQUssVUFBVSxFQUFFLENBQUM7Z0JBQ3JDLE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLEVBQUUsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ3BELENBQUM7aUJBQU0sSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEtBQUssRUFBRSxDQUFDO2dCQUM1QyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDdkIsQ0FBQztRQUNILENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxvREFBb0Q7UUFDdEQsQ0FBQztJQUNILENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQjtJQUNoQyxNQUFNLElBQUksR0FBRyxvQkFBb0IsQ0FBWSxhQUFhLENBQUMsQ0FBQztJQUM1RCxJQUFJLElBQUk7UUFBRSxVQUFVLENBQUMsSUFBSSxDQUFDLHNDQUFzQyxDQUFDLENBQUM7SUFDbEUsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IHJhbmRvbVVVSUQgfSBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHsgY3JlYXRlRW52UmVkaXNDbGllbnQgfSBmcm9tICcuL2Vudi1yZWRpcy5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuXG5jb25zdCBsb2NrTG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdsZWFkZXItbG9jaycpO1xuXG4vKipcbiAqIE1pbmltYWwgUmVkaXMgY2xpZW50IHN1cmZhY2UgbmVlZGVkIGZvciBhIGxlYWRlciBsb2NrIChhIHN1YnNldCBvZiBpb3JlZGlzKS5cbiAqIGBzZXRgIGlzIHZhcmlhZGljIHNvIGNhbGxlcnMgY2FuIHBhc3MgYCdQWCcsIHR0bCwgJ05YJ2AuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgTG9ja1JlZGlzIHtcbiAgc2V0KGtleTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nLCAuLi5hcmdzOiB1bmtub3duW10pOiBQcm9taXNlPHVua25vd24+O1xuICBnZXQoa2V5OiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZyB8IG51bGw+O1xuICBkZWwoLi4ua2V5czogc3RyaW5nW10pOiBQcm9taXNlPG51bWJlcj47XG4gIC8qKiBPcHRpb25hbCBMdWEgZXZhbCAoaW9yZWRpcyBoYXMgaXQpLiBXaGVuIHByZXNlbnQsIHJlbGVhc2UgdXNlcyBhbiBhdG9taWNcbiAgICogIGNvbXBhcmUtYW5kLWRlbGV0ZSBzbyBhIGxhcHNlZCBob2xkZXIgY2FuJ3QgZnJlZSBhIHN1Y2Nlc3NvcidzIGxvY2suICovXG4gIGV2YWw/KHNjcmlwdDogc3RyaW5nLCBudW1LZXlzOiBudW1iZXIsIC4uLmFyZ3M6IHVua25vd25bXSk6IFByb21pc2U8dW5rbm93bj47XG4gIC8qKiBHcmFjZWZ1bCBjbG9zZSAoaW9yZWRpcyBgcXVpdGApIOKAlCBjYWxsZWQgb24gc2h1dGRvd24gc28gdGhlIGNvbm5lY3Rpb25cbiAgICogIGRvZXNuJ3Qga2VlcCB0aGUgZXZlbnQgbG9vcCBhbGl2ZS4gKi9cbiAgcXVpdD8oKTogUHJvbWlzZTx1bmtub3duPjtcbiAgLyoqIEltbWVkaWF0ZSBjbG9zZSAoaW9yZWRpcyBgZGlzY29ubmVjdGApIOKAlCB0aGUgZmFsbGJhY2sgaWYgYHF1aXRgIGZhaWxzLiAqL1xuICBkaXNjb25uZWN0PygpOiB2b2lkO1xufVxuXG4vKipcbiAqIEJlc3QtZWZmb3J0IGNsb3NlIG9mIGEgbGVhZGVyLWxvY2sgUmVkaXMgY2xpZW50IG9uIHNodXRkb3duLiBQcmVmZXJzIGdyYWNlZnVsXG4gKiBgcXVpdGA7IGZhbGxzIGJhY2sgdG8gYGRpc2Nvbm5lY3RgLiBOby1vcCBmb3IgYSBudWxsIGNsaWVudCAvIG9uZSB3aXRob3V0XG4gKiBlaXRoZXIgbWV0aG9kLiBOZXZlciB0aHJvd3MuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjbG9zZUxlYWRlckxvY2socmVkaXM6IExvY2tSZWRpcyB8IG51bGwgfCB1bmRlZmluZWQpOiBQcm9taXNlPHZvaWQ+IHtcbiAgaWYgKCFyZWRpcykgcmV0dXJuO1xuICB0cnkge1xuICAgIGlmICh0eXBlb2YgcmVkaXMucXVpdCA9PT0gJ2Z1bmN0aW9uJykgeyBhd2FpdCByZWRpcy5xdWl0KCk7IHJldHVybjsgfVxuICB9IGNhdGNoIHtcbiAgICAvLyBmYWxsIHRocm91Z2ggdG8gZGlzY29ubmVjdFxuICB9XG4gIHRyeSB7IHJlZGlzLmRpc2Nvbm5lY3Q/LigpOyB9IGNhdGNoIHsgLyogYmVzdC1lZmZvcnQgKi8gfVxufVxuXG4vKiogQXRvbWljIFwiZGVsZXRlIG9ubHkgaWYgSSBzdGlsbCBvd24gaXRcIiDigJQgY2xvc2VzIHRoZSBnZXQtdGhlbi1kZWwgcmFjZSB3aGVyZVxuICogIHRoZSBsb2NrIGV4cGlyZXMgYW5kIGlzIHJlLWFjcXVpcmVkIGJldHdlZW4gdGhlIHR3byBjYWxscy4gKi9cbmNvbnN0IFJFTEVBU0VfSUZfT1dORVIgPVxuICAnaWYgcmVkaXMuY2FsbChcImdldFwiLCBLRVlTWzFdKSA9PSBBUkdWWzFdIHRoZW4gcmV0dXJuIHJlZGlzLmNhbGwoXCJkZWxcIiwgS0VZU1sxXSkgZWxzZSByZXR1cm4gMCBlbmQnO1xuXG4vKipcbiAqIFJ1biBgZm5gIG9ubHkgaWYgdGhpcyBwcm9jZXNzIHdpbnMgYSBzaG9ydC1saXZlZCBkaXN0cmlidXRlZCBsb2NrIOKAlCB0aGVcbiAqIGFjcm9zcy1wb2RzIFwic2luZ2xlIHJ1bm5lclwiIGd1YXJkIGZvciBwZXJpb2RpYyBqb2JzIChkaWdlc3QgZmx1c2hlcywgZXRjLikuXG4gKlxuICogVXNlcyBgU0VUIGtleSB0b2tlbiBOWCBQWCB0dGxgLCBzbyBleGFjdGx5IG9uZSBob2xkZXIgcnVucyBwZXIgd2luZG93IGFuZCB0aGVcbiAqIGxvY2sgYXV0by1leHBpcmVzIGlmIHRoYXQgaG9sZGVyIGRpZXMgbWlkLXJ1biAobm8gc3R1Y2sgbG9jaykuIFRoZSBsb2NrIGlzXG4gKiByZWxlYXNlZCBvbiBjb21wbGV0aW9uLCBidXQgb25seSBpZiB3ZSBzdGlsbCBvd24gaXQg4oCUIGEgc2xvd2VyIHByZWRlY2Vzc29yXG4gKiBuZXZlciBkZWxldGVzIGEgc3VjY2Vzc29yJ3MgbG9jay4gYHR0bE1zYCBzaG91bGQgY29tZm9ydGFibHkgZXhjZWVkIG9uZSBydW4uXG4gKlxuICogSWYgUmVkaXMgY2FuJ3QgYmUgcmVhY2hlZCB0aGUgcnVuIGlzIHNraXBwZWQgKHJldHVybnMgZmFsc2UpIHJhdGhlciB0aGFuXG4gKiByZWplY3Rpbmcg4oCUIGNhbGxlcnMgYXJlIHRpbWVycywgYW5kIGEgUmVkaXMgYmxpcCBtdXN0IG5vdCBjcmFzaCB0aGUgcHJvY2Vzcy5cbiAqXG4gKiBAcmV0dXJucyB0cnVlIGlmIHdlIGhlbGQgdGhlIGxvY2sgYW5kIHJhbiBgZm5gOyBmYWxzZSBpZiBhbm90aGVyIGhvbGRlciBkaWQgb3JcbiAqICAgdGhlIGxvY2sgY291bGRuJ3QgYmUgdGFrZW4uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB3aXRoTGVhZGVyTG9jayhcbiAgcmVkaXM6IExvY2tSZWRpcyxcbiAga2V5OiBzdHJpbmcsXG4gIHR0bE1zOiBudW1iZXIsXG4gIGZuOiAoKSA9PiBQcm9taXNlPHZvaWQ+LFxuKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IHRva2VuID0gcmFuZG9tVVVJRCgpO1xuICBsZXQgYWNxdWlyZWQ6IHVua25vd247XG4gIHRyeSB7XG4gICAgYWNxdWlyZWQgPSBhd2FpdCByZWRpcy5zZXQoa2V5LCB0b2tlbiwgJ1BYJywgdHRsTXMsICdOWCcpO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICAvLyBSZWRpcyB1bnJlYWNoYWJsZSAoaW5jbHVkaW5nIGJlZm9yZSB0aGUgZmlyc3QgY29ubmVjdGlvbiBjb21wbGV0ZXMpOiBub1xuICAgIC8vIHBvZCBjYW4gcHJvdmUgaXQncyB0aGUgbGVhZGVyLCBzbyBub2JvZHkgcnVucyB0aGlzIHdpbmRvdy4gU2tpcHBpbmcgaXNcbiAgICAvLyB0aGUgc2FmZSBzaWRlIOKAlCBydW5uaW5nIGhlcmUgY291bGQgZHVwbGljYXRlIGRlc3RydWN0aXZlIHdvcmsgYWNyb3NzIHBvZHMuXG4gICAgbG9ja0xvZ2dlci53YXJuKCdMZWFkZXIgbG9jayB1bmF2YWlsYWJsZTsgc2tpcHBpbmcgdGhpcyBydW4nLCB7XG4gICAgICBrZXksIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVyciksXG4gICAgfSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIGlmIChhY3F1aXJlZCAhPT0gJ09LJykgcmV0dXJuIGZhbHNlO1xuXG4gIHRyeSB7XG4gICAgYXdhaXQgZm4oKTtcbiAgfSBmaW5hbGx5IHtcbiAgICAvLyBSZWxlYXNlIG9ubHkgaWYgdGhlIGxvY2sgaXMgc3RpbGwgb3VycyDigJQgaWYgb3VyIHJ1biBvdmVycmFuIHRoZSBUVEwgYW5kXG4gICAgLy8gYW5vdGhlciBob2xkZXIgdG9vayBvdmVyLCByZWxlYXNpbmcgaGVyZSB3b3VsZCBmcmVlIHRoZWlyIGxvY2sgZWFybHkuXG4gICAgLy8gUHJlZmVyIGFuIGF0b21pYyBDQVMgKEx1YSk7IGZhbGwgYmFjayB0byBnZXQtdGhlbi1kZWwgZm9yIGNsaWVudHMgd2l0aG91dFxuICAgIC8vIGV2YWwgKHRoZSBzbWFsbCBub24tYXRvbWljIHdpbmRvdyBpcyBUVEwtYm91bmRlZCkuXG4gICAgdHJ5IHtcbiAgICAgIGlmICh0eXBlb2YgcmVkaXMuZXZhbCA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICBhd2FpdCByZWRpcy5ldmFsKFJFTEVBU0VfSUZfT1dORVIsIDEsIGtleSwgdG9rZW4pO1xuICAgICAgfSBlbHNlIGlmICgoYXdhaXQgcmVkaXMuZ2V0KGtleSkpID09PSB0b2tlbikge1xuICAgICAgICBhd2FpdCByZWRpcy5kZWwoa2V5KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIEJlc3QtZWZmb3J0OiB0aGUgVFRMIGV4cGlyZXMgdGhlIGxvY2sgcmVnYXJkbGVzcy5cbiAgICB9XG4gIH1cbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKlxuICogQSBgTG9ja1JlZGlzYCBiYWNrZWQgYnkgdGhlIHNoYXJlZCBlbnYgUmVkaXMgY2xpZW50IChzZWUgZW52LXJlZGlzLnRzKSDigJQgdGhlIGxlYWRlci1sb2NrIGNvdW50ZXJwYXJ0IHRvIGBjcmVhdGVFbnZSZWRpc0F1ZGl0U3Bvb2xgIC9cbiAqIGBjcmVhdGVFbnZSZWRpc1Rva2VuUmV2b2NhdGlvblN0b3JlYCwgZm9yIGEgc2VydmljZSB0aGF0IGhhcyBubyBCdWxsTVEgY2xpZW50IHRvXG4gKiBib3Jyb3cuIFJldHVybnMgYG51bGxgIHdoZW4gUmVkaXMgaXNuJ3QgY29uZmlndXJlZCwgc28gYSBjYWxsZXIgY2FuIGRlZ3JhZGUgdG9cbiAqIHJ1bm5pbmcgb24gZXZlcnkgcG9kICh0aGUgYXRvbWljIGd1YXJkcyBpdCB3cmFwcyBtdXN0IHN0aWxsIG1ha2UgdGhhdCBzYWZlKS5cbiAqIFRoZSBjbGllbnQgaXMgYSByZWFsIGlvcmVkaXMgaW5zdGFuY2U7IGlvcmVkaXMgZXhwb3NlcyBgc2V0YC9gZ2V0YC9gZGVsYC9gZXZhbGAsXG4gKiBzYXRpc2Z5aW5nIGBMb2NrUmVkaXNgIGluY2x1ZGluZyB0aGUgYXRvbWljIENBUyByZWxlYXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRW52UmVkaXNMb2NrKCk6IExvY2tSZWRpcyB8IG51bGwge1xuICBjb25zdCBpbnN0ID0gY3JlYXRlRW52UmVkaXNDbGllbnQ8TG9ja1JlZGlzPignbGVhZGVyLWxvY2snKTtcbiAgaWYgKGluc3QpIGxvY2tMb2dnZXIuaW5mbygnUmVkaXMgbGVhZGVyLWxvY2sgY2xpZW50IGluaXRpYWxpemVkJyk7XG4gIHJldHVybiBpbnN0O1xufVxuIl19
@@ -1,12 +1,35 @@
1
+ import type { Response } from 'express';
1
2
  import type { QuotaType, QuotaCheckResult } from '../types/common.js';
2
3
  import { type QuotaTier } from '../types/quota-tiers.js';
4
+ /**
5
+ * Service-principal `Authorization` header for a quota call made on behalf of
6
+ * `orgId` by the CURRENT service (`SERVICE_NAME`, the same identity the health
7
+ * router reports).
8
+ *
9
+ * The quota service's mutation endpoints (`/:orgId/increment`, `/decrement`)
10
+ * reject anything but a signed service principal or a system admin, so
11
+ * forwarding the end user's bearer token there is always a 403. Service tokens
12
+ * are also exempt from the quota service's per-IP rate limiter, so hot-path
13
+ * quota traffic from a busy pod can't exhaust that pod IP's bucket and silently
14
+ * turn enforcement off. Scoped to the target org with the lowest role (member).
15
+ */
16
+ export declare function getQuotaServiceAuthHeader(orgId: string): string;
3
17
  /**
4
18
  * Result of a synchronous quota reservation the atomic check+increment
5
19
  * variant. `exceeded: true` means the operation was rejected at the DB
6
20
  * level; the caller should return 429 without running the gated action.
7
21
  */
8
22
  export interface QuotaReserveResult {
23
+ /** True when the slot was NOT reserved — the gated action must not run. */
9
24
  exceeded: boolean;
25
+ /**
26
+ * Set (true) by the quota CLIENT when the reservation was denied because the
27
+ * quota service could not CONFIRM it (unreachable / timeout / circuit open /
28
+ * errored) — not because the org is over its limit. Always paired with
29
+ * `exceeded: true`. Answer with {@link sendQuotaReserveDenied}, which maps it to
30
+ * 503 instead of a misleading 429 "quota exceeded".
31
+ */
32
+ unavailable?: boolean;
10
33
  quota: {
11
34
  type: QuotaType;
12
35
  limit: number;
@@ -29,9 +52,9 @@ export interface QuotaService {
29
52
  * reserved. Use this for expensive resources where the post-hoc fire-
30
53
  * and-forget pattern allows concurrent over-spend.
31
54
  *
32
- * Fail-open semantics on transport errors: returns `exceeded: false` so
33
- * the caller can proceed and the request isn't blocked by quota-service
34
- * outages (matches `check()`).
55
+ * Fails CLOSED (`exceeded: true`) whenever the slot is not confirmed —
56
+ * unreachable, timeout, open circuit, non-ok, or a non-quota 429 — unless
57
+ * `QUOTA_RESERVE_FAIL_OPEN=true` (see the policy note at the top of this file).
35
58
  */
36
59
  reserve(orgId: string, quotaType: QuotaType, authHeader: string, amount?: number, requestId?: string): Promise<QuotaReserveResult>;
37
60
  /**
@@ -88,18 +111,20 @@ export interface QuotaServiceConfig {
88
111
  */
89
112
  export declare function createQuotaService(config?: QuotaServiceConfig): QuotaService;
90
113
  /**
91
- * Fire-and-forget quota increment with standardized error logging.
114
+ * Fire-and-forget quota METERING increment with standardized error logging.
92
115
  *
93
- * Wraps `quotaService.increment()` with a `.catch()` that logs a warning.
94
- * Eliminates the identical one-liner repeated across every read route.
116
+ * Authenticates as the calling SERVICE ({@link getQuotaServiceAuthHeader}), never
117
+ * with the end user's token: `/quotas/:orgId/increment` is service-principal /
118
+ * system-admin only, so a forwarded user JWT was always rejected (403) and the
119
+ * metered quota (e.g. `apiCalls`, and the `api_pack` bundle raising it) never
120
+ * moved.
95
121
  *
96
122
  * @param quotaService - Quota service client
97
- * @param orgId - Organization ID
123
+ * @param orgId - Organization ID whose counter is incremented
98
124
  * @param quotaType - Quota type to increment
99
- * @param authHeader - Authorization header value
100
125
  * @param logWarn - Logging function for warnings
101
126
  */
102
- export declare function incrementQuota(quotaService: QuotaService, orgId: string, quotaType: QuotaType, authHeader: string, logWarn: (message: string, data?: unknown) => void): void;
127
+ export declare function incrementQuota(quotaService: QuotaService, orgId: string, quotaType: QuotaType, logWarn: (message: string, data?: unknown) => void): void;
103
128
  /**
104
129
  * Atomic reserve helper for the "reserve + commit / rollback" pattern.
105
130
  * Use for expensive resources (pipelines, plugins, AI calls) where the
@@ -111,7 +136,7 @@ export declare function incrementQuota(quotaService: QuotaService, orgId: string
111
136
  * @example
112
137
  * ```typescript
113
138
  * const reservation = await reserveQuota(quotaService, orgId, 'pipelines', authHeader);
114
- * if (reservation.exceeded) return sendQuotaExceeded(res, 'pipelines', reservation.quota);
139
+ * if (reservation.exceeded) return sendQuotaReserveDenied(res, 'pipelines', reservation);
115
140
  * try {
116
141
  * await doExpensiveThing();
117
142
  * } catch (err) {
@@ -122,6 +147,15 @@ export declare function incrementQuota(quotaService: QuotaService, orgId: string
122
147
  * ```
123
148
  */
124
149
  export declare function reserveQuota(quotaService: QuotaService, orgId: string, quotaType: QuotaType, authHeader: string, amount?: number, requestId?: string): Promise<QuotaReserveResult>;
150
+ /**
151
+ * Answer a DENIED reservation (`reservation.exceeded === true`).
152
+ *
153
+ * - `unavailable` (the quota service couldn't confirm the slot) → **503**
154
+ * `SERVICE_UNAVAILABLE` with a short `Retry-After`: the org is not over its
155
+ * limit, so a 429 "quota exceeded" would send users to upgrade for an outage.
156
+ * - otherwise → the standard 429 `QUOTA_EXCEEDED` with the X-Quota-* headers.
157
+ */
158
+ export declare function sendQuotaReserveDenied(res: Response, quotaType: QuotaType, reservation: QuotaReserveResult): void;
125
159
  /**
126
160
  * Fire-and-forget rollback for a previously reserved quota slot.
127
161
  * Logs on failure but never throws the action that needed the rollback