@pipeline-builder/pipeline-data 4.3.2 → 4.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/lib/api/crud-service.d.ts +7 -0
  2. package/lib/api/crud-service.js +14 -3
  3. package/lib/api/ecosystem-privacy.d.ts +25 -0
  4. package/lib/api/ecosystem-privacy.js +58 -0
  5. package/lib/api/pipeline-step-resolution.d.ts +111 -0
  6. package/lib/api/pipeline-step-resolution.js +163 -0
  7. package/lib/api/plugin-consumption-policy.d.ts +8 -2
  8. package/lib/api/plugin-consumption-policy.js +20 -3
  9. package/lib/api/plugin-resolution.d.ts +26 -9
  10. package/lib/api/plugin-resolution.js +51 -33
  11. package/lib/api/public-directory.js +1 -1
  12. package/lib/api/reporting/analytics-queries.d.ts +6 -4
  13. package/lib/api/reporting/analytics-queries.js +15 -14
  14. package/lib/api/reporting/deploy-rollup.d.ts +2 -0
  15. package/lib/api/reporting/deploy-rollup.js +5 -1
  16. package/lib/api/reporting/failure-classifier.d.ts +2 -6
  17. package/lib/api/reporting/failure-classifier.js +22 -1
  18. package/lib/api/reporting/ingest.js +73 -16
  19. package/lib/api/reporting/plugin-exposure.d.ts +261 -15
  20. package/lib/api/reporting/plugin-exposure.js +423 -38
  21. package/lib/api/reporting/stakeholder/compose.d.ts +1 -1
  22. package/lib/api/reporting/stakeholder/compose.js +30 -1
  23. package/lib/api/reporting/stakeholder/store.d.ts +16 -2
  24. package/lib/api/reporting/stakeholder/store.js +17 -6
  25. package/lib/api/reporting/types.d.ts +80 -1
  26. package/lib/api/reporting/types.js +1 -1
  27. package/lib/api/reporting-service.d.ts +11 -4
  28. package/lib/api/reporting-service.js +106 -43
  29. package/lib/api/soft-delete-sweep.d.ts +8 -18
  30. package/lib/api/soft-delete-sweep.js +35 -5
  31. package/lib/api/support-cases.d.ts +380 -0
  32. package/lib/api/support-cases.js +671 -0
  33. package/lib/api/version-references.d.ts +80 -0
  34. package/lib/api/version-references.js +135 -0
  35. package/lib/core/query-filters.d.ts +3 -3
  36. package/lib/core/query-filters.js +1 -1
  37. package/lib/database/drizzle-schema.d.ts +4858 -2759
  38. package/lib/database/drizzle-schema.js +13 -3
  39. package/lib/database/index.d.ts +1 -1
  40. package/lib/database/index.js +2 -2
  41. package/lib/database/schema/ecosystem.d.ts +53 -0
  42. package/lib/database/schema/ecosystem.js +14 -3
  43. package/lib/database/schema/pipeline.d.ts +38 -0
  44. package/lib/database/schema/pipeline.js +20 -2
  45. package/lib/database/schema/plugin.d.ts +265 -0
  46. package/lib/database/schema/plugin.js +34 -3
  47. package/lib/database/schema/reporting-analytics.d.ts +786 -4
  48. package/lib/database/schema/reporting-analytics.js +111 -5
  49. package/lib/database/schema/support.d.ts +1093 -0
  50. package/lib/database/schema/support.js +323 -0
  51. package/lib/database/tenancy.d.ts +7 -5
  52. package/lib/database/tenancy.js +1 -1
  53. package/lib/index.d.ts +7 -3
  54. package/lib/index.js +11 -3
  55. package/lib/testing/exports.json +57 -0
  56. package/package.json +4 -3
@@ -1,27 +1,17 @@
1
- /**
2
- * Shared soft-delete retention purge.
3
- *
4
- * Every {@link CrudService} entity soft-deletes (sets `deletedAt` + a
5
- * `purge_after` deadline). This orchestrator runs one purge pass across a set
6
- * of such entities, hard-deleting tombstones whose deadline has passed. It is
7
- * the `run` callback a service hands to `createScheduler` (leader-locked), so
8
- * only one pod sweeps at a time.
9
- *
10
- * Runs in an explicit **sysadmin tenant scope** (`runWithTenantContext({
11
- * isSuperAdmin: true })`) so the underlying `purgeExpired` spans every org and
12
- * bypasses RLS on FORCE'd tables (plugins/pipelines/etc.). A bare no-tenant
13
- * context would see zero rows on those tables — and in production (`strict`
14
- * mode) `withTenantTx` throws outright — so the scope is established here rather
15
- * than left to the caller. This is a cross-tenant housekeeping job, not a
16
- * per-org operation.
17
- */
18
- import { type Scheduler } from '@pipeline-builder/api-core';
1
+ import { type Scheduler, type RemoteAuditAction } from '@pipeline-builder/api-core';
19
2
  /** The slice of a CrudService the sweep needs, plus a label for logs/metrics. */
20
3
  export interface PurgeableEntity {
21
4
  /** Table/entity label (e.g. 'pipeline') for per-table logging. */
22
5
  name: string;
23
6
  /** Hard-delete up to `limit` expired tombstones; returns rows purged. */
24
7
  purgeExpired(now: Date, limit?: number): Promise<number>;
8
+ /**
9
+ * The entity's manual-purge action (e.g. `pipeline.purge`), reused for the
10
+ * sweep's hard-deletes with `details.trigger: 'retention'`. Omitted for rows
11
+ * that are not user-facing records (pending attachment blobs and the like) —
12
+ * those purge unaudited.
13
+ */
14
+ auditAction?: RemoteAuditAction;
25
15
  }
26
16
  /**
27
17
  * The services that run a soft-delete purge. A closed union, not a `string`,
@@ -16,8 +16,12 @@
16
16
  * mode) `withTenantTx` throws outright — so the scope is established here rather
17
17
  * than left to the caller. This is a cross-tenant housekeeping job, not a
18
18
  * per-org operation.
19
+ *
20
+ * Each tick is one audited job run: entities that name an `auditAction` get one
21
+ * event per tick that purged rows — system actor, the tick's `runId`, the count.
19
22
  */
20
- import { createLogger, createScheduler, envBool, envInt, leaderLockKey, DEFAULT_LEADER_LOCK_TTL_MS } from '@pipeline-builder/api-core';
23
+ import { randomUUID } from 'node:crypto';
24
+ import { createLogger, createScheduler, envBool, envInt, leaderLockKey, DEFAULT_LEADER_LOCK_TTL_MS, recordAudit, runWithAuditContext, SYSTEM_ACTOR_ID, SYSTEM_ORG_ID, } from '@pipeline-builder/api-core';
21
25
  import { runWithTenantContext } from '../database/tenancy.js';
22
26
  const logger = createLogger('soft-delete-sweep');
23
27
  /** Kill-switch: `SOFT_DELETE_PURGE_ENABLED=false` disables all hard-purging
@@ -37,6 +41,9 @@ export async function runSoftDeletePurge(entities, opts = {}, run) {
37
41
  logger.debug('Soft-delete purge disabled (SOFT_DELETE_PURGE_ENABLED=false)');
38
42
  return counts;
39
43
  }
44
+ // One id per tick: every audit event this run records carries it (as the
45
+ // job context's `runId` and in `details`), so a reviewer can regroup a sweep.
46
+ const runId = randomUUID();
40
47
  const batchSize = opts.batchSize ?? 500;
41
48
  const maxBatches = opts.maxBatchesPerEntity ?? 20;
42
49
  // One `now` for the whole tick — rows that expire mid-sweep wait for the next.
@@ -44,7 +51,7 @@ export async function runSoftDeletePurge(entities, opts = {}, run) {
44
51
  // Establish a sysadmin scope so every `purgeExpired` (which routes through
45
52
  // `withTenantTx`) bypasses RLS and spans all orgs. Without this, FORCE'd
46
53
  // tables return zero rows and production `strict` mode throws.
47
- return runWithTenantContext({ isSuperAdmin: true }, async () => {
54
+ return runWithAuditContext({ job: { jobId: 'soft-delete-purge', runId, trigger: 'schedule' } }, () => runWithTenantContext({ isSuperAdmin: true }, async () => {
48
55
  for (const entity of entities) {
49
56
  // Between entities: the leader lock was lost (another pod is taking over) or
50
57
  // we are shutting down. The remaining entities are picked up next tick —
@@ -76,12 +83,35 @@ export async function runSoftDeletePurge(entities, opts = {}, run) {
76
83
  });
77
84
  }
78
85
  }
79
- if (total > 0)
86
+ if (total > 0) {
80
87
  logger.info('Purged expired soft-deleted rows', { entity: entity.name, count: total });
88
+ if (entity.auditAction)
89
+ auditRetentionPurge(entity.auditAction, entity.name, total, runId);
90
+ }
81
91
  counts[entity.name] = total;
82
92
  }
83
93
  return counts;
84
- });
94
+ }));
95
+ }
96
+ /**
97
+ * The audit event for one entity's hard-deletes in one tick. A cross-tenant
98
+ * system job, so it is attributed to the system actor in the system org;
99
+ * `targetType` is the entity and there is no single `targetId`. Never throws —
100
+ * an unbound audit client (a wiring bug) is logged, and the sweep carries on.
101
+ */
102
+ function auditRetentionPurge(action, entity, count, runId) {
103
+ try {
104
+ recordAudit({
105
+ action,
106
+ actorId: SYSTEM_ACTOR_ID,
107
+ orgId: SYSTEM_ORG_ID,
108
+ targetType: entity,
109
+ details: { trigger: 'retention', runId, count },
110
+ });
111
+ }
112
+ catch (err) {
113
+ logger.error('Retention purge audit not recorded', { entity, count, error: String(err) });
114
+ }
85
115
  }
86
116
  /**
87
117
  * Build (but do NOT start) a leader-locked scheduler that runs the soft-delete
@@ -120,4 +150,4 @@ export function createSoftDeletePurgeScheduler(opts) {
120
150
  lock: { key: leaderLockKey(opts.service, 'soft-delete-purge'), ttlMs: lockTtlMs },
121
151
  });
122
152
  }
123
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29mdC1kZWxldGUtc3dlZXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL3NvZnQtZGVsZXRlLXN3ZWVwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7R0FnQkc7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLGVBQWUsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFrQixhQUFhLEVBQUUsMEJBQTBCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN2SixPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUU5RCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQTBCakQ7NkVBQzZFO0FBQzdFLE1BQU0sVUFBVSx3QkFBd0I7SUFDdEMsT0FBTyxPQUFPLENBQUMsMkJBQTJCLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxrQkFBa0IsQ0FDdEMsUUFBMkIsRUFDM0IsSUFBSSxHQUEyQixFQUFFLEVBQ2pDLEdBQTZCO0lBRTdCLE1BQU0sTUFBTSxHQUEyQixFQUFFLENBQUM7SUFDMUMsSUFBSSxDQUFDLHdCQUF3QixFQUFFLEVBQUUsQ0FBQztRQUNoQyxNQUFNLENBQUMsS0FBSyxDQUFDLDhEQUE4RCxDQUFDLENBQUM7UUFDN0UsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxTQUFTLElBQUksR0FBRyxDQUFDO0lBQ3hDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxtQkFBbUIsSUFBSSxFQUFFLENBQUM7SUFDbEQsK0VBQStFO0lBQy9FLE1BQU0sR0FBRyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7SUFFdkIsMkVBQTJFO0lBQzNFLHlFQUF5RTtJQUN6RSwrREFBK0Q7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RUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBRXJHLE1BQU0sQ0FBQyxJQUFJLENBQUMsc0NBQXNDLEVBQUU7UUFDbEQsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPO1FBQ3JCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztRQUMxQyxhQUFhLEVBQUUsVUFBVSxHQUFHLFNBQVM7S0FDdEMsQ0FBQyxDQUFDO0lBRUgsT0FBTyxlQUFlLENBQUM7UUFDckIsSUFBSSxFQUFFLHFCQUFxQixJQUFJLENBQUMsT0FBTyxFQUFFO1FBQ3pDLFVBQVU7UUFDVixjQUFjO1FBQ2QsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsRUFBRSxHQUFHLE1BQU0sa0JBQWtCLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzNFLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxhQUFhLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxtQkFBbUIsQ0FBQyxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUU7S0FDbEYsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFNoYXJlZCBzb2Z0LWRlbGV0ZSByZXRlbnRpb24gcHVyZ2UuXG4gKlxuICogRXZlcnkge0BsaW5rIENydWRTZXJ2aWNlfSBlbnRpdHkgc29mdC1kZWxldGVzIChzZXRzIGBkZWxldGVkQXRgICsgYVxuICogYHB1cmdlX2FmdGVyYCBkZWFkbGluZSkuIFRoaXMgb3JjaGVzdHJhdG9yIHJ1bnMgb25lIHB1cmdlIHBhc3MgYWNyb3NzIGEgc2V0XG4gKiBvZiBzdWNoIGVudGl0aWVzLCBoYXJkLWRlbGV0aW5nIHRvbWJzdG9uZXMgd2hvc2UgZGVhZGxpbmUgaGFzIHBhc3NlZC4gSXQgaXNcbiAqIHRoZSBgcnVuYCBjYWxsYmFjayBhIHNlcnZpY2UgaGFuZHMgdG8gYGNyZWF0ZVNjaGVkdWxlcmAgKGxlYWRlci1sb2NrZWQpLCBzb1xuICogb25seSBvbmUgcG9kIHN3ZWVwcyBhdCBhIHRpbWUuXG4gKlxuICogUnVucyBpbiBhbiBleHBsaWNpdCAqKnN5c2FkbWluIHRlbmFudCBzY29wZSoqIChgcnVuV2l0aFRlbmFudENvbnRleHQoe1xuICogaXNTdXBlckFkbWluOiB0cnVlIH0pYCkgc28gdGhlIHVuZGVybHlpbmcgYHB1cmdlRXhwaXJlZGAgc3BhbnMgZXZlcnkgb3JnIGFuZFxuICogYnlwYXNzZXMgUkxTIG9uIEZPUkNFJ2QgdGFibGVzIChwbHVnaW5zL3BpcGVsaW5lcy9ldGMuKS4gQSBiYXJlIG5vLXRlbmFudFxuICogY29udGV4dCB3b3VsZCBzZWUgemVybyByb3dzIG9uIHRob3NlIHRhYmxlcyDigJQgYW5kIGluIHByb2R1Y3Rpb24gKGBzdHJpY3RgXG4gKiBtb2RlKSBgd2l0aFRlbmFudFR4YCB0aHJvd3Mgb3V0cmlnaHQg4oCUIHNvIHRoZSBzY29wZSBpcyBlc3RhYmxpc2hlZCBoZXJlIHJhdGhlclxuICogdGhhbiBsZWZ0IHRvIHRoZSBjYWxsZXIuIFRoaXMgaXMgYSBjcm9zcy10ZW5hbnQgaG91c2VrZWVwaW5nIGpvYiwgbm90IGFcbiAqIHBlci1vcmcgb3BlcmF0aW9uLlxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciwgY3JlYXRlU2NoZWR1bGVyLCBlbnZCb29sLCBlbnZJbnQsIHR5cGUgU2NoZWR1bGVyLCBsZWFkZXJMb2NrS2V5LCBERUZBVUxUX0xFQURFUl9MT0NLX1RUTF9NUyB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL2FwaS1jb3JlJztcbmltcG9ydCB7IHJ1bldpdGhUZW5hbnRDb250ZXh0IH0gZnJvbSAnLi4vZGF0YWJhc2UvdGVuYW5jeS5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignc29mdC1kZWxldGUtc3dlZXAnKTtcblxuLyoqIFRoZSBzbGljZSBvZiBhIENydWRTZXJ2aWNlIHRoZSBzd2VlcCBuZWVkcywgcGx1cyBhIGxhYmVsIGZvciBsb2dzL21ldHJpY3MuICovXG5leHBvcnQgaW50ZXJmYWNlIFB1cmdlYWJsZUVudGl0eSB7XG4gIC8qKiBUYWJsZS9lbnRpdHkgbGFiZWwgKGUuZy4gJ3BpcGVsaW5lJykgZm9yIHBlci10YWJsZSBsb2dnaW5nLiAqL1xuICBuYW1lOiBzdHJpbmc7XG4gIC8qKiBIYXJkLWRlbGV0ZSB1cCB0byBgbGltaXRgIGV4cGlyZWQgdG9tYnN0b25lczsgcmV0dXJucyByb3dzIHB1cmdlZC4gKi9cbiAgcHVyZ2VFeHBpcmVkKG5vdzogRGF0ZSwgbGltaXQ/OiBudW1iZXIpOiBQcm9taXNlPG51bWJlcj47XG59XG5cbi8qKlxuICogVGhlIHNlcnZpY2VzIHRoYXQgcnVuIGEgc29mdC1kZWxldGUgcHVyZ2UuIEEgY2xvc2VkIHVuaW9uLCBub3QgYSBgc3RyaW5nYCxcbiAqIGJlY2F1c2UgdGhlIHZhbHVlIGJlY29tZXMgdGhlIExPQ0sgS0VZOiB0d28gc2VydmljZXMgcGFzc2luZyB0aGUgc2FtZSBzdHJpbmdcbiAqIHdvdWxkIHNpbGVudGx5IHNoYXJlIG9uZSBsb2NrLCBhbmQgd2hpY2hldmVyIGxvc3QgaXQgd291bGQgc2ltcGx5IG5ldmVyIHN3ZWVwIOKAlFxuICogYSB0eXBvIHRoYXQgc3RvcHMgYSBwdXJnZSB3aXRoIG5vIGVycm9yIGFueXdoZXJlLlxuICovXG5leHBvcnQgdHlwZSBTb2Z0RGVsZXRlU3dlZXBTZXJ2aWNlID0gJ3BpcGVsaW5lJyB8ICdwbHVnaW4nIHwgJ21lc3NhZ2UnIHwgJ2NvbXBsaWFuY2UnIHwgJ3BsYXRmb3JtJyB8ICdyZXBvcnRpbmcnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFNvZnREZWxldGVQdXJnZU9wdGlvbnMge1xuICAvKiogUm93cyBwZXIgYmF0Y2ggcGVyIHRhYmxlIChkZWZhdWx0IDUwMCkuICovXG4gIGJhdGNoU2l6ZT86IG51bWJlcjtcbiAgLyoqIE1heCBiYXRjaGVzIHBlciB0YWJsZSBwZXIgdGljayAoZGVmYXVsdCAyMCDihpIg4omkIDEway90YWJsZS90aWNrKTsgdGhlIHJlc3RcbiAgICogIGlzIGRlZmVycmVkIHRvIHRoZSBuZXh0IHRpY2sgc28gYSBodWdlIGJhY2tsb2cgY2FuJ3QgYmxvdyB0aGUgbG9jayBUVEwuICovXG4gIG1heEJhdGNoZXNQZXJFbnRpdHk/OiBudW1iZXI7XG59XG5cbi8qKiBLaWxsLXN3aXRjaDogYFNPRlRfREVMRVRFX1BVUkdFX0VOQUJMRUQ9ZmFsc2VgIGRpc2FibGVzIGFsbCBoYXJkLXB1cmdpbmdcbiAqICAodG9tYnN0b25lcyBzdGlsbCBhY2N1bXVsYXRlICsgc3RheSByZXN0b3JhYmxlOyBub3RoaW5nIGlzIGRlc3Ryb3llZCkuICovXG5leHBvcnQgZnVuY3Rpb24gaXNTb2Z0RGVsZXRlUHVyZ2VFbmFibGVkKCk6IGJvb2xlYW4ge1xuICByZXR1cm4gZW52Qm9vbCgnU09GVF9ERUxFVEVfUFVSR0VfRU5BQkxFRCcsIHRydWUpO1xufVxuXG4vKipcbiAqIFJ1biBvbmUgcHVyZ2Ugc3dlZXAgYWNyb3NzIGBlbnRpdGllc2AuIExvb3BzIGVhY2ggZW50aXR5J3MgYHB1cmdlRXhwaXJlZGAgaW5cbiAqIGJhdGNoZXMgdW50aWwgYSBiYXRjaCByZXR1cm5zIGA8IGJhdGNoU2l6ZWAgKGRyYWluZWQpIG9yIHRoZSBwZXItdGljayBjYXAgaXNcbiAqIGhpdCAobG9nZ2VkOyBjb250aW51ZWQgbmV4dCB0aWNrKS4gTmV2ZXIgdGhyb3dzIOKAlCBhIGZhaWxpbmcgZW50aXR5IGlzIGxvZ2dlZFxuICogYW5kIHRoZSBzd2VlcCBtb3ZlcyBvbi4gUmV0dXJucyBwZXItZW50aXR5IHB1cmdlZCBjb3VudHMgKGFsc28gbG9nZ2VkKS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1blNvZnREZWxldGVQdXJnZShcbiAgZW50aXRpZXM6IFB1cmdlYWJsZUVudGl0eVtdLFxuICBvcHRzOiBTb2Z0RGVsZXRlUHVyZ2VPcHRpb25zID0ge30sXG4gIHJ1bj86IHsgc2lnbmFsOiBBYm9ydFNpZ25hbCB9LFxuKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBudW1iZXI+PiB7XG4gIGNvbnN0IGNvdW50czogUmVjb3JkPHN0cmluZywgbnVtYmVyPiA9IHt9O1xuICBpZiAoIWlzU29mdERlbGV0ZVB1cmdlRW5hYmxlZCgpKSB7XG4gICAgbG9nZ2VyLmRlYnVnKCdTb2Z0LWRlbGV0ZSBwdXJnZSBkaXNhYmxlZCAoU09GVF9ERUxFVEVfUFVSR0VfRU5BQkxFRD1mYWxzZSknKTtcbiAgICByZXR1cm4gY291bnRzO1xuICB9XG5cbiAgY29uc3QgYmF0Y2hTaXplID0gb3B0cy5iYXRjaFNpemUgPz8gNTAwO1xuICBjb25zdCBtYXhCYXRjaGVzID0gb3B0cy5tYXhCYXRjaGVzUGVyRW50aXR5ID8/IDIwO1xuICAvLyBPbmUgYG5vd2AgZm9yIHRoZSB3aG9sZSB0aWNrIOKAlCByb3dzIHRoYXQgZXhwaXJlIG1pZC1zd2VlcCB3YWl0IGZvciB0aGUgbmV4dC5cbiAgY29uc3Qgbm93ID0gbmV3IERhdGUoKTtcblxuICAvLyBFc3RhYmxpc2ggYSBzeXNhZG1pbiBzY29wZSBzbyBldmVyeSBgcHVyZ2VFeHBpcmVkYCAod2hpY2ggcm91dGVzIHRocm91Z2hcbiAgLy8gYHdpdGhUZW5hbnRUeGApIGJ5cGFzc2VzIFJMUyBhbmQgc3BhbnMgYWxsIG9yZ3MuIFdpdGhvdXQgdGhpcywgRk9SQ0UnZFxuICAvLyB0YWJsZXMgcmV0dXJuIHplcm8gcm93cyBhbmQgcHJvZHVjdGlvbiBgc3RyaWN0YCBtb2RlIHRocm93cy5cbiAgcmV0dXJuIHJ1bldpdGhUZW5hbnRDb250ZXh0KHsgaXNTdXBlckFkbWluOiB0cnVlIH0sIGFzeW5jICgpID0+IHtcbiAgICBmb3IgKGNvbnN0IGVudGl0eSBvZiBlbnRpdGllcykge1xuICAgICAgLy8gQmV0d2VlbiBlbnRpdGllczogdGhlIGxlYWRlciBsb2NrIHdhcyBsb3N0IChhbm90aGVyIHBvZCBpcyB0YWtpbmcgb3Zlcikgb3JcbiAgICAgIC8vIHdlIGFyZSBzaHV0dGluZyBkb3duLiBUaGUgcmVtYWluaW5nIGVudGl0aWVzIGFyZSBwaWNrZWQgdXAgbmV4dCB0aWNrIOKAlFxuICAgICAgLy8gdGhlaXIgdG9tYnN0b25lcyBhcmUgc3RpbGwgZXhwaXJlZCDigJQgYW5kIHN0b3BwaW5nIGxldHMgdGhlIGxvY2sgZ28uXG4gICAgICBpZiAocnVuPy5zaWduYWwuYWJvcnRlZCkge1xuICAgICAgICBsb2dnZXIuaW5mbygnU29mdC1kZWxldGUgcHVyZ2Ugc3RvcHBpbmcgZWFybHknLCB7IHJlYXNvbjogU3RyaW5nKHJ1bi5zaWduYWwucmVhc29uID8/ICdhYm9ydGVkJyksIGF0RW50aXR5OiBlbnRpdHkubmFtZSB9KTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBsZXQgdG90YWwgPSAwO1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBtYXhCYXRjaGVzOyBpKyspIHtcbiAgICAgICAgLy8gQWxzbyBiZXR3ZWVuIEJBVENIRVM6IG9uZSBlbnRpdHkgd2l0aCBhIGxhcmdlIGJhY2tsb2cgY2FuIHJ1biBmb3IgbWFueVxuICAgICAgICAvLyBiYXRjaGVzLCB3aGljaCBpcyBleGFjdGx5IHRoZSBjYXNlIGEgVFRMIHVzZWQgdG8gaGF2ZSB0byBjb3Zlci5cbiAgICAgICAgaWYgKHJ1bj8uc2lnbmFsLmFib3J0ZWQpIGJyZWFrO1xuICAgICAgICBsZXQgcHVyZ2VkOiBudW1iZXI7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcHVyZ2VkID0gYXdhaXQgZW50aXR5LnB1cmdlRXhwaXJlZChub3csIGJhdGNoU2l6ZSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIGxvZ2dlci5lcnJvcignUHVyZ2UgYmF0Y2ggZmFpbGVkJywgeyBlbnRpdHk6IGVudGl0eS5uYW1lLCBlcnJvcjogU3RyaW5nKGVycikgfSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgdG90YWwgKz0gcHVyZ2VkO1xuICAgICAgICBpZiAocHVyZ2VkIDwgYmF0Y2hTaXplKSBicmVhazsgLy8gZHJhaW5lZFxuICAgICAgICBpZiAoaSA9PT0gbWF4QmF0Y2hlcyAtIDEpIHtcbiAgICAgICAgICBsb2dnZXIud2FybignUHVyZ2UgaGl0IHBlci10aWNrIGNhcDsgcmVtYWluaW5nIHRvbWJzdG9uZXMgZGVmZXJyZWQgdG8gbmV4dCB0aWNrJywge1xuICAgICAgICAgICAgZW50aXR5OiBlbnRpdHkubmFtZSwgcHVyZ2VkVGhpc1RpY2s6IHRvdGFsLFxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAodG90YWwgPiAwKSBsb2dnZXIuaW5mbygnUHVyZ2VkIGV4cGlyZWQgc29mdC1kZWxldGVkIHJvd3MnLCB7IGVudGl0eTogZW50aXR5Lm5hbWUsIGNvdW50OiB0b3RhbCB9KTtcbiAgICAgIGNvdW50c1tlbnRpdHkubmFtZV0gPSB0b3RhbDtcbiAgICB9XG5cbiAgICByZXR1cm4gY291bnRzO1xuICB9KTtcbn1cblxuLy8gLS0gU2NoZWR1bGVyIGZhY3RvcnkgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuZXhwb3J0IGludGVyZmFjZSBTb2Z0RGVsZXRlUHVyZ2VTY2hlZHVsZXJPcHRpb25zIGV4dGVuZHMgU29mdERlbGV0ZVB1cmdlT3B0aW9ucyB7XG4gIC8qKiBTZXJ2aWNlIGxhYmVsIGZvciBsb2dzICsgdGhlIGxlYWRlci1sb2NrIGtleSAoZS5nLiAncGlwZWxpbmUnLCAncGx1Z2luJykuICovXG4gIHNlcnZpY2U6IFNvZnREZWxldGVTd2VlcFNlcnZpY2U7XG4gIC8qKiBFbnRpdGllcyB0byBzd2VlcCDigJQgdXN1YWxseSB0aGUgc2VydmljZSdzIG93biBDcnVkU2VydmljZSBzaW5nbGV0b25zLiAqL1xuICBlbnRpdGllczogUHVyZ2VhYmxlRW50aXR5W107XG59XG5cbi8qKlxuICogQnVpbGQgKGJ1dCBkbyBOT1Qgc3RhcnQpIGEgbGVhZGVyLWxvY2tlZCBzY2hlZHVsZXIgdGhhdCBydW5zIHRoZSBzb2Z0LWRlbGV0ZVxuICogcHVyZ2Ugc3dlZXAgZm9yIGBlbnRpdGllc2Agb24gYSBjYWRlbmNlLiBUaGUgY2FsbGVyIGAuc3RhcnQoKWBzIGl0IGFuZCB3aXJlc1xuICogYHN0b3AoKWAgdG8gU0lHVEVSTS4gUmV0dXJucyBudWxsIHdoZW4gcHVyZ2luZyBpcyBkaXNhYmxlZC5cbiAqXG4gKiBFbnYgY29uZmlnOlxuICogICBTT0ZUX0RFTEVURV9QVVJHRV9FTkFCTEVEICAgICAgICAgICAoZGVmYXVsdCB0cnVlOyBmYWxzZSDih5IgbnVsbCwgbm8gc3dlZXApXG4gKiAgIFNPRlRfREVMRVRFX1BVUkdFX0lOVEVSVkFMX0hPVVJTICAgIChkZWZhdWx0IDYpXG4gKiAgIFNPRlRfREVMRVRFX1BVUkdFX1NUQVJUVVBfREVMQVlfTVMgIChkZWZhdWx0IDEyMDAwMCDigJQgMiBtaW4gc2V0dGxlKVxuICogICBTT0ZUX0RFTEVURV9QVVJHRV9MT0NLX1RUTF9NUyAgICAgICAoZGVmYXVsdCA5MDAwMDAg4oCUIDE1IG1pbjsgb3V0bGFzdHMgYVxuICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3dlZXAsIHN0YXlzIHVuZGVyIHRoZSBpbnRlcnZhbClcbiAqXG4gKiBBIGNyb3NzLXBvZCBsZWFkZXIgbG9jayAod2hlbiBSZWRpcyBpcyBjb25maWd1cmVkKSBtZWFucyBvbmx5IG9uZSByZXBsaWNhXG4gKiBzd2VlcHMgcGVyIHdpbmRvdzsgd2l0aG91dCBSZWRpcyBpdCBydW5zIG9uIGV2ZXJ5IHBvZCDigJQgc3RpbGwgc2FmZSwgc2luY2UgdGhlXG4gKiBwdXJnZSBpcyBhIGJhdGNoZWQgaWRlbXBvdGVudCBERUxFVEUsIGp1c3QgcmVkdW5kYW50LlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlU29mdERlbGV0ZVB1cmdlU2NoZWR1bGVyKG9wdHM6IFNvZnREZWxldGVQdXJnZVNjaGVkdWxlck9wdGlvbnMpOiBTY2hlZHVsZXIgfCBudWxsIHtcbiAgaWYgKCFpc1NvZnREZWxldGVQdXJnZUVuYWJsZWQoKSkge1xuICAgIGxvZ2dlci5pbmZvKCdTb2Z0LWRlbGV0ZSBwdXJnZSBzY2hlZHVsZXIgZGlzYWJsZWQgKFNPRlRfREVMRVRFX1BVUkdFX0VOQUJMRUQ9ZmFsc2UpJywgeyBzZXJ2aWNlOiBvcHRzLnNlcnZpY2UgfSk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBjb25zdCBpbnRlcnZhbE1zID0gZW52SW50KCdTT0ZUX0RFTEVURV9QVVJHRV9JTlRFUlZBTF9IT1VSUycsIDYsIHsgbWluOiAxIH0pICogNjAgKiA2MCAqIDEwMDA7XG4gIGNvbnN0IHN0YXJ0dXBEZWxheU1zID0gZW52SW50KCdTT0ZUX0RFTEVURV9QVVJHRV9TVEFSVFVQX0RFTEFZX01TJywgMTIwXzAwMCwgeyBtaW46IDAgfSk7XG4gIGNvbnN0IGxvY2tUdGxNcyA9IGVudkludCgnU09GVF9ERUxFVEVfUFVSR0VfTE9DS19UVExfTVMnLCBERUZBVUxUX0xFQURFUl9MT0NLX1RUTF9NUywgeyBtaW46IDEwMDAgfSk7XG5cbiAgbG9nZ2VyLmluZm8oJ1NvZnQtZGVsZXRlIHB1cmdlIHNjaGVkdWxlciBzdGFydGluZycsIHtcbiAgICBzZXJ2aWNlOiBvcHRzLnNlcnZpY2UsXG4gICAgZW50aXRpZXM6IG9wdHMuZW50aXRpZXMubWFwKChlKSA9PiBlLm5hbWUpLFxuICAgIGludGVydmFsSG91cnM6IGludGVydmFsTXMgLyAzXzYwMF8wMDAsXG4gIH0pO1xuXG4gIHJldHVybiBjcmVhdGVTY2hlZHVsZXIoe1xuICAgIG5hbWU6IGBzb2Z0LWRlbGV0ZS1wdXJnZToke29wdHMuc2VydmljZX1gLFxuICAgIGludGVydmFsTXMsXG4gICAgc3RhcnR1cERlbGF5TXMsXG4gICAgcnVuOiBhc3luYyAocnVuKSA9PiB7IGF3YWl0IHJ1blNvZnREZWxldGVQdXJnZShvcHRzLmVudGl0aWVzLCBvcHRzLCBydW4pOyB9LFxuICAgIGxvY2s6IHsga2V5OiBsZWFkZXJMb2NrS2V5KG9wdHMuc2VydmljZSwgJ3NvZnQtZGVsZXRlLXB1cmdlJyksIHR0bE1zOiBsb2NrVHRsTXMgfSxcbiAgfSk7XG59XG4iXX0=
153
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29mdC1kZWxldGUtc3dlZXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL3NvZnQtZGVsZXRlLXN3ZWVwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFFSCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3pDLE9BQU8sRUFDTCxZQUFZLEVBQUUsZUFBZSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQWtCLGFBQWEsRUFBRSwwQkFBMEIsRUFDekcsV0FBVyxFQUFFLG1CQUFtQixFQUFFLGVBQWUsRUFBRSxhQUFhLEdBQ2pFLE1BQU0sNEJBQTRCLENBQUM7QUFDcEMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFOUQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFpQ2pEOzZFQUM2RTtBQUM3RSxNQUFNLFVBQVUsd0JBQXdCO0lBQ3RDLE9BQU8sT0FBTyxDQUFDLDJCQUEyQixFQUFFLElBQUksQ0FBQyxDQUFDO0FBQ3BELENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsa0JBQWtCLENBQ3RDLFFBQTJCLEVBQzNCLElBQUksR0FBMkIsRUFBRSxFQUNqQyxHQUE2QjtJQUU3QixNQUFNLE1BQU0sR0FBMkIsRUFBRSxDQUFDO0lBQzFDLElBQUksQ0FBQyx3QkFBd0IsRUFBRSxFQUFFLENBQUM7UUFDaEMsTUFBTSxDQUFDLEtBQUssQ0FBQyw4REFBOEQsQ0FBQyxDQUFDO1FBQzdFLE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFRCx5RUFBeUU7SUFDekUsOEVBQThFO0lBQzlFLE1BQU0sS0FBSyxHQUFHLFVBQVUsRUFBRSxDQUFDO0lBQzNCLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxTQUFTLElBQUksR0FBRyxD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QU87UUFDckIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQzFDLGFBQWEsRUFBRSxVQUFVLEdBQUcsU0FBUztLQUN0QyxDQUFDLENBQUM7SUFFSCxPQUFPLGVBQWUsQ0FBQztRQUNyQixJQUFJLEVBQUUscUJBQXFCLElBQUksQ0FBQyxPQUFPLEVBQUU7UUFDekMsVUFBVTtRQUNWLGNBQWM7UUFDZCxHQUFHLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxFQUFFLEdBQUcsTUFBTSxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDM0UsSUFBSSxFQUFFLEVBQUUsR0FBRyxFQUFFLGFBQWEsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLG1CQUFtQixDQUFDLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRTtLQUNsRixDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogU2hhcmVkIHNvZnQtZGVsZXRlIHJldGVudGlvbiBwdXJnZS5cbiAqXG4gKiBFdmVyeSB7QGxpbmsgQ3J1ZFNlcnZpY2V9IGVudGl0eSBzb2Z0LWRlbGV0ZXMgKHNldHMgYGRlbGV0ZWRBdGAgKyBhXG4gKiBgcHVyZ2VfYWZ0ZXJgIGRlYWRsaW5lKS4gVGhpcyBvcmNoZXN0cmF0b3IgcnVucyBvbmUgcHVyZ2UgcGFzcyBhY3Jvc3MgYSBzZXRcbiAqIG9mIHN1Y2ggZW50aXRpZXMsIGhhcmQtZGVsZXRpbmcgdG9tYnN0b25lcyB3aG9zZSBkZWFkbGluZSBoYXMgcGFzc2VkLiBJdCBpc1xuICogdGhlIGBydW5gIGNhbGxiYWNrIGEgc2VydmljZSBoYW5kcyB0byBgY3JlYXRlU2NoZWR1bGVyYCAobGVhZGVyLWxvY2tlZCksIHNvXG4gKiBvbmx5IG9uZSBwb2Qgc3dlZXBzIGF0IGEgdGltZS5cbiAqXG4gKiBSdW5zIGluIGFuIGV4cGxpY2l0ICoqc3lzYWRtaW4gdGVuYW50IHNjb3BlKiogKGBydW5XaXRoVGVuYW50Q29udGV4dCh7XG4gKiBpc1N1cGVyQWRtaW46IHRydWUgfSlgKSBzbyB0aGUgdW5kZXJseWluZyBgcHVyZ2VFeHBpcmVkYCBzcGFucyBldmVyeSBvcmcgYW5kXG4gKiBieXBhc3NlcyBSTFMgb24gRk9SQ0UnZCB0YWJsZXMgKHBsdWdpbnMvcGlwZWxpbmVzL2V0Yy4pLiBBIGJhcmUgbm8tdGVuYW50XG4gKiBjb250ZXh0IHdvdWxkIHNlZSB6ZXJvIHJvd3Mgb24gdGhvc2UgdGFibGVzIOKAlCBhbmQgaW4gcHJvZHVjdGlvbiAoYHN0cmljdGBcbiAqIG1vZGUpIGB3aXRoVGVuYW50VHhgIHRocm93cyBvdXRyaWdodCDigJQgc28gdGhlIHNjb3BlIGlzIGVzdGFibGlzaGVkIGhlcmUgcmF0aGVyXG4gKiB0aGFuIGxlZnQgdG8gdGhlIGNhbGxlci4gVGhpcyBpcyBhIGNyb3NzLXRlbmFudCBob3VzZWtlZXBpbmcgam9iLCBub3QgYVxuICogcGVyLW9yZyBvcGVyYXRpb24uXG4gKlxuICogRWFjaCB0aWNrIGlzIG9uZSBhdWRpdGVkIGpvYiBydW46IGVudGl0aWVzIHRoYXQgbmFtZSBhbiBgYXVkaXRBY3Rpb25gIGdldCBvbmVcbiAqIGV2ZW50IHBlciB0aWNrIHRoYXQgcHVyZ2VkIHJvd3Mg4oCUIHN5c3RlbSBhY3RvciwgdGhlIHRpY2sncyBgcnVuSWRgLCB0aGUgY291bnQuXG4gKi9cblxuaW1wb3J0IHsgcmFuZG9tVVVJRCB9IGZyb20gJ25vZGU6Y3J5cHRvJztcbmltcG9ydCB7XG4gIGNyZWF0ZUxvZ2dlciwgY3JlYXRlU2NoZWR1bGVyLCBlbnZCb29sLCBlbnZJbnQsIHR5cGUgU2NoZWR1bGVyLCBsZWFkZXJMb2NrS2V5LCBERUZBVUxUX0xFQURFUl9MT0NLX1RUTF9NUyxcbiAgcmVjb3JkQXVkaXQsIHJ1bldpdGhBdWRpdENvbnRleHQsIFNZU1RFTV9BQ1RPUl9JRCwgU1lTVEVNX09SR19JRCwgdHlwZSBSZW1vdGVBdWRpdEFjdGlvbixcbn0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgcnVuV2l0aFRlbmFudENvbnRleHQgfSBmcm9tICcuLi9kYXRhYmFzZS90ZW5hbmN5LmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdzb2Z0LWRlbGV0ZS1zd2VlcCcpO1xuXG4vKiogVGhlIHNsaWNlIG9mIGEgQ3J1ZFNlcnZpY2UgdGhlIHN3ZWVwIG5lZWRzLCBwbHVzIGEgbGFiZWwgZm9yIGxvZ3MvbWV0cmljcy4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUHVyZ2VhYmxlRW50aXR5IHtcbiAgLyoqIFRhYmxlL2VudGl0eSBsYWJlbCAoZS5nLiAncGlwZWxpbmUnKSBmb3IgcGVyLXRhYmxlIGxvZ2dpbmcuICovXG4gIG5hbWU6IHN0cmluZztcbiAgLyoqIEhhcmQtZGVsZXRlIHVwIHRvIGBsaW1pdGAgZXhwaXJlZCB0b21ic3RvbmVzOyByZXR1cm5zIHJvd3MgcHVyZ2VkLiAqL1xuICBwdXJnZUV4cGlyZWQobm93OiBEYXRlLCBsaW1pdD86IG51bWJlcik6IFByb21pc2U8bnVtYmVyPjtcbiAgLyoqXG4gICAqIFRoZSBlbnRpdHkncyBtYW51YWwtcHVyZ2UgYWN0aW9uIChlLmcuIGBwaXBlbGluZS5wdXJnZWApLCByZXVzZWQgZm9yIHRoZVxuICAgKiBzd2VlcCdzIGhhcmQtZGVsZXRlcyB3aXRoIGBkZXRhaWxzLnRyaWdnZXI6ICdyZXRlbnRpb24nYC4gT21pdHRlZCBmb3Igcm93c1xuICAgKiB0aGF0IGFyZSBub3QgdXNlci1mYWNpbmcgcmVjb3JkcyAocGVuZGluZyBhdHRhY2htZW50IGJsb2JzIGFuZCB0aGUgbGlrZSkg4oCUXG4gICAqIHRob3NlIHB1cmdlIHVuYXVkaXRlZC5cbiAgICovXG4gIGF1ZGl0QWN0aW9uPzogUmVtb3RlQXVkaXRBY3Rpb247XG59XG5cbi8qKlxuICogVGhlIHNlcnZpY2VzIHRoYXQgcnVuIGEgc29mdC1kZWxldGUgcHVyZ2UuIEEgY2xvc2VkIHVuaW9uLCBub3QgYSBgc3RyaW5nYCxcbiAqIGJlY2F1c2UgdGhlIHZhbHVlIGJlY29tZXMgdGhlIExPQ0sgS0VZOiB0d28gc2VydmljZXMgcGFzc2luZyB0aGUgc2FtZSBzdHJpbmdcbiAqIHdvdWxkIHNpbGVudGx5IHNoYXJlIG9uZSBsb2NrLCBhbmQgd2hpY2hldmVyIGxvc3QgaXQgd291bGQgc2ltcGx5IG5ldmVyIHN3ZWVwIOKAlFxuICogYSB0eXBvIHRoYXQgc3RvcHMgYSBwdXJnZSB3aXRoIG5vIGVycm9yIGFueXdoZXJlLlxuICovXG5leHBvcnQgdHlwZSBTb2Z0RGVsZXRlU3dlZXBTZXJ2aWNlID0gJ3BpcGVsaW5lJyB8ICdwbHVnaW4nIHwgJ21lc3NhZ2UnIHwgJ2NvbXBsaWFuY2UnIHwgJ3BsYXRmb3JtJyB8ICdyZXBvcnRpbmcnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFNvZnREZWxldGVQdXJnZU9wdGlvbnMge1xuICAvKiogUm93cyBwZXIgYmF0Y2ggcGVyIHRhYmxlIChkZWZhdWx0IDUwMCkuICovXG4gIGJhdGNoU2l6ZT86IG51bWJlcjtcbiAgLyoqIE1heCBiYXRjaGVzIHBlciB0YWJsZSBwZXIgdGljayAoZGVmYXVsdCAyMCDihpIg4omkIDEway90YWJsZS90aWNrKTsgdGhlIHJlc3RcbiAgICogIGlzIGRlZmVycmVkIHRvIHRoZSBuZXh0IHRpY2sgc28gYSBodWdlIGJhY2tsb2cgY2FuJ3QgYmxvdyB0aGUgbG9jayBUVEwuICovXG4gIG1heEJhdGNoZXNQZXJFbnRpdHk/OiBudW1iZXI7XG59XG5cbi8qKiBLaWxsLXN3aXRjaDogYFNPRlRfREVMRVRFX1BVUkdFX0VOQUJMRUQ9ZmFsc2VgIGRpc2FibGVzIGFsbCBoYXJkLXB1cmdpbmdcbiAqICAodG9tYnN0b25lcyBzdGlsbCBhY2N1bXVsYXRlICsgc3RheSByZXN0b3JhYmxlOyBub3RoaW5nIGlzIGRlc3Ryb3llZCkuICovXG5leHBvcnQgZnVuY3Rpb24gaXNTb2Z0RGVsZXRlUHVyZ2VFbmFibGVkKCk6IGJvb2xlYW4ge1xuICByZXR1cm4gZW52Qm9vbCgnU09GVF9ERUxFVEVfUFVSR0VfRU5BQkxFRCcsIHRydWUpO1xufVxuXG4vKipcbiAqIFJ1biBvbmUgcHVyZ2Ugc3dlZXAgYWNyb3NzIGBlbnRpdGllc2AuIExvb3BzIGVhY2ggZW50aXR5J3MgYHB1cmdlRXhwaXJlZGAgaW5cbiAqIGJhdGNoZXMgdW50aWwgYSBiYXRjaCByZXR1cm5zIGA8IGJhdGNoU2l6ZWAgKGRyYWluZWQpIG9yIHRoZSBwZXItdGljayBjYXAgaXNcbiAqIGhpdCAobG9nZ2VkOyBjb250aW51ZWQgbmV4dCB0aWNrKS4gTmV2ZXIgdGhyb3dzIOKAlCBhIGZhaWxpbmcgZW50aXR5IGlzIGxvZ2dlZFxuICogYW5kIHRoZSBzd2VlcCBtb3ZlcyBvbi4gUmV0dXJucyBwZXItZW50aXR5IHB1cmdlZCBjb3VudHMgKGFsc28gbG9nZ2VkKS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1blNvZnREZWxldGVQdXJnZShcbiAgZW50aXRpZXM6IFB1cmdlYWJsZUVudGl0eVtdLFxuICBvcHRzOiBTb2Z0RGVsZXRlUHVyZ2VPcHRpb25zID0ge30sXG4gIHJ1bj86IHsgc2lnbmFsOiBBYm9ydFNpZ25hbCB9LFxuKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBudW1iZXI+PiB7XG4gIGNvbnN0IGNvdW50czogUmVjb3JkPHN0cmluZywgbnVtYmVyPiA9IHt9O1xuICBpZiAoIWlzU29mdERlbGV0ZVB1cmdlRW5hYmxlZCgpKSB7XG4gICAgbG9nZ2VyLmRlYnVnKCdTb2Z0LWRlbGV0ZSBwdXJnZSBkaXNhYmxlZCAoU09GVF9ERUxFVEVfUFVSR0VfRU5BQkxFRD1mYWxzZSknKTtcbiAgICByZXR1cm4gY291bnRzO1xuICB9XG5cbiAgLy8gT25lIGlkIHBlciB0aWNrOiBldmVyeSBhdWRpdCBldmVudCB0aGlzIHJ1biByZWNvcmRzIGNhcnJpZXMgaXQgKGFzIHRoZVxuICAvLyBqb2IgY29udGV4dCdzIGBydW5JZGAgYW5kIGluIGBkZXRhaWxzYCksIHNvIGEgcmV2aWV3ZXIgY2FuIHJlZ3JvdXAgYSBzd2VlcC5cbiAgY29uc3QgcnVuSWQgPSByYW5kb21VVUlEKCk7XG4gIGNvbnN0IGJhdGNoU2l6ZSA9IG9wdHMuYmF0Y2hTaXplID8/IDUwMDtcbiAgY29uc3QgbWF4QmF0Y2hlcyA9IG9wdHMubWF4QmF0Y2hlc1BlckVudGl0eSA/PyAyMDtcbiAgLy8gT25lIGBub3dgIGZvciB0aGUgd2hvbGUgdGljayDigJQgcm93cyB0aGF0IGV4cGlyZSBtaWQtc3dlZXAgd2FpdCBmb3IgdGhlIG5leHQuXG4gIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCk7XG5cbiAgLy8gRXN0YWJsaXNoIGEgc3lzYWRtaW4gc2NvcGUgc28gZXZlcnkgYHB1cmdlRXhwaXJlZGAgKHdoaWNoIHJvdXRlcyB0aHJvdWdoXG4gIC8vIGB3aXRoVGVuYW50VHhgKSBieXBhc3NlcyBSTFMgYW5kIHNwYW5zIGFsbCBvcmdzLiBXaXRob3V0IHRoaXMsIEZPUkNFJ2RcbiAgLy8gdGFibGVzIHJldHVybiB6ZXJvIHJvd3MgYW5kIHByb2R1Y3Rpb24gYHN0cmljdGAgbW9kZSB0aHJvd3MuXG4gIHJldHVybiBydW5XaXRoQXVkaXRDb250ZXh0KHsgam9iOiB7IGpvYklkOiAnc29mdC1kZWxldGUtcHVyZ2UnLCBydW5JZCwgdHJpZ2dlcjogJ3NjaGVkdWxlJyB9IH0sICgpID0+IHJ1bldpdGhUZW5hbnRDb250ZXh0KHsgaXNTdXBlckFkbWluOiB0cnVlIH0sIGFzeW5jICgpID0+IHtcbiAgICBmb3IgKGNvbnN0IGVudGl0eSBvZiBlbnRpdGllcykge1xuICAgICAgLy8gQmV0d2VlbiBlbnRpdGllczogdGhlIGxlYWRlciBsb2NrIHdhcyBsb3N0IChhbm90aGVyIHBvZCBpcyB0YWtpbmcgb3Zlcikgb3JcbiAgICAgIC8vIHdlIGFyZSBzaHV0dGluZyBkb3duLiBUaGUgcmVtYWluaW5nIGVudGl0aWVzIGFyZSBwaWNrZWQgdXAgbmV4dCB0aWNrIOKAlFxuICAgICAgLy8gdGhlaXIgdG9tYnN0b25lcyBhcmUgc3RpbGwgZXhwaXJlZCDigJQgYW5kIHN0b3BwaW5nIGxldHMgdGhlIGxvY2sgZ28uXG4gICAgICBpZiAocnVuPy5zaWduYWwuYWJvcnRlZCkge1xuICAgICAgICBsb2dnZXIuaW5mbygnU29mdC1kZWxldGUgcHVyZ2Ugc3RvcHBpbmcgZWFybHknLCB7IHJlYXNvbjogU3RyaW5nKHJ1bi5zaWduYWwucmVhc29uID8/ICdhYm9ydGVkJyksIGF0RW50aXR5OiBlbnRpdHkubmFtZSB9KTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBsZXQgdG90YWwgPSAwO1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBtYXhCYXRjaGVzOyBpKyspIHtcbiAgICAgICAgLy8gQWxzbyBiZXR3ZWVuIEJBVENIRVM6IG9uZSBlbnRpdHkgd2l0aCBhIGxhcmdlIGJhY2tsb2cgY2FuIHJ1biBmb3IgbWFueVxuICAgICAgICAvLyBiYXRjaGVzLCB3aGljaCBpcyBleGFjdGx5IHRoZSBjYXNlIGEgVFRMIHVzZWQgdG8gaGF2ZSB0byBjb3Zlci5cbiAgICAgICAgaWYgKHJ1bj8uc2lnbmFsLmFib3J0ZWQpIGJyZWFrO1xuICAgICAgICBsZXQgcHVyZ2VkOiBudW1iZXI7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcHVyZ2VkID0gYXdhaXQgZW50aXR5LnB1cmdlRXhwaXJlZChub3csIGJhdGNoU2l6ZSk7XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIGxvZ2dlci5lcnJvcignUHVyZ2UgYmF0Y2ggZmFpbGVkJywgeyBlbnRpdHk6IGVudGl0eS5uYW1lLCBlcnJvcjogU3RyaW5nKGVycikgfSk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgdG90YWwgKz0gcHVyZ2VkO1xuICAgICAgICBpZiAocHVyZ2VkIDwgYmF0Y2hTaXplKSBicmVhazsgLy8gZHJhaW5lZFxuICAgICAgICBpZiAoaSA9PT0gbWF4QmF0Y2hlcyAtIDEpIHtcbiAgICAgICAgICBsb2dnZXIud2FybignUHVyZ2UgaGl0IHBlci10aWNrIGNhcDsgcmVtYWluaW5nIHRvbWJzdG9uZXMgZGVmZXJyZWQgdG8gbmV4dCB0aWNrJywge1xuICAgICAgICAgICAgZW50aXR5OiBlbnRpdHkubmFtZSwgcHVyZ2VkVGhpc1RpY2s6IHRvdGFsLFxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAodG90YWwgPiAwKSB7XG4gICAgICAgIGxvZ2dlci5pbmZvKCdQdXJnZWQgZXhwaXJlZCBzb2Z0LWRlbGV0ZWQgcm93cycsIHsgZW50aXR5OiBlbnRpdHkubmFtZSwgY291bnQ6IHRvdGFsIH0pO1xuICAgICAgICBpZiAoZW50aXR5LmF1ZGl0QWN0aW9uKSBhdWRpdFJldGVudGlvblB1cmdlKGVudGl0eS5hdWRpdEFjdGlvbiwgZW50aXR5Lm5hbWUsIHRvdGFsLCBydW5JZCk7XG4gICAgICB9XG4gICAgICBjb3VudHNbZW50aXR5Lm5hbWVdID0gdG90YWw7XG4gICAgfVxuXG4gICAgcmV0dXJuIGNvdW50cztcbiAgfSkpO1xufVxuXG4vKipcbiAqIFRoZSBhdWRpdCBldmVudCBmb3Igb25lIGVudGl0eSdzIGhhcmQtZGVsZXRlcyBpbiBvbmUgdGljay4gQSBjcm9zcy10ZW5hbnRcbiAqIHN5c3RlbSBqb2IsIHNvIGl0IGlzIGF0dHJpYnV0ZWQgdG8gdGhlIHN5c3RlbSBhY3RvciBpbiB0aGUgc3lzdGVtIG9yZztcbiAqIGB0YXJnZXRUeXBlYCBpcyB0aGUgZW50aXR5IGFuZCB0aGVyZSBpcyBubyBzaW5nbGUgYHRhcmdldElkYC4gTmV2ZXIgdGhyb3dzIOKAlFxuICogYW4gdW5ib3VuZCBhdWRpdCBjbGllbnQgKGEgd2lyaW5nIGJ1ZykgaXMgbG9nZ2VkLCBhbmQgdGhlIHN3ZWVwIGNhcnJpZXMgb24uXG4gKi9cbmZ1bmN0aW9uIGF1ZGl0UmV0ZW50aW9uUHVyZ2UoYWN0aW9uOiBSZW1vdGVBdWRpdEFjdGlvbiwgZW50aXR5OiBzdHJpbmcsIGNvdW50OiBudW1iZXIsIHJ1bklkOiBzdHJpbmcpOiB2b2lkIHtcbiAgdHJ5IHtcbiAgICByZWNvcmRBdWRpdCh7XG4gICAgICBhY3Rpb24sXG4gICAgICBhY3RvcklkOiBTWVNURU1fQUNUT1JfSUQsXG4gICAgICBvcmdJZDogU1lTVEVNX09SR19JRCxcbiAgICAgIHRhcmdldFR5cGU6IGVudGl0eSxcbiAgICAgIGRldGFpbHM6IHsgdHJpZ2dlcjogJ3JldGVudGlvbicsIHJ1bklkLCBjb3VudCB9LFxuICAgIH0pO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICBsb2dnZXIuZXJyb3IoJ1JldGVudGlvbiBwdXJnZSBhdWRpdCBub3QgcmVjb3JkZWQnLCB7IGVudGl0eSwgY291bnQsIGVycm9yOiBTdHJpbmcoZXJyKSB9KTtcbiAgfVxufVxuXG4vLyAtLSBTY2hlZHVsZXIgZmFjdG9yeSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5leHBvcnQgaW50ZXJmYWNlIFNvZnREZWxldGVQdXJnZVNjaGVkdWxlck9wdGlvbnMgZXh0ZW5kcyBTb2Z0RGVsZXRlUHVyZ2VPcHRpb25zIHtcbiAgLyoqIFNlcnZpY2UgbGFiZWwgZm9yIGxvZ3MgKyB0aGUgbGVhZGVyLWxvY2sga2V5IChlLmcuICdwaXBlbGluZScsICdwbHVnaW4nKS4gKi9cbiAgc2VydmljZTogU29mdERlbGV0ZVN3ZWVwU2VydmljZTtcbiAgLyoqIEVudGl0aWVzIHRvIHN3ZWVwIOKAlCB1c3VhbGx5IHRoZSBzZXJ2aWNlJ3Mgb3duIENydWRTZXJ2aWNlIHNpbmdsZXRvbnMuICovXG4gIGVudGl0aWVzOiBQdXJnZWFibGVFbnRpdHlbXTtcbn1cblxuLyoqXG4gKiBCdWlsZCAoYnV0IGRvIE5PVCBzdGFydCkgYSBsZWFkZXItbG9ja2VkIHNjaGVkdWxlciB0aGF0IHJ1bnMgdGhlIHNvZnQtZGVsZXRlXG4gKiBwdXJnZSBzd2VlcCBmb3IgYGVudGl0aWVzYCBvbiBhIGNhZGVuY2UuIFRoZSBjYWxsZXIgYC5zdGFydCgpYHMgaXQgYW5kIHdpcmVzXG4gKiBgc3RvcCgpYCB0byBTSUdURVJNLiBSZXR1cm5zIG51bGwgd2hlbiBwdXJnaW5nIGlzIGRpc2FibGVkLlxuICpcbiAqIEVudiBjb25maWc6XG4gKiAgIFNPRlRfREVMRVRFX1BVUkdFX0VOQUJMRUQgICAgICAgICAgIChkZWZhdWx0IHRydWU7IGZhbHNlIOKHkiBudWxsLCBubyBzd2VlcClcbiAqICAgU09GVF9ERUxFVEVfUFVSR0VfSU5URVJWQUxfSE9VUlMgICAgKGRlZmF1bHQgNilcbiAqICAgU09GVF9ERUxFVEVfUFVSR0VfU1RBUlRVUF9ERUxBWV9NUyAgKGRlZmF1bHQgMTIwMDAwIOKAlCAyIG1pbiBzZXR0bGUpXG4gKiAgIFNPRlRfREVMRVRFX1BVUkdFX0xPQ0tfVFRMX01TICAgICAgIChkZWZhdWx0IDkwMDAwMCDigJQgMTUgbWluOyBvdXRsYXN0cyBhXG4gKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzd2VlcCwgc3RheXMgdW5kZXIgdGhlIGludGVydmFsKVxuICpcbiAqIEEgY3Jvc3MtcG9kIGxlYWRlciBsb2NrICh3aGVuIFJlZGlzIGlzIGNvbmZpZ3VyZWQpIG1lYW5zIG9ubHkgb25lIHJlcGxpY2FcbiAqIHN3ZWVwcyBwZXIgd2luZG93OyB3aXRob3V0IFJlZGlzIGl0IHJ1bnMgb24gZXZlcnkgcG9kIOKAlCBzdGlsbCBzYWZlLCBzaW5jZSB0aGVcbiAqIHB1cmdlIGlzIGEgYmF0Y2hlZCBpZGVtcG90ZW50IERFTEVURSwganVzdCByZWR1bmRhbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVTb2Z0RGVsZXRlUHVyZ2VTY2hlZHVsZXIob3B0czogU29mdERlbGV0ZVB1cmdlU2NoZWR1bGVyT3B0aW9ucyk6IFNjaGVkdWxlciB8IG51bGwge1xuICBpZiAoIWlzU29mdERlbGV0ZVB1cmdlRW5hYmxlZCgpKSB7XG4gICAgbG9nZ2VyLmluZm8oJ1NvZnQtZGVsZXRlIHB1cmdlIHNjaGVkdWxlciBkaXNhYmxlZCAoU09GVF9ERUxFVEVfUFVSR0VfRU5BQkxFRD1mYWxzZSknLCB7IHNlcnZpY2U6IG9wdHMuc2VydmljZSB9KTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIGNvbnN0IGludGVydmFsTXMgPSBlbnZJbnQoJ1NPRlRfREVMRVRFX1BVUkdFX0lOVEVSVkFMX0hPVVJTJywgNiwgeyBtaW46IDEgfSkgKiA2MCAqIDYwICogMTAwMDtcbiAgY29uc3Qgc3RhcnR1cERlbGF5TXMgPSBlbnZJbnQoJ1NPRlRfREVMRVRFX1BVUkdFX1NUQVJUVVBfREVMQVlfTVMnLCAxMjBfMDAwLCB7IG1pbjogMCB9KTtcbiAgY29uc3QgbG9ja1R0bE1zID0gZW52SW50KCdTT0ZUX0RFTEVURV9QVVJHRV9MT0NLX1RUTF9NUycsIERFRkFVTFRfTEVBREVSX0xPQ0tfVFRMX01TLCB7IG1pbjogMTAwMCB9KTtcblxuICBsb2dnZXIuaW5mbygnU29mdC1kZWxldGUgcHVyZ2Ugc2NoZWR1bGVyIHN0YXJ0aW5nJywge1xuICAgIHNlcnZpY2U6IG9wdHMuc2VydmljZSxcbiAgICBlbnRpdGllczogb3B0cy5lbnRpdGllcy5tYXAoKGUpID0+IGUubmFtZSksXG4gICAgaW50ZXJ2YWxIb3VyczogaW50ZXJ2YWxNcyAvIDNfNjAwXzAwMCxcbiAgfSk7XG5cbiAgcmV0dXJuIGNyZWF0ZVNjaGVkdWxlcih7XG4gICAgbmFtZTogYHNvZnQtZGVsZXRlLXB1cmdlOiR7b3B0cy5zZXJ2aWNlfWAsXG4gICAgaW50ZXJ2YWxNcyxcbiAgICBzdGFydHVwRGVsYXlNcyxcbiAgICBydW46IGFzeW5jIChydW4pID0+IHsgYXdhaXQgcnVuU29mdERlbGV0ZVB1cmdlKG9wdHMuZW50aXRpZXMsIG9wdHMsIHJ1bik7IH0sXG4gICAgbG9jazogeyBrZXk6IGxlYWRlckxvY2tLZXkob3B0cy5zZXJ2aWNlLCAnc29mdC1kZWxldGUtcHVyZ2UnKSwgdHRsTXM6IGxvY2tUdGxNcyB9LFxuICB9KTtcbn1cbiJdfQ==
@@ -0,0 +1,380 @@
1
+ /**
2
+ * Support-case persistence: the workflow half of a support thread.
3
+ *
4
+ * Here rather than in a service because two callers need it — platform owns the
5
+ * entitlement check and the writes, and the message service owns the thread the
6
+ * case points at — and neither should grow its own copy of the allocator.
7
+ *
8
+ * `messages` keeps carrying the conversation. These functions only ever touch
9
+ * the workflow axis: identity, status, ownership and the response clock.
10
+ */
11
+ import type { SupportCase } from '../database/schema/support.js';
12
+ /** Statuses a case can be in. `awaiting_customer` is what stops the clock. */
13
+ export declare const SUPPORT_CASE_STATUSES: readonly ['open', 'awaiting_support', 'awaiting_customer', 'resolved', 'closed'];
14
+ export type SupportCaseStatus = (typeof SUPPORT_CASE_STATUSES)[number];
15
+ /** The statuses the staff queue asks about — the ones still on us. */
16
+ export declare const QUEUE_STATUSES: readonly SupportCaseStatus[];
17
+ /** What opening a case needs. `level` is frozen onto the row at open. */
18
+ export interface OpenCaseInput {
19
+ /** The root `messages.id`. Becomes the canonical case identity. */
20
+ threadId: string;
21
+ orgId: string;
22
+ /** The support level HELD AT OPEN, frozen so a mid-case downgrade cannot
23
+ * change the promise underneath someone already waiting. */
24
+ level: string;
25
+ severity: string;
26
+ /** The opener. Also the initial tenant owner when they are a contact. */
27
+ openedBy: string;
28
+ /** True when the case was opened by spending a one-time incident rather than
29
+ * against a contact seat, so incident usage is answerable from the cases
30
+ * themselves and not only from a balance delta. */
31
+ openedWithIncident?: boolean;
32
+ /**
33
+ * When the first response is due, already computed by the caller.
34
+ *
35
+ * Passed IN rather than derived here: the business calendar is deployment
36
+ * config (`SUPPORT_BUSINESS_HOURS_*`) read by api-core's `responseDueAt`, and
37
+ * this module must not reach for env to answer a product question. Null is a
38
+ * legitimate value — a level with no published target, or a calendar too
39
+ * broken to derive one — and is stored as "no deadline" rather than guessed.
40
+ */
41
+ responseDueAt?: Date | null;
42
+ }
43
+ /**
44
+ * Open a case for an existing thread, allocating its display number.
45
+ *
46
+ * One transaction on purpose: the number and the row commit together or neither
47
+ * does. `support_case_org_number_uq` is the backstop — if the allocator is ever
48
+ * bypassed, the insert fails rather than issuing two cases the one identifier
49
+ * customers quote back.
50
+ */
51
+ export declare function openSupportCase(input: OpenCaseInput): Promise<SupportCase>;
52
+ /** The case for a thread, or null. The thread root id IS the case identity. */
53
+ export declare function findCaseByThread(threadId: string): Promise<SupportCase | null>;
54
+ /**
55
+ * The staff queue: open cases across ALL tenants, oldest first.
56
+ *
57
+ * The one read with no org predicate, so it runs under sysadmin scope and is
58
+ * served by the partial `support_case_queue_idx`.
59
+ *
60
+ * Ordered by `response_due_at` ASCENDING, NULLS LAST — soonest deadline first,
61
+ * which is what "most at risk" actually means. Arrival order was the earlier
62
+ * proxy for it and is a poor one: a standard case opened on Monday is owed
63
+ * Wednesday's close while a priority case opened on Tuesday is owed Wednesday's
64
+ * close too, so arrival order puts them in the wrong sequence whenever levels
65
+ * are mixed. Cases with no deadline (an unrecognised level, or one opened while
66
+ * the calendar was misconfigured) sort last rather than first, since a missing
67
+ * deadline is not an urgent one; `createdAt` breaks ties so the order is stable.
68
+ */
69
+ export declare function listQueue(limit?: number): Promise<SupportCase[]>;
70
+ /**
71
+ * Premium cases whose response target is approaching with no first response yet.
72
+ *
73
+ * THE on-call sweep's only query, and the reason the pager is worth owning the
74
+ * trigger for: this is an EXACT per-case deadline, where the fleet alert
75
+ * (`SupportPremiumFirstResponseAtRisk`) is a p90 over six hours that can only
76
+ * say the distribution already drifted — never which case is about to breach.
77
+ *
78
+ * The predicate matches `support_case_response_due_idx` exactly, which is a
79
+ * PARTIAL index on `(response_due_at) WHERE level = 'support_premium' AND
80
+ * first_response_at IS NULL AND deleted_at IS NULL`. Note `level` and not
81
+ * `is_premium`: that column lives on support_CONTACTS and marks an upgraded
82
+ * contact, while a case freezes the opening contact's effective level at open —
83
+ * so the case's own promise is its `level`, and flipping the contact later must
84
+ * not change a deadline already published to a customer. That matters because this runs on a timer
85
+ * forever: the rows it wants are a tiny and shrinking fraction of the table, so
86
+ * the index stays small while the table grows, and without it every tick is a
87
+ * scan.
88
+ *
89
+ * Deliberately NOT filtered on status. A case that is `awaiting_support` and one
90
+ * that is `open` both owe a first response, and reading the status here would
91
+ * duplicate a rule that `first_response_at IS NULL` already states exactly.
92
+ */
93
+ export declare function listCasesDueForPage(leadMs: number, limit?: number): Promise<SupportCase[]>;
94
+ /** An org's own cases, newest first. */
95
+ export declare function listOrgCases(orgId: string, limit?: number): Promise<SupportCase[]>;
96
+ /**
97
+ * Stamp the first staff response, once.
98
+ *
99
+ * `first_response_at IS NULL` is in the WHERE clause rather than checked by the
100
+ * caller: this is THE number the response target is sold on, so a second reply
101
+ * must not be able to move it. Returns whether this call was the one that
102
+ * stamped it.
103
+ */
104
+ export declare function stampFirstResponse(caseId: string, at?: Date): Promise<boolean>;
105
+ /**
106
+ * Move a case's status, maintaining the response clock.
107
+ *
108
+ * Entering `awaiting_customer` anchors `awaitingSince`; leaving it folds the
109
+ * elapsed span into `awaitingTotalSeconds` and clears the anchor. Ageing then
110
+ * subtracts the accumulator, so a case we answered in an hour and the tenant
111
+ * replied to four days later does not read as a four-day case — which is the
112
+ * number no customer would accept.
113
+ *
114
+ * The arithmetic is done in SQL against the stored anchor rather than from a
115
+ * caller-supplied duration, so a clock skew or a lost round trip cannot inflate
116
+ * or erase paused time.
117
+ */
118
+ export declare function setCaseStatus(caseId: string, status: SupportCaseStatus, actorId: string): Promise<SupportCase | null>;
119
+ /**
120
+ * Re-rank a case's severity.
121
+ *
122
+ * STAFF ONLY at the route layer, and that is the governance point rather than a
123
+ * permission detail: the tenant chooses severity when opening, so if they could
124
+ * also change it afterwards they would unilaterally pick the response target
125
+ * they are owed. Severity decides which target applies, so re-ranking has to be
126
+ * ours.
127
+ */
128
+ export declare function setCaseSeverity(caseId: string, severity: string, actorId: string): Promise<string | null>;
129
+ /**
130
+ * Set or clear the STAFF owner.
131
+ *
132
+ * `null` is a legitimate value: it is the unowned pool, and how "I cannot take
133
+ * this" is expressed without inventing a pseudo-assignee.
134
+ */
135
+ export declare function assignCase(caseId: string, assignedTo: string | null, actorId: string): Promise<SupportCase | null>;
136
+ /**
137
+ * Assign or release MANY cases at once.
138
+ *
139
+ * One statement rather than a loop, which is the point: a staff member clearing
140
+ * a queue of thirty selects thirty and acts once, and thirty round trips would
141
+ * make the obvious interaction the slow one. It also makes the write ATOMIC — a
142
+ * partial bulk assign leaves the queue in a state nobody chose, and the operator
143
+ * cannot tell which half took.
144
+ *
145
+ * Returns the ids that actually moved rather than a count, so a caller can
146
+ * report precisely what happened when the selection included something stale:
147
+ * a case closed or reassigned by somebody else between the read and the click.
148
+ * That is the normal case on a shared queue, not an edge one.
149
+ */
150
+ export declare function assignCases(caseIds: readonly string[], assignedTo: string | null, actorId: string): Promise<string[]>;
151
+ /**
152
+ * Close MANY cases at once.
153
+ *
154
+ * Separate from {@link assignCases} rather than a generic bulk-update, because
155
+ * closing maintains the response clock — it folds the open awaiting span into
156
+ * the accumulator and stamps `closedAt` — and a generic setter would quietly
157
+ * skip that. The arithmetic is the same SQL `setCaseStatus` uses, for the same
158
+ * reason: done against the STORED anchor so a clock skew cannot inflate paused
159
+ * time.
160
+ *
161
+ * Only ever used to CLOSE. Bulk REOPENING is deliberately not offered: reopening
162
+ * is staff-only and case-by-case by design, and a bulk reopen is how a queue
163
+ * someone has finished with comes back all at once.
164
+ */
165
+ export declare function closeCases(caseIds: readonly string[], actorId: string): Promise<string[]>;
166
+ /**
167
+ * Hand the case to another tenant contact.
168
+ *
169
+ * Note what this does NOT touch: `createdBy`. That is the immutable audit fact
170
+ * of who raised the case — mutating it to express "someone else owns this now"
171
+ * would destroy the only record of who actually opened it. The caller must have
172
+ * already checked that the target is a live designated contact; this function
173
+ * will not infer entitlement from a user id.
174
+ */
175
+ export declare function transferCaseToContact(caseId: string, tenantContact: string, actorId: string): Promise<SupportCase | null>;
176
+ /**
177
+ * A departing contact's open cases, so removal reassigns rather than orphans.
178
+ *
179
+ * Served by the partial `support_case_tenant_contact_idx`.
180
+ */
181
+ export declare function openCasesForContact(orgId: string, userId: string): Promise<SupportCase[]>;
182
+ /**
183
+ * Cases raised across a scope within the last `windowDays` — the FAIR-USE count.
184
+ *
185
+ * WHAT IT IS FOR. Support is priced per CONTACT, and that unit assumes contacts
186
+ * proxy for case volume. At ten seats they do; at a hundred they do not — three
187
+ * named contacts become an internal help desk FORWARDING tickets, so the price
188
+ * buys the interface rather than the load. This is the measurement that notices.
189
+ *
190
+ * WHY NOT THE QUOTA SERVICE. `QUOTA_RESET_DAYS` makes every tracked quota a
191
+ * 3-day period, and a support allowance is monthly-shaped: five a month does not
192
+ * divide into three days without rounding to something either far too loose or
193
+ * useless. This table IS the counter — one range scan on
194
+ * `support_case_org_created_idx`, no period mismatch, and nothing to drift out
195
+ * of sync with reality.
196
+ *
197
+ * COUNTS EVERY CASE, INCLUDING CLOSED AND SOFT-DELETED. The question is how much
198
+ * support was CONSUMED in the window, and a case that was answered and closed
199
+ * consumed it just as much as an open one. Excluding closed cases would make the
200
+ * allowance trivially evadable by closing promptly — the opposite of the
201
+ * behaviour anyone wants to discourage.
202
+ */
203
+ export declare function countCasesInWindow(orgIds: readonly string[], windowDays: number): Promise<number>;
204
+ /**
205
+ * Record the customer's verdict on a case.
206
+ *
207
+ * ONLY ON A TERMINAL CASE, enforced in the WHERE clause rather than by the
208
+ * caller: rating a case still being worked would measure an answer nobody has
209
+ * finished giving, and putting the condition in SQL means no route can forget
210
+ * it.
211
+ *
212
+ * RE-RATEABLE on purpose. A customer who rates in frustration and comes back a
213
+ * day later should be able to change it; freezing the first number would make
214
+ * the metric measure how people felt at their worst moment. `satisfaction_at`
215
+ * moves with it, so the ANSWER and WHEN it was given stay together.
216
+ *
217
+ * Returns whether a row moved — false means the case is missing, soft-deleted,
218
+ * or not closed yet, which the caller must distinguish from a stored zero.
219
+ */
220
+ export declare function rateCase(caseId: string, satisfaction: number, comment: string | null): Promise<boolean>;
221
+ /** One staff note, as the queue UI renders it. */
222
+ export interface SupportCaseNote {
223
+ id: string;
224
+ caseId: string;
225
+ content: string;
226
+ createdBy: string;
227
+ createdAt: Date;
228
+ }
229
+ /**
230
+ * Add a staff-only note to a case.
231
+ *
232
+ * `orgId` is denormalised onto the row from the CASE, not taken from the author:
233
+ * a note belongs to the tenant whose case it annotates, and sourcing it from
234
+ * the staff author's own org (the system org) would put every note outside the
235
+ * tenant scope that is supposed to contain it.
236
+ *
237
+ * STAFF ONLY at the route layer, and the separate table is the reason that is
238
+ * safe: `messages` is read through one shared visibility predicate, so a flag
239
+ * there would leak an internal note from any read path that forgot the extra
240
+ * condition. A separate table cannot leak by omission — it has to be joined
241
+ * deliberately.
242
+ */
243
+ export declare function addCaseNote(caseId: string, orgId: string, content: string, createdBy: string): Promise<SupportCaseNote>;
244
+ /** Live notes on a case, oldest first — they read as a running commentary. */
245
+ export declare function listCaseNotes(caseId: string): Promise<SupportCaseNote[]>;
246
+ /**
247
+ * Soft-delete a note.
248
+ *
249
+ * Soft because a note is a record of what the desk believed at the time, and a
250
+ * dispute may turn on it. Returns whether a row moved, so a second delete is a
251
+ * reported no-op rather than a silent success.
252
+ */
253
+ export declare function deleteCaseNote(noteId: string, deletedBy: string): Promise<boolean>;
254
+ /** A reusable staff reply template. */
255
+ export interface CannedResponse {
256
+ id: string;
257
+ title: string;
258
+ body: string;
259
+ }
260
+ /**
261
+ * Live templates, alphabetical — the picker's only query.
262
+ *
263
+ * No org scope because the table has none: these are the vendor's own wording
264
+ * for recurring questions, authored once and reused across every customer.
265
+ * Scoping them per tenant would be the opposite of the point.
266
+ */
267
+ export declare function listCannedResponses(): Promise<CannedResponse[]>;
268
+ /** Create a template. */
269
+ export declare function createCannedResponse(title: string, body: string, actorId: string): Promise<CannedResponse>;
270
+ /**
271
+ * Edit a live template.
272
+ *
273
+ * Guarded on `is_active` so a retired template cannot be edited back into
274
+ * circulation — a resurrection should be a deliberate new row, not a side
275
+ * effect of fixing a typo.
276
+ */
277
+ export declare function updateCannedResponse(id: string, fields: {
278
+ title?: string;
279
+ body?: string;
280
+ }, actorId: string): Promise<boolean>;
281
+ /**
282
+ * Retire a template.
283
+ *
284
+ * SOFT, because a retired template is still the explanation of what a past
285
+ * reply said — deleting the row would leave old correspondence unaccountable
286
+ * for its own wording.
287
+ */
288
+ export declare function deleteCannedResponse(id: string, actorId: string): Promise<boolean>;
289
+ /**
290
+ * Live designated contacts across a set of orgs (an account subtree).
291
+ *
292
+ * Takes the whole scope in one query rather than per-org because the allowance
293
+ * POOLS at the root: a person designated on a team spends the account's
294
+ * allowance, so counting team-by-team and summing would be the same number by
295
+ * accident and the wrong shape the moment pooling changes.
296
+ */
297
+ export declare function countActiveSupportContacts(orgIds: readonly string[]): Promise<number>;
298
+ /**
299
+ * Live contacts for the tenant owner picker.
300
+ *
301
+ * SCOPE IS THE CALLER'S CHOICE, and the default is ONE ORG. The allowance pools
302
+ * subtree-wide, but ownership does not: a team reassigns a case only among its
303
+ * OWN contacts, so a pooled allowance never turns into one team's cases being
304
+ * readable by a sibling. Pass a single org id for the picker; pass the expanded
305
+ * subtree only where the pooled CAP is the question (see
306
+ * `countActiveSupportContacts`).
307
+ *
308
+ * Taking a list rather than a single id keeps that decision at the call site
309
+ * instead of hiding it here — the two scopes answer differently and the wrong
310
+ * one leaks cases across teams.
311
+ */
312
+ export declare function listActiveSupportContacts(orgIds: readonly string[]): Promise<{
313
+ orgId: string;
314
+ userId: string;
315
+ isPremium: boolean;
316
+ }[]>;
317
+ /**
318
+ * Whether this user is a live designated contact in any org of the scope.
319
+ *
320
+ * Transfer validation must pass the case's OWN org, not the subtree: accepting a
321
+ * sibling team's contact as a valid transfer target is exactly the cross-team
322
+ * leak the own-org ownership rule exists to prevent.
323
+ */
324
+ export declare function isSupportContact(orgIds: readonly string[], userId: string): Promise<boolean>;
325
+ /**
326
+ * Designate a support contact.
327
+ *
328
+ * The CAP is not enforced here — the caller holds it, because the limit lives on
329
+ * the Mongo org doc and this module only reaches Postgres. Mirrors how seats are
330
+ * capped in platform rather than in the row writer.
331
+ *
332
+ * `support_contact_org_user_uq` is partial on `is_active = true`, so
333
+ * re-designating someone previously revoked inserts a fresh row rather than
334
+ * colliding — the revoked row stays as the record that they once held it.
335
+ */
336
+ export declare function designateSupportContact(orgId: string, userId: string, designatedBy: string, isPremium?: boolean): Promise<void>;
337
+ /**
338
+ * Live contacts in a scope that are UPGRADED to premium — the cap's counterpart.
339
+ *
340
+ * Counted separately from the baseline total because the two caps are separate:
341
+ * an account holds N contacts (`supportContacts`) and upgrades M of them
342
+ * (`supportPremiumContacts`), M ≤ N. Pooled over whatever scope the caller
343
+ * passes, exactly like {@link countActiveSupportContacts} — subtree for the cap,
344
+ * one org for an ownership question.
345
+ */
346
+ export declare function countPremiumSupportContacts(orgIds: readonly string[]): Promise<number>;
347
+ /**
348
+ * Whether this user is a live PREMIUM contact in the scope.
349
+ *
350
+ * The case-open path reads this to decide which level to freeze onto a new case:
351
+ * premium when the opener is upgraded, the account's baseline otherwise. Pass the
352
+ * case's OWN org — the upgrade follows the person in the org that nominated
353
+ * them, and a sibling team's upgrade is not this org's to spend.
354
+ */
355
+ export declare function isPremiumSupportContact(orgIds: readonly string[], userId: string): Promise<boolean>;
356
+ /**
357
+ * Upgrade or downgrade a LIVE contact.
358
+ *
359
+ * Guarded on `is_active` so a revoked row cannot be upgraded back into
360
+ * relevance, and returns whether a row actually moved — the caller counts
361
+ * against the premium cap before calling, and a silent no-op would let that
362
+ * count drift from reality.
363
+ *
364
+ * Deliberately does NOT touch the case rows: `support_case.level` is frozen at
365
+ * open, so downgrading somebody never weakens a promise already made on a case
366
+ * they raised while upgraded.
367
+ */
368
+ export declare function setSupportContactPremium(orgId: string, userId: string, isPremium: boolean): Promise<boolean>;
369
+ /**
370
+ * Revoke a designation, freeing the slot.
371
+ *
372
+ * Returns the user's still-open cases so the caller can reassign them instead of
373
+ * orphaning — scope C4's answer. Revoking is deliberately not blocked on them:
374
+ * refusing to remove a departed employee because they still own cases would be
375
+ * worse than handing the cases on.
376
+ */
377
+ export declare function revokeSupportContact(orgId: string, userId: string, revokedBy: string): Promise<{
378
+ revoked: boolean;
379
+ openCases: SupportCase[];
380
+ }>;