@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,53 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { ErrorCode, getStatusForErrorCode } from '../types/error-codes.js';
4
+ /**
5
+ * Base application error with HTTP status code and error code.
6
+ * Extend this for domain-specific error types.
7
+ */
8
+ export class AppError extends Error {
9
+ statusCode;
10
+ code;
11
+ constructor(statusCode, code, message) {
12
+ super(message);
13
+ this.statusCode = statusCode;
14
+ this.code = code;
15
+ this.name = 'AppError';
16
+ }
17
+ }
18
+ /** 404 — Resource not found. */
19
+ export class NotFoundError extends AppError {
20
+ constructor(message) {
21
+ super(getStatusForErrorCode(ErrorCode.NOT_FOUND), ErrorCode.NOT_FOUND, message);
22
+ this.name = 'NotFoundError';
23
+ }
24
+ }
25
+ /** 403 — Insufficient permissions. */
26
+ export class ForbiddenError extends AppError {
27
+ constructor(message) {
28
+ super(getStatusForErrorCode(ErrorCode.INSUFFICIENT_PERMISSIONS), ErrorCode.INSUFFICIENT_PERMISSIONS, message);
29
+ this.name = 'ForbiddenError';
30
+ }
31
+ }
32
+ /** 400 — Validation / bad request. */
33
+ export class ValidationError extends AppError {
34
+ constructor(message) {
35
+ super(getStatusForErrorCode(ErrorCode.VALIDATION_ERROR), ErrorCode.VALIDATION_ERROR, message);
36
+ this.name = 'ValidationError';
37
+ }
38
+ }
39
+ /** 409 — Conflict / duplicate. */
40
+ export class ConflictError extends AppError {
41
+ constructor(message) {
42
+ super(getStatusForErrorCode(ErrorCode.CONFLICT), ErrorCode.CONFLICT, message);
43
+ this.name = 'ConflictError';
44
+ }
45
+ }
46
+ /** 401 — Unauthorized / authentication required. */
47
+ export class UnauthorizedError extends AppError {
48
+ constructor(message) {
49
+ super(getStatusForErrorCode(ErrorCode.UNAUTHORIZED), ErrorCode.UNAUTHORIZED, message);
50
+ this.name = 'UnauthorizedError';
51
+ }
52
+ }
53
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLWVycm9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9lcnJvcnMvYXBwLWVycm9ycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxTQUFTLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUUzRTs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sUUFBUyxTQUFRLEtBQUs7SUFFZixVQUFVO0lBQ1YsSUFBSTtJQUZ0QixZQUNrQixVQUFrQixFQUNsQixJQUFlLEVBQy9CLE9BQWU7UUFFZixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7MEJBSkMsVUFBVTtvQkFDVixJQUFJO1FBSXBCLElBQUksQ0FBQyxJQUFJLEdBQUcsVUFBVSxDQUFDO0lBQ3pCLENBQUM7Q0FDRjtBQUVELGdDQUFnQztBQUNoQyxNQUFNLE9BQU8sYUFBYyxTQUFRLFFBQVE7SUFDekMsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLEVBQUUsU0FBUyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNoRixJQUFJLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQztJQUM5QixDQUFDO0NBQ0Y7QUFFRCxzQ0FBc0M7QUFDdEMsTUFBTSxPQUFPLGNBQWUsU0FBUSxRQUFRO0lBQzFDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLHdCQUF3QixDQUFDLEVBQUUsU0FBUyxDQUFDLHdCQUF3QixFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzlHLElBQUksQ0FBQyxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBRUQsc0NBQXNDO0FBQ3RDLE1BQU0sT0FBTyxlQUFnQixTQUFRLFFBQVE7SUFDM0MsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxTQUFTLENBQUMsZ0JBQWdCLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUYsSUFBSSxDQUFDLElBQUksR0FBRyxpQkFBaUIsQ0FBQztJQUNoQyxDQUFDO0NBQ0Y7QUFFRCxrQ0FBa0M7QUFDbEMsTUFBTSxPQUFPLGFBQWMsU0FBUSxRQUFRO0lBQ3pDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDOUUsSUFBSSxDQUFDLElBQUksR0FBRyxlQUFlLENBQUM7SUFDOUIsQ0FBQztDQUNGO0FBRUQsb0RBQW9EO0FBQ3BELE1BQU0sT0FBTyxpQkFBa0IsU0FBUSxRQUFRO0lBQzdDLFlBQVksT0FBZTtRQUN6QixLQUFLLENBQUMscUJBQXFCLENBQUMsU0FBUyxDQUFDLFlBQVksQ0FBQyxFQUFFLFNBQVMsQ0FBQyxZQUFZLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDdEYsSUFBSSxDQUFDLElBQUksR0FBRyxtQkFBbUIsQ0FBQztJQUNsQyxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgRXJyb3JDb2RlLCBnZXRTdGF0dXNGb3JFcnJvckNvZGUgfSBmcm9tICcuLi90eXBlcy9lcnJvci1jb2Rlcy5qcyc7XG5cbi8qKlxuICogQmFzZSBhcHBsaWNhdGlvbiBlcnJvciB3aXRoIEhUVFAgc3RhdHVzIGNvZGUgYW5kIGVycm9yIGNvZGUuXG4gKiBFeHRlbmQgdGhpcyBmb3IgZG9tYWluLXNwZWNpZmljIGVycm9yIHR5cGVzLlxuICovXG5leHBvcnQgY2xhc3MgQXBwRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKFxuICAgIHB1YmxpYyByZWFkb25seSBzdGF0dXNDb2RlOiBudW1iZXIsXG4gICAgcHVibGljIHJlYWRvbmx5IGNvZGU6IEVycm9yQ29kZSxcbiAgICBtZXNzYWdlOiBzdHJpbmcsXG4gICkge1xuICAgIHN1cGVyKG1lc3NhZ2UpO1xuICAgIHRoaXMubmFtZSA9ICdBcHBFcnJvcic7XG4gIH1cbn1cblxuLyoqIDQwNCDigJQgUmVzb3VyY2Ugbm90IGZvdW5kLiAqL1xuZXhwb3J0IGNsYXNzIE5vdEZvdW5kRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuTk9UX0ZPVU5EKSwgRXJyb3JDb2RlLk5PVF9GT1VORCwgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ05vdEZvdW5kRXJyb3InO1xuICB9XG59XG5cbi8qKiA0MDMg4oCUIEluc3VmZmljaWVudCBwZXJtaXNzaW9ucy4gKi9cbmV4cG9ydCBjbGFzcyBGb3JiaWRkZW5FcnJvciBleHRlbmRzIEFwcEVycm9yIHtcbiAgY29uc3RydWN0b3IobWVzc2FnZTogc3RyaW5nKSB7XG4gICAgc3VwZXIoZ2V0U3RhdHVzRm9yRXJyb3JDb2RlKEVycm9yQ29kZS5JTlNVRkZJQ0lFTlRfUEVSTUlTU0lPTlMpLCBFcnJvckNvZGUuSU5TVUZGSUNJRU5UX1BFUk1JU1NJT05TLCBtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnRm9yYmlkZGVuRXJyb3InO1xuICB9XG59XG5cbi8qKiA0MDAg4oCUIFZhbGlkYXRpb24gLyBiYWQgcmVxdWVzdC4gKi9cbmV4cG9ydCBjbGFzcyBWYWxpZGF0aW9uRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuVkFMSURBVElPTl9FUlJPUiksIEVycm9yQ29kZS5WQUxJREFUSU9OX0VSUk9SLCBtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnVmFsaWRhdGlvbkVycm9yJztcbiAgfVxufVxuXG4vKiogNDA5IOKAlCBDb25mbGljdCAvIGR1cGxpY2F0ZS4gKi9cbmV4cG9ydCBjbGFzcyBDb25mbGljdEVycm9yIGV4dGVuZHMgQXBwRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBzdXBlcihnZXRTdGF0dXNGb3JFcnJvckNvZGUoRXJyb3JDb2RlLkNPTkZMSUNUKSwgRXJyb3JDb2RlLkNPTkZMSUNULCBtZXNzYWdlKTtcbiAgICB0aGlzLm5hbWUgPSAnQ29uZmxpY3RFcnJvcic7XG4gIH1cbn1cblxuLyoqIDQwMSDigJQgVW5hdXRob3JpemVkIC8gYXV0aGVudGljYXRpb24gcmVxdWlyZWQuICovXG5leHBvcnQgY2xhc3MgVW5hdXRob3JpemVkRXJyb3IgZXh0ZW5kcyBBcHBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKGdldFN0YXR1c0ZvckVycm9yQ29kZShFcnJvckNvZGUuVU5BVVRIT1JJWkVEKSwgRXJyb3JDb2RlLlVOQVVUSE9SSVpFRCwgbWVzc2FnZSk7XG4gICAgdGhpcy5uYW1lID0gJ1VuYXV0aG9yaXplZEVycm9yJztcbiAgfVxufVxuIl19
@@ -0,0 +1 @@
1
+ export * from './app-errors.js';
@@ -0,0 +1,4 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export * from './app-errors.js';
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZXJyb3JzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsY0FBYyxpQkFBaUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5leHBvcnQgKiBmcm9tICcuL2FwcC1lcnJvcnMuanMnO1xuIl19
@@ -0,0 +1,63 @@
1
+ import type { Request, Response } from 'express';
2
+ import type { Permission } from '../types/permissions.js';
3
+ import type { Visibility } from '../types/visibility.js';
4
+ /** The slice of a catalog entity the write gate needs. */
5
+ export interface VisibilityWriteTarget {
6
+ /** Rung the row currently sits at — see {@link Visibility}. */
7
+ visibility?: string;
8
+ /** Author. The only non-admin who may touch a `private` row. */
9
+ createdBy?: string;
10
+ }
11
+ /**
12
+ * Check whether the caller may modify a catalog entity, per its visibility rung.
13
+ * The single write gate behind every pipeline / plugin / template mutation.
14
+ *
15
+ * - `public` — a system admin, or the holder of `publishPermission` (the same
16
+ * permission that let them publish it in the first place, so whoever can
17
+ * publish can also manage what they published)
18
+ * - `org` — anyone who got this far: the resource's `:write` permission plus the
19
+ * org scoping the caller already passed IS the gate
20
+ * - `private` — the AUTHOR alone (plus system admins)
21
+ *
22
+ * The `private` branch is load-bearing on the TOMBSTONE paths (restore / purge),
23
+ * which are org-scoped rather than visibility-scoped because soft-delete is one
24
+ * shared code path — without it, any org member could resurrect someone else's
25
+ * personal draft. On the live paths the read predicate has already hidden the
26
+ * row, making this defense in depth.
27
+ *
28
+ * Returns `true` if the request may proceed; returns `false` having ALREADY sent
29
+ * a 403.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * if (!requireVisibilityWriteAccess(req, res, pipeline, userId, 'pipelines:publish')) return;
34
+ * await pipelineService.delete(id, orgId, userId);
35
+ * ```
36
+ */
37
+ export declare function requireVisibilityWriteAccess(req: Request, res: Response, resource: VisibilityWriteTarget, userId: string, publishPermission: Permission): boolean;
38
+ /**
39
+ * Resolve the effective visibility for an entity being created or updated.
40
+ *
41
+ * `public` requires the publish capability (superadmins pass via implicit-all);
42
+ * a caller who asks for it without the permission is clamped to `org` rather
43
+ * than dropped to a personal draft — they asked to SHARE it, and `org` is the
44
+ * widest rung they are entitled to. Anything else passes through.
45
+ *
46
+ * `fallback` is the rung an UNSPECIFIED visibility lands on, and it is
47
+ * deliberately per-entity even though the ladder itself is identical everywhere:
48
+ *
49
+ * - **templates** default to `private` — draft-first is the whole point of the
50
+ * personal rung; you polish a starter before publishing it.
51
+ * - **pipelines / plugins** default to `org` — they are team assets that deploy
52
+ * shared infrastructure, so creating one must not hide it from the team. A
53
+ * personal draft stays available, but opt-in.
54
+ *
55
+ * Same ladder, same gate, different centre of gravity — expressed as an argument
56
+ * rather than a second code path.
57
+ *
58
+ * Permission-based (not coarse-role-based): a bespoke custom Role can be granted
59
+ * publish rights and is no longer forced private by its `member` label. Built-in
60
+ * Admin/Owner bundles carry the publish permissions; the built-in Member bundle
61
+ * does not, so members top out at `org`.
62
+ */
63
+ export declare function resolveVisibility(req: Request, requested: string | undefined, publishPermission: Permission, fallback?: Visibility): Visibility;
@@ -0,0 +1,82 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { isSystemAdmin, userHasPermission } from '../middleware/auth.js';
4
+ import { ErrorCode } from '../types/error-codes.js';
5
+ import { sendError } from '../utils/response.js';
6
+ /**
7
+ * Check whether the caller may modify a catalog entity, per its visibility rung.
8
+ * The single write gate behind every pipeline / plugin / template mutation.
9
+ *
10
+ * - `public` — a system admin, or the holder of `publishPermission` (the same
11
+ * permission that let them publish it in the first place, so whoever can
12
+ * publish can also manage what they published)
13
+ * - `org` — anyone who got this far: the resource's `:write` permission plus the
14
+ * org scoping the caller already passed IS the gate
15
+ * - `private` — the AUTHOR alone (plus system admins)
16
+ *
17
+ * The `private` branch is load-bearing on the TOMBSTONE paths (restore / purge),
18
+ * which are org-scoped rather than visibility-scoped because soft-delete is one
19
+ * shared code path — without it, any org member could resurrect someone else's
20
+ * personal draft. On the live paths the read predicate has already hidden the
21
+ * row, making this defense in depth.
22
+ *
23
+ * Returns `true` if the request may proceed; returns `false` having ALREADY sent
24
+ * a 403.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * if (!requireVisibilityWriteAccess(req, res, pipeline, userId, 'pipelines:publish')) return;
29
+ * await pipelineService.delete(id, orgId, userId);
30
+ * ```
31
+ */
32
+ export function requireVisibilityWriteAccess(req, res, resource, userId, publishPermission) {
33
+ if (isSystemAdmin(req))
34
+ return true;
35
+ if (resource.visibility === 'public' && !userHasPermission(req, publishPermission)) {
36
+ sendError(res, 403, 'You lack permission to modify this public resource.', ErrorCode.INSUFFICIENT_PERMISSIONS);
37
+ return false;
38
+ }
39
+ // Fail closed on an anonymous/absent caller — an empty userId must never match
40
+ // an empty `createdBy` and hand over someone else's draft.
41
+ if (resource.visibility === 'private' && (!userId || resource.createdBy !== userId)) {
42
+ sendError(res, 403, 'Only the author can modify a private resource.', ErrorCode.INSUFFICIENT_PERMISSIONS);
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+ /**
48
+ * Resolve the effective visibility for an entity being created or updated.
49
+ *
50
+ * `public` requires the publish capability (superadmins pass via implicit-all);
51
+ * a caller who asks for it without the permission is clamped to `org` rather
52
+ * than dropped to a personal draft — they asked to SHARE it, and `org` is the
53
+ * widest rung they are entitled to. Anything else passes through.
54
+ *
55
+ * `fallback` is the rung an UNSPECIFIED visibility lands on, and it is
56
+ * deliberately per-entity even though the ladder itself is identical everywhere:
57
+ *
58
+ * - **templates** default to `private` — draft-first is the whole point of the
59
+ * personal rung; you polish a starter before publishing it.
60
+ * - **pipelines / plugins** default to `org` — they are team assets that deploy
61
+ * shared infrastructure, so creating one must not hide it from the team. A
62
+ * personal draft stays available, but opt-in.
63
+ *
64
+ * Same ladder, same gate, different centre of gravity — expressed as an argument
65
+ * rather than a second code path.
66
+ *
67
+ * Permission-based (not coarse-role-based): a bespoke custom Role can be granted
68
+ * publish rights and is no longer forced private by its `member` label. Built-in
69
+ * Admin/Owner bundles carry the publish permissions; the built-in Member bundle
70
+ * does not, so members top out at `org`.
71
+ */
72
+ export function resolveVisibility(req, requested, publishPermission, fallback = 'private') {
73
+ if (requested === 'public') {
74
+ return userHasPermission(req, publishPermission) ? 'public' : 'org';
75
+ }
76
+ if (requested === 'org')
77
+ return 'org';
78
+ if (requested === 'private')
79
+ return 'private';
80
+ return fallback;
81
+ }
82
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWNjZXNzLWhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaGVscGVycy9hY2Nlc3MtaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBR3RDLE9BQU8sRUFBRSxhQUFhLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN6RSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFHcEQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBVWpEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBeUJHO0FBQ0gsTUFBTSxVQUFVLDRCQUE0QixDQUMxQyxHQUFZLEVBQ1osR0FBYSxFQUNiLFFBQStCLEVBQy9CLE1BQWMsRUFDZCxpQkFBNkI7SUFFN0IsSUFBSSxhQUFhLENBQUMsR0FBRyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFFcEMsSUFBSSxRQUFRLENBQUMsVUFBVSxLQUFLLFFBQVEsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxpQkFBaUIsQ0FBQyxFQUFFLENBQUM7UUFDbkYsU0FBUyxDQUNQLEdBQUcsRUFDSCxHQUFHLEVBQ0gscURBQXFELEVBQ3JELFNBQVMsQ0FBQyx3QkFBd0IsQ0FDbkMsQ0FBQztRQUNGLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVELCtFQUErRTtJQUMvRSwyREFBMkQ7SUFDM0QsSUFBSSxRQUFRLENBQUMsVUFBVSxLQUFLLFNBQVMsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFJLFFBQVEsQ0FBQyxTQUFTLEtBQUssTUFBTSxDQUFDLEVBQUUsQ0FBQztRQUNwRixTQUFTLENBQ1AsR0FBRyxFQUNILEdBQUcsRUFDSCxnREFBZ0QsRUFDaEQsU0FBUyxDQUFDLHdCQUF3QixDQUNuQyxDQUFDO1FBQ0YsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FDL0IsR0FBWSxFQUNaLFNBQTZCLEVBQzdCLGlCQUE2QixFQUM3QixRQUFRLEdBQWUsU0FBUztJQUVoQyxJQUFJLFNBQVMsS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUMzQixPQUFPLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQztJQUN0RSxDQUFDO0lBQ0QsSUFBSSxTQUFTLEtBQUssS0FBSztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3RDLElBQUksU0FBUyxLQUFLLFNBQVM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUM5QyxPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB0eXBlIHsgUmVxdWVzdCwgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB7IGlzU3lzdGVtQWRtaW4sIHVzZXJIYXNQZXJtaXNzaW9uIH0gZnJvbSAnLi4vbWlkZGxld2FyZS9hdXRoLmpzJztcbmltcG9ydCB7IEVycm9yQ29kZSB9IGZyb20gJy4uL3R5cGVzL2Vycm9yLWNvZGVzLmpzJztcbmltcG9ydCB0eXBlIHsgUGVybWlzc2lvbiB9IGZyb20gJy4uL3R5cGVzL3Blcm1pc3Npb25zLmpzJztcbmltcG9ydCB0eXBlIHsgVmlzaWJpbGl0eSB9IGZyb20gJy4uL3R5cGVzL3Zpc2liaWxpdHkuanMnO1xuaW1wb3J0IHsgc2VuZEVycm9yIH0gZnJvbSAnLi4vdXRpbHMvcmVzcG9uc2UuanMnO1xuXG4vKiogVGhlIHNsaWNlIG9mIGEgY2F0YWxvZyBlbnRpdHkgdGhlIHdyaXRlIGdhdGUgbmVlZHMuICovXG5leHBvcnQgaW50ZXJmYWNlIFZpc2liaWxpdHlXcml0ZVRhcmdldCB7XG4gIC8qKiBSdW5nIHRoZSByb3cgY3VycmVudGx5IHNpdHMgYXQg4oCUIHNlZSB7QGxpbmsgVmlzaWJpbGl0eX0uICovXG4gIHZpc2liaWxpdHk/OiBzdHJpbmc7XG4gIC8qKiBBdXRob3IuIFRoZSBvbmx5IG5vbi1hZG1pbiB3aG8gbWF5IHRvdWNoIGEgYHByaXZhdGVgIHJvdy4gKi9cbiAgY3JlYXRlZEJ5Pzogc3RyaW5nO1xufVxuXG4vKipcbiAqIENoZWNrIHdoZXRoZXIgdGhlIGNhbGxlciBtYXkgbW9kaWZ5IGEgY2F0YWxvZyBlbnRpdHksIHBlciBpdHMgdmlzaWJpbGl0eSBydW5nLlxuICogVGhlIHNpbmdsZSB3cml0ZSBnYXRlIGJlaGluZCBldmVyeSBwaXBlbGluZSAvIHBsdWdpbiAvIHRlbXBsYXRlIG11dGF0aW9uLlxuICpcbiAqIC0gYHB1YmxpY2Ag4oCUIGEgc3lzdGVtIGFkbWluLCBvciB0aGUgaG9sZGVyIG9mIGBwdWJsaXNoUGVybWlzc2lvbmAgKHRoZSBzYW1lXG4gKiAgIHBlcm1pc3Npb24gdGhhdCBsZXQgdGhlbSBwdWJsaXNoIGl0IGluIHRoZSBmaXJzdCBwbGFjZSwgc28gd2hvZXZlciBjYW5cbiAqICAgcHVibGlzaCBjYW4gYWxzbyBtYW5hZ2Ugd2hhdCB0aGV5IHB1Ymxpc2hlZClcbiAqIC0gYG9yZ2Ag4oCUIGFueW9uZSB3aG8gZ290IHRoaXMgZmFyOiB0aGUgcmVzb3VyY2UncyBgOndyaXRlYCBwZXJtaXNzaW9uIHBsdXMgdGhlXG4gKiAgIG9yZyBzY29waW5nIHRoZSBjYWxsZXIgYWxyZWFkeSBwYXNzZWQgSVMgdGhlIGdhdGVcbiAqIC0gYHByaXZhdGVgIOKAlCB0aGUgQVVUSE9SIGFsb25lIChwbHVzIHN5c3RlbSBhZG1pbnMpXG4gKlxuICogVGhlIGBwcml2YXRlYCBicmFuY2ggaXMgbG9hZC1iZWFyaW5nIG9uIHRoZSBUT01CU1RPTkUgcGF0aHMgKHJlc3RvcmUgLyBwdXJnZSksXG4gKiB3aGljaCBhcmUgb3JnLXNjb3BlZCByYXRoZXIgdGhhbiB2aXNpYmlsaXR5LXNjb3BlZCBiZWNhdXNlIHNvZnQtZGVsZXRlIGlzIG9uZVxuICogc2hhcmVkIGNvZGUgcGF0aCDigJQgd2l0aG91dCBpdCwgYW55IG9yZyBtZW1iZXIgY291bGQgcmVzdXJyZWN0IHNvbWVvbmUgZWxzZSdzXG4gKiBwZXJzb25hbCBkcmFmdC4gT24gdGhlIGxpdmUgcGF0aHMgdGhlIHJlYWQgcHJlZGljYXRlIGhhcyBhbHJlYWR5IGhpZGRlbiB0aGVcbiAqIHJvdywgbWFraW5nIHRoaXMgZGVmZW5zZSBpbiBkZXB0aC5cbiAqXG4gKiBSZXR1cm5zIGB0cnVlYCBpZiB0aGUgcmVxdWVzdCBtYXkgcHJvY2VlZDsgcmV0dXJucyBgZmFsc2VgIGhhdmluZyBBTFJFQURZIHNlbnRcbiAqIGEgNDAzLlxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBpZiAoIXJlcXVpcmVWaXNpYmlsaXR5V3JpdGVBY2Nlc3MocmVxLCByZXMsIHBpcGVsaW5lLCB1c2VySWQsICdwaXBlbGluZXM6cHVibGlzaCcpKSByZXR1cm47XG4gKiBhd2FpdCBwaXBlbGluZVNlcnZpY2UuZGVsZXRlKGlkLCBvcmdJZCwgdXNlcklkKTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVxdWlyZVZpc2liaWxpdHlXcml0ZUFjY2VzcyhcbiAgcmVxOiBSZXF1ZXN0LFxuICByZXM6IFJlc3BvbnNlLFxuICByZXNvdXJjZTogVmlzaWJpbGl0eVdyaXRlVGFyZ2V0LFxuICB1c2VySWQ6IHN0cmluZyxcbiAgcHVibGlzaFBlcm1pc3Npb246IFBlcm1pc3Npb24sXG4pOiBib29sZWFuIHtcbiAgaWYgKGlzU3lzdGVtQWRtaW4ocmVxKSkgcmV0dXJuIHRydWU7XG5cbiAgaWYgKHJlc291cmNlLnZpc2liaWxpdHkgPT09ICdwdWJsaWMnICYmICF1c2VySGFzUGVybWlzc2lvbihyZXEsIHB1Ymxpc2hQZXJtaXNzaW9uKSkge1xuICAgIHNlbmRFcnJvcihcbiAgICAgIHJlcyxcbiAgICAgIDQwMyxcbiAgICAgICdZb3UgbGFjayBwZXJtaXNzaW9uIHRvIG1vZGlmeSB0aGlzIHB1YmxpYyByZXNvdXJjZS4nLFxuICAgICAgRXJyb3JDb2RlLklOU1VGRklDSUVOVF9QRVJNSVNTSU9OUyxcbiAgICApO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIC8vIEZhaWwgY2xvc2VkIG9uIGFuIGFub255bW91cy9hYnNlbnQgY2FsbGVyIOKAlCBhbiBlbXB0eSB1c2VySWQgbXVzdCBuZXZlciBtYXRjaFxuICAvLyBhbiBlbXB0eSBgY3JlYXRlZEJ5YCBhbmQgaGFuZCBvdmVyIHNvbWVvbmUgZWxzZSdzIGRyYWZ0LlxuICBpZiAocmVzb3VyY2UudmlzaWJpbGl0eSA9PT0gJ3ByaXZhdGUnICYmICghdXNlcklkIHx8IHJlc291cmNlLmNyZWF0ZWRCeSAhPT0gdXNlcklkKSkge1xuICAgIHNlbmRFcnJvcihcbiAgICAgIHJlcyxcbiAgICAgIDQwMyxcbiAgICAgICdPbmx5IHRoZSBhdXRob3IgY2FuIG1vZGlmeSBhIHByaXZhdGUgcmVzb3VyY2UuJyxcbiAgICAgIEVycm9yQ29kZS5JTlNVRkZJQ0lFTlRfUEVSTUlTU0lPTlMsXG4gICAgKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIHRoZSBlZmZlY3RpdmUgdmlzaWJpbGl0eSBmb3IgYW4gZW50aXR5IGJlaW5nIGNyZWF0ZWQgb3IgdXBkYXRlZC5cbiAqXG4gKiBgcHVibGljYCByZXF1aXJlcyB0aGUgcHVibGlzaCBjYXBhYmlsaXR5IChzdXBlcmFkbWlucyBwYXNzIHZpYSBpbXBsaWNpdC1hbGwpO1xuICogYSBjYWxsZXIgd2hvIGFza3MgZm9yIGl0IHdpdGhvdXQgdGhlIHBlcm1pc3Npb24gaXMgY2xhbXBlZCB0byBgb3JnYCByYXRoZXJcbiAqIHRoYW4gZHJvcHBlZCB0byBhIHBlcnNvbmFsIGRyYWZ0IOKAlCB0aGV5IGFza2VkIHRvIFNIQVJFIGl0LCBhbmQgYG9yZ2AgaXMgdGhlXG4gKiB3aWRlc3QgcnVuZyB0aGV5IGFyZSBlbnRpdGxlZCB0by4gQW55dGhpbmcgZWxzZSBwYXNzZXMgdGhyb3VnaC5cbiAqXG4gKiBgZmFsbGJhY2tgIGlzIHRoZSBydW5nIGFuIFVOU1BFQ0lGSUVEIHZpc2liaWxpdHkgbGFuZHMgb24sIGFuZCBpdCBpc1xuICogZGVsaWJlcmF0ZWx5IHBlci1lbnRpdHkgZXZlbiB0aG91Z2ggdGhlIGxhZGRlciBpdHNlbGYgaXMgaWRlbnRpY2FsIGV2ZXJ5d2hlcmU6XG4gKlxuICogLSAqKnRlbXBsYXRlcyoqIGRlZmF1bHQgdG8gYHByaXZhdGVgIOKAlCBkcmFmdC1maXJzdCBpcyB0aGUgd2hvbGUgcG9pbnQgb2YgdGhlXG4gKiAgIHBlcnNvbmFsIHJ1bmc7IHlvdSBwb2xpc2ggYSBzdGFydGVyIGJlZm9yZSBwdWJsaXNoaW5nIGl0LlxuICogLSAqKnBpcGVsaW5lcyAvIHBsdWdpbnMqKiBkZWZhdWx0IHRvIGBvcmdgIOKAlCB0aGV5IGFyZSB0ZWFtIGFzc2V0cyB0aGF0IGRlcGxveVxuICogICBzaGFyZWQgaW5mcmFzdHJ1Y3R1cmUsIHNvIGNyZWF0aW5nIG9uZSBtdXN0IG5vdCBoaWRlIGl0IGZyb20gdGhlIHRlYW0uIEFcbiAqICAgcGVyc29uYWwgZHJhZnQgc3RheXMgYXZhaWxhYmxlLCBidXQgb3B0LWluLlxuICpcbiAqIFNhbWUgbGFkZGVyLCBzYW1lIGdhdGUsIGRpZmZlcmVudCBjZW50cmUgb2YgZ3Jhdml0eSDigJQgZXhwcmVzc2VkIGFzIGFuIGFyZ3VtZW50XG4gKiByYXRoZXIgdGhhbiBhIHNlY29uZCBjb2RlIHBhdGguXG4gKlxuICogUGVybWlzc2lvbi1iYXNlZCAobm90IGNvYXJzZS1yb2xlLWJhc2VkKTogYSBiZXNwb2tlIGN1c3RvbSBSb2xlIGNhbiBiZSBncmFudGVkXG4gKiBwdWJsaXNoIHJpZ2h0cyBhbmQgaXMgbm8gbG9uZ2VyIGZvcmNlZCBwcml2YXRlIGJ5IGl0cyBgbWVtYmVyYCBsYWJlbC4gQnVpbHQtaW5cbiAqIEFkbWluL093bmVyIGJ1bmRsZXMgY2FycnkgdGhlIHB1Ymxpc2ggcGVybWlzc2lvbnM7IHRoZSBidWlsdC1pbiBNZW1iZXIgYnVuZGxlXG4gKiBkb2VzIG5vdCwgc28gbWVtYmVycyB0b3Agb3V0IGF0IGBvcmdgLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVZpc2liaWxpdHkoXG4gIHJlcTogUmVxdWVzdCxcbiAgcmVxdWVzdGVkOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4gIHB1Ymxpc2hQZXJtaXNzaW9uOiBQZXJtaXNzaW9uLFxuICBmYWxsYmFjazogVmlzaWJpbGl0eSA9ICdwcml2YXRlJyxcbik6IFZpc2liaWxpdHkge1xuICBpZiAocmVxdWVzdGVkID09PSAncHVibGljJykge1xuICAgIHJldHVybiB1c2VySGFzUGVybWlzc2lvbihyZXEsIHB1Ymxpc2hQZXJtaXNzaW9uKSA/ICdwdWJsaWMnIDogJ29yZyc7XG4gIH1cbiAgaWYgKHJlcXVlc3RlZCA9PT0gJ29yZycpIHJldHVybiAnb3JnJztcbiAgaWYgKHJlcXVlc3RlZCA9PT0gJ3ByaXZhdGUnKSByZXR1cm4gJ3ByaXZhdGUnO1xuICByZXR1cm4gZmFsbGJhY2s7XG59XG4iXX0=
@@ -0,0 +1,9 @@
1
+ import type { Response } from 'express';
2
+ /**
3
+ * Coerce listed fields on a DB-returned record to arrays. Drizzle/pg sometimes
4
+ * returns `null` for jsonb columns when the row was inserted with a missing
5
+ * key — callers expect `[]` so map iteration doesn't crash.
6
+ */
7
+ export declare function normalizeArrayFields<T extends Record<string, unknown>>(record: T, arrayFields: (keyof T)[]): T;
8
+ /** Send a 404 NOT_FOUND error with a standard `${entityName} not found.` message. */
9
+ export declare function sendEntityNotFound(res: Response, entityName: string): void;
@@ -0,0 +1,23 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { ErrorCode } from '../types/error-codes.js';
4
+ import { sendError } from '../utils/response.js';
5
+ /**
6
+ * Coerce listed fields on a DB-returned record to arrays. Drizzle/pg sometimes
7
+ * returns `null` for jsonb columns when the row was inserted with a missing
8
+ * key — callers expect `[]` so map iteration doesn't crash.
9
+ */
10
+ export function normalizeArrayFields(record, arrayFields) {
11
+ const normalized = { ...record };
12
+ for (const field of arrayFields) {
13
+ if (field in normalized && !Array.isArray(normalized[field])) {
14
+ normalized[field] = [];
15
+ }
16
+ }
17
+ return normalized;
18
+ }
19
+ /** Send a 404 NOT_FOUND error with a standard `${entityName} not found.` message. */
20
+ export function sendEntityNotFound(res, entityName) {
21
+ sendError(res, 404, `${entityName} not found.`, ErrorCode.NOT_FOUND);
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3J1ZC1oZWxwZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2hlbHBlcnMvY3J1ZC1oZWxwZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFHdEMsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ3BELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUVqRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUNsQyxNQUFTLEVBQ1QsV0FBd0I7SUFFeEIsTUFBTSxVQUFVLEdBQUcsRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDO0lBQ2pDLEtBQUssTUFBTSxLQUFLLElBQUksV0FBVyxFQUFFLENBQUM7UUFDaEMsSUFBSSxLQUFLLElBQUksVUFBVSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQzVELFVBQXNDLENBQUMsS0FBZSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ2hFLENBQUM7SUFDSCxDQUFDO0lBQ0QsT0FBTyxVQUFVLENBQUM7QUFDcEIsQ0FBQztBQUVELHFGQUFxRjtBQUNyRixNQUFNLFVBQVUsa0JBQWtCLENBQUMsR0FBYSxFQUFFLFVBQWtCO0lBQ2xFLFNBQVMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsVUFBVSxhQUFhLEVBQUUsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHR5cGUgeyBSZXNwb25zZSB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHsgRXJyb3JDb2RlIH0gZnJvbSAnLi4vdHlwZXMvZXJyb3ItY29kZXMuanMnO1xuaW1wb3J0IHsgc2VuZEVycm9yIH0gZnJvbSAnLi4vdXRpbHMvcmVzcG9uc2UuanMnO1xuXG4vKipcbiAqIENvZXJjZSBsaXN0ZWQgZmllbGRzIG9uIGEgREItcmV0dXJuZWQgcmVjb3JkIHRvIGFycmF5cy4gRHJpenpsZS9wZyBzb21ldGltZXNcbiAqIHJldHVybnMgYG51bGxgIGZvciBqc29uYiBjb2x1bW5zIHdoZW4gdGhlIHJvdyB3YXMgaW5zZXJ0ZWQgd2l0aCBhIG1pc3NpbmdcbiAqIGtleSDigJQgY2FsbGVycyBleHBlY3QgYFtdYCBzbyBtYXAgaXRlcmF0aW9uIGRvZXNuJ3QgY3Jhc2guXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVBcnJheUZpZWxkczxUIGV4dGVuZHMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4+KFxuICByZWNvcmQ6IFQsXG4gIGFycmF5RmllbGRzOiAoa2V5b2YgVClbXSxcbik6IFQge1xuICBjb25zdCBub3JtYWxpemVkID0geyAuLi5yZWNvcmQgfTtcbiAgZm9yIChjb25zdCBmaWVsZCBvZiBhcnJheUZpZWxkcykge1xuICAgIGlmIChmaWVsZCBpbiBub3JtYWxpemVkICYmICFBcnJheS5pc0FycmF5KG5vcm1hbGl6ZWRbZmllbGRdKSkge1xuICAgICAgKG5vcm1hbGl6ZWQgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pW2ZpZWxkIGFzIHN0cmluZ10gPSBbXTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIG5vcm1hbGl6ZWQ7XG59XG5cbi8qKiBTZW5kIGEgNDA0IE5PVF9GT1VORCBlcnJvciB3aXRoIGEgc3RhbmRhcmQgYCR7ZW50aXR5TmFtZX0gbm90IGZvdW5kLmAgbWVzc2FnZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZW5kRW50aXR5Tm90Rm91bmQocmVzOiBSZXNwb25zZSwgZW50aXR5TmFtZTogc3RyaW5nKTogdm9pZCB7XG4gIHNlbmRFcnJvcihyZXMsIDQwNCwgYCR7ZW50aXR5TmFtZX0gbm90IGZvdW5kLmAsIEVycm9yQ29kZS5OT1RfRk9VTkQpO1xufVxuIl19
@@ -0,0 +1,6 @@
1
+ export * from './crud-helpers.js';
2
+ export * from './access-helpers.js';
3
+ export * from './restore-helpers.js';
4
+ export * from './sse-helpers.js';
5
+ export * from './org-hierarchy.js';
6
+ export * from './org-hierarchy-http.js';
@@ -0,0 +1,9 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export * from './crud-helpers.js';
4
+ export * from './access-helpers.js';
5
+ export * from './restore-helpers.js';
6
+ export * from './sse-helpers.js';
7
+ export * from './org-hierarchy.js';
8
+ export * from './org-hierarchy-http.js';
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaGVscGVycy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxxQkFBcUIsQ0FBQztBQUNwQyxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHlCQUF5QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmV4cG9ydCAqIGZyb20gJy4vY3J1ZC1oZWxwZXJzLmpzJztcbmV4cG9ydCAqIGZyb20gJy4vYWNjZXNzLWhlbHBlcnMuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9yZXN0b3JlLWhlbHBlcnMuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9zc2UtaGVscGVycy5qcyc7XG5leHBvcnQgKiBmcm9tICcuL29yZy1oaWVyYXJjaHkuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9vcmctaGllcmFyY2h5LWh0dHAuanMnO1xuIl19
@@ -0,0 +1,74 @@
1
+ import type { ServiceConfig } from '../types/common.js';
2
+ /** Options shared by the platform org-hierarchy HTTP lookups. */
3
+ export interface OrgHierarchyHttpOptions {
4
+ /** Platform service host/port (+ optional default timeout) for the client. */
5
+ service: ServiceConfig;
6
+ /** Service name minted into the service-auth JWT `sub` (e.g. 'compliance'). */
7
+ serviceName: string;
8
+ /**
9
+ * Org id embedded in the signed service token's org context. Defaults to the
10
+ * queried `orgId`; pass a system org id for a system-scoped lookup.
11
+ */
12
+ authOrgId?: string;
13
+ /** Role carried by the service token (default 'member' — least privilege). */
14
+ role?: 'owner' | 'admin' | 'member';
15
+ /** Extra request headers merged after `Authorization` (e.g. `x-org-id`). */
16
+ headers?: Record<string, string>;
17
+ /** Per-request timeout override in ms (falls back to the client default). */
18
+ timeout?: number;
19
+ /**
20
+ * When true, a non-2xx response THROWS instead of resolving to `undefined`.
21
+ * Default false (a 4xx/5xx maps to `undefined`, the fail-soft policy the
22
+ * message/reporting root-resolution callers rely on). A caller for whom a
23
+ * failed lookup must fail-CLOSED — e.g. a compliance scan, where treating a
24
+ * team org as parent-less would silently skip inherited blocking rules — sets
25
+ * this so it can distinguish "root org" (`undefined`) from "lookup failed".
26
+ */
27
+ throwOnHttpError?: boolean;
28
+ }
29
+ /**
30
+ * Resolve an org's direct parent id via platform's
31
+ * `GET /organization/:id/parent`. Returns `undefined` for a root org. A non-2xx
32
+ * response resolves to `undefined` by default, or THROWS when
33
+ * `throwOnHttpError` is set. Always throws on a transport failure
34
+ * (connection/timeout) so the caller can apply its own fallback.
35
+ */
36
+ export declare function fetchParentOrgId(orgId: string, opts: OrgHierarchyHttpOptions): Promise<string | undefined>;
37
+ /**
38
+ * Resolve `[self, ...descendantOrgIds]` for an org via platform's
39
+ * `GET /organization/:id/descendants`. Returns the id list only when it is
40
+ * larger than the org itself (a real subtree), else `undefined`; also
41
+ * `undefined` on a non-2xx response. Throws on a transport failure so the
42
+ * caller can apply its own fallback.
43
+ *
44
+ * This is a trust boundary: the response is validated element-by-element rather
45
+ * than cast — every entry must be a string, and any non-string is dropped — so
46
+ * a malformed payload can never smuggle a non-string into the returned
47
+ * `string[]`.
48
+ */
49
+ export declare function fetchOrgDescendants(orgId: string, opts: OrgHierarchyHttpOptions): Promise<string[] | undefined>;
50
+ /**
51
+ * Batch-resolve org id → display NAME via platform's `POST /organization/names`.
52
+ * Returns a lowercased `{ [orgId]: name }` map — ids the platform doesn't know
53
+ * are simply absent (caller falls back to the raw id).
54
+ *
55
+ * Best-effort by design: a non-2xx response (e.g. the caller isn't a service
56
+ * principal) or a malformed body yields `{}`, and it throws ONLY on a transport
57
+ * failure (connection/timeout) — matching the fail-soft policy of the other
58
+ * lookups here so a name-enrichment read never fails the caller's request. The
59
+ * response is validated entry-by-entry (only string→string pairs survive), so a
60
+ * malformed payload can't smuggle a non-string into the returned map.
61
+ */
62
+ export declare function fetchOrgNames(orgIds: string[], opts: OrgHierarchyHttpOptions): Promise<Record<string, string>>;
63
+ /**
64
+ * Check whether `userId` is an ACTIVE member of `orgId` via platform's
65
+ * `GET /organization/:id/members/:userId/exists`. Returns the boolean the
66
+ * platform reports.
67
+ *
68
+ * Returns `undefined` (— "couldn't determine") on a non-2xx response, and
69
+ * throws only on a transport failure (connection/timeout) — so the caller
70
+ * distinguishes a definitive not-a-member (`false`) from an indeterminate
71
+ * lookup (`undefined`) and applies its own fail-open/closed policy. The `path`
72
+ * differs from the parent/descendants shape, so this builds its own request.
73
+ */
74
+ export declare function fetchOrgMembership(orgId: string, userId: string, opts: OrgHierarchyHttpOptions): Promise<boolean | undefined>;
@@ -0,0 +1,144 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Org → team hierarchy lookups over HTTP against the platform service.
5
+ *
6
+ * The Mongoose-based traversal in {@link ./org-hierarchy} is for services that
7
+ * own the org table (platform, quota). Everyone else reads the hierarchy over
8
+ * the wire from platform's authoritative `/organization/:id/*` endpoints. This
9
+ * module centralizes the transport for those reads — URL construction, the
10
+ * signed service-token auth header, and timeout+retry via the shared
11
+ * {@link InternalHttpClient} — so compliance, reporting, and any future caller
12
+ * share ONE mechanism instead of each hand-rolling a `fetch`/client with its
13
+ * own auth and failure semantics.
14
+ *
15
+ * These functions deliberately do NOT swallow transport errors: a connection
16
+ * failure/timeout propagates so each caller keeps its own fallback policy (log
17
+ * level, degrade-to-self vs. return-undefined). A non-2xx response is treated
18
+ * as "no data" (returns undefined), matching what the callers previously did.
19
+ */
20
+ import { getServiceAuthHeader, SYSTEM_ORG_ID } from '../middleware/auth.js';
21
+ import { InternalHttpClient } from '../services/http-client.js';
22
+ function buildRequest(orgId, suffix, opts) {
23
+ const client = new InternalHttpClient(opts.service);
24
+ const path = `/organization/${encodeURIComponent(orgId)}/${suffix}`;
25
+ const headers = {
26
+ Authorization: getServiceAuthHeader({
27
+ serviceName: opts.serviceName,
28
+ orgId: opts.authOrgId ?? orgId,
29
+ role: opts.role ?? 'member',
30
+ }),
31
+ ...opts.headers,
32
+ };
33
+ return { client, path, headers };
34
+ }
35
+ /**
36
+ * Resolve an org's direct parent id via platform's
37
+ * `GET /organization/:id/parent`. Returns `undefined` for a root org. A non-2xx
38
+ * response resolves to `undefined` by default, or THROWS when
39
+ * `throwOnHttpError` is set. Always throws on a transport failure
40
+ * (connection/timeout) so the caller can apply its own fallback.
41
+ */
42
+ export async function fetchParentOrgId(orgId, opts) {
43
+ const { client, path, headers } = buildRequest(orgId, 'parent', opts);
44
+ const res = await client.get(path, { headers, timeout: opts.timeout });
45
+ if (res.statusCode >= 400) {
46
+ if (opts.throwOnHttpError) {
47
+ throw new Error(`parent-org lookup failed for ${orgId}: HTTP ${res.statusCode}`);
48
+ }
49
+ return undefined;
50
+ }
51
+ return res.body?.data?.parentOrgId ?? undefined;
52
+ }
53
+ /**
54
+ * Resolve `[self, ...descendantOrgIds]` for an org via platform's
55
+ * `GET /organization/:id/descendants`. Returns the id list only when it is
56
+ * larger than the org itself (a real subtree), else `undefined`; also
57
+ * `undefined` on a non-2xx response. Throws on a transport failure so the
58
+ * caller can apply its own fallback.
59
+ *
60
+ * This is a trust boundary: the response is validated element-by-element rather
61
+ * than cast — every entry must be a string, and any non-string is dropped — so
62
+ * a malformed payload can never smuggle a non-string into the returned
63
+ * `string[]`.
64
+ */
65
+ export async function fetchOrgDescendants(orgId, opts) {
66
+ const { client, path, headers } = buildRequest(orgId, 'descendants', opts);
67
+ const res = await client.get(path, { headers, timeout: opts.timeout });
68
+ if (res.statusCode >= 400)
69
+ return undefined;
70
+ const ids = res.body?.data?.orgIds;
71
+ if (!Array.isArray(ids))
72
+ return undefined;
73
+ const orgIds = ids.filter((id) => typeof id === 'string');
74
+ // Only meaningful when the (validated) subtree is larger than the org itself.
75
+ return orgIds.length > 1 ? orgIds : undefined;
76
+ }
77
+ /**
78
+ * Batch-resolve org id → display NAME via platform's `POST /organization/names`.
79
+ * Returns a lowercased `{ [orgId]: name }` map — ids the platform doesn't know
80
+ * are simply absent (caller falls back to the raw id).
81
+ *
82
+ * Best-effort by design: a non-2xx response (e.g. the caller isn't a service
83
+ * principal) or a malformed body yields `{}`, and it throws ONLY on a transport
84
+ * failure (connection/timeout) — matching the fail-soft policy of the other
85
+ * lookups here so a name-enrichment read never fails the caller's request. The
86
+ * response is validated entry-by-entry (only string→string pairs survive), so a
87
+ * malformed payload can't smuggle a non-string into the returned map.
88
+ */
89
+ export async function fetchOrgNames(orgIds, opts) {
90
+ if (orgIds.length === 0)
91
+ return {};
92
+ const client = new InternalHttpClient(opts.service);
93
+ const headers = {
94
+ Authorization: getServiceAuthHeader({
95
+ serviceName: opts.serviceName,
96
+ // A cross-org registry read, not a tenant-scoped op — default to the system
97
+ // org context when the caller doesn't pin one.
98
+ orgId: opts.authOrgId ?? SYSTEM_ORG_ID,
99
+ role: opts.role ?? 'member',
100
+ }),
101
+ ...opts.headers,
102
+ };
103
+ const res = await client.post('/organization/names', { orgIds }, { headers, timeout: opts.timeout });
104
+ if (res.statusCode >= 400)
105
+ return {};
106
+ const names = res.body?.data?.names;
107
+ if (!names || typeof names !== 'object')
108
+ return {};
109
+ const out = {};
110
+ for (const [id, name] of Object.entries(names)) {
111
+ if (typeof name === 'string')
112
+ out[id.toLowerCase()] = name;
113
+ }
114
+ return out;
115
+ }
116
+ /**
117
+ * Check whether `userId` is an ACTIVE member of `orgId` via platform's
118
+ * `GET /organization/:id/members/:userId/exists`. Returns the boolean the
119
+ * platform reports.
120
+ *
121
+ * Returns `undefined` (— "couldn't determine") on a non-2xx response, and
122
+ * throws only on a transport failure (connection/timeout) — so the caller
123
+ * distinguishes a definitive not-a-member (`false`) from an indeterminate
124
+ * lookup (`undefined`) and applies its own fail-open/closed policy. The `path`
125
+ * differs from the parent/descendants shape, so this builds its own request.
126
+ */
127
+ export async function fetchOrgMembership(orgId, userId, opts) {
128
+ const client = new InternalHttpClient(opts.service);
129
+ const path = `/organization/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}/exists`;
130
+ const headers = {
131
+ Authorization: getServiceAuthHeader({
132
+ serviceName: opts.serviceName,
133
+ orgId: opts.authOrgId ?? orgId,
134
+ role: opts.role ?? 'member',
135
+ }),
136
+ ...opts.headers,
137
+ };
138
+ const res = await client.get(path, { headers, timeout: opts.timeout });
139
+ if (res.statusCode >= 400)
140
+ return undefined;
141
+ const isMember = res.body?.data?.isMember;
142
+ return typeof isMember === 'boolean' ? isMember : undefined;
143
+ }
144
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnLWhpZXJhcmNoeS1odHRwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2hlbHBlcnMvb3JnLWhpZXJhcmNoeS1odHRwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7R0FnQkc7QUFFSCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsYUFBYSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDNUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUErQmhFLFNBQVMsWUFBWSxDQUNuQixLQUFhLEVBQ2IsTUFBZ0MsRUFDaEMsSUFBNkI7SUFFN0IsTUFBTSxNQUFNLEdBQUcsSUFBSSxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDcEQsTUFBTSxJQUFJLEdBQUcsaUJBQWlCLGtCQUFrQixDQUFDLEtBQUssQ0FBQyxJQUFJLE1BQU0sRUFBRSxDQUFDO0lBQ3BFLE1BQU0sT0FBTyxHQUEyQjtRQUN0QyxhQUFhLEVBQUUsb0JBQW9CLENBQUM7WUFDbEMsV0FBVyxFQUFFLElBQUksQ0FBQyxXQUFXO1lBQzdCLEtBQUssRUFBRSxJQUFJLENBQUMsU0FBUyxJQUFJLEtBQUs7WUFDOUIsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLElBQUksUUFBUTtTQUM1QixDQUFDO1FBQ0YsR0FBRyxJQUFJLENBQUMsT0FBTztLQUNoQixDQUFDO0lBQ0YsT0FBTyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUM7QUFDbkMsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsZ0JBQWdCLENBQUMsS0FBYSxFQUFFLElBQTZCO0lBQ2pGLE1BQU0sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxHQUFHLFlBQVksQ0FBQyxLQUFLLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FB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QUksRUFBRSxRQUFRLENBQUM7SUFDMUMsT0FBTyxPQUFPLFFBQVEsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO0FBQzlELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBPcmcg4oaSIHRlYW0gaGllcmFyY2h5IGxvb2t1cHMgb3ZlciBIVFRQIGFnYWluc3QgdGhlIHBsYXRmb3JtIHNlcnZpY2UuXG4gKlxuICogVGhlIE1vbmdvb3NlLWJhc2VkIHRyYXZlcnNhbCBpbiB7QGxpbmsgLi9vcmctaGllcmFyY2h5fSBpcyBmb3Igc2VydmljZXMgdGhhdFxuICogb3duIHRoZSBvcmcgdGFibGUgKHBsYXRmb3JtLCBxdW90YSkuIEV2ZXJ5b25lIGVsc2UgcmVhZHMgdGhlIGhpZXJhcmNoeSBvdmVyXG4gKiB0aGUgd2lyZSBmcm9tIHBsYXRmb3JtJ3MgYXV0aG9yaXRhdGl2ZSBgL29yZ2FuaXphdGlvbi86aWQvKmAgZW5kcG9pbnRzLiBUaGlzXG4gKiBtb2R1bGUgY2VudHJhbGl6ZXMgdGhlIHRyYW5zcG9ydCBmb3IgdGhvc2UgcmVhZHMg4oCUIFVSTCBjb25zdHJ1Y3Rpb24sIHRoZVxuICogc2lnbmVkIHNlcnZpY2UtdG9rZW4gYXV0aCBoZWFkZXIsIGFuZCB0aW1lb3V0K3JldHJ5IHZpYSB0aGUgc2hhcmVkXG4gKiB7QGxpbmsgSW50ZXJuYWxIdHRwQ2xpZW50fSDigJQgc28gY29tcGxpYW5jZSwgcmVwb3J0aW5nLCBhbmQgYW55IGZ1dHVyZSBjYWxsZXJcbiAqIHNoYXJlIE9ORSBtZWNoYW5pc20gaW5zdGVhZCBvZiBlYWNoIGhhbmQtcm9sbGluZyBhIGBmZXRjaGAvY2xpZW50IHdpdGggaXRzXG4gKiBvd24gYXV0aCBhbmQgZmFpbHVyZSBzZW1hbnRpY3MuXG4gKlxuICogVGhlc2UgZnVuY3Rpb25zIGRlbGliZXJhdGVseSBkbyBOT1Qgc3dhbGxvdyB0cmFuc3BvcnQgZXJyb3JzOiBhIGNvbm5lY3Rpb25cbiAqIGZhaWx1cmUvdGltZW91dCBwcm9wYWdhdGVzIHNvIGVhY2ggY2FsbGVyIGtlZXBzIGl0cyBvd24gZmFsbGJhY2sgcG9saWN5IChsb2dcbiAqIGxldmVsLCBkZWdyYWRlLXRvLXNlbGYgdnMuIHJldHVybi11bmRlZmluZWQpLiBBIG5vbi0yeHggcmVzcG9uc2UgaXMgdHJlYXRlZFxuICogYXMgXCJubyBkYXRhXCIgKHJldHVybnMgdW5kZWZpbmVkKSwgbWF0Y2hpbmcgd2hhdCB0aGUgY2FsbGVycyBwcmV2aW91c2x5IGRpZC5cbiAqL1xuXG5pbXBvcnQgeyBnZXRTZXJ2aWNlQXV0aEhlYWRlciwgU1lTVEVNX09SR19JRCB9IGZyb20gJy4uL21pZGRsZXdhcmUvYXV0aC5qcyc7XG5pbXBvcnQgeyBJbnRlcm5hbEh0dHBDbGllbnQgfSBmcm9tICcuLi9zZXJ2aWNlcy9odHRwLWNsaWVudC5qcyc7XG5pbXBvcnQgdHlwZSB7IFNlcnZpY2VDb25maWcgfSBmcm9tICcuLi90eXBlcy9jb21tb24uanMnO1xuXG4vKiogT3B0aW9ucyBzaGFyZWQgYnkgdGhlIHBsYXRmb3JtIG9yZy1oaWVyYXJjaHkgSFRUUCBsb29rdXBzLiAqL1xuZXhwb3J0IGludGVyZmFjZSBPcmdIaWVyYXJjaHlIdHRwT3B0aW9ucyB7XG4gIC8qKiBQbGF0Zm9ybSBzZXJ2aWNlIGhvc3QvcG9ydCAoKyBvcHRpb25hbCBkZWZhdWx0IHRpbWVvdXQpIGZvciB0aGUgY2xpZW50LiAqL1xuICBzZXJ2aWNlOiBTZXJ2aWNlQ29uZmlnO1xuICAvKiogU2VydmljZSBuYW1lIG1pbnRlZCBpbnRvIHRoZSBzZXJ2aWNlLWF1dGggSldUIGBzdWJgIChlLmcuICdjb21wbGlhbmNlJykuICovXG4gIHNlcnZpY2VOYW1lOiBzdHJpbmc7XG4gIC8qKlxuICAgKiBPcmcgaWQgZW1iZWRkZWQgaW4gdGhlIHNpZ25lZCBzZXJ2aWNlIHRva2VuJ3Mgb3JnIGNvbnRleHQuIERlZmF1bHRzIHRvIHRoZVxuICAgKiBxdWVyaWVkIGBvcmdJZGA7IHBhc3MgYSBzeXN0ZW0gb3JnIGlkIGZvciBhIHN5c3RlbS1zY29wZWQgbG9va3VwLlxuICAgKi9cbiAgYXV0aE9yZ0lkPzogc3RyaW5nO1xuICAvKiogUm9sZSBjYXJyaWVkIGJ5IHRoZSBzZXJ2aWNlIHRva2VuIChkZWZhdWx0ICdtZW1iZXInIOKAlCBsZWFzdCBwcml2aWxlZ2UpLiAqL1xuICByb2xlPzogJ293bmVyJyB8ICdhZG1pbicgfCAnbWVtYmVyJztcbiAgLyoqIEV4dHJhIHJlcXVlc3QgaGVhZGVycyBtZXJnZWQgYWZ0ZXIgYEF1dGhvcml6YXRpb25gIChlLmcuIGB4LW9yZy1pZGApLiAqL1xuICBoZWFkZXJzPzogUmVjb3JkPHN0cmluZywgc3RyaW5nPjtcbiAgLyoqIFBlci1yZXF1ZXN0IHRpbWVvdXQgb3ZlcnJpZGUgaW4gbXMgKGZhbGxzIGJhY2sgdG8gdGhlIGNsaWVudCBkZWZhdWx0KS4gKi9cbiAgdGltZW91dD86IG51bWJlcjtcbiAgLyoqXG4gICAqIFdoZW4gdHJ1ZSwgYSBub24tMnh4IHJlc3BvbnNlIFRIUk9XUyBpbnN0ZWFkIG9mIHJlc29sdmluZyB0byBgdW5kZWZpbmVkYC5cbiAgICogRGVmYXVsdCBmYWxzZSAoYSA0eHgvNXh4IG1hcHMgdG8gYHVuZGVmaW5lZGAsIHRoZSBmYWlsLXNvZnQgcG9saWN5IHRoZVxuICAgKiBtZXNzYWdlL3JlcG9ydGluZyByb290LXJlc29sdXRpb24gY2FsbGVycyByZWx5IG9uKS4gQSBjYWxsZXIgZm9yIHdob20gYVxuICAgKiBmYWlsZWQgbG9va3VwIG11c3QgZmFpbC1DTE9TRUQg4oCUIGUuZy4gYSBjb21wbGlhbmNlIHNjYW4sIHdoZXJlIHRyZWF0aW5nIGFcbiAgICogdGVhbSBvcmcgYXMgcGFyZW50LWxlc3Mgd291bGQgc2lsZW50bHkgc2tpcCBpbmhlcml0ZWQgYmxvY2tpbmcgcnVsZXMg4oCUIHNldHNcbiAgICogdGhpcyBzbyBpdCBjYW4gZGlzdGluZ3Vpc2ggXCJyb290IG9yZ1wiIChgdW5kZWZpbmVkYCkgZnJvbSBcImxvb2t1cCBmYWlsZWRcIi5cbiAgICovXG4gIHRocm93T25IdHRwRXJyb3I/OiBib29sZWFuO1xufVxuXG5mdW5jdGlvbiBidWlsZFJlcXVlc3QoXG4gIG9yZ0lkOiBzdHJpbmcsXG4gIHN1ZmZpeDogJ3BhcmVudCcgfCAnZGVzY2VuZGFudHMnLFxuICBvcHRzOiBPcmdIaWVyYXJjaHlIdHRwT3B0aW9ucyxcbik6IHsgY2xpZW50OiBJbnRlcm5hbEh0dHBDbGllbnQ7IHBhdGg6IHN0cmluZzsgaGVhZGVyczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiB9IHtcbiAgY29uc3QgY2xpZW50ID0gbmV3IEludGVybmFsSHR0cENsaWVudChvcHRzLnNlcnZpY2UpO1xuICBjb25zdCBwYXRoID0gYC9vcmdhbml6YXRpb24vJHtlbmNvZGVVUklDb21wb25lbnQob3JnSWQpfS8ke3N1ZmZpeH1gO1xuICBjb25zdCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgIEF1dGhvcml6YXRpb246IGdldFNlcnZpY2VBdXRoSGVhZGVyKHtcbiAgICAgIHNlcnZpY2VOYW1lOiBvcHRzLnNlcnZpY2VOYW1lLFxuICAgICAgb3JnSWQ6IG9wdHMuYXV0aE9yZ0lkID8/IG9yZ0lkLFxuICAgICAgcm9sZTogb3B0cy5yb2xlID8/ICdtZW1iZXInLFxuICAgIH0pLFxuICAgIC4uLm9wdHMuaGVhZGVycyxcbiAgfTtcbiAgcmV0dXJuIHsgY2xpZW50LCBwYXRoLCBoZWFkZXJzIH07XG59XG5cbi8qKlxuICogUmVzb2x2ZSBhbiBvcmcncyBkaXJlY3QgcGFyZW50IGlkIHZpYSBwbGF0Zm9ybSdzXG4gKiBgR0VUIC9vcmdhbml6YXRpb24vOmlkL3BhcmVudGAuIFJldHVybnMgYHVuZGVmaW5lZGAgZm9yIGEgcm9vdCBvcmcuIEEgbm9uLTJ4eFxuICogcmVzcG9uc2UgcmVzb2x2ZXMgdG8gYHVuZGVmaW5lZGAgYnkgZGVmYXVsdCwgb3IgVEhST1dTIHdoZW5cbiAqIGB0aHJvd09uSHR0cEVycm9yYCBpcyBzZXQuIEFsd2F5cyB0aHJvd3Mgb24gYSB0cmFuc3BvcnQgZmFpbHVyZVxuICogKGNvbm5lY3Rpb24vdGltZW91dCkgc28gdGhlIGNhbGxlciBjYW4gYXBwbHkgaXRzIG93biBmYWxsYmFjay5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZldGNoUGFyZW50T3JnSWQob3JnSWQ6IHN0cmluZywgb3B0czogT3JnSGllcmFyY2h5SHR0cE9wdGlvbnMpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICBjb25zdCB7IGNsaWVudCwgcGF0aCwgaGVhZGVycyB9ID0gYnVpbGRSZXF1ZXN0KG9yZ0lkLCAncGFyZW50Jywgb3B0cyk7XG4gIGNvbnN0IHJlcyA9IGF3YWl0IGNsaWVudC5nZXQ8eyBkYXRhPzogeyBwYXJlbnRPcmdJZD86IHN0cmluZyB8IG51bGwgfSB9PihwYXRoLCB7IGhlYWRlcnMsIHRpbWVvdXQ6IG9wdHMudGltZW91dCB9KTtcbiAgaWYgKHJlcy5zdGF0dXNDb2RlID49IDQwMCkge1xuICAgIGlmIChvcHRzLnRocm93T25IdHRwRXJyb3IpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgcGFyZW50LW9yZyBsb29rdXAgZmFpbGVkIGZvciAke29yZ0lkfTogSFRUUCAke3Jlcy5zdGF0dXNDb2RlfWApO1xuICAgIH1cbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG4gIHJldHVybiByZXMuYm9keT8uZGF0YT8ucGFyZW50T3JnSWQgPz8gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYFtzZWxmLCAuLi5kZXNjZW5kYW50T3JnSWRzXWAgZm9yIGFuIG9yZyB2aWEgcGxhdGZvcm0nc1xuICogYEdFVCAvb3JnYW5pemF0aW9uLzppZC9kZXNjZW5kYW50c2AuIFJldHVybnMgdGhlIGlkIGxpc3Qgb25seSB3aGVuIGl0IGlzXG4gKiBsYXJnZXIgdGhhbiB0aGUgb3JnIGl0c2VsZiAoYSByZWFsIHN1YnRyZWUpLCBlbHNlIGB1bmRlZmluZWRgOyBhbHNvXG4gKiBgdW5kZWZpbmVkYCBvbiBhIG5vbi0yeHggcmVzcG9uc2UuIFRocm93cyBvbiBhIHRyYW5zcG9ydCBmYWlsdXJlIHNvIHRoZVxuICogY2FsbGVyIGNhbiBhcHBseSBpdHMgb3duIGZhbGxiYWNrLlxuICpcbiAqIFRoaXMgaXMgYSB0cnVzdCBib3VuZGFyeTogdGhlIHJlc3BvbnNlIGlzIHZhbGlkYXRlZCBlbGVtZW50LWJ5LWVsZW1lbnQgcmF0aGVyXG4gKiB0aGFuIGNhc3Qg4oCUIGV2ZXJ5IGVudHJ5IG11c3QgYmUgYSBzdHJpbmcsIGFuZCBhbnkgbm9uLXN0cmluZyBpcyBkcm9wcGVkIOKAlCBzb1xuICogYSBtYWxmb3JtZWQgcGF5bG9hZCBjYW4gbmV2ZXIgc211Z2dsZSBhIG5vbi1zdHJpbmcgaW50byB0aGUgcmV0dXJuZWRcbiAqIGBzdHJpbmdbXWAuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBmZXRjaE9yZ0Rlc2NlbmRhbnRzKG9yZ0lkOiBzdHJpbmcsIG9wdHM6IE9yZ0hpZXJhcmNoeUh0dHBPcHRpb25zKTogUHJvbWlzZTxzdHJpbmdbXSB8IHVuZGVmaW5lZD4ge1xuICBjb25zdCB7IGNsaWVudCwgcGF0aCwgaGVhZGVycyB9ID0gYnVpbGRSZXF1ZXN0KG9yZ0lkLCAnZGVzY2VuZGFudHMnLCBvcHRzKTtcbiAgY29uc3QgcmVzID0gYXdhaXQgY2xpZW50LmdldDx7IGRhdGE/OiB7IG9yZ0lkcz86IHVua25vd24gfSB9PihwYXRoLCB7IGhlYWRlcnMsIHRpbWVvdXQ6IG9wdHMudGltZW91dCB9KTtcbiAgaWYgKHJlcy5zdGF0dXNDb2RlID49IDQwMCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgaWRzID0gcmVzLmJvZHk/LmRhdGE/Lm9yZ0lkcztcbiAgaWYgKCFBcnJheS5pc0FycmF5KGlkcykpIHJldHVybiB1bmRlZmluZWQ7XG4gIGNvbnN0IG9yZ0lkcyA9IGlkcy5maWx0ZXIoKGlkKTogaWQgaXMgc3RyaW5nID0+IHR5cGVvZiBpZCA9PT0gJ3N0cmluZycpO1xuICAvLyBPbmx5IG1lYW5pbmdmdWwgd2hlbiB0aGUgKHZhbGlkYXRlZCkgc3VidHJlZSBpcyBsYXJnZXIgdGhhbiB0aGUgb3JnIGl0c2VsZi5cbiAgcmV0dXJuIG9yZ0lkcy5sZW5ndGggPiAxID8gb3JnSWRzIDogdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIEJhdGNoLXJlc29sdmUgb3JnIGlkIOKGkiBkaXNwbGF5IE5BTUUgdmlhIHBsYXRmb3JtJ3MgYFBPU1QgL29yZ2FuaXphdGlvbi9uYW1lc2AuXG4gKiBSZXR1cm5zIGEgbG93ZXJjYXNlZCBgeyBbb3JnSWRdOiBuYW1lIH1gIG1hcCDigJQgaWRzIHRoZSBwbGF0Zm9ybSBkb2Vzbid0IGtub3dcbiAqIGFyZSBzaW1wbHkgYWJzZW50IChjYWxsZXIgZmFsbHMgYmFjayB0byB0aGUgcmF3IGlkKS5cbiAqXG4gKiBCZXN0LWVmZm9ydCBieSBkZXNpZ246IGEgbm9uLTJ4eCByZXNwb25zZSAoZS5nLiB0aGUgY2FsbGVyIGlzbid0IGEgc2VydmljZVxuICogcHJpbmNpcGFsKSBvciBhIG1hbGZvcm1lZCBib2R5IHlpZWxkcyBge31gLCBhbmQgaXQgdGhyb3dzIE9OTFkgb24gYSB0cmFuc3BvcnRcbiAqIGZhaWx1cmUgKGNvbm5lY3Rpb24vdGltZW91dCkg4oCUIG1hdGNoaW5nIHRoZSBmYWlsLXNvZnQgcG9saWN5IG9mIHRoZSBvdGhlclxuICogbG9va3VwcyBoZXJlIHNvIGEgbmFtZS1lbnJpY2htZW50IHJlYWQgbmV2ZXIgZmFpbHMgdGhlIGNhbGxlcidzIHJlcXVlc3QuIFRoZVxuICogcmVzcG9uc2UgaXMgdmFsaWRhdGVkIGVudHJ5LWJ5LWVudHJ5IChvbmx5IHN0cmluZ+KGknN0cmluZyBwYWlycyBzdXJ2aXZlKSwgc28gYVxuICogbWFsZm9ybWVkIHBheWxvYWQgY2FuJ3Qgc211Z2dsZSBhIG5vbi1zdHJpbmcgaW50byB0aGUgcmV0dXJuZWQgbWFwLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZmV0Y2hPcmdOYW1lcyhcbiAgb3JnSWRzOiBzdHJpbmdbXSxcbiAgb3B0czogT3JnSGllcmFyY2h5SHR0cE9wdGlvbnMsXG4pOiBQcm9taXNlPFJlY29yZDxzdHJpbmcsIHN0cmluZz4+IHtcbiAgaWYgKG9yZ0lkcy5sZW5ndGggPT09IDApIHJldHVybiB7fTtcbiAgY29uc3QgY2xpZW50ID0gbmV3IEludGVybmFsSHR0cENsaWVudChvcHRzLnNlcnZpY2UpO1xuICBjb25zdCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgIEF1dGhvcml6YXRpb246IGdldFNlcnZpY2VBdXRoSGVhZGVyKHtcbiAgICAgIHNlcnZpY2VOYW1lOiBvcHRzLnNlcnZpY2VOYW1lLFxuICAgICAgLy8gQSBjcm9zcy1vcmcgcmVnaXN0cnkgcmVhZCwgbm90IGEgdGVuYW50LXNjb3BlZCBvcCDigJQgZGVmYXVsdCB0byB0aGUgc3lzdGVtXG4gICAgICAvLyBvcmcgY29udGV4dCB3aGVuIHRoZSBjYWxsZXIgZG9lc24ndCBwaW4gb25lLlxuICAgICAgb3JnSWQ6IG9wdHMuYXV0aE9yZ0lkID8/IFNZU1RFTV9PUkdfSUQsXG4gICAgICByb2xlOiBvcHRzLnJvbGUgPz8gJ21lbWJlcicsXG4gICAgfSksXG4gICAgLi4ub3B0cy5oZWFkZXJzLFxuICB9O1xuICBjb25zdCByZXMgPSBhd2FpdCBjbGllbnQucG9zdDx7IGRhdGE/OiB7IG5hbWVzPzogdW5rbm93biB9IH0+KFxuICAgICcvb3JnYW5pemF0aW9uL25hbWVzJyxcbiAgICB7IG9yZ0lkcyB9LFxuICAgIHsgaGVhZGVycywgdGltZW91dDogb3B0cy50aW1lb3V0IH0sXG4gICk7XG4gIGlmIChyZXMuc3RhdHVzQ29kZSA+PSA0MDApIHJldHVybiB7fTtcbiAgY29uc3QgbmFtZXMgPSByZXMuYm9keT8uZGF0YT8ubmFtZXM7XG4gIGlmICghbmFtZXMgfHwgdHlwZW9mIG5hbWVzICE9PSAnb2JqZWN0JykgcmV0dXJuIHt9O1xuICBjb25zdCBvdXQ6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fTtcbiAgZm9yIChjb25zdCBbaWQsIG5hbWVdIG9mIE9iamVjdC5lbnRyaWVzKG5hbWVzIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KSkge1xuICAgIGlmICh0eXBlb2YgbmFtZSA9PT0gJ3N0cmluZycpIG91dFtpZC50b0xvd2VyQ2FzZSgpXSA9IG5hbWU7XG4gIH1cbiAgcmV0dXJuIG91dDtcbn1cblxuLyoqXG4gKiBDaGVjayB3aGV0aGVyIGB1c2VySWRgIGlzIGFuIEFDVElWRSBtZW1iZXIgb2YgYG9yZ0lkYCB2aWEgcGxhdGZvcm0nc1xuICogYEdFVCAvb3JnYW5pemF0aW9uLzppZC9tZW1iZXJzLzp1c2VySWQvZXhpc3RzYC4gUmV0dXJucyB0aGUgYm9vbGVhbiB0aGVcbiAqIHBsYXRmb3JtIHJlcG9ydHMuXG4gKlxuICogUmV0dXJucyBgdW5kZWZpbmVkYCAo4oCUIFwiY291bGRuJ3QgZGV0ZXJtaW5lXCIpIG9uIGEgbm9uLTJ4eCByZXNwb25zZSwgYW5kXG4gKiB0aHJvd3Mgb25seSBvbiBhIHRyYW5zcG9ydCBmYWlsdXJlIChjb25uZWN0aW9uL3RpbWVvdXQpIOKAlCBzbyB0aGUgY2FsbGVyXG4gKiBkaXN0aW5ndWlzaGVzIGEgZGVmaW5pdGl2ZSBub3QtYS1tZW1iZXIgKGBmYWxzZWApIGZyb20gYW4gaW5kZXRlcm1pbmF0ZVxuICogbG9va3VwIChgdW5kZWZpbmVkYCkgYW5kIGFwcGxpZXMgaXRzIG93biBmYWlsLW9wZW4vY2xvc2VkIHBvbGljeS4gVGhlIGBwYXRoYFxuICogZGlmZmVycyBmcm9tIHRoZSBwYXJlbnQvZGVzY2VuZGFudHMgc2hhcGUsIHNvIHRoaXMgYnVpbGRzIGl0cyBvd24gcmVxdWVzdC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZldGNoT3JnTWVtYmVyc2hpcChcbiAgb3JnSWQ6IHN0cmluZyxcbiAgdXNlcklkOiBzdHJpbmcsXG4gIG9wdHM6IE9yZ0hpZXJhcmNoeUh0dHBPcHRpb25zLFxuKTogUHJvbWlzZTxib29sZWFuIHwgdW5kZWZpbmVkPiB7XG4gIGNvbnN0IGNsaWVudCA9IG5ldyBJbnRlcm5hbEh0dHBDbGllbnQob3B0cy5zZXJ2aWNlKTtcbiAgY29uc3QgcGF0aCA9IGAvb3JnYW5pemF0aW9uLyR7ZW5jb2RlVVJJQ29tcG9uZW50KG9yZ0lkKX0vbWVtYmVycy8ke2VuY29kZVVSSUNvbXBvbmVudCh1c2VySWQpfS9leGlzdHNgO1xuICBjb25zdCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuICAgIEF1dGhvcml6YXRpb246IGdldFNlcnZpY2VBdXRoSGVhZGVyKHtcbiAgICAgIHNlcnZpY2VOYW1lOiBvcHRzLnNlcnZpY2VOYW1lLFxuICAgICAgb3JnSWQ6IG9wdHMuYXV0aE9yZ0lkID8/IG9yZ0lkLFxuICAgICAgcm9sZTogb3B0cy5yb2xlID8/ICdtZW1iZXInLFxuICAgIH0pLFxuICAgIC4uLm9wdHMuaGVhZGVycyxcbiAgfTtcbiAgY29uc3QgcmVzID0gYXdhaXQgY2xpZW50LmdldDx7IGRhdGE/OiB7IGlzTWVtYmVyPzogdW5rbm93biB9IH0+KHBhdGgsIHsgaGVhZGVycywgdGltZW91dDogb3B0cy50aW1lb3V0IH0pO1xuICBpZiAocmVzLnN0YXR1c0NvZGUgPj0gNDAwKSByZXR1cm4gdW5kZWZpbmVkO1xuICBjb25zdCBpc01lbWJlciA9IHJlcy5ib2R5Py5kYXRhPy5pc01lbWJlcjtcbiAgcmV0dXJuIHR5cGVvZiBpc01lbWJlciA9PT0gJ2Jvb2xlYW4nID8gaXNNZW1iZXIgOiB1bmRlZmluZWQ7XG59XG4iXX0=
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Org → team hierarchy traversal (org-team-hierarchy proposal).
3
+ *
4
+ * A "team" is an organization whose `parentOrgId` points at another org; a root
5
+ * org has `parentOrgId = null`. These helpers walk that single self-referential
6
+ * column, but hold **no database dependency** of their own: each takes a query
7
+ * callback so every service (platform, quota, …) can share the cycle-safe,
8
+ * depth-capped traversal logic while reading its own Mongoose model with its own
9
+ * `_id` casting rules.
10
+ *
11
+ * - {@link resolveOrgLineageWith} — UP: direct parent + root of the chain.
12
+ * - {@link resolveRootOrgIdWith} — UP: just the root id.
13
+ * - {@link isAncestorOrgWith} — UP: is A an ancestor of B?
14
+ * - {@link expandOrgScopeWith} — DOWN: self + all descendant org ids.
15
+ *
16
+ * Every org is flat today (`parentOrgId` null on all rows), so lineage resolves
17
+ * to `{ rootOrgId: self }` and scope to `[self]` — i.e. these are no-ops until
18
+ * orgs get parents.
19
+ */
20
+ /** Hard ceiling on ancestry/descendant traversal — cycle + abuse guard. */
21
+ export declare const MAX_ORG_DEPTH = 16;
22
+ /** Fetch an org's direct `parentOrgId`, or undefined for a root/missing org. */
23
+ export type GetParentOrgId = (orgId: string) => Promise<string | undefined>;
24
+ /** Fetch the direct child org ids of every org in `frontier` (one round of BFS). */
25
+ export type GetChildOrgIds = (frontier: string[]) => Promise<string[]>;
26
+ /** Normalize a Mixed/ObjectId/string org id to a non-empty string, or undefined. */
27
+ export declare function toOrgIdString(v: unknown): string | undefined;
28
+ export interface OrgLineage {
29
+ /** The org's direct parent id, or `undefined` when it's a root org. */
30
+ parentOrgId?: string;
31
+ /** Top of the ancestry chain. Equals the input `orgId` for a root org. */
32
+ rootOrgId: string;
33
+ }
34
+ /**
35
+ * Walk `parentOrgId` from `orgId` up to the root. Returns the direct parent (if
36
+ * any) and the root org id (the input itself when the org has no parent). On a
37
+ * detected cycle, a missing org, or the depth cap, traversal stops and the last
38
+ * reached id is treated as the root.
39
+ */
40
+ export declare function resolveOrgLineageWith(orgId: string, getParent: GetParentOrgId): Promise<OrgLineage>;
41
+ /** Walk `parentOrgId` up to the root and return just the root id. */
42
+ export declare function resolveRootOrgIdWith(orgId: string, getParent: GetParentOrgId): Promise<string>;
43
+ /**
44
+ * True when `ancestorOrgId` is an ancestor of `candidateOrgId` — i.e.
45
+ * `candidateOrgId` lives somewhere in the subtree rooted at `ancestorOrgId`.
46
+ * Walks `candidateOrgId`'s parent chain upward, depth-capped and cycle-safe.
47
+ * Returns `false` when the two are equal (an org is not its own ancestor) or
48
+ * unrelated.
49
+ */
50
+ export declare function isAncestorOrgWith(ancestorOrgId: string, candidateOrgId: string, getParent: GetParentOrgId): Promise<boolean>;
51
+ /**
52
+ * Expand `orgId` to itself plus every descendant org id (breadth-first over
53
+ * `parentOrgId`). This is the scope a parent org "sees" for visibility and
54
+ * analytics rollups: `[self, ...descendants]`. Returns `[orgId]` when the org
55
+ * has no children. Cycle-safe and depth-capped.
56
+ */
57
+ export declare function expandOrgScopeWith(orgId: string, getChildren: GetChildOrgIds): Promise<string[]>;