@pipeline-builder/api-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +98 -0
- package/lib/constants/ai-providers.d.ts +52 -0
- package/lib/constants/ai-providers.js +129 -0
- package/lib/constants/http-status.d.ts +20 -0
- package/lib/constants/http-status.js +26 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.js +6 -0
- package/lib/constants/time.d.ts +10 -0
- package/lib/constants/time.js +13 -0
- package/lib/errors/app-errors.d.ts +30 -0
- package/lib/errors/app-errors.js +53 -0
- package/lib/errors/index.d.ts +1 -0
- package/lib/errors/index.js +4 -0
- package/lib/helpers/access-helpers.d.ts +63 -0
- package/lib/helpers/access-helpers.js +82 -0
- package/lib/helpers/crud-helpers.d.ts +9 -0
- package/lib/helpers/crud-helpers.js +23 -0
- package/lib/helpers/index.d.ts +6 -0
- package/lib/helpers/index.js +9 -0
- package/lib/helpers/org-hierarchy-http.d.ts +74 -0
- package/lib/helpers/org-hierarchy-http.js +144 -0
- package/lib/helpers/org-hierarchy.d.ts +57 -0
- package/lib/helpers/org-hierarchy.js +110 -0
- package/lib/helpers/restore-helpers.d.ts +49 -0
- package/lib/helpers/restore-helpers.js +78 -0
- package/lib/helpers/sse-helpers.d.ts +20 -0
- package/lib/helpers/sse-helpers.js +45 -0
- package/lib/index.d.ts +58 -0
- package/lib/index.js +71 -0
- package/lib/middleware/auth.d.ts +208 -0
- package/lib/middleware/auth.js +588 -0
- package/lib/middleware/index.d.ts +3 -0
- package/lib/middleware/index.js +6 -0
- package/lib/middleware/mongo-sanitize.d.ts +32 -0
- package/lib/middleware/mongo-sanitize.js +100 -0
- package/lib/middleware/step-up.d.ts +45 -0
- package/lib/middleware/step-up.js +138 -0
- package/lib/openapi/extend-zod.d.ts +1 -0
- package/lib/openapi/extend-zod.js +6 -0
- package/lib/openapi/index.d.ts +2 -0
- package/lib/openapi/index.js +5 -0
- package/lib/openapi/registry.d.ts +17 -0
- package/lib/openapi/registry.js +38 -0
- package/lib/openapi/routes/ask-routes.d.ts +7 -0
- package/lib/openapi/routes/ask-routes.js +62 -0
- package/lib/openapi/routes/billing-routes.d.ts +1 -0
- package/lib/openapi/routes/billing-routes.js +66 -0
- package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
- package/lib/openapi/routes/image-registry-routes.js +98 -0
- package/lib/openapi/routes/index.d.ts +5 -0
- package/lib/openapi/routes/index.js +28 -0
- package/lib/openapi/routes/message-routes.d.ts +1 -0
- package/lib/openapi/routes/message-routes.js +105 -0
- package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-routes.js +105 -0
- package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-template-routes.js +61 -0
- package/lib/openapi/routes/plugin-routes.d.ts +1 -0
- package/lib/openapi/routes/plugin-routes.js +96 -0
- package/lib/openapi/routes/quota-routes.d.ts +1 -0
- package/lib/openapi/routes/quota-routes.js +62 -0
- package/lib/openapi/schema-registry.d.ts +25 -0
- package/lib/openapi/schema-registry.js +91 -0
- package/lib/routes/health.d.ts +41 -0
- package/lib/routes/health.js +82 -0
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/index.js +4 -0
- package/lib/services/audit-spool.d.ts +80 -0
- package/lib/services/audit-spool.js +172 -0
- package/lib/services/cache-service.d.ts +124 -0
- package/lib/services/cache-service.js +276 -0
- package/lib/services/circuit-breaker.d.ts +45 -0
- package/lib/services/circuit-breaker.js +138 -0
- package/lib/services/compliance-client.d.ts +46 -0
- package/lib/services/compliance-client.js +128 -0
- package/lib/services/compliance-event-subscriber.d.ts +16 -0
- package/lib/services/compliance-event-subscriber.js +72 -0
- package/lib/services/durable-event-bus.d.ts +96 -0
- package/lib/services/durable-event-bus.js +204 -0
- package/lib/services/entity-events.d.ts +48 -0
- package/lib/services/entity-events.js +79 -0
- package/lib/services/env-redis.d.ts +44 -0
- package/lib/services/env-redis.js +94 -0
- package/lib/services/http-client.d.ts +143 -0
- package/lib/services/http-client.js +335 -0
- package/lib/services/index.d.ts +18 -0
- package/lib/services/index.js +20 -0
- package/lib/services/leader-lock.d.ts +45 -0
- package/lib/services/leader-lock.js +85 -0
- package/lib/services/quota.d.ts +130 -0
- package/lib/services/quota.js +264 -0
- package/lib/services/remote-audit-client.d.ts +160 -0
- package/lib/services/remote-audit-client.js +390 -0
- package/lib/services/retry-strategy.d.ts +74 -0
- package/lib/services/retry-strategy.js +118 -0
- package/lib/services/scheduler.d.ts +41 -0
- package/lib/services/scheduler.js +86 -0
- package/lib/services/service-boot.d.ts +20 -0
- package/lib/services/service-boot.js +28 -0
- package/lib/services/sse-ticket-store.d.ts +51 -0
- package/lib/services/sse-ticket-store.js +130 -0
- package/lib/services/token-revocation.d.ts +52 -0
- package/lib/services/token-revocation.js +120 -0
- package/lib/testing/tier-mock.d.ts +44 -0
- package/lib/testing/tier-mock.js +65 -0
- package/lib/types/audit-events.d.ts +69 -0
- package/lib/types/audit-events.js +4 -0
- package/lib/types/catalog-metadata.d.ts +36 -0
- package/lib/types/catalog-metadata.js +21 -0
- package/lib/types/common.d.ts +255 -0
- package/lib/types/common.js +41 -0
- package/lib/types/error-codes.d.ts +54 -0
- package/lib/types/error-codes.js +107 -0
- package/lib/types/feature-flags.d.ts +36 -0
- package/lib/types/feature-flags.js +128 -0
- package/lib/types/http.d.ts +27 -0
- package/lib/types/http.js +4 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +14 -0
- package/lib/types/permissions.d.ts +65 -0
- package/lib/types/permissions.js +131 -0
- package/lib/types/pipeline-template.d.ts +30 -0
- package/lib/types/pipeline-template.js +13 -0
- package/lib/types/pipeline.d.ts +59 -0
- package/lib/types/pipeline.js +31 -0
- package/lib/types/quota-tiers.d.ts +79 -0
- package/lib/types/quota-tiers.js +221 -0
- package/lib/types/visibility.d.ts +20 -0
- package/lib/types/visibility.js +22 -0
- package/lib/utils/alias-resolver.d.ts +37 -0
- package/lib/utils/alias-resolver.js +68 -0
- package/lib/utils/audit.d.ts +15 -0
- package/lib/utils/audit.js +39 -0
- package/lib/utils/aws-scrub.d.ts +11 -0
- package/lib/utils/aws-scrub.js +67 -0
- package/lib/utils/compliance-attributes.d.ts +6 -0
- package/lib/utils/compliance-attributes.js +69 -0
- package/lib/utils/concurrency.d.ts +9 -0
- package/lib/utils/concurrency.js +29 -0
- package/lib/utils/crash-handlers.d.ts +18 -0
- package/lib/utils/crash-handlers.js +36 -0
- package/lib/utils/env.d.ts +28 -0
- package/lib/utils/env.js +54 -0
- package/lib/utils/headers.d.ts +18 -0
- package/lib/utils/headers.js +21 -0
- package/lib/utils/identity.d.ts +40 -0
- package/lib/utils/identity.js +51 -0
- package/lib/utils/index.d.ts +17 -0
- package/lib/utils/index.js +20 -0
- package/lib/utils/logger.d.ts +38 -0
- package/lib/utils/logger.js +174 -0
- package/lib/utils/metric-emitter.d.ts +36 -0
- package/lib/utils/metric-emitter.js +32 -0
- package/lib/utils/object.d.ts +13 -0
- package/lib/utils/object.js +18 -0
- package/lib/utils/params.d.ts +150 -0
- package/lib/utils/params.js +206 -0
- package/lib/utils/response.d.ts +117 -0
- package/lib/utils/response.js +223 -0
- package/lib/utils/safe-require.d.ts +13 -0
- package/lib/utils/safe-require.js +19 -0
- package/lib/utils/secret-encryption.d.ts +176 -0
- package/lib/utils/secret-encryption.js +374 -0
- package/lib/utils/ssrf.d.ts +30 -0
- package/lib/utils/ssrf.js +106 -0
- package/lib/validation/ai-schemas.d.ts +56 -0
- package/lib/validation/ai-schemas.js +78 -0
- package/lib/validation/common-schemas.d.ts +133 -0
- package/lib/validation/common-schemas.js +94 -0
- package/lib/validation/index.d.ts +7 -0
- package/lib/validation/index.js +10 -0
- package/lib/validation/message-schemas.d.ts +160 -0
- package/lib/validation/message-schemas.js +156 -0
- package/lib/validation/middleware.d.ts +72 -0
- package/lib/validation/middleware.js +65 -0
- package/lib/validation/pipeline-schemas.d.ts +181 -0
- package/lib/validation/pipeline-schemas.js +101 -0
- package/lib/validation/pipeline-template-schemas.d.ts +155 -0
- package/lib/validation/pipeline-template-schemas.js +66 -0
- package/lib/validation/plugin-schemas.d.ts +171 -0
- package/lib/validation/plugin-schemas.js +81 -0
- package/openapi.yaml +292 -0
- package/package.json +167 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { RedisCacheClient } from './cache-service.js';
|
|
2
|
+
import type { TokenRevocationStore } from '../middleware/auth.js';
|
|
3
|
+
/**
|
|
4
|
+
* Redis key namespace for the per-user current `tokenVersion`. The platform
|
|
5
|
+
* PUBLISHES to this key on every privilege change; the stateless services READ
|
|
6
|
+
* it in `requireAuth` (via {@link createRedisTokenRevocationStore}) to reject
|
|
7
|
+
* tokens minted before that change. Publisher and readers MUST share this exact
|
|
8
|
+
* prefix — hence both helpers live here, in one place.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TOKEN_REVOCATION_KEY_PREFIX = "authrev:tv:";
|
|
11
|
+
/** The revocation key for a user id. */
|
|
12
|
+
export declare function tokenRevocationKey(userId: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Build a {@link TokenRevocationStore} backed by a Redis client, for a stateless
|
|
15
|
+
* service to register via `setTokenRevocationStore`. Reads the current
|
|
16
|
+
* `tokenVersion` the platform published for the user.
|
|
17
|
+
*
|
|
18
|
+
* Fail-open by contract: a miss, a parse failure, or a Redis error all yield
|
|
19
|
+
* `null` (— `requireAuth` treats that as "no known revocation"), so a Redis
|
|
20
|
+
* outage degrades to the pre-existing behaviour rather than locking users out.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createRedisTokenRevocationStore(redis: RedisCacheClient): TokenRevocationStore;
|
|
23
|
+
/**
|
|
24
|
+
* Build a {@link TokenRevocationStore} backed by a Redis client that is lazily
|
|
25
|
+
* constructed from the standard `REDIS_URL` or `REDIS_HOST`/`REDIS_PORT` env —
|
|
26
|
+
* for a stateless service that keeps NO Redis client of its own. The client is
|
|
27
|
+
* built on first `getCurrentVersion` call and memoized; `ioredis` is loaded via
|
|
28
|
+
* a guarded dynamic require so merely importing this never breaks a build/test
|
|
29
|
+
* where Redis isn't present.
|
|
30
|
+
*
|
|
31
|
+
* FULLY FAIL-OPEN: no env configured, an ioredis load failure, a connect error,
|
|
32
|
+
* or any read failure all resolve to `null` ("no known revocation"), so a
|
|
33
|
+
* service degrades to natural token expiry rather than locking users out. A
|
|
34
|
+
* service opts in with a single boot line:
|
|
35
|
+
* `setTokenRevocationStore(createEnvRedisTokenRevocationStore());`
|
|
36
|
+
*/
|
|
37
|
+
export declare function createEnvRedisTokenRevocationStore(): TokenRevocationStore;
|
|
38
|
+
/**
|
|
39
|
+
* Publish a user's current `tokenVersion` so the stateless services see the
|
|
40
|
+
* revocation immediately (platform side). Best-effort — never throws; a failure
|
|
41
|
+
* just means the services fall back to natural token expiry for this change.
|
|
42
|
+
*
|
|
43
|
+
* The key is written with a TTL equal to the access-token lifetime: any token
|
|
44
|
+
* that could still carry an older version has expired by the time the entry
|
|
45
|
+
* lapses, so an expired entry can never cause a false "revoked" nor a missed one.
|
|
46
|
+
*
|
|
47
|
+
* @param redis - the platform's Redis client
|
|
48
|
+
* @param userId - the user whose sessions changed
|
|
49
|
+
* @param tokenVersion - the user's NEW (post-increment) tokenVersion
|
|
50
|
+
* @param ttlSeconds - access-token lifetime in seconds
|
|
51
|
+
*/
|
|
52
|
+
export declare function publishTokenRevocation(redis: RedisCacheClient, userId: string, tokenVersion: number, ttlSeconds: number): Promise<void>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { createEnvRedisClient } from './env-redis.js';
|
|
4
|
+
import { createLogger } from '../utils/logger.js';
|
|
5
|
+
import { emitCounter } from '../utils/metric-emitter.js';
|
|
6
|
+
const logger = createLogger('token-revocation');
|
|
7
|
+
/**
|
|
8
|
+
* Redis key namespace for the per-user current `tokenVersion`. The platform
|
|
9
|
+
* PUBLISHES to this key on every privilege change; the stateless services READ
|
|
10
|
+
* it in `requireAuth` (via {@link createRedisTokenRevocationStore}) to reject
|
|
11
|
+
* tokens minted before that change. Publisher and readers MUST share this exact
|
|
12
|
+
* prefix — hence both helpers live here, in one place.
|
|
13
|
+
*/
|
|
14
|
+
export const TOKEN_REVOCATION_KEY_PREFIX = 'authrev:tv:';
|
|
15
|
+
/** The revocation key for a user id. */
|
|
16
|
+
export function tokenRevocationKey(userId) {
|
|
17
|
+
return `${TOKEN_REVOCATION_KEY_PREFIX}${userId}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a {@link TokenRevocationStore} backed by a Redis client, for a stateless
|
|
21
|
+
* service to register via `setTokenRevocationStore`. Reads the current
|
|
22
|
+
* `tokenVersion` the platform published for the user.
|
|
23
|
+
*
|
|
24
|
+
* Fail-open by contract: a miss, a parse failure, or a Redis error all yield
|
|
25
|
+
* `null` (— `requireAuth` treats that as "no known revocation"), so a Redis
|
|
26
|
+
* outage degrades to the pre-existing behaviour rather than locking users out.
|
|
27
|
+
*/
|
|
28
|
+
export function createRedisTokenRevocationStore(redis) {
|
|
29
|
+
return {
|
|
30
|
+
async getCurrentVersion(userId) {
|
|
31
|
+
try {
|
|
32
|
+
const raw = await redis.get(tokenRevocationKey(userId));
|
|
33
|
+
if (raw === null || raw === undefined || raw.trim() === '')
|
|
34
|
+
return null;
|
|
35
|
+
// Strict integer parse: `Number` (unlike `parseInt`) rejects trailing
|
|
36
|
+
// garbage ("5abc" → NaN), so a corrupted entry fails open (null → "not
|
|
37
|
+
// revoked") rather than being read as a bogus version. The empty-string
|
|
38
|
+
// guard above matters because `Number('')` is 0, not NaN.
|
|
39
|
+
const n = Number(raw);
|
|
40
|
+
return Number.isInteger(n) ? n : null;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// Redis was reachable enough to try but the read errored — this is a
|
|
44
|
+
// DEGRADATION: while it persists, forced logouts / privilege revocations
|
|
45
|
+
// silently stop taking effect until natural token expiry. Emit a counter
|
|
46
|
+
// so `TokenRevocationFailingOpen` can alert on a SUSTAINED rate (mirrors
|
|
47
|
+
// the quota_fail_open_total pattern). Not emitted for the "Redis not
|
|
48
|
+
// configured" path (createEnvRedisTokenRevocationStore → null client),
|
|
49
|
+
// which is a deliberate opt-out, not a degradation.
|
|
50
|
+
emitCounter('token_revocation_fail_open_total', { reason: 'read-error' });
|
|
51
|
+
logger.debug('Token-revocation read failed (fail-open)', {
|
|
52
|
+
userId, error: err instanceof Error ? err.message : String(err),
|
|
53
|
+
});
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build a {@link TokenRevocationStore} backed by a Redis client that is lazily
|
|
61
|
+
* constructed from the standard `REDIS_URL` or `REDIS_HOST`/`REDIS_PORT` env —
|
|
62
|
+
* for a stateless service that keeps NO Redis client of its own. The client is
|
|
63
|
+
* built on first `getCurrentVersion` call and memoized; `ioredis` is loaded via
|
|
64
|
+
* a guarded dynamic require so merely importing this never breaks a build/test
|
|
65
|
+
* where Redis isn't present.
|
|
66
|
+
*
|
|
67
|
+
* FULLY FAIL-OPEN: no env configured, an ioredis load failure, a connect error,
|
|
68
|
+
* or any read failure all resolve to `null` ("no known revocation"), so a
|
|
69
|
+
* service degrades to natural token expiry rather than locking users out. A
|
|
70
|
+
* service opts in with a single boot line:
|
|
71
|
+
* `setTokenRevocationStore(createEnvRedisTokenRevocationStore());`
|
|
72
|
+
*/
|
|
73
|
+
export function createEnvRedisTokenRevocationStore() {
|
|
74
|
+
// undefined = not yet attempted; null = attempted and unavailable (stay off).
|
|
75
|
+
let cached;
|
|
76
|
+
function build() {
|
|
77
|
+
// Shared env-configured ioredis construction (fail-open, error-listener
|
|
78
|
+
// attached, null when Redis isn't configured/available); the reader stays
|
|
79
|
+
// fail-open — a null client just falls back to natural token expiry.
|
|
80
|
+
const inst = createEnvRedisClient('revocation-reader');
|
|
81
|
+
if (inst)
|
|
82
|
+
logger.info('Redis token-revocation reader initialized');
|
|
83
|
+
return inst;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
async getCurrentVersion(userId) {
|
|
87
|
+
if (cached === undefined)
|
|
88
|
+
cached = build();
|
|
89
|
+
if (!cached)
|
|
90
|
+
return null;
|
|
91
|
+
return createRedisTokenRevocationStore(cached).getCurrentVersion(userId);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Publish a user's current `tokenVersion` so the stateless services see the
|
|
97
|
+
* revocation immediately (platform side). Best-effort — never throws; a failure
|
|
98
|
+
* just means the services fall back to natural token expiry for this change.
|
|
99
|
+
*
|
|
100
|
+
* The key is written with a TTL equal to the access-token lifetime: any token
|
|
101
|
+
* that could still carry an older version has expired by the time the entry
|
|
102
|
+
* lapses, so an expired entry can never cause a false "revoked" nor a missed one.
|
|
103
|
+
*
|
|
104
|
+
* @param redis - the platform's Redis client
|
|
105
|
+
* @param userId - the user whose sessions changed
|
|
106
|
+
* @param tokenVersion - the user's NEW (post-increment) tokenVersion
|
|
107
|
+
* @param ttlSeconds - access-token lifetime in seconds
|
|
108
|
+
*/
|
|
109
|
+
export async function publishTokenRevocation(redis, userId, tokenVersion, ttlSeconds) {
|
|
110
|
+
try {
|
|
111
|
+
// ioredis-style variadic SET with expiry: SET key val EX ttl.
|
|
112
|
+
await redis.set(tokenRevocationKey(userId), String(tokenVersion), 'EX', Math.max(1, Math.floor(ttlSeconds)));
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
logger.warn('Token-revocation publish failed (services fall back to token expiry)', {
|
|
116
|
+
userId, error: err instanceof Error ? err.message : String(err),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tcmV2b2NhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy90b2tlbi1yZXZvY2F0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFHdEMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFdEQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUV6RCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsa0JBQWtCLENBQUMsQ0FBQztBQUVoRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSwyQkFBMkIsR0FBRyxhQUFhLENBQUM7QUFFekQsd0NBQXdDO0FBQ3hDLE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxNQUFjO0lBQy9DLE9BQU8sR0FBRywyQkFBMkIsR0FBRyxNQUFNLEVBQUUsQ0FBQztBQUNuRCxDQUFDO0FBRUQ7Ozs7Ozs7O0dBUUc7QUFDSCxNQUFNLFVBQVUsK0JBQStCLENBQUMsS0FBdUI7SUFDckUsT0FBTztRQUNMLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxNQUFjO1lBQ3BDLElBQUksQ0FBQztnQkFDSCxNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsa0JBQWtCLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztnQkFDeEQsSUFBSSxHQUFHLEtBQUssSUFBSSxJQUFJLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUU7b0JBQUUsT0FBTyxJQUFJLENBQUM7Z0JBQ3hFLHNFQUFzRTtnQkFDdEUsdUVBQXVFO2dCQUN2RSx3RUFBd0U7Z0JBQ3hFLDBEQUEwRDtnQkFDMUQsTUFBTSxDQUFDLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUN0QixPQUFPLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1lBQ3hDLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLHFFQUFxRTtnQkFDckUseUVBQXlFO2dCQUN6RSx5RUFBeUU7Z0JBQ3pFLHlFQUF5RTtnQkFDekUscUVBQXFFO2dCQUNyRSx1RUFBdUU7Z0JBQ3ZFLG9EQUFvRDtnQkFDcEQsV0FBVyxDQUFDLGtDQUFrQyxFQUFFLEVBQUUsTUFBTSxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUM7Z0JBQzFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsMENBQTBDLEVBQUU7b0JBQ3ZELE1BQU0sRUFBRSxLQUFLLEVBQUUsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQztpQkFDaEUsQ0FBQyxDQUFDO2dCQUNILE9BQU8sSUFBSSxDQUFDO1lBQ2QsQ0FBQztRQUNILENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLFVBQVUsa0NBQWtDO0lBQ2hELDhFQUE4RTtJQUM5RSxJQUFJLE1BQTJDLENBQUM7SUFFaEQsU0FBUyxLQUFLO1FBQ1osd0VBQXdFO1FBQ3hFLDBFQUEwRTtRQUMxRSxxRUFBcUU7UUFDckUsTUFBTSxJQUFJLEdBQUcsb0JBQW9CLENBQW1CLG1CQUFtQixDQUFDLENBQUM7UUFDekUsSUFBSSxJQUFJO1lBQUUsTUFBTSxDQUFDLElBQUksQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDO1FBQ25FLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELE9BQU87UUFDTCxLQUFLLENBQUMsaUJBQWlCLENBQUMsTUFBYztZQUNwQyxJQUFJLE1BQU0sS0FBSyxTQUFTO2dCQUFFLE1BQU0sR0FBRyxLQUFLLEVBQUUsQ0FBQztZQUMzQyxJQUFJLENBQUMsTUFBTTtnQkFBRSxPQUFPLElBQUksQ0FBQztZQUN6QixPQUFPLCtCQUErQixDQUFDLE1BQU0sQ0FBQyxDQUFDLGlCQUFpQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzNFLENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLHNCQUFzQixDQUMxQyxLQUF1QixFQUN2QixNQUFjLEVBQ2QsWUFBb0IsRUFDcEIsVUFBa0I7SUFFbEIsSUFBSSxDQUFDO1FBQ0gsOERBQThEO1FBQzlELE1BQU0sS0FBSyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsRUFBRSxNQUFNLENBQUMsWUFBWSxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQy9HLENBQUM7SUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1FBQ2IsTUFBTSxDQUFDLElBQUksQ0FBQyxzRUFBc0UsRUFBRTtZQUNsRixNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUM7U0FDaEUsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztBQUNILENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHR5cGUgeyBSZWRpc0NhY2hlQ2xpZW50IH0gZnJvbSAnLi9jYWNoZS1zZXJ2aWNlLmpzJztcbmltcG9ydCB7IGNyZWF0ZUVudlJlZGlzQ2xpZW50IH0gZnJvbSAnLi9lbnYtcmVkaXMuanMnO1xuaW1wb3J0IHR5cGUgeyBUb2tlblJldm9jYXRpb25TdG9yZSB9IGZyb20gJy4uL21pZGRsZXdhcmUvYXV0aC5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIgfSBmcm9tICcuLi91dGlscy9tZXRyaWMtZW1pdHRlci5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcigndG9rZW4tcmV2b2NhdGlvbicpO1xuXG4vKipcbiAqIFJlZGlzIGtleSBuYW1lc3BhY2UgZm9yIHRoZSBwZXItdXNlciBjdXJyZW50IGB0b2tlblZlcnNpb25gLiBUaGUgcGxhdGZvcm1cbiAqIFBVQkxJU0hFUyB0byB0aGlzIGtleSBvbiBldmVyeSBwcml2aWxlZ2UgY2hhbmdlOyB0aGUgc3RhdGVsZXNzIHNlcnZpY2VzIFJFQURcbiAqIGl0IGluIGByZXF1aXJlQXV0aGAgKHZpYSB7QGxpbmsgY3JlYXRlUmVkaXNUb2tlblJldm9jYXRpb25TdG9yZX0pIHRvIHJlamVjdFxuICogdG9rZW5zIG1pbnRlZCBiZWZvcmUgdGhhdCBjaGFuZ2UuIFB1Ymxpc2hlciBhbmQgcmVhZGVycyBNVVNUIHNoYXJlIHRoaXMgZXhhY3RcbiAqIHByZWZpeCDigJQgaGVuY2UgYm90aCBoZWxwZXJzIGxpdmUgaGVyZSwgaW4gb25lIHBsYWNlLlxuICovXG5leHBvcnQgY29uc3QgVE9LRU5fUkVWT0NBVElPTl9LRVlfUFJFRklYID0gJ2F1dGhyZXY6dHY6JztcblxuLyoqIFRoZSByZXZvY2F0aW9uIGtleSBmb3IgYSB1c2VyIGlkLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRva2VuUmV2b2NhdGlvbktleSh1c2VySWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBgJHtUT0tFTl9SRVZPQ0FUSU9OX0tFWV9QUkVGSVh9JHt1c2VySWR9YDtcbn1cblxuLyoqXG4gKiBCdWlsZCBhIHtAbGluayBUb2tlblJldm9jYXRpb25TdG9yZX0gYmFja2VkIGJ5IGEgUmVkaXMgY2xpZW50LCBmb3IgYSBzdGF0ZWxlc3NcbiAqIHNlcnZpY2UgdG8gcmVnaXN0ZXIgdmlhIGBzZXRUb2tlblJldm9jYXRpb25TdG9yZWAuIFJlYWRzIHRoZSBjdXJyZW50XG4gKiBgdG9rZW5WZXJzaW9uYCB0aGUgcGxhdGZvcm0gcHVibGlzaGVkIGZvciB0aGUgdXNlci5cbiAqXG4gKiBGYWlsLW9wZW4gYnkgY29udHJhY3Q6IGEgbWlzcywgYSBwYXJzZSBmYWlsdXJlLCBvciBhIFJlZGlzIGVycm9yIGFsbCB5aWVsZFxuICogYG51bGxgICjigJQgYHJlcXVpcmVBdXRoYCB0cmVhdHMgdGhhdCBhcyBcIm5vIGtub3duIHJldm9jYXRpb25cIiksIHNvIGEgUmVkaXNcbiAqIG91dGFnZSBkZWdyYWRlcyB0byB0aGUgcHJlLWV4aXN0aW5nIGJlaGF2aW91ciByYXRoZXIgdGhhbiBsb2NraW5nIHVzZXJzIG91dC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVJlZGlzVG9rZW5SZXZvY2F0aW9uU3RvcmUocmVkaXM6IFJlZGlzQ2FjaGVDbGllbnQpOiBUb2tlblJldm9jYXRpb25TdG9yZSB7XG4gIHJldHVybiB7XG4gICAgYXN5bmMgZ2V0Q3VycmVudFZlcnNpb24odXNlcklkOiBzdHJpbmcpOiBQcm9taXNlPG51bWJlciB8IG51bGw+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJhdyA9IGF3YWl0IHJlZGlzLmdldCh0b2tlblJldm9jYXRpb25LZXkodXNlcklkKSk7XG4gICAgICAgIGlmIChyYXcgPT09IG51bGwgfHwgcmF3ID09PSB1bmRlZmluZWQgfHwgcmF3LnRyaW0oKSA9PT0gJycpIHJldHVybiBudWxsO1xuICAgICAgICAvLyBTdHJpY3QgaW50ZWdlciBwYXJzZTogYE51bWJlcmAgKHVubGlrZSBgcGFyc2VJbnRgKSByZWplY3RzIHRyYWlsaW5nXG4gICAgICAgIC8vIGdhcmJhZ2UgKFwiNWFiY1wiIOKGkiBOYU4pLCBzbyBhIGNvcnJ1cHRlZCBlbnRyeSBmYWlscyBvcGVuIChudWxsIOKGkiBcIm5vdFxuICAgICAgICAvLyByZXZva2VkXCIpIHJhdGhlciB0aGFuIGJlaW5nIHJlYWQgYXMgYSBib2d1cyB2ZXJzaW9uLiBUaGUgZW1wdHktc3RyaW5nXG4gICAgICAgIC8vIGd1YXJkIGFib3ZlIG1hdHRlcnMgYmVjYXVzZSBgTnVtYmVyKCcnKWAgaXMgMCwgbm90IE5hTi5cbiAgICAgICAgY29uc3QgbiA9IE51bWJlcihyYXcpO1xuICAgICAgICByZXR1cm4gTnVtYmVyLmlzSW50ZWdlcihuKSA/IG4gOiBudWxsO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIC8vIFJlZGlzIHdhcyByZWFjaGFibGUgZW5vdWdoIHRvIHRyeSBidXQgdGhlIHJlYWQgZXJyb3JlZCDigJQgdGhpcyBpcyBhXG4gICAgICAgIC8vIERFR1JBREFUSU9OOiB3aGlsZSBpdCBwZXJzaXN0cywgZm9yY2VkIGxvZ291dHMgLyBwcml2aWxlZ2UgcmV2b2NhdGlvbnNcbiAgICAgICAgLy8gc2lsZW50bHkgc3RvcCB0YWtpbmcgZWZmZWN0IHVudGlsIG5hdHVyYWwgdG9rZW4gZXhwaXJ5LiBFbWl0IGEgY291bnRlclxuICAgICAgICAvLyBzbyBgVG9rZW5SZXZvY2F0aW9uRmFpbGluZ09wZW5gIGNhbiBhbGVydCBvbiBhIFNVU1RBSU5FRCByYXRlIChtaXJyb3JzXG4gICAgICAgIC8vIHRoZSBxdW90YV9mYWlsX29wZW5fdG90YWwgcGF0dGVybikuIE5vdCBlbWl0dGVkIGZvciB0aGUgXCJSZWRpcyBub3RcbiAgICAgICAgLy8gY29uZmlndXJlZFwiIHBhdGggKGNyZWF0ZUVudlJlZGlzVG9rZW5SZXZvY2F0aW9uU3RvcmUg4oaSIG51bGwgY2xpZW50KSxcbiAgICAgICAgLy8gd2hpY2ggaXMgYSBkZWxpYmVyYXRlIG9wdC1vdXQsIG5vdCBhIGRlZ3JhZGF0aW9uLlxuICAgICAgICBlbWl0Q291bnRlcigndG9rZW5fcmV2b2NhdGlvbl9mYWlsX29wZW5fdG90YWwnLCB7IHJlYXNvbjogJ3JlYWQtZXJyb3InIH0pO1xuICAgICAgICBsb2dnZXIuZGVidWcoJ1Rva2VuLXJldm9jYXRpb24gcmVhZCBmYWlsZWQgKGZhaWwtb3BlbiknLCB7XG4gICAgICAgICAgdXNlcklkLCBlcnJvcjogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpLFxuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgfSxcbiAgfTtcbn1cblxuLyoqXG4gKiBCdWlsZCBhIHtAbGluayBUb2tlblJldm9jYXRpb25TdG9yZX0gYmFja2VkIGJ5IGEgUmVkaXMgY2xpZW50IHRoYXQgaXMgbGF6aWx5XG4gKiBjb25zdHJ1Y3RlZCBmcm9tIHRoZSBzdGFuZGFyZCBgUkVESVNfVVJMYCBvciBgUkVESVNfSE9TVGAvYFJFRElTX1BPUlRgIGVudiDigJRcbiAqIGZvciBhIHN0YXRlbGVzcyBzZXJ2aWNlIHRoYXQga2VlcHMgTk8gUmVkaXMgY2xpZW50IG9mIGl0cyBvd24uIFRoZSBjbGllbnQgaXNcbiAqIGJ1aWx0IG9uIGZpcnN0IGBnZXRDdXJyZW50VmVyc2lvbmAgY2FsbCBhbmQgbWVtb2l6ZWQ7IGBpb3JlZGlzYCBpcyBsb2FkZWQgdmlhXG4gKiBhIGd1YXJkZWQgZHluYW1pYyByZXF1aXJlIHNvIG1lcmVseSBpbXBvcnRpbmcgdGhpcyBuZXZlciBicmVha3MgYSBidWlsZC90ZXN0XG4gKiB3aGVyZSBSZWRpcyBpc24ndCBwcmVzZW50LlxuICpcbiAqIEZVTExZIEZBSUwtT1BFTjogbm8gZW52IGNvbmZpZ3VyZWQsIGFuIGlvcmVkaXMgbG9hZCBmYWlsdXJlLCBhIGNvbm5lY3QgZXJyb3IsXG4gKiBvciBhbnkgcmVhZCBmYWlsdXJlIGFsbCByZXNvbHZlIHRvIGBudWxsYCAoXCJubyBrbm93biByZXZvY2F0aW9uXCIpLCBzbyBhXG4gKiBzZXJ2aWNlIGRlZ3JhZGVzIHRvIG5hdHVyYWwgdG9rZW4gZXhwaXJ5IHJhdGhlciB0aGFuIGxvY2tpbmcgdXNlcnMgb3V0LiBBXG4gKiBzZXJ2aWNlIG9wdHMgaW4gd2l0aCBhIHNpbmdsZSBib290IGxpbmU6XG4gKiAgIGBzZXRUb2tlblJldm9jYXRpb25TdG9yZShjcmVhdGVFbnZSZWRpc1Rva2VuUmV2b2NhdGlvblN0b3JlKCkpO2BcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVudlJlZGlzVG9rZW5SZXZvY2F0aW9uU3RvcmUoKTogVG9rZW5SZXZvY2F0aW9uU3RvcmUge1xuICAvLyB1bmRlZmluZWQgPSBub3QgeWV0IGF0dGVtcHRlZDsgbnVsbCA9IGF0dGVtcHRlZCBhbmQgdW5hdmFpbGFibGUgKHN0YXkgb2ZmKS5cbiAgbGV0IGNhY2hlZDogUmVkaXNDYWNoZUNsaWVudCB8IG51bGwgfCB1bmRlZmluZWQ7XG5cbiAgZnVuY3Rpb24gYnVpbGQoKTogUmVkaXNDYWNoZUNsaWVudCB8IG51bGwge1xuICAgIC8vIFNoYXJlZCBlbnYtY29uZmlndXJlZCBpb3JlZGlzIGNvbnN0cnVjdGlvbiAoZmFpbC1vcGVuLCBlcnJvci1saXN0ZW5lclxuICAgIC8vIGF0dGFjaGVkLCBudWxsIHdoZW4gUmVkaXMgaXNuJ3QgY29uZmlndXJlZC9hdmFpbGFibGUpOyB0aGUgcmVhZGVyIHN0YXlzXG4gICAgLy8gZmFpbC1vcGVuIOKAlCBhIG51bGwgY2xpZW50IGp1c3QgZmFsbHMgYmFjayB0byBuYXR1cmFsIHRva2VuIGV4cGlyeS5cbiAgICBjb25zdCBpbnN0ID0gY3JlYXRlRW52UmVkaXNDbGllbnQ8UmVkaXNDYWNoZUNsaWVudD4oJ3Jldm9jYXRpb24tcmVhZGVyJyk7XG4gICAgaWYgKGluc3QpIGxvZ2dlci5pbmZvKCdSZWRpcyB0b2tlbi1yZXZvY2F0aW9uIHJlYWRlciBpbml0aWFsaXplZCcpO1xuICAgIHJldHVybiBpbnN0O1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBhc3luYyBnZXRDdXJyZW50VmVyc2lvbih1c2VySWQ6IHN0cmluZyk6IFByb21pc2U8bnVtYmVyIHwgbnVsbD4ge1xuICAgICAgaWYgKGNhY2hlZCA9PT0gdW5kZWZpbmVkKSBjYWNoZWQgPSBidWlsZCgpO1xuICAgICAgaWYgKCFjYWNoZWQpIHJldHVybiBudWxsO1xuICAgICAgcmV0dXJuIGNyZWF0ZVJlZGlzVG9rZW5SZXZvY2F0aW9uU3RvcmUoY2FjaGVkKS5nZXRDdXJyZW50VmVyc2lvbih1c2VySWQpO1xuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogUHVibGlzaCBhIHVzZXIncyBjdXJyZW50IGB0b2tlblZlcnNpb25gIHNvIHRoZSBzdGF0ZWxlc3Mgc2VydmljZXMgc2VlIHRoZVxuICogcmV2b2NhdGlvbiBpbW1lZGlhdGVseSAocGxhdGZvcm0gc2lkZSkuIEJlc3QtZWZmb3J0IOKAlCBuZXZlciB0aHJvd3M7IGEgZmFpbHVyZVxuICoganVzdCBtZWFucyB0aGUgc2VydmljZXMgZmFsbCBiYWNrIHRvIG5hdHVyYWwgdG9rZW4gZXhwaXJ5IGZvciB0aGlzIGNoYW5nZS5cbiAqXG4gKiBUaGUga2V5IGlzIHdyaXR0ZW4gd2l0aCBhIFRUTCBlcXVhbCB0byB0aGUgYWNjZXNzLXRva2VuIGxpZmV0aW1lOiBhbnkgdG9rZW5cbiAqIHRoYXQgY291bGQgc3RpbGwgY2FycnkgYW4gb2xkZXIgdmVyc2lvbiBoYXMgZXhwaXJlZCBieSB0aGUgdGltZSB0aGUgZW50cnlcbiAqIGxhcHNlcywgc28gYW4gZXhwaXJlZCBlbnRyeSBjYW4gbmV2ZXIgY2F1c2UgYSBmYWxzZSBcInJldm9rZWRcIiBub3IgYSBtaXNzZWQgb25lLlxuICpcbiAqIEBwYXJhbSByZWRpcyAtIHRoZSBwbGF0Zm9ybSdzIFJlZGlzIGNsaWVudFxuICogQHBhcmFtIHVzZXJJZCAtIHRoZSB1c2VyIHdob3NlIHNlc3Npb25zIGNoYW5nZWRcbiAqIEBwYXJhbSB0b2tlblZlcnNpb24gLSB0aGUgdXNlcidzIE5FVyAocG9zdC1pbmNyZW1lbnQpIHRva2VuVmVyc2lvblxuICogQHBhcmFtIHR0bFNlY29uZHMgLSBhY2Nlc3MtdG9rZW4gbGlmZXRpbWUgaW4gc2Vjb25kc1xuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcHVibGlzaFRva2VuUmV2b2NhdGlvbihcbiAgcmVkaXM6IFJlZGlzQ2FjaGVDbGllbnQsXG4gIHVzZXJJZDogc3RyaW5nLFxuICB0b2tlblZlcnNpb246IG51bWJlcixcbiAgdHRsU2Vjb25kczogbnVtYmVyLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIHRyeSB7XG4gICAgLy8gaW9yZWRpcy1zdHlsZSB2YXJpYWRpYyBTRVQgd2l0aCBleHBpcnk6IFNFVCBrZXkgdmFsIEVYIHR0bC5cbiAgICBhd2FpdCByZWRpcy5zZXQodG9rZW5SZXZvY2F0aW9uS2V5KHVzZXJJZCksIFN0cmluZyh0b2tlblZlcnNpb24pLCAnRVgnLCBNYXRoLm1heCgxLCBNYXRoLmZsb29yKHR0bFNlY29uZHMpKSk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZ2dlci53YXJuKCdUb2tlbi1yZXZvY2F0aW9uIHB1Ymxpc2ggZmFpbGVkIChzZXJ2aWNlcyBmYWxsIGJhY2sgdG8gdG9rZW4gZXhwaXJ5KScsIHtcbiAgICAgIHVzZXJJZCwgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSxcbiAgICB9KTtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tier fixture for the per-package `@pipeline-builder/api-core` test
|
|
3
|
+
* mocks (each suite's `test/helpers/mock-api-core.ts`).
|
|
4
|
+
*
|
|
5
|
+
* WHY A SEPARATE MODULE: every service mocks `@pipeline-builder/api-core` via
|
|
6
|
+
* `jest.unstable_mockModule('@pipeline-builder/api-core', …)`. That intercepts the
|
|
7
|
+
* *package* specifier only — this file is imported through the DEEP built path
|
|
8
|
+
* `@pipeline-builder/api-core/lib/testing/tier-mock.js`, a different specifier the
|
|
9
|
+
* mock does NOT intercept, so a mock can pull real, complete tier data without
|
|
10
|
+
* self-referencing its own mock. api-core has no `exports` map, so the subpath
|
|
11
|
+
* resolves; both sides are ESM so there's no interop wrinkle.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT BUYS: the tier NAME LIST is sourced from the real `VALID_TIERS`, so
|
|
14
|
+
* adding a tier in `quota-tiers.ts` flows into every mock automatically — no more
|
|
15
|
+
* "add a tier → hand-edit N mocks, discover the misses one gate round at a time"
|
|
16
|
+
* (which is exactly how the `unlimited` tier broke pipeline-core/api-server/platform).
|
|
17
|
+
* `mockQuotaTiers()` builds a COMPLETE `QUOTA_TIERS` (an entry for every tier) from
|
|
18
|
+
* caller-supplied per-tier limits, defaulting any unspecified tier to uncapped — so
|
|
19
|
+
* a new tier can never leave a `QUOTA_TIERS[tier].limits` undefined at mock load.
|
|
20
|
+
*/
|
|
21
|
+
import { type FeatureFlag } from '../types/feature-flags.js';
|
|
22
|
+
import { type QuotaTier, type QuotaTierLimits } from '../types/quota-tiers.js';
|
|
23
|
+
/** The real, complete tier-name list — the single source mocks derive from. */
|
|
24
|
+
export declare const MOCK_TIER_NAMES: readonly QuotaTier[];
|
|
25
|
+
/** Mirrors api-core's `isValidTier`, driven by the shared name list. */
|
|
26
|
+
export declare const mockIsValidTier: (t: string) => boolean;
|
|
27
|
+
type TierPreset = {
|
|
28
|
+
label: string;
|
|
29
|
+
limits: QuotaTierLimits;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Build a COMPLETE `QUOTA_TIERS` map (one entry per real tier) for a mock.
|
|
33
|
+
* Pass the per-tier limit overrides a suite actually asserts on; every other
|
|
34
|
+
* dimension (and every unspecified tier) falls back to uncapped, so the map is
|
|
35
|
+
* always exhaustive over `VALID_TIERS` — the property that keeps a newly-added
|
|
36
|
+
* tier from crashing mock load.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mockQuotaTiers(limitsByTier?: Partial<Record<QuotaTier, Partial<QuotaTierLimits>>>): Record<QuotaTier, TierPreset>;
|
|
39
|
+
/**
|
|
40
|
+
* Build a COMPLETE `TIER_FEATURES` map. Defaults every tier to all feature flags
|
|
41
|
+
* (the common mock stance); pass overrides for suites that assert a narrower set.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mockTierFeatures(featuresByTier?: Partial<Record<QuotaTier, readonly FeatureFlag[]>>): Record<QuotaTier, readonly FeatureFlag[]>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Shared tier fixture for the per-package `@pipeline-builder/api-core` test
|
|
5
|
+
* mocks (each suite's `test/helpers/mock-api-core.ts`).
|
|
6
|
+
*
|
|
7
|
+
* WHY A SEPARATE MODULE: every service mocks `@pipeline-builder/api-core` via
|
|
8
|
+
* `jest.unstable_mockModule('@pipeline-builder/api-core', …)`. That intercepts the
|
|
9
|
+
* *package* specifier only — this file is imported through the DEEP built path
|
|
10
|
+
* `@pipeline-builder/api-core/lib/testing/tier-mock.js`, a different specifier the
|
|
11
|
+
* mock does NOT intercept, so a mock can pull real, complete tier data without
|
|
12
|
+
* self-referencing its own mock. api-core has no `exports` map, so the subpath
|
|
13
|
+
* resolves; both sides are ESM so there's no interop wrinkle.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IT BUYS: the tier NAME LIST is sourced from the real `VALID_TIERS`, so
|
|
16
|
+
* adding a tier in `quota-tiers.ts` flows into every mock automatically — no more
|
|
17
|
+
* "add a tier → hand-edit N mocks, discover the misses one gate round at a time"
|
|
18
|
+
* (which is exactly how the `unlimited` tier broke pipeline-core/api-server/platform).
|
|
19
|
+
* `mockQuotaTiers()` builds a COMPLETE `QUOTA_TIERS` (an entry for every tier) from
|
|
20
|
+
* caller-supplied per-tier limits, defaulting any unspecified tier to uncapped — so
|
|
21
|
+
* a new tier can never leave a `QUOTA_TIERS[tier].limits` undefined at mock load.
|
|
22
|
+
*/
|
|
23
|
+
import { ALL_FEATURE_FLAGS } from '../types/feature-flags.js';
|
|
24
|
+
import { VALID_TIERS } from '../types/quota-tiers.js';
|
|
25
|
+
/** The real, complete tier-name list — the single source mocks derive from. */
|
|
26
|
+
export const MOCK_TIER_NAMES = VALID_TIERS;
|
|
27
|
+
/** Mirrors api-core's `isValidTier`, driven by the shared name list. */
|
|
28
|
+
export const mockIsValidTier = (t) => MOCK_TIER_NAMES.includes(t);
|
|
29
|
+
/** Every quota dimension uncapped — the default for tiers a caller doesn't specify. */
|
|
30
|
+
const uncappedLimits = () => ({
|
|
31
|
+
seats: -1,
|
|
32
|
+
plugins: -1,
|
|
33
|
+
pipelines: -1,
|
|
34
|
+
apiCalls: -1,
|
|
35
|
+
aiCalls: -1,
|
|
36
|
+
storageBytes: -1,
|
|
37
|
+
dashboards: -1,
|
|
38
|
+
alertRules: -1,
|
|
39
|
+
alertDestinations: -1,
|
|
40
|
+
idpConfigs: -1,
|
|
41
|
+
eventRetentionDays: -1,
|
|
42
|
+
doraRetentionDays: -1,
|
|
43
|
+
});
|
|
44
|
+
const titleCase = (t) => t.charAt(0).toUpperCase() + t.slice(1);
|
|
45
|
+
/**
|
|
46
|
+
* Build a COMPLETE `QUOTA_TIERS` map (one entry per real tier) for a mock.
|
|
47
|
+
* Pass the per-tier limit overrides a suite actually asserts on; every other
|
|
48
|
+
* dimension (and every unspecified tier) falls back to uncapped, so the map is
|
|
49
|
+
* always exhaustive over `VALID_TIERS` — the property that keeps a newly-added
|
|
50
|
+
* tier from crashing mock load.
|
|
51
|
+
*/
|
|
52
|
+
export function mockQuotaTiers(limitsByTier = {}) {
|
|
53
|
+
return Object.fromEntries(MOCK_TIER_NAMES.map((t) => [
|
|
54
|
+
t,
|
|
55
|
+
{ label: titleCase(t), limits: { ...uncappedLimits(), ...(limitsByTier[t] ?? {}) } },
|
|
56
|
+
]));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a COMPLETE `TIER_FEATURES` map. Defaults every tier to all feature flags
|
|
60
|
+
* (the common mock stance); pass overrides for suites that assert a narrower set.
|
|
61
|
+
*/
|
|
62
|
+
export function mockTierFeatures(featuresByTier = {}) {
|
|
63
|
+
return Object.fromEntries(MOCK_TIER_NAMES.map((t) => [t, featuresByTier[t] ?? [...ALL_FEATURE_FLAGS]]));
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGllci1tb2NrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Rlc3RpbmcvdGllci1tb2NrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFFSCxPQUFPLEVBQUUsaUJBQWlCLEVBQW9CLE1BQU0sMkJBQTJCLENBQUM7QUFDaEYsT0FBTyxFQUFFLFdBQVcsRUFBd0MsTUFBTSx5QkFBeUIsQ0FBQztBQUU1RiwrRUFBK0U7QUFDL0UsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUF5QixXQUFXLENBQUM7QUFFakUsd0VBQXdFO0FBQ3hFLE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxDQUFDLENBQVMsRUFBVyxFQUFFLENBQ25ELGVBQXFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBRXJELHVGQUF1RjtBQUN2RixNQUFNLGNBQWMsR0FBRyxHQUFvQixFQUFFLENBQUMsQ0FBQztJQUM3QyxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ1QsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNYLFNBQVMsRUFBRSxDQUFDLENBQUM7SUFDYixRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ1osT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNYLFlBQVksRUFBRSxDQUFDLENBQUM7SUFDaEIsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUNkLFVBQVUsRUFBRSxDQUFDLENBQUM7SUFDZCxpQkFBaUIsRUFBRSxDQUFDLENBQUM7SUFDckIsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUNkLGtCQUFrQixFQUFFLENBQUMsQ0FBQztJQUN0QixpQkFBaUIsRUFBRSxDQUFDLENBQUM7Q0FDdEIsQ0FBQyxDQUFDO0FBRUgsTUFBTSxTQUFTLEdBQUcsQ0FBQyxDQUFTLEVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUloRjs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUM1QixZQUFZLEdBQXlELEVBQUU7SUFFdkUsT0FBTyxNQUFNLENBQUMsV0FBVyxDQUN2QixlQUFlLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUN6QixDQUFDO1FBQ0QsRUFBRSxLQUFLLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxFQUFFLEdBQUcsY0FBYyxFQUFFLEVBQUUsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsRUFBRSxFQUFFO0tBQ3JGLENBQUMsQ0FDOEIsQ0FBQztBQUNyQyxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUM5QixjQUFjLEdBQXVELEVBQUU7SUFFdkUsT0FBTyxNQUFNLENBQUMsV0FBVyxDQUN2QixlQUFlLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxjQUFjLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLGlCQUFpQixDQUFDLENBQUMsQ0FBQyxDQUNoQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBTaGFyZWQgdGllciBmaXh0dXJlIGZvciB0aGUgcGVyLXBhY2thZ2UgYEBwaXBlbGluZS1idWlsZGVyL2FwaS1jb3JlYCB0ZXN0XG4gKiBtb2NrcyAoZWFjaCBzdWl0ZSdzIGB0ZXN0L2hlbHBlcnMvbW9jay1hcGktY29yZS50c2ApLlxuICpcbiAqIFdIWSBBIFNFUEFSQVRFIE1PRFVMRTogZXZlcnkgc2VydmljZSBtb2NrcyBgQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmVgIHZpYVxuICogYGplc3QudW5zdGFibGVfbW9ja01vZHVsZSgnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnLCDigKYpYC4gVGhhdCBpbnRlcmNlcHRzIHRoZVxuICogKnBhY2thZ2UqIHNwZWNpZmllciBvbmx5IOKAlCB0aGlzIGZpbGUgaXMgaW1wb3J0ZWQgdGhyb3VnaCB0aGUgREVFUCBidWlsdCBwYXRoXG4gKiBgQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUvbGliL3Rlc3RpbmcvdGllci1tb2NrLmpzYCwgYSBkaWZmZXJlbnQgc3BlY2lmaWVyIHRoZVxuICogbW9jayBkb2VzIE5PVCBpbnRlcmNlcHQsIHNvIGEgbW9jayBjYW4gcHVsbCByZWFsLCBjb21wbGV0ZSB0aWVyIGRhdGEgd2l0aG91dFxuICogc2VsZi1yZWZlcmVuY2luZyBpdHMgb3duIG1vY2suIGFwaS1jb3JlIGhhcyBubyBgZXhwb3J0c2AgbWFwLCBzbyB0aGUgc3VicGF0aFxuICogcmVzb2x2ZXM7IGJvdGggc2lkZXMgYXJlIEVTTSBzbyB0aGVyZSdzIG5vIGludGVyb3Agd3JpbmtsZS5cbiAqXG4gKiBXSEFUIElUIEJVWVM6IHRoZSB0aWVyIE5BTUUgTElTVCBpcyBzb3VyY2VkIGZyb20gdGhlIHJlYWwgYFZBTElEX1RJRVJTYCwgc29cbiAqIGFkZGluZyBhIHRpZXIgaW4gYHF1b3RhLXRpZXJzLnRzYCBmbG93cyBpbnRvIGV2ZXJ5IG1vY2sgYXV0b21hdGljYWxseSDigJQgbm8gbW9yZVxuICogXCJhZGQgYSB0aWVyIOKGkiBoYW5kLWVkaXQgTiBtb2NrcywgZGlzY292ZXIgdGhlIG1pc3NlcyBvbmUgZ2F0ZSByb3VuZCBhdCBhIHRpbWVcIlxuICogKHdoaWNoIGlzIGV4YWN0bHkgaG93IHRoZSBgdW5saW1pdGVkYCB0aWVyIGJyb2tlIHBpcGVsaW5lLWNvcmUvYXBpLXNlcnZlci9wbGF0Zm9ybSkuXG4gKiBgbW9ja1F1b3RhVGllcnMoKWAgYnVpbGRzIGEgQ09NUExFVEUgYFFVT1RBX1RJRVJTYCAoYW4gZW50cnkgZm9yIGV2ZXJ5IHRpZXIpIGZyb21cbiAqIGNhbGxlci1zdXBwbGllZCBwZXItdGllciBsaW1pdHMsIGRlZmF1bHRpbmcgYW55IHVuc3BlY2lmaWVkIHRpZXIgdG8gdW5jYXBwZWQg4oCUIHNvXG4gKiBhIG5ldyB0aWVyIGNhbiBuZXZlciBsZWF2ZSBhIGBRVU9UQV9USUVSU1t0aWVyXS5saW1pdHNgIHVuZGVmaW5lZCBhdCBtb2NrIGxvYWQuXG4gKi9cblxuaW1wb3J0IHsgQUxMX0ZFQVRVUkVfRkxBR1MsIHR5cGUgRmVhdHVyZUZsYWcgfSBmcm9tICcuLi90eXBlcy9mZWF0dXJlLWZsYWdzLmpzJztcbmltcG9ydCB7IFZBTElEX1RJRVJTLCB0eXBlIFF1b3RhVGllciwgdHlwZSBRdW90YVRpZXJMaW1pdHMgfSBmcm9tICcuLi90eXBlcy9xdW90YS10aWVycy5qcyc7XG5cbi8qKiBUaGUgcmVhbCwgY29tcGxldGUgdGllci1uYW1lIGxpc3Qg4oCUIHRoZSBzaW5nbGUgc291cmNlIG1vY2tzIGRlcml2ZSBmcm9tLiAqL1xuZXhwb3J0IGNvbnN0IE1PQ0tfVElFUl9OQU1FUzogcmVhZG9ubHkgUXVvdGFUaWVyW10gPSBWQUxJRF9USUVSUztcblxuLyoqIE1pcnJvcnMgYXBpLWNvcmUncyBgaXNWYWxpZFRpZXJgLCBkcml2ZW4gYnkgdGhlIHNoYXJlZCBuYW1lIGxpc3QuICovXG5leHBvcnQgY29uc3QgbW9ja0lzVmFsaWRUaWVyID0gKHQ6IHN0cmluZyk6IGJvb2xlYW4gPT5cbiAgKE1PQ0tfVElFUl9OQU1FUyBhcyByZWFkb25seSBzdHJpbmdbXSkuaW5jbHVkZXModCk7XG5cbi8qKiBFdmVyeSBxdW90YSBkaW1lbnNpb24gdW5jYXBwZWQg4oCUIHRoZSBkZWZhdWx0IGZvciB0aWVycyBhIGNhbGxlciBkb2Vzbid0IHNwZWNpZnkuICovXG5jb25zdCB1bmNhcHBlZExpbWl0cyA9ICgpOiBRdW90YVRpZXJMaW1pdHMgPT4gKHtcbiAgc2VhdHM6IC0xLFxuICBwbHVnaW5zOiAtMSxcbiAgcGlwZWxpbmVzOiAtMSxcbiAgYXBpQ2FsbHM6IC0xLFxuICBhaUNhbGxzOiAtMSxcbiAgc3RvcmFnZUJ5dGVzOiAtMSxcbiAgZGFzaGJvYXJkczogLTEsXG4gIGFsZXJ0UnVsZXM6IC0xLFxuICBhbGVydERlc3RpbmF0aW9uczogLTEsXG4gIGlkcENvbmZpZ3M6IC0xLFxuICBldmVudFJldGVudGlvbkRheXM6IC0xLFxuICBkb3JhUmV0ZW50aW9uRGF5czogLTEsXG59KTtcblxuY29uc3QgdGl0bGVDYXNlID0gKHQ6IHN0cmluZyk6IHN0cmluZyA9PiB0LmNoYXJBdCgwKS50b1VwcGVyQ2FzZSgpICsgdC5zbGljZSgxKTtcblxudHlwZSBUaWVyUHJlc2V0ID0geyBsYWJlbDogc3RyaW5nOyBsaW1pdHM6IFF1b3RhVGllckxpbWl0cyB9O1xuXG4vKipcbiAqIEJ1aWxkIGEgQ09NUExFVEUgYFFVT1RBX1RJRVJTYCBtYXAgKG9uZSBlbnRyeSBwZXIgcmVhbCB0aWVyKSBmb3IgYSBtb2NrLlxuICogUGFzcyB0aGUgcGVyLXRpZXIgbGltaXQgb3ZlcnJpZGVzIGEgc3VpdGUgYWN0dWFsbHkgYXNzZXJ0cyBvbjsgZXZlcnkgb3RoZXJcbiAqIGRpbWVuc2lvbiAoYW5kIGV2ZXJ5IHVuc3BlY2lmaWVkIHRpZXIpIGZhbGxzIGJhY2sgdG8gdW5jYXBwZWQsIHNvIHRoZSBtYXAgaXNcbiAqIGFsd2F5cyBleGhhdXN0aXZlIG92ZXIgYFZBTElEX1RJRVJTYCDigJQgdGhlIHByb3BlcnR5IHRoYXQga2VlcHMgYSBuZXdseS1hZGRlZFxuICogdGllciBmcm9tIGNyYXNoaW5nIG1vY2sgbG9hZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1vY2tRdW90YVRpZXJzKFxuICBsaW1pdHNCeVRpZXI6IFBhcnRpYWw8UmVjb3JkPFF1b3RhVGllciwgUGFydGlhbDxRdW90YVRpZXJMaW1pdHM+Pj4gPSB7fSxcbik6IFJlY29yZDxRdW90YVRpZXIsIFRpZXJQcmVzZXQ+IHtcbiAgcmV0dXJuIE9iamVjdC5mcm9tRW50cmllcyhcbiAgICBNT0NLX1RJRVJfTkFNRVMubWFwKCh0KSA9PiBbXG4gICAgICB0LFxuICAgICAgeyBsYWJlbDogdGl0bGVDYXNlKHQpLCBsaW1pdHM6IHsgLi4udW5jYXBwZWRMaW1pdHMoKSwgLi4uKGxpbWl0c0J5VGllclt0XSA/PyB7fSkgfSB9LFxuICAgIF0pLFxuICApIGFzIFJlY29yZDxRdW90YVRpZXIsIFRpZXJQcmVzZXQ+O1xufVxuXG4vKipcbiAqIEJ1aWxkIGEgQ09NUExFVEUgYFRJRVJfRkVBVFVSRVNgIG1hcC4gRGVmYXVsdHMgZXZlcnkgdGllciB0byBhbGwgZmVhdHVyZSBmbGFnc1xuICogKHRoZSBjb21tb24gbW9jayBzdGFuY2UpOyBwYXNzIG92ZXJyaWRlcyBmb3Igc3VpdGVzIHRoYXQgYXNzZXJ0IGEgbmFycm93ZXIgc2V0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gbW9ja1RpZXJGZWF0dXJlcyhcbiAgZmVhdHVyZXNCeVRpZXI6IFBhcnRpYWw8UmVjb3JkPFF1b3RhVGllciwgcmVhZG9ubHkgRmVhdHVyZUZsYWdbXT4+ID0ge30sXG4pOiBSZWNvcmQ8UXVvdGFUaWVyLCByZWFkb25seSBGZWF0dXJlRmxhZ1tdPiB7XG4gIHJldHVybiBPYmplY3QuZnJvbUVudHJpZXMoXG4gICAgTU9DS19USUVSX05BTUVTLm1hcCgodCkgPT4gW3QsIGZlYXR1cmVzQnlUaWVyW3RdID8/IFsuLi5BTExfRkVBVFVSRV9GTEFHU11dKSxcbiAgKSBhcyBSZWNvcmQ8UXVvdGFUaWVyLCByZWFkb25seSBGZWF0dXJlRmxhZ1tdPjtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-service audit events emitted via structured logs.
|
|
3
|
+
*
|
|
4
|
+
* Platform's own audit events (user.login, org.create, …) live in MongoDB
|
|
5
|
+
* via the AuditEvent model — that's a separate persistence path scoped to
|
|
6
|
+
* the platform service. The events below are emitted by other services
|
|
7
|
+
* (image-registry, etc.) via the `emitAudit` helper as structured log
|
|
8
|
+
* lines with `eventCategory: 'audit'`, scraped by Loki for queryable
|
|
9
|
+
* audit history.
|
|
10
|
+
*
|
|
11
|
+
* Add a new event:
|
|
12
|
+
* 1. Extend the union type below.
|
|
13
|
+
* 2. Document it in `docs/audit-events.md` (or wherever the audit-events
|
|
14
|
+
* doc lives) including any payload-specific fields.
|
|
15
|
+
* 3. Emit via `emitAudit(logger, 'event.name', { … })` from the route.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Correlation context shared by every cross-service audit event. Optional so
|
|
19
|
+
* existing emit sites compile unchanged; when populated, these let an operator
|
|
20
|
+
* pivot a Loki audit line to the originating HTTP request (`requestId`) or the
|
|
21
|
+
* end-to-end distributed trace (`traceId`) — and line them up with platform's
|
|
22
|
+
* MongoDB audit events, which carry the same two fields.
|
|
23
|
+
*/
|
|
24
|
+
export interface AuditCorrelation {
|
|
25
|
+
/** Originating request id (propagated `x-request-id`). */
|
|
26
|
+
requestId?: string;
|
|
27
|
+
/** OpenTelemetry trace id of the active span, when tracing is enabled. */
|
|
28
|
+
traceId?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Tag-copy emitted from image-registry's POST /api/images/copy. */
|
|
31
|
+
export interface RegistryTagCopyAudit extends AuditCorrelation {
|
|
32
|
+
event: 'registry.tag.copy';
|
|
33
|
+
actor: string;
|
|
34
|
+
source: string;
|
|
35
|
+
target: string;
|
|
36
|
+
sourceDigest: string;
|
|
37
|
+
targetDigest: string;
|
|
38
|
+
/** True when target's repo starts with `system/` — the highest-privilege case. */
|
|
39
|
+
isPromotionToSystem: boolean;
|
|
40
|
+
mounted: {
|
|
41
|
+
manifests: number;
|
|
42
|
+
blobs: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Tag-delete emitted from image-registry's DELETE /api/images/:name/manifests/:reference. */
|
|
46
|
+
export interface RegistryTagDeleteAudit extends AuditCorrelation {
|
|
47
|
+
event: 'registry.tag.delete';
|
|
48
|
+
actor: string;
|
|
49
|
+
repo: string;
|
|
50
|
+
ref: string;
|
|
51
|
+
digest: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Repository prune emitted from image-registry's DELETE /api/images/:name —
|
|
55
|
+
* deletes every manifest in a repo (used to remove empty/dead repos from the
|
|
56
|
+
* catalog). Unlike the tag-delete above, there's no single `ref`/`digest`:
|
|
57
|
+
* the counts summarize the whole repo.
|
|
58
|
+
*/
|
|
59
|
+
export interface RegistryRepoDeleteAudit extends AuditCorrelation {
|
|
60
|
+
event: 'registry.repo.delete';
|
|
61
|
+
actor: string;
|
|
62
|
+
repo: string;
|
|
63
|
+
/** Count of unique manifest digests actually deleted. */
|
|
64
|
+
deletedManifests: number;
|
|
65
|
+
/** Count of tags the repo held before the prune. */
|
|
66
|
+
deletedTags: number;
|
|
67
|
+
}
|
|
68
|
+
/** Union of every cross-service audit event currently emitted. */
|
|
69
|
+
export type AuditEvent = RegistryTagCopyAudit | RegistryTagDeleteAudit | RegistryRepoDeleteAudit;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
export {};
|
|
4
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXVkaXQtZXZlbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3R5cGVzL2F1ZGl0LWV2ZW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogQ3Jvc3Mtc2VydmljZSBhdWRpdCBldmVudHMgZW1pdHRlZCB2aWEgc3RydWN0dXJlZCBsb2dzLlxuICpcbiAqIFBsYXRmb3JtJ3Mgb3duIGF1ZGl0IGV2ZW50cyAodXNlci5sb2dpbiwgb3JnLmNyZWF0ZSwg4oCmKSBsaXZlIGluIE1vbmdvREJcbiAqIHZpYSB0aGUgQXVkaXRFdmVudCBtb2RlbCDigJQgdGhhdCdzIGEgc2VwYXJhdGUgcGVyc2lzdGVuY2UgcGF0aCBzY29wZWQgdG9cbiAqIHRoZSBwbGF0Zm9ybSBzZXJ2aWNlLiBUaGUgZXZlbnRzIGJlbG93IGFyZSBlbWl0dGVkIGJ5IG90aGVyIHNlcnZpY2VzXG4gKiAoaW1hZ2UtcmVnaXN0cnksIGV0Yy4pIHZpYSB0aGUgYGVtaXRBdWRpdGAgaGVscGVyIGFzIHN0cnVjdHVyZWQgbG9nXG4gKiBsaW5lcyB3aXRoIGBldmVudENhdGVnb3J5OiAnYXVkaXQnYCwgc2NyYXBlZCBieSBMb2tpIGZvciBxdWVyeWFibGVcbiAqIGF1ZGl0IGhpc3RvcnkuXG4gKlxuICogQWRkIGEgbmV3IGV2ZW50OlxuICogICAxLiBFeHRlbmQgdGhlIHVuaW9uIHR5cGUgYmVsb3cuXG4gKiAgIDIuIERvY3VtZW50IGl0IGluIGBkb2NzL2F1ZGl0LWV2ZW50cy5tZGAgKG9yIHdoZXJldmVyIHRoZSBhdWRpdC1ldmVudHNcbiAqICAgICAgZG9jIGxpdmVzKSBpbmNsdWRpbmcgYW55IHBheWxvYWQtc3BlY2lmaWMgZmllbGRzLlxuICogICAzLiBFbWl0IHZpYSBgZW1pdEF1ZGl0KGxvZ2dlciwgJ2V2ZW50Lm5hbWUnLCB7IOKApiB9KWAgZnJvbSB0aGUgcm91dGUuXG4gKi9cblxuLyoqXG4gKiBDb3JyZWxhdGlvbiBjb250ZXh0IHNoYXJlZCBieSBldmVyeSBjcm9zcy1zZXJ2aWNlIGF1ZGl0IGV2ZW50LiBPcHRpb25hbCBzb1xuICogZXhpc3RpbmcgZW1pdCBzaXRlcyBjb21waWxlIHVuY2hhbmdlZDsgd2hlbiBwb3B1bGF0ZWQsIHRoZXNlIGxldCBhbiBvcGVyYXRvclxuICogcGl2b3QgYSBMb2tpIGF1ZGl0IGxpbmUgdG8gdGhlIG9yaWdpbmF0aW5nIEhUVFAgcmVxdWVzdCAoYHJlcXVlc3RJZGApIG9yIHRoZVxuICogZW5kLXRvLWVuZCBkaXN0cmlidXRlZCB0cmFjZSAoYHRyYWNlSWRgKSDigJQgYW5kIGxpbmUgdGhlbSB1cCB3aXRoIHBsYXRmb3JtJ3NcbiAqIE1vbmdvREIgYXVkaXQgZXZlbnRzLCB3aGljaCBjYXJyeSB0aGUgc2FtZSB0d28gZmllbGRzLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0Q29ycmVsYXRpb24ge1xuICAvKiogT3JpZ2luYXRpbmcgcmVxdWVzdCBpZCAocHJvcGFnYXRlZCBgeC1yZXF1ZXN0LWlkYCkuICovXG4gIHJlcXVlc3RJZD86IHN0cmluZztcbiAgLyoqIE9wZW5UZWxlbWV0cnkgdHJhY2UgaWQgb2YgdGhlIGFjdGl2ZSBzcGFuLCB3aGVuIHRyYWNpbmcgaXMgZW5hYmxlZC4gKi9cbiAgdHJhY2VJZD86IHN0cmluZztcbn1cblxuLyoqIFRhZy1jb3B5IGVtaXR0ZWQgZnJvbSBpbWFnZS1yZWdpc3RyeSdzIFBPU1QgL2FwaS9pbWFnZXMvY29weS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVnaXN0cnlUYWdDb3B5QXVkaXQgZXh0ZW5kcyBBdWRpdENvcnJlbGF0aW9uIHtcbiAgZXZlbnQ6ICdyZWdpc3RyeS50YWcuY29weSc7XG4gIGFjdG9yOiBzdHJpbmc7XG4gIHNvdXJjZTogc3RyaW5nO1xuICB0YXJnZXQ6IHN0cmluZztcbiAgc291cmNlRGlnZXN0OiBzdHJpbmc7XG4gIHRhcmdldERpZ2VzdDogc3RyaW5nO1xuICAvKiogVHJ1ZSB3aGVuIHRhcmdldCdzIHJlcG8gc3RhcnRzIHdpdGggYHN5c3RlbS9gIOKAlCB0aGUgaGlnaGVzdC1wcml2aWxlZ2UgY2FzZS4gKi9cbiAgaXNQcm9tb3Rpb25Ub1N5c3RlbTogYm9vbGVhbjtcbiAgbW91bnRlZDogeyBtYW5pZmVzdHM6IG51bWJlcjsgYmxvYnM6IG51bWJlciB9O1xufVxuXG4vKiogVGFnLWRlbGV0ZSBlbWl0dGVkIGZyb20gaW1hZ2UtcmVnaXN0cnkncyBERUxFVEUgL2FwaS9pbWFnZXMvOm5hbWUvbWFuaWZlc3RzLzpyZWZlcmVuY2UuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlZ2lzdHJ5VGFnRGVsZXRlQXVkaXQgZXh0ZW5kcyBBdWRpdENvcnJlbGF0aW9uIHtcbiAgZXZlbnQ6ICdyZWdpc3RyeS50YWcuZGVsZXRlJztcbiAgYWN0b3I6IHN0cmluZztcbiAgcmVwbzogc3RyaW5nO1xuICByZWY6IHN0cmluZztcbiAgZGlnZXN0OiBzdHJpbmc7XG59XG5cbi8qKlxuICogUmVwb3NpdG9yeSBwcnVuZSBlbWl0dGVkIGZyb20gaW1hZ2UtcmVnaXN0cnkncyBERUxFVEUgL2FwaS9pbWFnZXMvOm5hbWUg4oCUXG4gKiBkZWxldGVzIGV2ZXJ5IG1hbmlmZXN0IGluIGEgcmVwbyAodXNlZCB0byByZW1vdmUgZW1wdHkvZGVhZCByZXBvcyBmcm9tIHRoZVxuICogY2F0YWxvZykuIFVubGlrZSB0aGUgdGFnLWRlbGV0ZSBhYm92ZSwgdGhlcmUncyBubyBzaW5nbGUgYHJlZmAvYGRpZ2VzdGA6XG4gKiB0aGUgY291bnRzIHN1bW1hcml6ZSB0aGUgd2hvbGUgcmVwby5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZWdpc3RyeVJlcG9EZWxldGVBdWRpdCBleHRlbmRzIEF1ZGl0Q29ycmVsYXRpb24ge1xuICBldmVudDogJ3JlZ2lzdHJ5LnJlcG8uZGVsZXRlJztcbiAgYWN0b3I6IHN0cmluZztcbiAgcmVwbzogc3RyaW5nO1xuICAvKiogQ291bnQgb2YgdW5pcXVlIG1hbmlmZXN0IGRpZ2VzdHMgYWN0dWFsbHkgZGVsZXRlZC4gKi9cbiAgZGVsZXRlZE1hbmlmZXN0czogbnVtYmVyO1xuICAvKiogQ291bnQgb2YgdGFncyB0aGUgcmVwbyBoZWxkIGJlZm9yZSB0aGUgcHJ1bmUuICovXG4gIGRlbGV0ZWRUYWdzOiBudW1iZXI7XG59XG5cbi8qKiBVbmlvbiBvZiBldmVyeSBjcm9zcy1zZXJ2aWNlIGF1ZGl0IGV2ZW50IGN1cnJlbnRseSBlbWl0dGVkLiAqL1xuZXhwb3J0IHR5cGUgQXVkaXRFdmVudCA9IFJlZ2lzdHJ5VGFnQ29weUF1ZGl0IHwgUmVnaXN0cnlUYWdEZWxldGVBdWRpdCB8IFJlZ2lzdHJ5UmVwb0RlbGV0ZUF1ZGl0O1xuIl19
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Developer-portal catalog metadata shared across catalog entities
|
|
3
|
+
* (pipelines and plugins today; any future catalogable entity next).
|
|
4
|
+
*
|
|
5
|
+
* These are the ownership/lifecycle/classification fields an internal
|
|
6
|
+
* developer portal keys its catalog, "my services" views, and scorecards
|
|
7
|
+
* off of — the counterpart to Backstage's `metadata`/`spec.owner`/
|
|
8
|
+
* `spec.lifecycle`. Kept here (not per-schema) so the DB schema, the Zod
|
|
9
|
+
* validation, and the API all draw from one source of truth.
|
|
10
|
+
*/
|
|
11
|
+
/** Lifecycle stage of a catalog entity. */
|
|
12
|
+
export declare const LIFECYCLE_STAGES: readonly ['experimental', 'production', 'deprecated'];
|
|
13
|
+
export type Lifecycle = (typeof LIFECYCLE_STAGES)[number];
|
|
14
|
+
/** Default lifecycle applied when none is supplied (matches the DB column default). */
|
|
15
|
+
export declare const DEFAULT_LIFECYCLE: Lifecycle;
|
|
16
|
+
/** Operational criticality of a catalog entity (optional/unset by default). */
|
|
17
|
+
export declare const CRITICALITY_LEVELS: readonly ['low', 'medium', 'high', 'critical'];
|
|
18
|
+
export type Criticality = (typeof CRITICALITY_LEVELS)[number];
|
|
19
|
+
/** Whether an owner reference points at an individual user or a team/org. */
|
|
20
|
+
export declare const OWNER_TYPES: readonly ['user', 'team'];
|
|
21
|
+
export type OwnerType = (typeof OWNER_TYPES)[number];
|
|
22
|
+
/** A titled external link attached to a catalog entity (docs, dashboard, runbook, …). */
|
|
23
|
+
export interface EntityLink {
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly url: string;
|
|
26
|
+
/** Optional icon hint the UI may map to a glyph (e.g. 'docs', 'dashboard'). */
|
|
27
|
+
readonly icon?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Free-form typed classification labels: `{ team: 'payments', tier: 'gold' }`. */
|
|
30
|
+
export type EntityLabels = Record<string, string>;
|
|
31
|
+
/** Owner reference for a catalog entity. */
|
|
32
|
+
export interface CatalogOwnership {
|
|
33
|
+
/** User id (ownerType='user') or team/org id (ownerType='team'). */
|
|
34
|
+
readonly ownerId?: string | null;
|
|
35
|
+
readonly ownerType?: OwnerType | null;
|
|
36
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Developer-portal catalog metadata shared across catalog entities
|
|
5
|
+
* (pipelines and plugins today; any future catalogable entity next).
|
|
6
|
+
*
|
|
7
|
+
* These are the ownership/lifecycle/classification fields an internal
|
|
8
|
+
* developer portal keys its catalog, "my services" views, and scorecards
|
|
9
|
+
* off of — the counterpart to Backstage's `metadata`/`spec.owner`/
|
|
10
|
+
* `spec.lifecycle`. Kept here (not per-schema) so the DB schema, the Zod
|
|
11
|
+
* validation, and the API all draw from one source of truth.
|
|
12
|
+
*/
|
|
13
|
+
/** Lifecycle stage of a catalog entity. */
|
|
14
|
+
export const LIFECYCLE_STAGES = ['experimental', 'production', 'deprecated'];
|
|
15
|
+
/** Default lifecycle applied when none is supplied (matches the DB column default). */
|
|
16
|
+
export const DEFAULT_LIFECYCLE = 'production';
|
|
17
|
+
/** Operational criticality of a catalog entity (optional/unset by default). */
|
|
18
|
+
export const CRITICALITY_LEVELS = ['low', 'medium', 'high', 'critical'];
|
|
19
|
+
/** Whether an owner reference points at an individual user or a team/org. */
|
|
20
|
+
export const OWNER_TYPES = ['user', 'team'];
|
|
21
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2F0YWxvZy1tZXRhZGF0YS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlcy9jYXRhbG9nLW1ldGFkYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7OztHQVNHO0FBRUgsMkNBQTJDO0FBQzNDLE1BQU0sQ0FBQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsY0FBYyxFQUFFLFlBQVksRUFBRSxZQUFZLENBQVUsQ0FBQztBQUd0Rix1RkFBdUY7QUFDdkYsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQWMsWUFBWSxDQUFDO0FBRXpELCtFQUErRTtBQUMvRSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLEtBQUssRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFLFVBQVUsQ0FBVSxDQUFDO0FBR2pGLDZFQUE2RTtBQUM3RSxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBEZXZlbG9wZXItcG9ydGFsIGNhdGFsb2cgbWV0YWRhdGEgc2hhcmVkIGFjcm9zcyBjYXRhbG9nIGVudGl0aWVzXG4gKiAocGlwZWxpbmVzIGFuZCBwbHVnaW5zIHRvZGF5OyBhbnkgZnV0dXJlIGNhdGFsb2dhYmxlIGVudGl0eSBuZXh0KS5cbiAqXG4gKiBUaGVzZSBhcmUgdGhlIG93bmVyc2hpcC9saWZlY3ljbGUvY2xhc3NpZmljYXRpb24gZmllbGRzIGFuIGludGVybmFsXG4gKiBkZXZlbG9wZXIgcG9ydGFsIGtleXMgaXRzIGNhdGFsb2csIFwibXkgc2VydmljZXNcIiB2aWV3cywgYW5kIHNjb3JlY2FyZHNcbiAqIG9mZiBvZiDigJQgdGhlIGNvdW50ZXJwYXJ0IHRvIEJhY2tzdGFnZSdzIGBtZXRhZGF0YWAvYHNwZWMub3duZXJgL1xuICogYHNwZWMubGlmZWN5Y2xlYC4gS2VwdCBoZXJlIChub3QgcGVyLXNjaGVtYSkgc28gdGhlIERCIHNjaGVtYSwgdGhlIFpvZFxuICogdmFsaWRhdGlvbiwgYW5kIHRoZSBBUEkgYWxsIGRyYXcgZnJvbSBvbmUgc291cmNlIG9mIHRydXRoLlxuICovXG5cbi8qKiBMaWZlY3ljbGUgc3RhZ2Ugb2YgYSBjYXRhbG9nIGVudGl0eS4gKi9cbmV4cG9ydCBjb25zdCBMSUZFQ1lDTEVfU1RBR0VTID0gWydleHBlcmltZW50YWwnLCAncHJvZHVjdGlvbicsICdkZXByZWNhdGVkJ10gYXMgY29uc3Q7XG5leHBvcnQgdHlwZSBMaWZlY3ljbGUgPSAodHlwZW9mIExJRkVDWUNMRV9TVEFHRVMpW251bWJlcl07XG5cbi8qKiBEZWZhdWx0IGxpZmVjeWNsZSBhcHBsaWVkIHdoZW4gbm9uZSBpcyBzdXBwbGllZCAobWF0Y2hlcyB0aGUgREIgY29sdW1uIGRlZmF1bHQpLiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfTElGRUNZQ0xFOiBMaWZlY3ljbGUgPSAncHJvZHVjdGlvbic7XG5cbi8qKiBPcGVyYXRpb25hbCBjcml0aWNhbGl0eSBvZiBhIGNhdGFsb2cgZW50aXR5IChvcHRpb25hbC91bnNldCBieSBkZWZhdWx0KS4gKi9cbmV4cG9ydCBjb25zdCBDUklUSUNBTElUWV9MRVZFTFMgPSBbJ2xvdycsICdtZWRpdW0nLCAnaGlnaCcsICdjcml0aWNhbCddIGFzIGNvbnN0O1xuZXhwb3J0IHR5cGUgQ3JpdGljYWxpdHkgPSAodHlwZW9mIENSSVRJQ0FMSVRZX0xFVkVMUylbbnVtYmVyXTtcblxuLyoqIFdoZXRoZXIgYW4gb3duZXIgcmVmZXJlbmNlIHBvaW50cyBhdCBhbiBpbmRpdmlkdWFsIHVzZXIgb3IgYSB0ZWFtL29yZy4gKi9cbmV4cG9ydCBjb25zdCBPV05FUl9UWVBFUyA9IFsndXNlcicsICd0ZWFtJ10gYXMgY29uc3Q7XG5leHBvcnQgdHlwZSBPd25lclR5cGUgPSAodHlwZW9mIE9XTkVSX1RZUEVTKVtudW1iZXJdO1xuXG4vKiogQSB0aXRsZWQgZXh0ZXJuYWwgbGluayBhdHRhY2hlZCB0byBhIGNhdGFsb2cgZW50aXR5IChkb2NzLCBkYXNoYm9hcmQsIHJ1bmJvb2ssIOKApikuICovXG5leHBvcnQgaW50ZXJmYWNlIEVudGl0eUxpbmsge1xuICByZWFkb25seSB0aXRsZTogc3RyaW5nO1xuICByZWFkb25seSB1cmw6IHN0cmluZztcbiAgLyoqIE9wdGlvbmFsIGljb24gaGludCB0aGUgVUkgbWF5IG1hcCB0byBhIGdseXBoIChlLmcuICdkb2NzJywgJ2Rhc2hib2FyZCcpLiAqL1xuICByZWFkb25seSBpY29uPzogc3RyaW5nO1xufVxuXG4vKiogRnJlZS1mb3JtIHR5cGVkIGNsYXNzaWZpY2F0aW9uIGxhYmVsczogYHsgdGVhbTogJ3BheW1lbnRzJywgdGllcjogJ2dvbGQnIH1gLiAqL1xuZXhwb3J0IHR5cGUgRW50aXR5TGFiZWxzID0gUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcblxuLyoqIE93bmVyIHJlZmVyZW5jZSBmb3IgYSBjYXRhbG9nIGVudGl0eS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQ2F0YWxvZ093bmVyc2hpcCB7XG4gIC8qKiBVc2VyIGlkIChvd25lclR5cGU9J3VzZXInKSBvciB0ZWFtL29yZyBpZCAob3duZXJUeXBlPSd0ZWFtJykuICovXG4gIHJlYWRvbmx5IG93bmVySWQ/OiBzdHJpbmcgfCBudWxsO1xuICByZWFkb25seSBvd25lclR5cGU/OiBPd25lclR5cGUgfCBudWxsO1xufVxuIl19
|