@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,79 @@
1
+ /** Available quota tier identifiers.
2
+ *
3
+ * `unlimited` is a special tier: every quota is `-1` (uncapped) and it includes
4
+ * every feature/add-on bundle. It is the DEFAULT when billing is disabled (no
5
+ * metering/enforcement surface exists, so nothing is capped), and it is NEVER
6
+ * shown or selectable when billing is enabled — see `STANDARD_TIERS`. */
7
+ export type QuotaTier = 'developer' | 'pro' | 'team' | 'enterprise' | 'unlimited';
8
+ /** Limit values for each quota type within a tier. */
9
+ export interface QuotaTierLimits {
10
+ plugins: number;
11
+ pipelines: number;
12
+ apiCalls: number;
13
+ aiCalls: number;
14
+ /**
15
+ * Aggregate registry storage cap in BYTES. Counted across every repo
16
+ * under the org's `org-{orgId}/` namespace. -1 means unlimited.
17
+ * push-gate reads this; the image-registry rejects token issuance for
18
+ * `push` scope when the org's measured usage exceeds the limit.
19
+ */
20
+ storageBytes: number;
21
+ /** Count-quotas on the user-editable feature tables added to close per-org
22
+ * DoS via spam. -1 means unlimited. */
23
+ dashboards: number;
24
+ alertRules: number;
25
+ alertDestinations: number;
26
+ idpConfigs: number;
27
+ /**
28
+ * Max org members (active users / seats). -1 = unlimited. This is the "Team"
29
+ * tier differentiator — enforced at member-invite time (an invite is blocked
30
+ * when active members + pending invites would exceed this).
31
+ */
32
+ seats: number;
33
+ /**
34
+ * Reporting retention in DAYS — NOT a flow quota (never decremented, absent
35
+ * from `VALID_QUOTA_TYPES`): these ride the tier-baseline + bundle-grant math
36
+ * purely as an entitlement the billing service syncs to reporting's
37
+ * `dora_settings`. -1 = unlimited (the sweep skips the org entirely).
38
+ * `eventRetentionDays` — non-deploy `pipeline_events` (STAGE/ACTION/build).
39
+ * `doraRetentionDays` — deploy events + `deployment_outcomes` + `incidents`,
40
+ * and the per-org report-query window for DORA routes.
41
+ * Extendable via the `retention_pack` / `dora_history_pack` add-on bundles.
42
+ */
43
+ eventRetentionDays: number;
44
+ doraRetentionDays: number;
45
+ }
46
+ /** Full preset for a single tier (label + limits). */
47
+ export interface QuotaTierPreset {
48
+ label: string;
49
+ limits: QuotaTierLimits;
50
+ }
51
+ export declare const QUOTA_TIERS: Record<QuotaTier, QuotaTierPreset>;
52
+ /** All valid tier names (incl. `unlimited`). Used for validation of a *stored*
53
+ * tier — `unlimited` is a valid value even though it's never offered for sale. */
54
+ export declare const VALID_TIERS: readonly QuotaTier[];
55
+ /** The standard, billing-selectable tiers — the ones offered as purchasable plans
56
+ * and shown in tier pickers. Derived as "every valid tier except `unlimited`",
57
+ * which is the billing-DISABLED default and is never displayed/selectable when
58
+ * billing is enabled. Kept DRY off `VALID_TIERS` so adding a tier flows through. */
59
+ export declare const STANDARD_TIERS: readonly QuotaTier[];
60
+ /** Tiers that permit the org → team hierarchy (nesting teams under a root). The
61
+ * billing-off `unlimited` tier is team-capable (it's the most permissive); the
62
+ * entry-level `developer`/`pro` tiers are not. Single source for the parent-
63
+ * eligibility + downgrade-overcap checks so they can't drift. */
64
+ export declare const TEAM_CAPABLE_TIERS: readonly QuotaTier[];
65
+ export declare function tierAllowsTeams(tier: string | undefined | null): boolean;
66
+ /** Whether the billing service is enabled in this deployment (`BILLING_ENABLED`,
67
+ * opt-out default-on — mirrors the billing/platform configs). With billing OFF
68
+ * there's no metering/enforcement surface, so the default tier is `unlimited`. */
69
+ export declare function isBillingEnabled(): boolean;
70
+ /**
71
+ * Default tier assigned to new organizations.
72
+ * - Billing DISABLED → `unlimited` (nothing is metered, so everything is uncapped).
73
+ * - Billing ENABLED → `DEFAULT_QUOTA_TIER` if it's a valid standard tier, else `developer`.
74
+ */
75
+ export declare const DEFAULT_TIER: QuotaTier;
76
+ /** Check whether a string is a valid QuotaTier. */
77
+ export declare function isValidTier(value: string): value is QuotaTier;
78
+ /** Get the default limits for a given tier (falls back to developer). */
79
+ export declare function getTierLimits(tier: string): QuotaTierLimits;
@@ -0,0 +1,221 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Preset limits for each tier. -1 means unlimited.
5
+ *
6
+ * AI calls are sized much smaller than `apiCalls` because each call has
7
+ * external provider cost (~$0.01$0.10/call). Developer tier allows light
8
+ * exploration (50/period); Pro lifts to 2,500; Enterprise is capped high
9
+ * (25,000) — fair-use, not literally unlimited.
10
+ *
11
+ * `storageBytes` is the aggregate registry cap per org. Sized
12
+ * around plugin-image realities: a typical plugin image is 200500 MB;
13
+ * Developer's 2 GB holds ~4 versions × ~500 MB, Pro's 50 GB covers a
14
+ * mature catalog. Operators can override per-org via the quota CRUD API.
15
+ */
16
+ const GB = 1024 * 1024 * 1024;
17
+ /**
18
+ * Code-default limits per tier — the fallback when no env override is set.
19
+ * -1 means unlimited. (Counts sized to "comfortably enough for one team, not
20
+ * script spam": 20 dashboards, 50 alert rules, 10 destinations, 1 IdP config
21
+ * for Developer; scaled up for Pro; -1 (uncapped) for the cheap count-quotas
22
+ * on Team/Enterprise while cost-driving quotas — incl. apiCalls — stay finite.)
23
+ */
24
+ const DEFAULT_TIER_LIMITS = {
25
+ // Free tier: single seat, 2 pipelines, apiCalls CAPPED (was unlimited — a
26
+ // DoS/abuse hole on a shared resource); aiCalls small (each has ~$0.01-0.10
27
+ // external cost). Lean base — growth happens by upgrading, not stacking here.
28
+ developer: {
29
+ plugins: 25,
30
+ pipelines: 2,
31
+ apiCalls: 25_000,
32
+ aiCalls: 25,
33
+ storageBytes: 2 * GB,
34
+ dashboards: 20,
35
+ alertRules: 50,
36
+ alertDestinations: 10,
37
+ idpConfigs: 1,
38
+ seats: 1,
39
+ eventRetentionDays: 30,
40
+ doraRetentionDays: 180,
41
+ },
42
+ // Pro = individual power user, single seat, individual pricing. A 2nd seat (or
43
+ // team collaboration) requires the Team tier — the `seat` add-on is Team+ only,
44
+ // keeping Team the collaboration tier. Pro's value over Developer is its richer
45
+ // call/storage/plugin ceilings + feature add-ons (SSO/audit/DORA), not seats.
46
+ pro: {
47
+ plugins: 50,
48
+ pipelines: 5,
49
+ apiCalls: 250_000,
50
+ aiCalls: 1_000,
51
+ storageBytes: 25 * GB,
52
+ dashboards: 200,
53
+ alertRules: 500,
54
+ alertDestinations: 50,
55
+ idpConfigs: 5,
56
+ seats: 1,
57
+ eventRetentionDays: 30,
58
+ doraRetentionDays: 180,
59
+ },
60
+ // Team = collaboration tier; the seat limit (3) is the real differentiator over
61
+ // Pro (single-seat) — it's the multi-seat collaboration floor. Lean base ($79):
62
+ // growth rides the seat/pipeline/etc add-ons (land-and-expand). Limits sit
63
+ // between Pro and Enterprise.
64
+ team: {
65
+ plugins: 75,
66
+ pipelines: 6,
67
+ apiCalls: 500_000,
68
+ aiCalls: 2_500,
69
+ storageBytes: 60 * GB,
70
+ dashboards: -1,
71
+ alertRules: -1,
72
+ alertDestinations: -1,
73
+ idpConfigs: 5,
74
+ seats: 3,
75
+ eventRetentionDays: 30,
76
+ doraRetentionDays: 180,
77
+ },
78
+ // Enterprise ($599, held): the all-inclusive tier — 15 seats, 30 pipelines, ALL
79
+ // premium features included (see TIER_FEATURES). FAIR-USE — cost drivers (apiCalls,
80
+ // aiCalls, storageBytes) capped so one account can't run up unbounded
81
+ // compute/provider/storage cost on a flat price; cheap count-quotas stay -1.
82
+ enterprise: {
83
+ plugins: 150,
84
+ pipelines: 30,
85
+ apiCalls: 900_000,
86
+ aiCalls: 9_000,
87
+ storageBytes: 250 * GB,
88
+ dashboards: -1,
89
+ alertRules: -1,
90
+ alertDestinations: -1,
91
+ idpConfigs: -1,
92
+ seats: 15,
93
+ eventRetentionDays: 30,
94
+ doraRetentionDays: 180,
95
+ },
96
+ // Unlimited: EVERY quota uncapped (-1). The default when billing is disabled
97
+ // (no enforcement surface) — never shown/selectable when billing is enabled.
98
+ unlimited: {
99
+ plugins: -1,
100
+ pipelines: -1,
101
+ apiCalls: -1,
102
+ aiCalls: -1,
103
+ storageBytes: -1,
104
+ dashboards: -1,
105
+ alertRules: -1,
106
+ alertDestinations: -1,
107
+ idpConfigs: -1,
108
+ seats: -1,
109
+ eventRetentionDays: -1,
110
+ doraRetentionDays: -1,
111
+ },
112
+ };
113
+ /**
114
+ * Read an integer env var, falling back to the code default. `-1` = unlimited.
115
+ * Rejects (→ fallback) anything that isn't a clean integer string and any value
116
+ * `< -1`: a decimal (`50.5`) or a nonsense negative (`-5`) would otherwise ship a
117
+ * broken finite limit (parity with `envCents`'s rigor for prices).
118
+ */
119
+ function envInt(name, fallback) {
120
+ const raw = process.env[name];
121
+ if (raw === undefined || raw === '')
122
+ return fallback;
123
+ const trimmed = raw.trim();
124
+ if (!/^-?\d+$/.test(trimmed))
125
+ return fallback; // no decimals / non-numeric
126
+ const n = parseInt(trimmed, 10);
127
+ return Number.isNaN(n) || n < -1 ? fallback : n;
128
+ }
129
+ /** Read a string env var, falling back to the code default. */
130
+ function envStr(name, fallback) {
131
+ const raw = process.env[name];
132
+ return raw === undefined || raw === '' ? fallback : raw;
133
+ }
134
+ /**
135
+ * Per-tier limits, each overridable via `QUOTA_TIER_<TIER>_<LIMIT>` env vars
136
+ * (e.g. `QUOTA_TIER_PRO_PIPELINES=250`, `QUOTA_TIER_DEVELOPER_STORAGE_BYTES=...`).
137
+ * Unset/empty → the code default. `-1` = unlimited. Read once at module load —
138
+ * on k8s these flow in via the `app-env` ConfigMap (built from `.env`).
139
+ */
140
+ function tierLimits(tier) {
141
+ // `unlimited` is uncapped BY DEFINITION: EVERY quota is -1 and it is NOT
142
+ // env-overridable. Reading QUOTA_TIER_UNLIMITED_* here would let a stray env
143
+ // var silently cap the billing-DISABLED default tier — surfacing as a spurious
144
+ // "quota exceeded" on a deploy that meters nothing. Derive -1 for every field
145
+ // from the limits shape so a newly-added quota is uncapped here automatically.
146
+ if (tier === 'unlimited') {
147
+ const uncapped = { ...DEFAULT_TIER_LIMITS.unlimited };
148
+ for (const k of Object.keys(uncapped))
149
+ uncapped[k] = -1;
150
+ return uncapped;
151
+ }
152
+ const d = DEFAULT_TIER_LIMITS[tier];
153
+ const T = tier.toUpperCase();
154
+ return {
155
+ plugins: envInt(`QUOTA_TIER_${T}_PLUGINS`, d.plugins),
156
+ pipelines: envInt(`QUOTA_TIER_${T}_PIPELINES`, d.pipelines),
157
+ apiCalls: envInt(`QUOTA_TIER_${T}_API_CALLS`, d.apiCalls),
158
+ aiCalls: envInt(`QUOTA_TIER_${T}_AI_CALLS`, d.aiCalls),
159
+ storageBytes: envInt(`QUOTA_TIER_${T}_STORAGE_BYTES`, d.storageBytes),
160
+ dashboards: envInt(`QUOTA_TIER_${T}_DASHBOARDS`, d.dashboards),
161
+ alertRules: envInt(`QUOTA_TIER_${T}_ALERT_RULES`, d.alertRules),
162
+ alertDestinations: envInt(`QUOTA_TIER_${T}_ALERT_DESTINATIONS`, d.alertDestinations),
163
+ idpConfigs: envInt(`QUOTA_TIER_${T}_IDP_CONFIGS`, d.idpConfigs),
164
+ seats: envInt(`QUOTA_TIER_${T}_SEATS`, d.seats),
165
+ eventRetentionDays: envInt(`QUOTA_TIER_${T}_EVENT_RETENTION_DAYS`, d.eventRetentionDays),
166
+ doraRetentionDays: envInt(`QUOTA_TIER_${T}_DORA_RETENTION_DAYS`, d.doraRetentionDays),
167
+ };
168
+ }
169
+ // Labels are overridable via `QUOTA_TIER_<TIER>_LABEL` (display name only).
170
+ export const QUOTA_TIERS = {
171
+ developer: { label: envStr('QUOTA_TIER_DEVELOPER_LABEL', 'Developer'), limits: tierLimits('developer') },
172
+ pro: { label: envStr('QUOTA_TIER_PRO_LABEL', 'Pro'), limits: tierLimits('pro') },
173
+ team: { label: envStr('QUOTA_TIER_TEAM_LABEL', 'Team'), limits: tierLimits('team') },
174
+ enterprise: { label: envStr('QUOTA_TIER_ENTERPRISE_LABEL', 'Enterprise'), limits: tierLimits('enterprise') },
175
+ unlimited: { label: envStr('QUOTA_TIER_UNLIMITED_LABEL', 'Unlimited'), limits: tierLimits('unlimited') },
176
+ };
177
+ /** All valid tier names (incl. `unlimited`). Used for validation of a *stored*
178
+ * tier — `unlimited` is a valid value even though it's never offered for sale. */
179
+ export const VALID_TIERS = Object.keys(QUOTA_TIERS);
180
+ /** The standard, billing-selectable tiers — the ones offered as purchasable plans
181
+ * and shown in tier pickers. Derived as "every valid tier except `unlimited`",
182
+ * which is the billing-DISABLED default and is never displayed/selectable when
183
+ * billing is enabled. Kept DRY off `VALID_TIERS` so adding a tier flows through. */
184
+ export const STANDARD_TIERS = VALID_TIERS.filter((t) => t !== 'unlimited');
185
+ /** Tiers that permit the org → team hierarchy (nesting teams under a root). The
186
+ * billing-off `unlimited` tier is team-capable (it's the most permissive); the
187
+ * entry-level `developer`/`pro` tiers are not. Single source for the parent-
188
+ * eligibility + downgrade-overcap checks so they can't drift. */
189
+ export const TEAM_CAPABLE_TIERS = ['team', 'enterprise', 'unlimited'];
190
+ export function tierAllowsTeams(tier) {
191
+ return !!tier && TEAM_CAPABLE_TIERS.includes(tier);
192
+ }
193
+ /** Whether the billing service is enabled in this deployment (`BILLING_ENABLED`,
194
+ * opt-out default-on — mirrors the billing/platform configs). With billing OFF
195
+ * there's no metering/enforcement surface, so the default tier is `unlimited`. */
196
+ export function isBillingEnabled() {
197
+ return (process.env.BILLING_ENABLED || 'true').toLowerCase() !== 'false';
198
+ }
199
+ /**
200
+ * Default tier assigned to new organizations.
201
+ * - Billing DISABLED → `unlimited` (nothing is metered, so everything is uncapped).
202
+ * - Billing ENABLED → `DEFAULT_QUOTA_TIER` if it's a valid standard tier, else `developer`.
203
+ */
204
+ export const DEFAULT_TIER = !isBillingEnabled()
205
+ ? 'unlimited'
206
+ : (isValidTier(process.env.DEFAULT_QUOTA_TIER ?? '') && process.env.DEFAULT_QUOTA_TIER !== 'unlimited'
207
+ ? process.env.DEFAULT_QUOTA_TIER
208
+ : 'developer');
209
+ /** Check whether a string is a valid QuotaTier. */
210
+ export function isValidTier(value) {
211
+ // Use the explicit tuple, not `value in QUOTA_TIERS`: `in` walks the prototype
212
+ // chain, so inherited Object keys ('toString', 'constructor', …) would return
213
+ // true and then `getTierLimits` would read `.limits` off a function → crash.
214
+ // `tier` is attacker-influenceable (JWT / request body / DEFAULT_QUOTA_TIER env).
215
+ return VALID_TIERS.includes(value);
216
+ }
217
+ /** Get the default limits for a given tier (falls back to developer). */
218
+ export function getTierLimits(tier) {
219
+ return isValidTier(tier) ? QUOTA_TIERS[tier].limits : QUOTA_TIERS.developer.limits;
220
+ }
221
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVvdGEtdGllcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdHlwZXMvcXVvdGEtdGllcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQXVEdEM7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0gsTUFBTSxFQUFFLEdBQUcsSUFBSSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFFOUI7Ozs7OztHQU1HO0FBQ0gsTUFBTSxtQkFBbUIsR0FBdUM7SUFDOUQsMEVBQTBFO0lBQzFFLDRFQUE0RTtJQUM1RSw4RUFBOEU7SUFDOUUsU0FBUyxFQUFFO1FBQ1QsT0FBTyxFQUFFLEVBQUU7UUFDWCxTQUFTLEVBQUUsQ0FBQztRQUNaLFFBQVEsRUFBRSxNQUFNO1FBQ2hCLE9BQU8sRUFBRSxFQUFFO1FBQ1gsWUFBWSxFQUFFLENBQUMsR0FBRyxFQUFFO1FBQ3BCLFVBQVUsRUFBRSxFQUFFO1FBQ2QsVUFBVSxFQUFFLEVBQUU7UUFDZCxpQkFBaUIsRUFBRSxFQUFFO1FBQ3JCLFVBQVUsRUFBRSxDQUFDO1FBQ2IsS0FBSyxFQUFFLENBQUM7UUFDUixrQkFBa0IsRUFBRSxFQUFFO1FBQ3RCLGlCQUFpQixFQUFFLEdBQUc7S0FDdkI7SUFDRCwrRUFBK0U7SUFDL0UsZ0ZBQWdGO0lBQ2hGLGdGQUFnRjtJQUNoRiw4RUFBOEU7SUFDOUUsR0FBRyxFQUFFO1FBQ0gsT0FBTyxFQUFFLEVBQUU7UUFDWCxTQUFTLEVBQUUsQ0FBQztRQUNaLFFBQVEsRUFBRSxPQUFPO1FBQ2pCLE9BQU8sRUFBRSxLQUFLO1FBQ2QsWUFBWSxFQUFFLEVBQUUsR0FBRyxFQUFFO1FBQ3JCLFVBQVUsRUFBRSxHQUFHO1FBQ2YsVUFBVSxFQUFFLEdBQUc7UUFDZixpQkFBaUIsRUFBRSxFQUFFO1FBQ3JCLFVBQVUsRUFBRSxDQUFDO1FBQ2IsS0FBSyxFQUFFLENBQUM7UUFDUixrQkFBa0IsRUFBRSxFQUFFO1FBQ3RCLGlCQUFpQixFQUFF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QUFRLFdBQWlDLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzVELENBQUM7QUFFRCx5RUFBeUU7QUFDekUsTUFBTSxVQUFVLGFBQWEsQ0FBQyxJQUFZO0lBQ3hDLE9BQU8sV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFBLENBQUMsQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztBQUNwRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKiBBdmFpbGFibGUgcXVvdGEgdGllciBpZGVudGlmaWVycy5cbiAqXG4gKiBgdW5saW1pdGVkYCBpcyBhIHNwZWNpYWwgdGllcjogZXZlcnkgcXVvdGEgaXMgYC0xYCAodW5jYXBwZWQpIGFuZCBpdCBpbmNsdWRlc1xuICogZXZlcnkgZmVhdHVyZS9hZGQtb24gYnVuZGxlLiBJdCBpcyB0aGUgREVGQVVMVCB3aGVuIGJpbGxpbmcgaXMgZGlzYWJsZWQgKG5vXG4gKiBtZXRlcmluZy9lbmZvcmNlbWVudCBzdXJmYWNlIGV4aXN0cywgc28gbm90aGluZyBpcyBjYXBwZWQpLCBhbmQgaXQgaXMgTkVWRVJcbiAqIHNob3duIG9yIHNlbGVjdGFibGUgd2hlbiBiaWxsaW5nIGlzIGVuYWJsZWQg4oCUIHNlZSBgU1RBTkRBUkRfVElFUlNgLiAqL1xuZXhwb3J0IHR5cGUgUXVvdGFUaWVyID0gJ2RldmVsb3BlcicgfCAncHJvJyB8ICd0ZWFtJyB8ICdlbnRlcnByaXNlJyB8ICd1bmxpbWl0ZWQnO1xuXG4vKiogTGltaXQgdmFsdWVzIGZvciBlYWNoIHF1b3RhIHR5cGUgd2l0aGluIGEgdGllci4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUXVvdGFUaWVyTGltaXRzIHtcbiAgcGx1Z2luczogbnVtYmVyO1xuICBwaXBlbGluZXM6IG51bWJlcjtcbiAgYXBpQ2FsbHM6IG51bWJlcjtcbiAgYWlDYWxsczogbnVtYmVyO1xuICAvKipcbiAgICogQWdncmVnYXRlIHJlZ2lzdHJ5IHN0b3JhZ2UgY2FwIGluIEJZVEVTLiBDb3VudGVkIGFjcm9zcyBldmVyeSByZXBvXG4gICAqIHVuZGVyIHRoZSBvcmcncyBgb3JnLXtvcmdJZH0vYCBuYW1lc3BhY2UuIC0xIG1lYW5zIHVubGltaXRlZC5cbiAgICogcHVzaC1nYXRlIHJlYWRzIHRoaXM7IHRoZSBpbWFnZS1yZWdpc3RyeSByZWplY3RzIHRva2VuIGlzc3VhbmNlIGZvclxuICAgKiBgcHVzaGAgc2NvcGUgd2hlbiB0aGUgb3JnJ3MgbWVhc3VyZWQgdXNhZ2UgZXhjZWVkcyB0aGUgbGltaXQuXG4gICAqL1xuICBzdG9yYWdlQnl0ZXM6IG51bWJlcjtcbiAgLyoqIENvdW50LXF1b3RhcyBvbiB0aGUgdXNlci1lZGl0YWJsZSBmZWF0dXJlIHRhYmxlcyBhZGRlZCB0byBjbG9zZSBwZXItb3JnXG4gICAqICBEb1MgdmlhIHNwYW0uIC0xIG1lYW5zIHVubGltaXRlZC4gKi9cbiAgZGFzaGJvYXJkczogbnVtYmVyO1xuICBhbGVydFJ1bGVzOiBudW1iZXI7XG4gIGFsZXJ0RGVzdGluYXRpb25zOiBudW1iZXI7XG4gIGlkcENvbmZpZ3M6IG51bWJlcjtcbiAgLyoqXG4gICAqIE1heCBvcmcgbWVtYmVycyAoYWN0aXZlIHVzZXJzIC8gc2VhdHMpLiAtMSA9IHVubGltaXRlZC4gVGhpcyBpcyB0aGUgXCJUZWFtXCJcbiAgICogdGllciBkaWZmZXJlbnRpYXRvciDigJQgZW5mb3JjZWQgYXQgbWVtYmVyLWludml0ZSB0aW1lIChhbiBpbnZpdGUgaXMgYmxvY2tlZFxuICAgKiB3aGVuIGFjdGl2ZSBtZW1iZXJzICsgcGVuZGluZyBpbnZpdGVzIHdvdWxkIGV4Y2VlZCB0aGlzKS5cbiAgICovXG4gIHNlYXRzOiBudW1iZXI7XG4gIC8qKlxuICAgKiBSZXBvcnRpbmcgcmV0ZW50aW9uIGluIERBWVMg4oCUIE5PVCBhIGZsb3cgcXVvdGEgKG5ldmVyIGRlY3JlbWVudGVkLCBhYnNlbnRcbiAgICogZnJvbSBgVkFMSURfUVVPVEFfVFlQRVNgKTogdGhlc2UgcmlkZSB0aGUgdGllci1iYXNlbGluZSArIGJ1bmRsZS1ncmFudCBtYXRoXG4gICAqIHB1cmVseSBhcyBhbiBlbnRpdGxlbWVudCB0aGUgYmlsbGluZyBzZXJ2aWNlIHN5bmNzIHRvIHJlcG9ydGluZydzXG4gICAqIGBkb3JhX3NldHRpbmdzYC4gLTEgPSB1bmxpbWl0ZWQgKHRoZSBzd2VlcCBza2lwcyB0aGUgb3JnIGVudGlyZWx5KS5cbiAgICogICBgZXZlbnRSZXRlbnRpb25EYXlzYCDigJQgbm9uLWRlcGxveSBgcGlwZWxpbmVfZXZlbnRzYCAoU1RBR0UvQUNUSU9OL2J1aWxkKS5cbiAgICogICBgZG9yYVJldGVudGlvbkRheXNgICDigJQgZGVwbG95IGV2ZW50cyArIGBkZXBsb3ltZW50X291dGNvbWVzYCArIGBpbmNpZGVudHNgLFxuICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgYW5kIHRoZSBwZXItb3JnIHJlcG9ydC1xdWVyeSB3aW5kb3cgZm9yIERPUkEgcm91dGVzLlxuICAgKiBFeHRlbmRhYmxlIHZpYSB0aGUgYHJldGVudGlvbl9wYWNrYCAvIGBkb3JhX2hpc3RvcnlfcGFja2AgYWRkLW9uIGJ1bmRsZXMuXG4gICAqL1xuICBldmVudFJldGVudGlvbkRheXM6IG51bWJlcjtcbiAgZG9yYVJldGVudGlvbkRheXM6IG51bWJlcjtcbn1cblxuLyoqIEZ1bGwgcHJlc2V0IGZvciBhIHNpbmdsZSB0aWVyIChsYWJlbCArIGxpbWl0cykuICovXG5leHBvcnQgaW50ZXJmYWNlIFF1b3RhVGllclByZXNldCB7XG4gIGxhYmVsOiBzdHJpbmc7XG4gIGxpbWl0czogUXVvdGFUaWVyTGltaXRzO1xufVxuXG4vKipcbiAqIFByZXNldCBsaW1pdHMgZm9yIGVhY2ggdGllci4gLTEgbWVhbnMgdW5saW1pdGVkLlxuICpcbiAqIEFJIGNhbGxzIGFyZSBzaXplZCBtdWNoIHNtYWxsZXIgdGhhbiBgYXBpQ2FsbHNgIGJlY2F1c2UgZWFjaCBjYWxsIGhhc1xuICogZXh0ZXJuYWwgcHJvdmlkZXIgY29zdCAofiQwLjAxJDAuMTAvY2FsbCkuIERldmVsb3BlciB0aWVyIGFsbG93cyBsaWdodFxuICogZXhwbG9yYXRpb24gKDUwL3BlcmlvZCk7IFBybyBsaWZ0cyB0byAyLDUwMDsgRW50ZXJwcmlzZSBpcyBjYXBwZWQgaGlnaFxuICogKDI1LDAwMCkg4oCUIGZhaXItdXNlLCBub3QgbGl0ZXJhbGx5IHVubGltaXRlZC5cbiAqXG4gKiBgc3RvcmFnZUJ5dGVzYCBpcyB0aGUgYWdncmVnYXRlIHJlZ2lzdHJ5IGNhcCBwZXIgb3JnLiBTaXplZFxuICogYXJvdW5kIHBsdWdpbi1pbWFnZSByZWFsaXRpZXM6IGEgdHlwaWNhbCBwbHVnaW4gaW1hZ2UgaXMgMjAwNTAwIE1CO1xuICogRGV2ZWxvcGVyJ3MgMiBHQiBob2xkcyB+NCB2ZXJzaW9ucyDDlyB+NTAwIE1CLCBQcm8ncyA1MCBHQiBjb3ZlcnMgYVxuICogbWF0dXJlIGNhdGFsb2cuIE9wZXJhdG9ycyBjYW4gb3ZlcnJpZGUgcGVyLW9yZyB2aWEgdGhlIHF1b3RhIENSVUQgQVBJLlxuICovXG5jb25zdCBHQiA9IDEwMjQgKiAxMDI0ICogMTAyNDtcblxuLyoqXG4gKiBDb2RlLWRlZmF1bHQgbGltaXRzIHBlciB0aWVyIOKAlCB0aGUgZmFsbGJhY2sgd2hlbiBubyBlbnYgb3ZlcnJpZGUgaXMgc2V0LlxuICogLTEgbWVhbnMgdW5saW1pdGVkLiAoQ291bnRzIHNpemVkIHRvIFwiY29tZm9ydGFibHkgZW5vdWdoIGZvciBvbmUgdGVhbSwgbm90XG4gKiBzY3JpcHQgc3BhbVwiOiAyMCBkYXNoYm9hcmRzLCA1MCBhbGVydCBydWxlcywgMTAgZGVzdGluYXRpb25zLCAxIElkUCBjb25maWdcbiAqIGZvciBEZXZlbG9wZXI7IHNjYWxlZCB1cCBmb3IgUHJvOyAtMSAodW5jYXBwZWQpIGZvciB0aGUgY2hlYXAgY291bnQtcXVvdGFzXG4gKiBvbiBUZWFtL0VudGVycHJpc2Ugd2hpbGUgY29zdC1kcml2aW5nIHF1b3RhcyDigJQgaW5jbC4gYXBpQ2FsbHMg4oCUIHN0YXkgZmluaXRlLilcbiAqL1xuY29uc3QgREVGQVVMVF9USUVSX0xJTUlUUzogUmVjb3JkPFF1b3RhVGllciwgUXVvdGFUaWVyTGltaXRzPiA9IHtcbiAgLy8gRnJlZSB0aWVyOiBzaW5nbGUgc2VhdCwgMiBwaXBlbGluZXMsIGFwaUNhbGxzIENBUFBFRCAod2FzIHVubGltaXRlZCDigJQgYVxuICAvLyBEb1MvYWJ1c2UgaG9sZSBvbiBhIHNoYXJlZCByZXNvdXJjZSk7IGFpQ2FsbHMgc21hbGwgKGVhY2ggaGFzIH4kMC4wMS0wLjEwXG4gIC8vIGV4dGVybmFsIGNvc3QpLiBMZWFuIGJhc2Ug4oCUIGdyb3d0aCBoYXBwZW5zIGJ5IHVwZ3JhZGluZywgbm90IHN0YWNraW5nIGhlcmUuXG4gIGRldmVsb3Blcjoge1xuICAgIHBsdWdpbnM6IDI1LFxuICAgIHBpcGVsaW5lczogMixcbiAgICBhcGlDYWxsczogMjVfMDAwLFxuICAgIGFpQ2FsbHM6IDI1LFxuICAgIHN0b3JhZ2VCeXRlczogMiAqIEdCLFxuICAgIGRhc2hib2FyZHM6IDIwLFxuICAgIGFsZXJ0UnVsZXM6IDUwLFxuICAgIGFsZXJ0RGVzdGluYXRpb25zOiAxMCxcbiAgICBpZHBDb25maWdzOiAxLFxuICAgIHNlYXRzOiAxLFxuICAgIGV2ZW50UmV0ZW50aW9uRGF5czogMzAsXG4gICAgZG9yYVJldGVudGlvbkRheXM6IDE4MCxcbiAgfSxcbiAgLy8gUHJvID0gaW5kaXZpZHVhbCBwb3dlciB1c2VyLCBzaW5nbGUgc2VhdCwgaW5kaXZpZHVhbCBwcmljaW5nLiBBIDJuZCBzZWF0IChvclxuICAvLyB0ZWFtIGNvbGxhYm9yYXRpb24pIHJlcXVpcmVzIHRoZSBUZWFtIHRpZXIg4oCUIHRoZSBgc2VhdGAgYWRkLW9uIGlzIFRlYW0rIG9ubHksXG4gIC8vIGtlZXBpbmcgVGVhbSB0aGUgY29sbGFib3JhdGlvbiB0aWVyLiBQcm8ncyB2YWx1ZSBvdmVyIERldmVsb3BlciBpcyBpdHMgcmljaGVyXG4gIC8vIGNhbGwvc3RvcmFnZS9wbHVnaW4gY2VpbGluZ3MgKyBmZWF0dXJlIGFkZC1vbnMgKFNTTy9hdWRpdC9ET1JBKSwgbm90IHNlYXRzLlxuICBwcm86IHtcbiAgICBwbHVnaW5zOiA1MCxcbiAgICBwaXBlbGluZXM6IDUsXG4gICAgYXBpQ2FsbHM6IDI1MF8wMDAsXG4gICAgYWlDYWxsczogMV8wMDAsXG4gICAgc3RvcmFnZUJ5dGVzOiAyNSAqIEdCLFxuICAgIGRhc2hib2FyZHM6IDIwMCxcbiAgICBhbGVydFJ1bGVzOiA1MDAsXG4gICAgYWxlcnREZXN0aW5hdGlvbnM6IDUwLFxuICAgIGlkcENvbmZpZ3M6IDUsXG4gICAgc2VhdHM6IDEsXG4gICAgZXZlbnRSZXRlbnRpb25EYXlzOiAzMCxcbiAgICBkb3JhUmV0ZW50aW9uRGF5czogMTgwLFxuICB9LFxuICAvLyBUZWFtID0gY29sbGFib3JhdGlvbiB0aWVyOyB0aGUgc2VhdCBsaW1pdCAoMykgaXMgdGhlIHJlYWwgZGlmZmVyZW50aWF0b3Igb3ZlclxuICAvLyBQcm8gKHNpbmdsZS1zZWF0KSDigJQgaXQncyB0aGUgbXVsdGktc2VhdCBjb2xsYWJvcmF0aW9uIGZsb29yLiBMZWFuIGJhc2UgKCQ3OSk6XG4gIC8vIGdyb3d0aCByaWRlcyB0aGUgc2VhdC9waXBlbGluZS9ldGMgYWRkLW9ucyAobGFuZC1hbmQtZXhwYW5kKS4gTGltaXRzIHNpdFxuICAvLyBiZXR3ZWVuIFBybyBhbmQgRW50ZXJwcmlzZS5cbiAgdGVhbToge1xuICAgIHBsdWdpbnM6IDc1LFxuICAgIHBpcGVsaW5lczogNixcbiAgICBhcGlDYWxsczogNTAwXzAwMCxcbiAgICBhaUNhbGxzOiAyXzUwMCxcbiAgICBzdG9yYWdlQnl0ZXM6IDYwICogR0IsXG4gICAgZGFzaGJvYXJkczogLTEsXG4gICAgYWxlcnRSdWxlczogLTEsXG4gICAgYWxlcnREZXN0aW5hdGlvbnM6IC0xLFxuICAgIGlkcENvbmZpZ3M6IDUsXG4gICAgc2VhdHM6IDMsXG4gICAgZXZlbnRSZXRlbnRpb25EYXlzOiAzMCxcbiAgICBkb3JhUmV0ZW50aW9uRGF5czogMTgwLFxuICB9LFxuICAvLyBFbnRlcnByaXNlICgkNTk5LCBoZWxkKTogdGhlIGFsbC1pbmNsdXNpdmUgdGllciDigJQgMTUgc2VhdHMsIDMwIHBpcGVsaW5lcywgQUxMXG4gIC8vIHByZW1pdW0gZmVhdHVyZXMgaW5jbHVkZWQgKHNlZSBUSUVSX0ZFQVRVUkVTKS4gRkFJUi1VU0Ug4oCUIGNvc3QgZHJpdmVycyAoYXBpQ2FsbHMsXG4gIC8vIGFpQ2FsbHMsIHN0b3JhZ2VCeXRlcykgY2FwcGVkIHNvIG9uZSBhY2NvdW50IGNhbid0IHJ1biB1cCB1bmJvdW5kZWRcbiAgLy8gY29tcHV0ZS9wcm92aWRlci9zdG9yYWdlIGNvc3Qgb24gYSBmbGF0IHByaWNlOyBjaGVhcCBjb3VudC1xdW90YXMgc3RheSAtMS5cbiAgZW50ZXJwcmlzZToge1xuICAgIHBsdWdpbnM6IDE1MCxcbiAgICBwaXBlbGluZXM6IDMwLFxuICAgIGFwaUNhbGxzOiA5MDBfMDAwLFxuICAgIGFpQ2FsbHM6IDlfMDAwLFxuICAgIHN0b3JhZ2VCeXRlczogMjUwICogR0IsXG4gICAgZGFzaGJvYXJkczogLTEsXG4gICAgYWxlcnRSdWxlczogLTEsXG4gICAgYWxlcnREZXN0aW5hdGlvbnM6IC0xLFxuICAgIGlkcENvbmZpZ3M6IC0xLFxuICAgIHNlYXRzOiAxNSxcbiAgICBldmVudFJldGVudGlvbkRheXM6IDMwLFxuICAgIGRvcmFSZXRlbnRpb25EYXlzOiAxODAsXG4gIH0sXG4gIC8vIFVubGltaXRlZDogRVZFUlkgcXVvdGEgdW5jYXBwZWQgKC0xKS4gVGhlIGRlZmF1bHQgd2hlbiBiaWxsaW5nIGlzIGRpc2FibGVkXG4gIC8vIChubyBlbmZvcmNlbWVudCBzdXJmYWNlKSDigJQgbmV2ZXIgc2hvd24vc2VsZWN0YWJsZSB3aGVuIGJpbGxpbmcgaXMgZW5hYmxlZC5cbiAgdW5saW1pdGVkOiB7XG4gICAgcGx1Z2luczogLTEsXG4gICAgcGlwZWxpbmVzOiAtMSxcbiAgICBhcGlDYWxsczogLTEsXG4gICAgYWlDYWxsczogLTEsXG4gICAgc3RvcmFnZUJ5dGVzOiAtMSxcbiAgICBkYXNoYm9hcmRzOiAtMSxcbiAgICBhbGVydFJ1bGVzOiAtMSxcbiAgICBhbGVydERlc3RpbmF0aW9uczogLTEsXG4gICAgaWRwQ29uZmlnczogLTEsXG4gICAgc2VhdHM6IC0xLFxuICAgIGV2ZW50UmV0ZW50aW9uRGF5czogLTEsXG4gICAgZG9yYVJldGVudGlvbkRheXM6IC0xLFxuICB9LFxufTtcblxuLyoqXG4gKiBSZWFkIGFuIGludGVnZXIgZW52IHZhciwgZmFsbGluZyBiYWNrIHRvIHRoZSBjb2RlIGRlZmF1bHQuIGAtMWAgPSB1bmxpbWl0ZWQuXG4gKiBSZWplY3RzICjihpIgZmFsbGJhY2spIGFueXRoaW5nIHRoYXQgaXNuJ3QgYSBjbGVhbiBpbnRlZ2VyIHN0cmluZyBhbmQgYW55IHZhbHVlXG4gKiBgPCAtMWA6IGEgZGVjaW1hbCAoYDUwLjVgKSBvciBhIG5vbnNlbnNlIG5lZ2F0aXZlIChgLTVgKSB3b3VsZCBvdGhlcndpc2Ugc2hpcCBhXG4gKiBicm9rZW4gZmluaXRlIGxpbWl0IChwYXJpdHkgd2l0aCBgZW52Q2VudHNgJ3Mgcmlnb3IgZm9yIHByaWNlcykuXG4gKi9cbmZ1bmN0aW9uIGVudkludChuYW1lOiBzdHJpbmcsIGZhbGxiYWNrOiBudW1iZXIpOiBudW1iZXIge1xuICBjb25zdCByYXcgPSBwcm9jZXNzLmVudltuYW1lXTtcbiAgaWYgKHJhdyA9PT0gdW5kZWZpbmVkIHx8IHJhdyA9PT0gJycpIHJldHVybiBmYWxsYmFjaztcbiAgY29uc3QgdHJpbW1lZCA9IHJhdy50cmltKCk7XG4gIGlmICghL14tP1xcZCskLy50ZXN0KHRyaW1tZWQpKSByZXR1cm4gZmFsbGJhY2s7IC8vIG5vIGRlY2ltYWxzIC8gbm9uLW51bWVyaWNcbiAgY29uc3QgbiA9IHBhcnNlSW50KHRyaW1tZWQsIDEwKTtcbiAgcmV0dXJuIE51bWJlci5pc05hTihuKSB8fCBuIDwgLTEgPyBmYWxsYmFjayA6IG47XG59XG5cbi8qKiBSZWFkIGEgc3RyaW5nIGVudiB2YXIsIGZhbGxpbmcgYmFjayB0byB0aGUgY29kZSBkZWZhdWx0LiAqL1xuZnVuY3Rpb24gZW52U3RyKG5hbWU6IHN0cmluZywgZmFsbGJhY2s6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IHJhdyA9IHByb2Nlc3MuZW52W25hbWVdO1xuICByZXR1cm4gcmF3ID09PSB1bmRlZmluZWQgfHwgcmF3ID09PSAnJyA/IGZhbGxiYWNrIDogcmF3O1xufVxuXG4vKipcbiAqIFBlci10aWVyIGxpbWl0cywgZWFjaCBvdmVycmlkYWJsZSB2aWEgYFFVT1RBX1RJRVJfPFRJRVI+XzxMSU1JVD5gIGVudiB2YXJzXG4gKiAoZS5nLiBgUVVPVEFfVElFUl9QUk9fUElQRUxJTkVTPTI1MGAsIGBRVU9UQV9USUVSX0RFVkVMT1BFUl9TVE9SQUdFX0JZVEVTPS4uLmApLlxuICogVW5zZXQvZW1wdHkg4oaSIHRoZSBjb2RlIGRlZmF1bHQuIGAtMWAgPSB1bmxpbWl0ZWQuIFJlYWQgb25jZSBhdCBtb2R1bGUgbG9hZCDigJRcbiAqIG9uIGs4cyB0aGVzZSBmbG93IGluIHZpYSB0aGUgYGFwcC1lbnZgIENvbmZpZ01hcCAoYnVpbHQgZnJvbSBgLmVudmApLlxuICovXG5mdW5jdGlvbiB0aWVyTGltaXRzKHRpZXI6IFF1b3RhVGllcik6IFF1b3RhVGllckxpbWl0cyB7XG4gIC8vIGB1bmxpbWl0ZWRgIGlzIHVuY2FwcGVkIEJZIERFRklOSVRJT046IEVWRVJZIHF1b3RhIGlzIC0xIGFuZCBpdCBpcyBOT1RcbiAgLy8gZW52LW92ZXJyaWRhYmxlLiBSZWFkaW5nIFFVT1RBX1RJRVJfVU5MSU1JVEVEXyogaGVyZSB3b3VsZCBsZXQgYSBzdHJheSBlbnZcbiAgLy8gdmFyIHNpbGVudGx5IGNhcCB0aGUgYmlsbGluZy1ESVNBQkxFRCBkZWZhdWx0IHRpZXIg4oCUIHN1cmZhY2luZyBhcyBhIHNwdXJpb3VzXG4gIC8vIFwicXVvdGEgZXhjZWVkZWRcIiBvbiBhIGRlcGxveSB0aGF0IG1ldGVycyBub3RoaW5nLiBEZXJpdmUgLTEgZm9yIGV2ZXJ5IGZpZWxkXG4gIC8vIGZyb20gdGhlIGxpbWl0cyBzaGFwZSBzbyBhIG5ld2x5LWFkZGVkIHF1b3RhIGlzIHVuY2FwcGVkIGhlcmUgYXV0b21hdGljYWxseS5cbiAgaWYgKHRpZXIgPT09ICd1bmxpbWl0ZWQnKSB7XG4gICAgY29uc3QgdW5jYXBwZWQgPSB7IC4uLkRFRkFVTFRfVElFUl9MSU1JVFMudW5saW1pdGVkIH07XG4gICAgZm9yIChjb25zdCBrIG9mIE9iamVjdC5rZXlzKHVuY2FwcGVkKSBhcyAoa2V5b2YgUXVvdGFUaWVyTGltaXRzKVtdKSB1bmNhcHBlZFtrXSA9IC0xO1xuICAgIHJldHVybiB1bmNhcHBlZDtcbiAgfVxuICBjb25zdCBkID0gREVGQVVMVF9USUVSX0xJTUlUU1t0aWVyXTtcbiAgY29uc3QgVCA9IHRpZXIudG9VcHBlckNhc2UoKTtcbiAgcmV0dXJuIHtcbiAgICBwbHVnaW5zOiBlbnZJbnQoYFFVT1RBX1RJRVJfJHtUfV9QTFVHSU5TYCwgZC5wbHVnaW5zKSxcbiAgICBwaXBlbGluZXM6IGVudkludChgUVVPVEFfVElFUl8ke1R9X1BJUEVMSU5FU2AsIGQucGlwZWxpbmVzKSxcbiAgICBhcGlDYWxsczogZW52SW50KGBRVU9UQV9USUVSXyR7VH1fQVBJX0NBTExTYCwgZC5hcGlDYWxscyksXG4gICAgYWlDYWxsczogZW52SW50KGBRVU9UQV9USUVSXyR7VH1fQUlfQ0FMTFNgLCBkLmFpQ2FsbHMpLFxuICAgIHN0b3JhZ2VCeXRlczogZW52SW50KGBRVU9UQV9USUVSXyR7VH1fU1RPUkFHRV9CWVRFU2AsIGQuc3RvcmFnZUJ5dGVzKSxcbiAgICBkYXNoYm9hcmRzOiBlbnZJbnQoYFFVT1RBX1RJRVJfJHtUfV9EQVNIQk9BUkRTYCwgZC5kYXNoYm9hcmRzKSxcbiAgICBhbGVydFJ1bGVzOiBlbnZJbnQoYFFVT1RBX1RJRVJfJHtUfV9BTEVSVF9SVUxFU2AsIGQuYWxlcnRSdWxlcyksXG4gICAgYWxlcnREZXN0aW5hdGlvbnM6IGVudkludChgUVVPVEFfVElFUl8ke1R9X0FMRVJUX0RFU1RJTkFUSU9OU2AsIGQuYWxlcnREZXN0aW5hdGlvbnMpLFxuICAgIGlkcENvbmZpZ3M6IGVudkludChgUVVPVEFfVElFUl8ke1R9X0lEUF9DT05GSUdTYCwgZC5pZHBDb25maWdzKSxcbiAgICBzZWF0czogZW52SW50KGBRVU9UQV9USUVSXyR7VH1fU0VBVFNgLCBkLnNlYXRzKSxcbiAgICBldmVudFJldGVudGlvbkRheXM6IGVudkludChgUVVPVEFfVElFUl8ke1R9X0VWRU5UX1JFVEVOVElPTl9EQVlTYCwgZC5ldmVudFJldGVudGlvbkRheXMpLFxuICAgIGRvcmFSZXRlbnRpb25EYXlzOiBlbnZJbnQoYFFVT1RBX1RJRVJfJHtUfV9ET1JBX1JFVEVOVElPTl9EQVlTYCwgZC5kb3JhUmV0ZW50aW9uRGF5cyksXG4gIH07XG59XG5cbi8vIExhYmVscyBhcmUgb3ZlcnJpZGFibGUgdmlhIGBRVU9UQV9USUVSXzxUSUVSPl9MQUJFTGAgKGRpc3BsYXkgbmFtZSBvbmx5KS5cbmV4cG9ydCBjb25zdCBRVU9UQV9USUVSUzogUmVjb3JkPFF1b3RhVGllciwgUXVvdGFUaWVyUHJlc2V0PiA9IHtcbiAgZGV2ZWxvcGVyOiB7IGxhYmVsOiBlbnZTdHIoJ1FVT1RBX1RJRVJfREVWRUxPUEVSX0xBQkVMJywgJ0RldmVsb3BlcicpLCBsaW1pdHM6IHRpZXJMaW1pdHMoJ2RldmVsb3BlcicpIH0sXG4gIHBybzogeyBsYWJlbDogZW52U3RyKCdRVU9UQV9USUVSX1BST19MQUJFTCcsICdQcm8nKSwgbGltaXRzOiB0aWVyTGltaXRzKCdwcm8nKSB9LFxuICB0ZWFtOiB7IGxhYmVsOiBlbnZTdHIoJ1FVT1RBX1RJRVJfVEVBTV9MQUJFTCcsICdUZWFtJyksIGxpbWl0czogdGllckxpbWl0cygndGVhbScpIH0sXG4gIGVudGVycHJpc2U6IHsgbGFiZWw6IGVudlN0cignUVVPVEFfVElFUl9FTlRFUlBSSVNFX0xBQkVMJywgJ0VudGVycHJpc2UnKSwgbGltaXRzOiB0aWVyTGltaXRzKCdlbnRlcnByaXNlJykgfSxcbiAgdW5saW1pdGVkOiB7IGxhYmVsOiBlbnZTdHIoJ1FVT1RBX1RJRVJfVU5MSU1JVEVEX0xBQkVMJywgJ1VubGltaXRlZCcpLCBsaW1pdHM6IHRpZXJMaW1pdHMoJ3VubGltaXRlZCcpIH0sXG59O1xuXG4vKiogQWxsIHZhbGlkIHRpZXIgbmFtZXMgKGluY2wuIGB1bmxpbWl0ZWRgKS4gVXNlZCBmb3IgdmFsaWRhdGlvbiBvZiBhICpzdG9yZWQqXG4gKiAgdGllciDigJQgYHVubGltaXRlZGAgaXMgYSB2YWxpZCB2YWx1ZSBldmVuIHRob3VnaCBpdCdzIG5ldmVyIG9mZmVyZWQgZm9yIHNhbGUuICovXG5leHBvcnQgY29uc3QgVkFMSURfVElFUlM6IHJlYWRvbmx5IFF1b3RhVGllcltdID0gT2JqZWN0LmtleXMoUVVPVEFfVElFUlMpIGFzIFF1b3RhVGllcltdO1xuXG4vKiogVGhlIHN0YW5kYXJkLCBiaWxsaW5nLXNlbGVjdGFibGUgdGllcnMg4oCUIHRoZSBvbmVzIG9mZmVyZWQgYXMgcHVyY2hhc2FibGUgcGxhbnNcbiAqICBhbmQgc2hvd24gaW4gdGllciBwaWNrZXJzLiBEZXJpdmVkIGFzIFwiZXZlcnkgdmFsaWQgdGllciBleGNlcHQgYHVubGltaXRlZGBcIixcbiAqICB3aGljaCBpcyB0aGUgYmlsbGluZy1ESVNBQkxFRCBkZWZhdWx0IGFuZCBpcyBuZXZlciBkaXNwbGF5ZWQvc2VsZWN0YWJsZSB3aGVuXG4gKiAgYmlsbGluZyBpcyBlbmFibGVkLiBLZXB0IERSWSBvZmYgYFZBTElEX1RJRVJTYCBzbyBhZGRpbmcgYSB0aWVyIGZsb3dzIHRocm91Z2guICovXG5leHBvcnQgY29uc3QgU1RBTkRBUkRfVElFUlM6IHJlYWRvbmx5IFF1b3RhVGllcltdID0gVkFMSURfVElFUlMuZmlsdGVyKCh0KSA9PiB0ICE9PSAndW5saW1pdGVkJyk7XG5cbi8qKiBUaWVycyB0aGF0IHBlcm1pdCB0aGUgb3JnIOKGkiB0ZWFtIGhpZXJhcmNoeSAobmVzdGluZyB0ZWFtcyB1bmRlciBhIHJvb3QpLiBUaGVcbiAqICBiaWxsaW5nLW9mZiBgdW5saW1pdGVkYCB0aWVyIGlzIHRlYW0tY2FwYWJsZSAoaXQncyB0aGUgbW9zdCBwZXJtaXNzaXZlKTsgdGhlXG4gKiAgZW50cnktbGV2ZWwgYGRldmVsb3BlcmAvYHByb2AgdGllcnMgYXJlIG5vdC4gU2luZ2xlIHNvdXJjZSBmb3IgdGhlIHBhcmVudC1cbiAqICBlbGlnaWJpbGl0eSArIGRvd25ncmFkZS1vdmVyY2FwIGNoZWNrcyBzbyB0aGV5IGNhbid0IGRyaWZ0LiAqL1xuZXhwb3J0IGNvbnN0IFRFQU1fQ0FQQUJMRV9USUVSUzogcmVhZG9ubHkgUXVvdGFUaWVyW10gPSBbJ3RlYW0nLCAnZW50ZXJwcmlzZScsICd1bmxpbWl0ZWQnXTtcbmV4cG9ydCBmdW5jdGlvbiB0aWVyQWxsb3dzVGVhbXModGllcjogc3RyaW5nIHwgdW5kZWZpbmVkIHwgbnVsbCk6IGJvb2xlYW4ge1xuICByZXR1cm4gISF0aWVyICYmIChURUFNX0NBUEFCTEVfVElFUlMgYXMgcmVhZG9ubHkgc3RyaW5nW10pLmluY2x1ZGVzKHRpZXIpO1xufVxuXG4vKiogV2hldGhlciB0aGUgYmlsbGluZyBzZXJ2aWNlIGlzIGVuYWJsZWQgaW4gdGhpcyBkZXBsb3ltZW50IChgQklMTElOR19FTkFCTEVEYCxcbiAqICBvcHQtb3V0IGRlZmF1bHQtb24g4oCUIG1pcnJvcnMgdGhlIGJpbGxpbmcvcGxhdGZvcm0gY29uZmlncykuIFdpdGggYmlsbGluZyBPRkZcbiAqICB0aGVyZSdzIG5vIG1ldGVyaW5nL2VuZm9yY2VtZW50IHN1cmZhY2UsIHNvIHRoZSBkZWZhdWx0IHRpZXIgaXMgYHVubGltaXRlZGAuICovXG5leHBvcnQgZnVuY3Rpb24gaXNCaWxsaW5nRW5hYmxlZCgpOiBib29sZWFuIHtcbiAgcmV0dXJuIChwcm9jZXNzLmVudi5CSUxMSU5HX0VOQUJMRUQgfHwgJ3RydWUnKS50b0xvd2VyQ2FzZSgpICE9PSAnZmFsc2UnO1xufVxuXG4vKipcbiAqIERlZmF1bHQgdGllciBhc3NpZ25lZCB0byBuZXcgb3JnYW5pemF0aW9ucy5cbiAqIC0gQmlsbGluZyBESVNBQkxFRCDihpIgYHVubGltaXRlZGAgKG5vdGhpbmcgaXMgbWV0ZXJlZCwgc28gZXZlcnl0aGluZyBpcyB1bmNhcHBlZCkuXG4gKiAtIEJpbGxpbmcgRU5BQkxFRCAg4oaSIGBERUZBVUxUX1FVT1RBX1RJRVJgIGlmIGl0J3MgYSB2YWxpZCBzdGFuZGFyZCB0aWVyLCBlbHNlIGBkZXZlbG9wZXJgLlxuICovXG5leHBvcnQgY29uc3QgREVGQVVMVF9USUVSOiBRdW90YVRpZXIgPSAhaXNCaWxsaW5nRW5hYmxlZCgpXG4gID8gJ3VubGltaXRlZCdcbiAgOiAoaXNWYWxpZFRpZXIocHJvY2Vzcy5lbnYuREVGQVVMVF9RVU9UQV9USUVSID8/ICcnKSAmJiBwcm9jZXNzLmVudi5ERUZBVUxUX1FVT1RBX1RJRVIgIT09ICd1bmxpbWl0ZWQnXG4gICAgPyAocHJvY2Vzcy5lbnYuREVGQVVMVF9RVU9UQV9USUVSIGFzIFF1b3RhVGllcilcbiAgICA6ICdkZXZlbG9wZXInKTtcblxuLyoqIENoZWNrIHdoZXRoZXIgYSBzdHJpbmcgaXMgYSB2YWxpZCBRdW90YVRpZXIuICovXG5leHBvcnQgZnVuY3Rpb24gaXNWYWxpZFRpZXIodmFsdWU6IHN0cmluZyk6IHZhbHVlIGlzIFF1b3RhVGllciB7XG4gIC8vIFVzZSB0aGUgZXhwbGljaXQgdHVwbGUsIG5vdCBgdmFsdWUgaW4gUVVPVEFfVElFUlNgOiBgaW5gIHdhbGtzIHRoZSBwcm90b3R5cGVcbiAgLy8gY2hhaW4sIHNvIGluaGVyaXRlZCBPYmplY3Qga2V5cyAoJ3RvU3RyaW5nJywgJ2NvbnN0cnVjdG9yJywg4oCmKSB3b3VsZCByZXR1cm5cbiAgLy8gdHJ1ZSBhbmQgdGhlbiBgZ2V0VGllckxpbWl0c2Agd291bGQgcmVhZCBgLmxpbWl0c2Agb2ZmIGEgZnVuY3Rpb24g4oaSIGNyYXNoLlxuICAvLyBgdGllcmAgaXMgYXR0YWNrZXItaW5mbHVlbmNlYWJsZSAoSldUIC8gcmVxdWVzdCBib2R5IC8gREVGQVVMVF9RVU9UQV9USUVSIGVudikuXG4gIHJldHVybiAoVkFMSURfVElFUlMgYXMgcmVhZG9ubHkgc3RyaW5nW10pLmluY2x1ZGVzKHZhbHVlKTtcbn1cblxuLyoqIEdldCB0aGUgZGVmYXVsdCBsaW1pdHMgZm9yIGEgZ2l2ZW4gdGllciAoZmFsbHMgYmFjayB0byBkZXZlbG9wZXIpLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFRpZXJMaW1pdHModGllcjogc3RyaW5nKTogUXVvdGFUaWVyTGltaXRzIHtcbiAgcmV0dXJuIGlzVmFsaWRUaWVyKHRpZXIpID8gUVVPVEFfVElFUlNbdGllcl0ubGltaXRzOiBRVU9UQV9USUVSUy5kZXZlbG9wZXIubGltaXRzO1xufVxuIl19
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The single sharing model for every catalog entity: pipelines, plugins,
3
+ * pipeline templates, and dashboards.
4
+ *
5
+ * Three rungs, widening left to right:
6
+ *
7
+ * - `private` — only the AUTHOR (`createdBy`) can see or edit it. A personal
8
+ * draft: somewhere to iterate before anyone else is affected.
9
+ * - `org` — everyone in the owning org can see it; the resource's `:write`
10
+ * permission edits it. This is the default working state for shared content.
11
+ * - `public` — shared beyond the org: a team also sees its parent org's public
12
+ * rows, and the system org's public rows are the shared catalog every org
13
+ * sees. Requires the resource's `:publish` permission.
14
+ *
15
+ * Enforcement lives in exactly two places, so no entity can drift:
16
+ * `AccessControlQueryBuilder` builds the read predicate, and
17
+ * `requireVisibilityWriteAccess` gates the writes.
18
+ */
19
+ export declare const VISIBILITIES: readonly ['private', 'org', 'public'];
20
+ export type Visibility = (typeof VISIBILITIES)[number];
@@ -0,0 +1,22 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * The single sharing model for every catalog entity: pipelines, plugins,
5
+ * pipeline templates, and dashboards.
6
+ *
7
+ * Three rungs, widening left to right:
8
+ *
9
+ * - `private` — only the AUTHOR (`createdBy`) can see or edit it. A personal
10
+ * draft: somewhere to iterate before anyone else is affected.
11
+ * - `org` — everyone in the owning org can see it; the resource's `:write`
12
+ * permission edits it. This is the default working state for shared content.
13
+ * - `public` — shared beyond the org: a team also sees its parent org's public
14
+ * rows, and the system org's public rows are the shared catalog every org
15
+ * sees. Requires the resource's `:publish` permission.
16
+ *
17
+ * Enforcement lives in exactly two places, so no entity can drift:
18
+ * `AccessControlQueryBuilder` builds the read predicate, and
19
+ * `requireVisibilityWriteAccess` gates the writes.
20
+ */
21
+ export const VISIBILITIES = ['private', 'org', 'public'];
22
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlzaWJpbGl0eS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90eXBlcy92aXNpYmlsaXR5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLENBQUMsU0FBUyxFQUFFLEtBQUssRUFBRSxRQUFRLENBQVUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRoZSBzaW5nbGUgc2hhcmluZyBtb2RlbCBmb3IgZXZlcnkgY2F0YWxvZyBlbnRpdHk6IHBpcGVsaW5lcywgcGx1Z2lucyxcbiAqIHBpcGVsaW5lIHRlbXBsYXRlcywgYW5kIGRhc2hib2FyZHMuXG4gKlxuICogVGhyZWUgcnVuZ3MsIHdpZGVuaW5nIGxlZnQgdG8gcmlnaHQ6XG4gKlxuICogLSBgcHJpdmF0ZWAg4oCUIG9ubHkgdGhlIEFVVEhPUiAoYGNyZWF0ZWRCeWApIGNhbiBzZWUgb3IgZWRpdCBpdC4gQSBwZXJzb25hbFxuICogICBkcmFmdDogc29tZXdoZXJlIHRvIGl0ZXJhdGUgYmVmb3JlIGFueW9uZSBlbHNlIGlzIGFmZmVjdGVkLlxuICogLSBgb3JnYCAgICAg4oCUIGV2ZXJ5b25lIGluIHRoZSBvd25pbmcgb3JnIGNhbiBzZWUgaXQ7IHRoZSByZXNvdXJjZSdzIGA6d3JpdGVgXG4gKiAgIHBlcm1pc3Npb24gZWRpdHMgaXQuIFRoaXMgaXMgdGhlIGRlZmF1bHQgd29ya2luZyBzdGF0ZSBmb3Igc2hhcmVkIGNvbnRlbnQuXG4gKiAtIGBwdWJsaWNgICDigJQgc2hhcmVkIGJleW9uZCB0aGUgb3JnOiBhIHRlYW0gYWxzbyBzZWVzIGl0cyBwYXJlbnQgb3JnJ3MgcHVibGljXG4gKiAgIHJvd3MsIGFuZCB0aGUgc3lzdGVtIG9yZydzIHB1YmxpYyByb3dzIGFyZSB0aGUgc2hhcmVkIGNhdGFsb2cgZXZlcnkgb3JnXG4gKiAgIHNlZXMuIFJlcXVpcmVzIHRoZSByZXNvdXJjZSdzIGA6cHVibGlzaGAgcGVybWlzc2lvbi5cbiAqXG4gKiBFbmZvcmNlbWVudCBsaXZlcyBpbiBleGFjdGx5IHR3byBwbGFjZXMsIHNvIG5vIGVudGl0eSBjYW4gZHJpZnQ6XG4gKiBgQWNjZXNzQ29udHJvbFF1ZXJ5QnVpbGRlcmAgYnVpbGRzIHRoZSByZWFkIHByZWRpY2F0ZSwgYW5kXG4gKiBgcmVxdWlyZVZpc2liaWxpdHlXcml0ZUFjY2Vzc2AgZ2F0ZXMgdGhlIHdyaXRlcy5cbiAqL1xuZXhwb3J0IGNvbnN0IFZJU0lCSUxJVElFUyA9IFsncHJpdmF0ZScsICdvcmcnLCAncHVibGljJ10gYXMgY29uc3Q7XG5leHBvcnQgdHlwZSBWaXNpYmlsaXR5ID0gKHR5cGVvZiBWSVNJQklMSVRJRVMpW251bWJlcl07XG4iXX0=
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Clear the memoized alias set so the next access re-reads `SUPPORT_ALIASES`.
3
+ * The cache is process-lifetime by design (env is fixed per process); this hook
4
+ * exists so tests can vary the env between cases.
5
+ */
6
+ export declare function resetSupportAliasesCache(): void;
7
+ /** Well-known fallback support alias used when SUPPORT_ALIASES is unconfigured. */
8
+ export declare const DEFAULT_SUPPORT_ALIAS = "support@pipeline-builder";
9
+ /**
10
+ * The PRIMARY (first-configured) support alias — for display / prefill in UIs
11
+ * (e.g. the compose "To" field), so the shown value is driven by SUPPORT_ALIASES
12
+ * rather than hardcoded. Falls back to {@link DEFAULT_SUPPORT_ALIAS} when the env
13
+ * is unset. (All configured aliases still resolve via {@link resolveRecipientAlias}.)
14
+ */
15
+ export declare function getPrimarySupportAlias(): string;
16
+ /**
17
+ * ALL configured support aliases (order preserved), for UIs that surface every
18
+ * support inbox as a distinct recipient suggestion. Falls back to a single
19
+ * {@link DEFAULT_SUPPORT_ALIAS} when SUPPORT_ALIASES is unset.
20
+ */
21
+ export declare function getAllSupportAliases(): string[];
22
+ /** Result of alias resolution. */
23
+ export interface AliasResolution {
24
+ /** The resolved organization ID (e.g., 'system'). */
25
+ resolvedOrgId: string;
26
+ /** Whether the input was an alias that got resolved. */
27
+ wasAlias: boolean;
28
+ /** The original input value, useful for audit logging. */
29
+ originalValue: string;
30
+ }
31
+ /**
32
+ * Resolve an email-like alias to an actual organization ID.
33
+ *
34
+ * If the input matches a configured support alias, it resolves to the system
35
+ * org ID. Otherwise the input is returned as-is (lowercased).
36
+ */
37
+ export declare function resolveRecipientAlias(recipientOrgId: string): AliasResolution;
@@ -0,0 +1,68 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { SYSTEM_ORG_ID } from '../middleware/auth.js';
4
+ /** Lazily-cached set of support aliases, parsed from SUPPORT_ALIASES on first access. */
5
+ let _supportAliases;
6
+ function getSupportAliases() {
7
+ if (!_supportAliases) {
8
+ const raw = process.env.SUPPORT_ALIASES || '';
9
+ _supportAliases = new Set(raw
10
+ .split(',')
11
+ .map(alias => alias.trim().toLowerCase())
12
+ .filter(alias => alias.length > 0));
13
+ }
14
+ return _supportAliases;
15
+ }
16
+ /**
17
+ * Clear the memoized alias set so the next access re-reads `SUPPORT_ALIASES`.
18
+ * The cache is process-lifetime by design (env is fixed per process); this hook
19
+ * exists so tests can vary the env between cases.
20
+ */
21
+ export function resetSupportAliasesCache() {
22
+ _supportAliases = undefined;
23
+ }
24
+ /** Well-known fallback support alias used when SUPPORT_ALIASES is unconfigured. */
25
+ export const DEFAULT_SUPPORT_ALIAS = 'support@pipeline-builder';
26
+ /**
27
+ * The PRIMARY (first-configured) support alias — for display / prefill in UIs
28
+ * (e.g. the compose "To" field), so the shown value is driven by SUPPORT_ALIASES
29
+ * rather than hardcoded. Falls back to {@link DEFAULT_SUPPORT_ALIAS} when the env
30
+ * is unset. (All configured aliases still resolve via {@link resolveRecipientAlias}.)
31
+ */
32
+ export function getPrimarySupportAlias() {
33
+ for (const alias of getSupportAliases())
34
+ return alias;
35
+ return DEFAULT_SUPPORT_ALIAS;
36
+ }
37
+ /**
38
+ * ALL configured support aliases (order preserved), for UIs that surface every
39
+ * support inbox as a distinct recipient suggestion. Falls back to a single
40
+ * {@link DEFAULT_SUPPORT_ALIAS} when SUPPORT_ALIASES is unset.
41
+ */
42
+ export function getAllSupportAliases() {
43
+ const all = [...getSupportAliases()];
44
+ return all.length > 0 ? all : [DEFAULT_SUPPORT_ALIAS];
45
+ }
46
+ /**
47
+ * Resolve an email-like alias to an actual organization ID.
48
+ *
49
+ * If the input matches a configured support alias, it resolves to the system
50
+ * org ID. Otherwise the input is returned as-is (lowercased).
51
+ */
52
+ export function resolveRecipientAlias(recipientOrgId) {
53
+ const normalized = recipientOrgId.trim().toLowerCase();
54
+ const aliases = getSupportAliases();
55
+ if (aliases.has(normalized)) {
56
+ return {
57
+ resolvedOrgId: SYSTEM_ORG_ID,
58
+ wasAlias: true,
59
+ originalValue: recipientOrgId,
60
+ };
61
+ }
62
+ return {
63
+ resolvedOrgId: normalized,
64
+ wasAlias: false,
65
+ originalValue: recipientOrgId,
66
+ };
67
+ }
68
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxpYXMtcmVzb2x2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvYWxpYXMtcmVzb2x2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFdEQseUZBQXlGO0FBQ3pGLElBQUksZUFBd0MsQ0FBQztBQUU3QyxTQUFTLGlCQUFpQjtJQUN4QixJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7UUFDckIsTUFBTSxHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLElBQUksRUFBRSxDQUFDO1FBQzlDLGVBQWUsR0FBRyxJQUFJLEdBQUcsQ0FDdkIsR0FBRzthQUNBLEtBQUssQ0FBQyxHQUFHLENBQUM7YUFDVixHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7YUFDeEMsTUFBTSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FDckMsQ0FBQztJQUNKLENBQUM7SUFDRCxPQUFPLGVBQWUsQ0FBQztBQUN6QixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSx3QkFBd0I7SUFDdEMsZUFBZSxHQUFHLFNBQVMsQ0FBQztBQUM5QixDQUFDO0FBRUQsbUZBQW1GO0FBQ25GLE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLDBCQUEwQixDQUFDO0FBRWhFOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLHNCQUFzQjtJQUNwQyxLQUFLLE1BQU0sS0FBSyxJQUFJLGlCQUFpQixFQUFFO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFDdEQsT0FBTyxxQkFBcUIsQ0FBQztBQUMvQixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxvQkFBb0I7SUFDbEMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxHQUFHLGlCQUFpQixFQUFFLENBQUMsQ0FBQztJQUNyQyxPQUFPLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMscUJBQXFCLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBWUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQUMsY0FBc0I7SUFDMUQsTUFBTSxVQUFVLEdBQUcsY0FBYyxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sT0FBTyxHQUFHLGlCQUFpQixFQUFFLENBQUM7SUFFcEMsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDNUIsT0FBTztZQUNMLGFBQWEsRUFBRSxhQUFhO1lBQzVCLFFBQVEsRUFBRSxJQUFJO1lBQ2QsYUFBYSxFQUFFLGNBQWM7U0FDOUIsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPO1FBQ0wsYUFBYSxFQUFFLFVBQVU7UUFDekIsUUFBUSxFQUFFLEtBQUs7UUFDZixhQUFhLEVBQUUsY0FBYztLQUM5QixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBTWVNURU1fT1JHX0lEIH0gZnJvbSAnLi4vbWlkZGxld2FyZS9hdXRoLmpzJztcblxuLyoqIExhemlseS1jYWNoZWQgc2V0IG9mIHN1cHBvcnQgYWxpYXNlcywgcGFyc2VkIGZyb20gU1VQUE9SVF9BTElBU0VTIG9uIGZpcnN0IGFjY2Vzcy4gKi9cbmxldCBfc3VwcG9ydEFsaWFzZXM6IFNldDxzdHJpbmc+IHwgdW5kZWZpbmVkO1xuXG5mdW5jdGlvbiBnZXRTdXBwb3J0QWxpYXNlcygpOiBTZXQ8c3RyaW5nPiB7XG4gIGlmICghX3N1cHBvcnRBbGlhc2VzKSB7XG4gICAgY29uc3QgcmF3ID0gcHJvY2Vzcy5lbnYuU1VQUE9SVF9BTElBU0VTIHx8ICcnO1xuICAgIF9zdXBwb3J0QWxpYXNlcyA9IG5ldyBTZXQoXG4gICAgICByYXdcbiAgICAgICAgLnNwbGl0KCcsJylcbiAgICAgICAgLm1hcChhbGlhcyA9PiBhbGlhcy50cmltKCkudG9Mb3dlckNhc2UoKSlcbiAgICAgICAgLmZpbHRlcihhbGlhcyA9PiBhbGlhcy5sZW5ndGggPiAwKSxcbiAgICApO1xuICB9XG4gIHJldHVybiBfc3VwcG9ydEFsaWFzZXM7XG59XG5cbi8qKlxuICogQ2xlYXIgdGhlIG1lbW9pemVkIGFsaWFzIHNldCBzbyB0aGUgbmV4dCBhY2Nlc3MgcmUtcmVhZHMgYFNVUFBPUlRfQUxJQVNFU2AuXG4gKiBUaGUgY2FjaGUgaXMgcHJvY2Vzcy1saWZldGltZSBieSBkZXNpZ24gKGVudiBpcyBmaXhlZCBwZXIgcHJvY2Vzcyk7IHRoaXMgaG9va1xuICogZXhpc3RzIHNvIHRlc3RzIGNhbiB2YXJ5IHRoZSBlbnYgYmV0d2VlbiBjYXNlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc2V0U3VwcG9ydEFsaWFzZXNDYWNoZSgpOiB2b2lkIHtcbiAgX3N1cHBvcnRBbGlhc2VzID0gdW5kZWZpbmVkO1xufVxuXG4vKiogV2VsbC1rbm93biBmYWxsYmFjayBzdXBwb3J0IGFsaWFzIHVzZWQgd2hlbiBTVVBQT1JUX0FMSUFTRVMgaXMgdW5jb25maWd1cmVkLiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfU1VQUE9SVF9BTElBUyA9ICdzdXBwb3J0QHBpcGVsaW5lLWJ1aWxkZXInO1xuXG4vKipcbiAqIFRoZSBQUklNQVJZIChmaXJzdC1jb25maWd1cmVkKSBzdXBwb3J0IGFsaWFzIOKAlCBmb3IgZGlzcGxheSAvIHByZWZpbGwgaW4gVUlzXG4gKiAoZS5nLiB0aGUgY29tcG9zZSBcIlRvXCIgZmllbGQpLCBzbyB0aGUgc2hvd24gdmFsdWUgaXMgZHJpdmVuIGJ5IFNVUFBPUlRfQUxJQVNFU1xuICogcmF0aGVyIHRoYW4gaGFyZGNvZGVkLiBGYWxscyBiYWNrIHRvIHtAbGluayBERUZBVUxUX1NVUFBPUlRfQUxJQVN9IHdoZW4gdGhlIGVudlxuICogaXMgdW5zZXQuIChBbGwgY29uZmlndXJlZCBhbGlhc2VzIHN0aWxsIHJlc29sdmUgdmlhIHtAbGluayByZXNvbHZlUmVjaXBpZW50QWxpYXN9LilcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldFByaW1hcnlTdXBwb3J0QWxpYXMoKTogc3RyaW5nIHtcbiAgZm9yIChjb25zdCBhbGlhcyBvZiBnZXRTdXBwb3J0QWxpYXNlcygpKSByZXR1cm4gYWxpYXM7XG4gIHJldHVybiBERUZBVUxUX1NVUFBPUlRfQUxJQVM7XG59XG5cbi8qKlxuICogQUxMIGNvbmZpZ3VyZWQgc3VwcG9ydCBhbGlhc2VzIChvcmRlciBwcmVzZXJ2ZWQpLCBmb3IgVUlzIHRoYXQgc3VyZmFjZSBldmVyeVxuICogc3VwcG9ydCBpbmJveCBhcyBhIGRpc3RpbmN0IHJlY2lwaWVudCBzdWdnZXN0aW9uLiBGYWxscyBiYWNrIHRvIGEgc2luZ2xlXG4gKiB7QGxpbmsgREVGQVVMVF9TVVBQT1JUX0FMSUFTfSB3aGVuIFNVUFBPUlRfQUxJQVNFUyBpcyB1bnNldC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEFsbFN1cHBvcnRBbGlhc2VzKCk6IHN0cmluZ1tdIHtcbiAgY29uc3QgYWxsID0gWy4uLmdldFN1cHBvcnRBbGlhc2VzKCldO1xuICByZXR1cm4gYWxsLmxlbmd0aCA+IDAgPyBhbGwgOiBbREVGQVVMVF9TVVBQT1JUX0FMSUFTXTtcbn1cblxuLyoqIFJlc3VsdCBvZiBhbGlhcyByZXNvbHV0aW9uLiAqL1xuZXhwb3J0IGludGVyZmFjZSBBbGlhc1Jlc29sdXRpb24ge1xuICAvKiogVGhlIHJlc29sdmVkIG9yZ2FuaXphdGlvbiBJRCAoZS5nLiwgJ3N5c3RlbScpLiAqL1xuICByZXNvbHZlZE9yZ0lkOiBzdHJpbmc7XG4gIC8qKiBXaGV0aGVyIHRoZSBpbnB1dCB3YXMgYW4gYWxpYXMgdGhhdCBnb3QgcmVzb2x2ZWQuICovXG4gIHdhc0FsaWFzOiBib29sZWFuO1xuICAvKiogVGhlIG9yaWdpbmFsIGlucHV0IHZhbHVlLCB1c2VmdWwgZm9yIGF1ZGl0IGxvZ2dpbmcuICovXG4gIG9yaWdpbmFsVmFsdWU6IHN0cmluZztcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGFuIGVtYWlsLWxpa2UgYWxpYXMgdG8gYW4gYWN0dWFsIG9yZ2FuaXphdGlvbiBJRC5cbiAqXG4gKiBJZiB0aGUgaW5wdXQgbWF0Y2hlcyBhIGNvbmZpZ3VyZWQgc3VwcG9ydCBhbGlhcywgaXQgcmVzb2x2ZXMgdG8gdGhlIHN5c3RlbVxuICogb3JnIElELiBPdGhlcndpc2UgdGhlIGlucHV0IGlzIHJldHVybmVkIGFzLWlzIChsb3dlcmNhc2VkKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVSZWNpcGllbnRBbGlhcyhyZWNpcGllbnRPcmdJZDogc3RyaW5nKTogQWxpYXNSZXNvbHV0aW9uIHtcbiAgY29uc3Qgbm9ybWFsaXplZCA9IHJlY2lwaWVudE9yZ0lkLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuICBjb25zdCBhbGlhc2VzID0gZ2V0U3VwcG9ydEFsaWFzZXMoKTtcblxuICBpZiAoYWxpYXNlcy5oYXMobm9ybWFsaXplZCkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgcmVzb2x2ZWRPcmdJZDogU1lTVEVNX09SR19JRCxcbiAgICAgIHdhc0FsaWFzOiB0cnVlLFxuICAgICAgb3JpZ2luYWxWYWx1ZTogcmVjaXBpZW50T3JnSWQsXG4gICAgfTtcbiAgfVxuXG4gIHJldHVybiB7XG4gICAgcmVzb2x2ZWRPcmdJZDogbm9ybWFsaXplZCxcbiAgICB3YXNBbGlhczogZmFsc2UsXG4gICAgb3JpZ2luYWxWYWx1ZTogcmVjaXBpZW50T3JnSWQsXG4gIH07XG59XG5cbiJdfQ==
@@ -0,0 +1,15 @@
1
+ import type winston from 'winston';
2
+ import type { AuditEvent } from '../types/audit-events.js';
3
+ /**
4
+ * Emit a cross-service audit event as a structured log line.
5
+ *
6
+ * Best-effort: if the underlying logger transport fails, the caller's
7
+ * mutation still succeeds — we never let audit-write failure roll back
8
+ * the user-visible operation. The line is tagged `eventCategory: 'audit'`
9
+ * so downstream log aggregators (Loki, CloudWatch) can route audit
10
+ * events into a separate index.
11
+ *
12
+ * For platform's own audit events (user.login, org.create, …), use
13
+ * platform's `helpers/audit.ts` instead — those persist to MongoDB.
14
+ */
15
+ export declare function emitAudit(logger: winston.Logger, audit: AuditEvent): void;
@@ -0,0 +1,39 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { scrubAwsIdentifiers } from './aws-scrub.js';
4
+ /**
5
+ * Emit a cross-service audit event as a structured log line.
6
+ *
7
+ * Best-effort: if the underlying logger transport fails, the caller's
8
+ * mutation still succeeds — we never let audit-write failure roll back
9
+ * the user-visible operation. The line is tagged `eventCategory: 'audit'`
10
+ * so downstream log aggregators (Loki, CloudWatch) can route audit
11
+ * events into a separate index.
12
+ *
13
+ * For platform's own audit events (user.login, org.create, …), use
14
+ * platform's `helpers/audit.ts` instead — those persist to MongoDB.
15
+ */
16
+ export function emitAudit(logger, audit) {
17
+ try {
18
+ // Scrub any AWS account-id-shaped values (e.g. an ARN in `details`) before the
19
+ // event lands in the durable log store — the logger's secret-key redaction
20
+ // doesn't cover account ids, and this is exactly the persistence boundary the
21
+ // "never persist an AWS account id" rule targets.
22
+ logger.info('audit', scrubAwsIdentifiers({ eventCategory: 'audit', ...audit }));
23
+ }
24
+ catch (err) {
25
+ // The winston logger normally never throws synchronously, but if a
26
+ // transport explodes during a route call, we don't want the mutation
27
+ // to fail because the audit write didn't land.
28
+ try {
29
+ logger.warn('Failed to emit audit event', {
30
+ event: audit.event,
31
+ error: err instanceof Error ? err.message : String(err),
32
+ });
33
+ }
34
+ catch {
35
+ // Logger itself is unrecoverable — give up silently.
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXVkaXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvYXVkaXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUd0QyxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUdyRDs7Ozs7Ozs7Ozs7R0FXRztBQUNILE1BQU0sVUFBVSxTQUFTLENBQ3ZCLE1BQXNCLEVBQ3RCLEtBQWlCO0lBRWpCLElBQUksQ0FBQztRQUNILCtFQUErRTtRQUMvRSwyRUFBMkU7UUFDM0UsOEVBQThFO1FBQzlFLGtEQUFrRDtRQUNsRCxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxtQkFBbUIsQ0FBQyxFQUFFLGFBQWEsRUFBRSxPQUFPLEVBQUUsR0FBRyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDbEYsQ0FBQztJQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7UUFDYixtRUFBbUU7UUFDbkUscUVBQXFFO1FBQ3JFLCtDQUErQztRQUMvQyxJQUFJLENBQUM7WUFDSCxNQUFNLENBQUMsSUFBSSxDQUFDLDRCQUE0QixFQUFFO2dCQUN4QyxLQUFLLEVBQUUsS0FBSyxDQUFDLEtBQUs7Z0JBQ2xCLEtBQUssRUFBRSxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDO2FBQ3hELENBQUMsQ0FBQztRQUNMLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCxxREFBcUQ7UUFDdkQsQ0FBQztJQUNILENBQUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHdpbnN0b24gZnJvbSAnd2luc3Rvbic7XG5pbXBvcnQgeyBzY3J1YkF3c0lkZW50aWZpZXJzIH0gZnJvbSAnLi9hd3Mtc2NydWIuanMnO1xuaW1wb3J0IHR5cGUgeyBBdWRpdEV2ZW50IH0gZnJvbSAnLi4vdHlwZXMvYXVkaXQtZXZlbnRzLmpzJztcblxuLyoqXG4gKiBFbWl0IGEgY3Jvc3Mtc2VydmljZSBhdWRpdCBldmVudCBhcyBhIHN0cnVjdHVyZWQgbG9nIGxpbmUuXG4gKlxuICogQmVzdC1lZmZvcnQ6IGlmIHRoZSB1bmRlcmx5aW5nIGxvZ2dlciB0cmFuc3BvcnQgZmFpbHMsIHRoZSBjYWxsZXInc1xuICogbXV0YXRpb24gc3RpbGwgc3VjY2VlZHMg4oCUIHdlIG5ldmVyIGxldCBhdWRpdC13cml0ZSBmYWlsdXJlIHJvbGwgYmFja1xuICogdGhlIHVzZXItdmlzaWJsZSBvcGVyYXRpb24uIFRoZSBsaW5lIGlzIHRhZ2dlZCBgZXZlbnRDYXRlZ29yeTogJ2F1ZGl0J2BcbiAqIHNvIGRvd25zdHJlYW0gbG9nIGFnZ3JlZ2F0b3JzIChMb2tpLCBDbG91ZFdhdGNoKSBjYW4gcm91dGUgYXVkaXRcbiAqIGV2ZW50cyBpbnRvIGEgc2VwYXJhdGUgaW5kZXguXG4gKlxuICogRm9yIHBsYXRmb3JtJ3Mgb3duIGF1ZGl0IGV2ZW50cyAodXNlci5sb2dpbiwgb3JnLmNyZWF0ZSwg4oCmKSwgdXNlXG4gKiBwbGF0Zm9ybSdzIGBoZWxwZXJzL2F1ZGl0LnRzYCBpbnN0ZWFkIOKAlCB0aG9zZSBwZXJzaXN0IHRvIE1vbmdvREIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBlbWl0QXVkaXQoXG4gIGxvZ2dlcjogd2luc3Rvbi5Mb2dnZXIsXG4gIGF1ZGl0OiBBdWRpdEV2ZW50LFxuKTogdm9pZCB7XG4gIHRyeSB7XG4gICAgLy8gU2NydWIgYW55IEFXUyBhY2NvdW50LWlkLXNoYXBlZCB2YWx1ZXMgKGUuZy4gYW4gQVJOIGluIGBkZXRhaWxzYCkgYmVmb3JlIHRoZVxuICAgIC8vIGV2ZW50IGxhbmRzIGluIHRoZSBkdXJhYmxlIGxvZyBzdG9yZSDigJQgdGhlIGxvZ2dlcidzIHNlY3JldC1rZXkgcmVkYWN0aW9uXG4gICAgLy8gZG9lc24ndCBjb3ZlciBhY2NvdW50IGlkcywgYW5kIHRoaXMgaXMgZXhhY3RseSB0aGUgcGVyc2lzdGVuY2UgYm91bmRhcnkgdGhlXG4gICAgLy8gXCJuZXZlciBwZXJzaXN0IGFuIEFXUyBhY2NvdW50IGlkXCIgcnVsZSB0YXJnZXRzLlxuICAgIGxvZ2dlci5pbmZvKCdhdWRpdCcsIHNjcnViQXdzSWRlbnRpZmllcnMoeyBldmVudENhdGVnb3J5OiAnYXVkaXQnLCAuLi5hdWRpdCB9KSk7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIC8vIFRoZSB3aW5zdG9uIGxvZ2dlciBub3JtYWxseSBuZXZlciB0aHJvd3Mgc3luY2hyb25vdXNseSwgYnV0IGlmIGFcbiAgICAvLyB0cmFuc3BvcnQgZXhwbG9kZXMgZHVyaW5nIGEgcm91dGUgY2FsbCwgd2UgZG9uJ3Qgd2FudCB0aGUgbXV0YXRpb25cbiAgICAvLyB0byBmYWlsIGJlY2F1c2UgdGhlIGF1ZGl0IHdyaXRlIGRpZG4ndCBsYW5kLlxuICAgIHRyeSB7XG4gICAgICBsb2dnZXIud2FybignRmFpbGVkIHRvIGVtaXQgYXVkaXQgZXZlbnQnLCB7XG4gICAgICAgIGV2ZW50OiBhdWRpdC5ldmVudCxcbiAgICAgICAgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSxcbiAgICAgIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gTG9nZ2VyIGl0c2VsZiBpcyB1bnJlY292ZXJhYmxlIOKAlCBnaXZlIHVwIHNpbGVudGx5LlxuICAgIH1cbiAgfVxufVxuIl19
@@ -0,0 +1,11 @@
1
+ /** Redact every AWS-account-id-shaped token (incl. the account field of any ARN) from a string. */
2
+ export declare function scrubAwsIdentifiersFromString(input: string): string;
3
+ /**
4
+ * Deep-scrub a value (string / array / plain object) of AWS account identifiers,
5
+ * returning a scrubbed copy — the input is never mutated. String values have
6
+ * account-id tokens redacted; any key matching {@link ACCOUNT_KEY_RE} with a
7
+ * string/number value is redacted wholesale. Non-string primitives pass through
8
+ * unchanged (a bare number is only redacted when it sits under an account-named
9
+ * key, to avoid clobbering legitimate numeric fields).
10
+ */
11
+ export declare function scrubAwsIdentifiers<T>(value: T): T;