@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.
Files changed (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/lib/constants/ai-providers.d.ts +52 -0
  4. package/lib/constants/ai-providers.js +129 -0
  5. package/lib/constants/http-status.d.ts +20 -0
  6. package/lib/constants/http-status.js +26 -0
  7. package/lib/constants/index.d.ts +3 -0
  8. package/lib/constants/index.js +6 -0
  9. package/lib/constants/time.d.ts +10 -0
  10. package/lib/constants/time.js +13 -0
  11. package/lib/errors/app-errors.d.ts +30 -0
  12. package/lib/errors/app-errors.js +53 -0
  13. package/lib/errors/index.d.ts +1 -0
  14. package/lib/errors/index.js +4 -0
  15. package/lib/helpers/access-helpers.d.ts +63 -0
  16. package/lib/helpers/access-helpers.js +82 -0
  17. package/lib/helpers/crud-helpers.d.ts +9 -0
  18. package/lib/helpers/crud-helpers.js +23 -0
  19. package/lib/helpers/index.d.ts +6 -0
  20. package/lib/helpers/index.js +9 -0
  21. package/lib/helpers/org-hierarchy-http.d.ts +74 -0
  22. package/lib/helpers/org-hierarchy-http.js +144 -0
  23. package/lib/helpers/org-hierarchy.d.ts +57 -0
  24. package/lib/helpers/org-hierarchy.js +110 -0
  25. package/lib/helpers/restore-helpers.d.ts +49 -0
  26. package/lib/helpers/restore-helpers.js +78 -0
  27. package/lib/helpers/sse-helpers.d.ts +20 -0
  28. package/lib/helpers/sse-helpers.js +45 -0
  29. package/lib/index.d.ts +58 -0
  30. package/lib/index.js +71 -0
  31. package/lib/middleware/auth.d.ts +208 -0
  32. package/lib/middleware/auth.js +588 -0
  33. package/lib/middleware/index.d.ts +3 -0
  34. package/lib/middleware/index.js +6 -0
  35. package/lib/middleware/mongo-sanitize.d.ts +32 -0
  36. package/lib/middleware/mongo-sanitize.js +100 -0
  37. package/lib/middleware/step-up.d.ts +45 -0
  38. package/lib/middleware/step-up.js +138 -0
  39. package/lib/openapi/extend-zod.d.ts +1 -0
  40. package/lib/openapi/extend-zod.js +6 -0
  41. package/lib/openapi/index.d.ts +2 -0
  42. package/lib/openapi/index.js +5 -0
  43. package/lib/openapi/registry.d.ts +17 -0
  44. package/lib/openapi/registry.js +38 -0
  45. package/lib/openapi/routes/ask-routes.d.ts +7 -0
  46. package/lib/openapi/routes/ask-routes.js +62 -0
  47. package/lib/openapi/routes/billing-routes.d.ts +1 -0
  48. package/lib/openapi/routes/billing-routes.js +66 -0
  49. package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
  50. package/lib/openapi/routes/image-registry-routes.js +98 -0
  51. package/lib/openapi/routes/index.d.ts +5 -0
  52. package/lib/openapi/routes/index.js +28 -0
  53. package/lib/openapi/routes/message-routes.d.ts +1 -0
  54. package/lib/openapi/routes/message-routes.js +105 -0
  55. package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
  56. package/lib/openapi/routes/pipeline-routes.js +105 -0
  57. package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
  58. package/lib/openapi/routes/pipeline-template-routes.js +61 -0
  59. package/lib/openapi/routes/plugin-routes.d.ts +1 -0
  60. package/lib/openapi/routes/plugin-routes.js +96 -0
  61. package/lib/openapi/routes/quota-routes.d.ts +1 -0
  62. package/lib/openapi/routes/quota-routes.js +62 -0
  63. package/lib/openapi/schema-registry.d.ts +25 -0
  64. package/lib/openapi/schema-registry.js +91 -0
  65. package/lib/routes/health.d.ts +41 -0
  66. package/lib/routes/health.js +82 -0
  67. package/lib/routes/index.d.ts +1 -0
  68. package/lib/routes/index.js +4 -0
  69. package/lib/services/audit-spool.d.ts +80 -0
  70. package/lib/services/audit-spool.js +172 -0
  71. package/lib/services/cache-service.d.ts +124 -0
  72. package/lib/services/cache-service.js +276 -0
  73. package/lib/services/circuit-breaker.d.ts +45 -0
  74. package/lib/services/circuit-breaker.js +138 -0
  75. package/lib/services/compliance-client.d.ts +46 -0
  76. package/lib/services/compliance-client.js +128 -0
  77. package/lib/services/compliance-event-subscriber.d.ts +16 -0
  78. package/lib/services/compliance-event-subscriber.js +72 -0
  79. package/lib/services/durable-event-bus.d.ts +96 -0
  80. package/lib/services/durable-event-bus.js +204 -0
  81. package/lib/services/entity-events.d.ts +48 -0
  82. package/lib/services/entity-events.js +79 -0
  83. package/lib/services/env-redis.d.ts +44 -0
  84. package/lib/services/env-redis.js +94 -0
  85. package/lib/services/http-client.d.ts +143 -0
  86. package/lib/services/http-client.js +335 -0
  87. package/lib/services/index.d.ts +18 -0
  88. package/lib/services/index.js +20 -0
  89. package/lib/services/leader-lock.d.ts +45 -0
  90. package/lib/services/leader-lock.js +85 -0
  91. package/lib/services/quota.d.ts +130 -0
  92. package/lib/services/quota.js +264 -0
  93. package/lib/services/remote-audit-client.d.ts +160 -0
  94. package/lib/services/remote-audit-client.js +390 -0
  95. package/lib/services/retry-strategy.d.ts +74 -0
  96. package/lib/services/retry-strategy.js +118 -0
  97. package/lib/services/scheduler.d.ts +41 -0
  98. package/lib/services/scheduler.js +86 -0
  99. package/lib/services/service-boot.d.ts +20 -0
  100. package/lib/services/service-boot.js +28 -0
  101. package/lib/services/sse-ticket-store.d.ts +51 -0
  102. package/lib/services/sse-ticket-store.js +130 -0
  103. package/lib/services/token-revocation.d.ts +52 -0
  104. package/lib/services/token-revocation.js +120 -0
  105. package/lib/testing/tier-mock.d.ts +44 -0
  106. package/lib/testing/tier-mock.js +65 -0
  107. package/lib/types/audit-events.d.ts +69 -0
  108. package/lib/types/audit-events.js +4 -0
  109. package/lib/types/catalog-metadata.d.ts +36 -0
  110. package/lib/types/catalog-metadata.js +21 -0
  111. package/lib/types/common.d.ts +255 -0
  112. package/lib/types/common.js +41 -0
  113. package/lib/types/error-codes.d.ts +54 -0
  114. package/lib/types/error-codes.js +107 -0
  115. package/lib/types/feature-flags.d.ts +36 -0
  116. package/lib/types/feature-flags.js +128 -0
  117. package/lib/types/http.d.ts +27 -0
  118. package/lib/types/http.js +4 -0
  119. package/lib/types/index.d.ts +11 -0
  120. package/lib/types/index.js +14 -0
  121. package/lib/types/permissions.d.ts +65 -0
  122. package/lib/types/permissions.js +131 -0
  123. package/lib/types/pipeline-template.d.ts +30 -0
  124. package/lib/types/pipeline-template.js +13 -0
  125. package/lib/types/pipeline.d.ts +59 -0
  126. package/lib/types/pipeline.js +31 -0
  127. package/lib/types/quota-tiers.d.ts +79 -0
  128. package/lib/types/quota-tiers.js +221 -0
  129. package/lib/types/visibility.d.ts +20 -0
  130. package/lib/types/visibility.js +22 -0
  131. package/lib/utils/alias-resolver.d.ts +37 -0
  132. package/lib/utils/alias-resolver.js +68 -0
  133. package/lib/utils/audit.d.ts +15 -0
  134. package/lib/utils/audit.js +39 -0
  135. package/lib/utils/aws-scrub.d.ts +11 -0
  136. package/lib/utils/aws-scrub.js +67 -0
  137. package/lib/utils/compliance-attributes.d.ts +6 -0
  138. package/lib/utils/compliance-attributes.js +69 -0
  139. package/lib/utils/concurrency.d.ts +9 -0
  140. package/lib/utils/concurrency.js +29 -0
  141. package/lib/utils/crash-handlers.d.ts +18 -0
  142. package/lib/utils/crash-handlers.js +36 -0
  143. package/lib/utils/env.d.ts +28 -0
  144. package/lib/utils/env.js +54 -0
  145. package/lib/utils/headers.d.ts +18 -0
  146. package/lib/utils/headers.js +21 -0
  147. package/lib/utils/identity.d.ts +40 -0
  148. package/lib/utils/identity.js +51 -0
  149. package/lib/utils/index.d.ts +17 -0
  150. package/lib/utils/index.js +20 -0
  151. package/lib/utils/logger.d.ts +38 -0
  152. package/lib/utils/logger.js +174 -0
  153. package/lib/utils/metric-emitter.d.ts +36 -0
  154. package/lib/utils/metric-emitter.js +32 -0
  155. package/lib/utils/object.d.ts +13 -0
  156. package/lib/utils/object.js +18 -0
  157. package/lib/utils/params.d.ts +150 -0
  158. package/lib/utils/params.js +206 -0
  159. package/lib/utils/response.d.ts +117 -0
  160. package/lib/utils/response.js +223 -0
  161. package/lib/utils/safe-require.d.ts +13 -0
  162. package/lib/utils/safe-require.js +19 -0
  163. package/lib/utils/secret-encryption.d.ts +176 -0
  164. package/lib/utils/secret-encryption.js +374 -0
  165. package/lib/utils/ssrf.d.ts +30 -0
  166. package/lib/utils/ssrf.js +106 -0
  167. package/lib/validation/ai-schemas.d.ts +56 -0
  168. package/lib/validation/ai-schemas.js +78 -0
  169. package/lib/validation/common-schemas.d.ts +133 -0
  170. package/lib/validation/common-schemas.js +94 -0
  171. package/lib/validation/index.d.ts +7 -0
  172. package/lib/validation/index.js +10 -0
  173. package/lib/validation/message-schemas.d.ts +160 -0
  174. package/lib/validation/message-schemas.js +156 -0
  175. package/lib/validation/middleware.d.ts +72 -0
  176. package/lib/validation/middleware.js +65 -0
  177. package/lib/validation/pipeline-schemas.d.ts +181 -0
  178. package/lib/validation/pipeline-schemas.js +101 -0
  179. package/lib/validation/pipeline-template-schemas.d.ts +155 -0
  180. package/lib/validation/pipeline-template-schemas.js +66 -0
  181. package/lib/validation/plugin-schemas.d.ts +171 -0
  182. package/lib/validation/plugin-schemas.js +81 -0
  183. package/openapi.yaml +292 -0
  184. 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