@rudderhq/server 0.7.0 → 0.7.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 (139) hide show
  1. package/dist/bootstrap/register-api-routes.js +1 -1
  2. package/dist/bootstrap/register-api-routes.js.map +1 -1
  3. package/dist/bundled-plugins/plugin-linear/dist/worker.js +3 -1
  4. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
  5. package/dist/product-analytics-collector-config.d.ts +33 -0
  6. package/dist/product-analytics-collector-config.d.ts.map +1 -0
  7. package/dist/product-analytics-collector-config.js +67 -0
  8. package/dist/product-analytics-collector-config.js.map +1 -0
  9. package/dist/product-analytics-collector-server.d.ts +26 -0
  10. package/dist/product-analytics-collector-server.d.ts.map +1 -0
  11. package/dist/product-analytics-collector-server.js +150 -0
  12. package/dist/product-analytics-collector-server.js.map +1 -0
  13. package/dist/routes/instance-settings.d.ts +1 -0
  14. package/dist/routes/instance-settings.d.ts.map +1 -1
  15. package/dist/routes/instance-settings.js +58 -0
  16. package/dist/routes/instance-settings.js.map +1 -1
  17. package/dist/routes/product-analytics-collector-report.d.ts +7 -0
  18. package/dist/routes/product-analytics-collector-report.d.ts.map +1 -0
  19. package/dist/routes/product-analytics-collector-report.js +159 -0
  20. package/dist/routes/product-analytics-collector-report.js.map +1 -0
  21. package/dist/routes/product-analytics-collector.d.ts +14 -2
  22. package/dist/routes/product-analytics-collector.d.ts.map +1 -1
  23. package/dist/routes/product-analytics-collector.js +141 -4
  24. package/dist/routes/product-analytics-collector.js.map +1 -1
  25. package/dist/routes/product-analytics.d.ts.map +1 -1
  26. package/dist/routes/product-analytics.js +40 -3
  27. package/dist/routes/product-analytics.js.map +1 -1
  28. package/dist/services/instance-settings.d.ts.map +1 -1
  29. package/dist/services/instance-settings.js +4 -0
  30. package/dist/services/instance-settings.js.map +1 -1
  31. package/dist/services/product-analytics-collector-maintenance.d.ts +25 -0
  32. package/dist/services/product-analytics-collector-maintenance.d.ts.map +1 -0
  33. package/dist/services/product-analytics-collector-maintenance.js +309 -0
  34. package/dist/services/product-analytics-collector-maintenance.js.map +1 -0
  35. package/dist/services/product-analytics-collector-maintenance.test.d.ts +2 -0
  36. package/dist/services/product-analytics-collector-maintenance.test.d.ts.map +1 -0
  37. package/dist/services/product-analytics-collector-maintenance.test.js +14 -0
  38. package/dist/services/product-analytics-collector-maintenance.test.js.map +1 -0
  39. package/dist/services/product-analytics-collector.d.ts +75 -3
  40. package/dist/services/product-analytics-collector.d.ts.map +1 -1
  41. package/dist/services/product-analytics-collector.js +363 -12
  42. package/dist/services/product-analytics-collector.js.map +1 -1
  43. package/dist/services/product-analytics-collector.test.js +25 -0
  44. package/dist/services/product-analytics-collector.test.js.map +1 -1
  45. package/dist/services/product-analytics-fixture.d.ts +7 -0
  46. package/dist/services/product-analytics-fixture.d.ts.map +1 -0
  47. package/dist/services/product-analytics-fixture.js +65 -0
  48. package/dist/services/product-analytics-fixture.js.map +1 -0
  49. package/dist/services/product-analytics-prd-gates.test.d.ts +2 -0
  50. package/dist/services/product-analytics-prd-gates.test.d.ts.map +1 -0
  51. package/dist/services/product-analytics-prd-gates.test.js +135 -0
  52. package/dist/services/product-analytics-prd-gates.test.js.map +1 -0
  53. package/dist/services/product-analytics.d.ts +17 -5
  54. package/dist/services/product-analytics.d.ts.map +1 -1
  55. package/dist/services/product-analytics.js +94 -12
  56. package/dist/services/product-analytics.js.map +1 -1
  57. package/package.json +17 -14
  58. package/ui-dist/assets/{LibraryLiveSurface-DeUIV5N_.js → LibraryLiveSurface-CZED7oKj.js} +1 -1
  59. package/ui-dist/assets/{abnfDiagram-VRR7QNED-BaoLM9Pd.js → abnfDiagram-VRR7QNED-BoPyIoAq.js} +1 -1
  60. package/ui-dist/assets/{arc-Cx0L6o2z.js → arc-DTYs4RCH.js} +1 -1
  61. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-zVfZP7v4.js → architectureDiagram-ZJ3FMSHR-BGfp2S_3.js} +1 -1
  62. package/ui-dist/assets/{blockDiagram-677ZJIJ3-BiWWFP9R.js → blockDiagram-677ZJIJ3-aW0EGIUf.js} +1 -1
  63. package/ui-dist/assets/{c4Diagram-LMCZKHZV-CX65GSJ6.js → c4Diagram-LMCZKHZV--1itKgA2.js} +1 -1
  64. package/ui-dist/assets/channel-DLNwhPnC.js +1 -0
  65. package/ui-dist/assets/{chunk-2Q5K7J3B-CH6PlelX.js → chunk-2Q5K7J3B-ZIqIozPQ.js} +1 -1
  66. package/ui-dist/assets/{chunk-32BRIVSS-C7ZEXYO8.js → chunk-32BRIVSS-DiwBEZVQ.js} +1 -1
  67. package/ui-dist/assets/{chunk-5VM5RSS4-CyInWuue.js → chunk-5VM5RSS4-CLOrTmL8.js} +1 -1
  68. package/ui-dist/assets/{chunk-EX3LRPZG-D58qFIdM.js → chunk-EX3LRPZG-D6xZzF47.js} +1 -1
  69. package/ui-dist/assets/{chunk-JWPE2WC7-CJ3Qx30f.js → chunk-JWPE2WC7-B9tYvTZl.js} +1 -1
  70. package/ui-dist/assets/{chunk-MOJQB5TN-BO4bKVLH.js → chunk-MOJQB5TN-D_qo-SzM.js} +1 -1
  71. package/ui-dist/assets/{chunk-RYQCIY6F-DxBmxcYc.js → chunk-RYQCIY6F-ChPHGc2P.js} +1 -1
  72. package/ui-dist/assets/{chunk-V7JOEXUC-Chdnd6Lq.js → chunk-V7JOEXUC-Dk6GbZYp.js} +1 -1
  73. package/ui-dist/assets/{chunk-VR4S4FIN-GkW_x_pD.js → chunk-VR4S4FIN-B6AREC1T.js} +1 -1
  74. package/ui-dist/assets/{chunk-XXDRQBXY-BJyBGcSF.js → chunk-XXDRQBXY-CUyO-JUr.js} +1 -1
  75. package/ui-dist/assets/classDiagram-OUVF2IWQ-JsIc-rlc.js +1 -0
  76. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-JsIc-rlc.js +1 -0
  77. package/ui-dist/assets/{cose-bilkent-JH36ORCC-BWP2r8TX.js → cose-bilkent-JH36ORCC-BCxIInLZ.js} +1 -1
  78. package/ui-dist/assets/{cynefin-VYW2F7L2-C08dfI3w.js → cynefin-VYW2F7L2-DqsnpnHA.js} +1 -1
  79. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-h6dXjTaO.js → cynefinDiagram-TSTJHNR4-BD7QQNLW.js} +1 -1
  80. package/ui-dist/assets/{dagre-VKFMJZFB-CWDgoFQD.js → dagre-VKFMJZFB-DaE160f7.js} +1 -1
  81. package/ui-dist/assets/{diagram-FQU43EPY-CK1j48d-.js → diagram-FQU43EPY-DZ0c59y8.js} +1 -1
  82. package/ui-dist/assets/{diagram-G47NLZAW-CoSiG57q.js → diagram-G47NLZAW-C_413g5E.js} +1 -1
  83. package/ui-dist/assets/{diagram-NH7WQ7WH-DRG-xXGQ.js → diagram-NH7WQ7WH-D_o9pXB-.js} +1 -1
  84. package/ui-dist/assets/{diagram-OA4YK3LP-D04mMwYs.js → diagram-OA4YK3LP-BI2xMqiW.js} +1 -1
  85. package/ui-dist/assets/{diagram-WEI45ONY-ZkC1ZQMq.js → diagram-WEI45ONY-hhM8R_14.js} +1 -1
  86. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-BxMIw9PZ.js → ebnfDiagram-CCIWWBDH-DfFgEtmG.js} +1 -1
  87. package/ui-dist/assets/{erDiagram-Q63AITRT-B4Zwbn84.js → erDiagram-Q63AITRT-CCw4ysHK.js} +1 -1
  88. package/ui-dist/assets/{flowDiagram-23GEKE2U-D5pYz8Px.js → flowDiagram-23GEKE2U-CzC1SMmo.js} +1 -1
  89. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-RSramodc.js → ganttDiagram-NO4QXBWP-CbPJIrIw.js} +1 -1
  90. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-BFQfCkMd.js → gitGraphDiagram-IHSO6WYX-jCxJ66_C.js} +1 -1
  91. package/ui-dist/assets/{graph-DuSS8poS.js → graph-BfNZ9Jzj.js} +1 -1
  92. package/ui-dist/assets/{index-DRbt_Svi.js → index-B3h9Vdxx.js} +1 -1
  93. package/ui-dist/assets/{index-9U78M9Np.js → index-B8q4cbOz.js} +1 -1
  94. package/ui-dist/assets/{index-olsHIcXs.js → index-BE1vSI6a.js} +1 -1
  95. package/ui-dist/assets/{index-B-NP8fOI.js → index-BKn4zRjf.js} +1 -1
  96. package/ui-dist/assets/{index-BKXUkeeU.js → index-BULll2e0.js} +1 -1
  97. package/ui-dist/assets/{index-BpvytaKr.js → index-BjxRqzZK.js} +1 -1
  98. package/ui-dist/assets/{index-Cyq94-QT.js → index-Bwo-C8W5.js} +1 -1
  99. package/ui-dist/assets/{index-BADa8mxG.js → index-ByUFNj0O.js} +1 -1
  100. package/ui-dist/assets/{index-CHbXMgL0.js → index-Chop-Dpa.js} +1 -1
  101. package/ui-dist/assets/{index-BU0KLNTt.js → index-Ck-ZNQeD.js} +541 -528
  102. package/ui-dist/assets/index-CxPYXL3t.css +1 -0
  103. package/ui-dist/assets/{index-wZlfBtwh.js → index-D00XdcOq.js} +1 -1
  104. package/ui-dist/assets/{index-BGcgy63z.js → index-D_K-9VDs.js} +1 -1
  105. package/ui-dist/assets/{index-DzdRgH-y.js → index-DiMDROQY.js} +1 -1
  106. package/ui-dist/assets/{index-5tzBDABj.js → index-DmjAbzHq.js} +1 -1
  107. package/ui-dist/assets/{index-De3vaw_P.js → index-SkHIC7Ma.js} +1 -1
  108. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-f5i4pO3J.js → infoDiagram-FWYZ7A6U-BkDimGw3.js} +1 -1
  109. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-CVEdv5i_.js → ishikawaDiagram-FXEZZL3T-BAyUfn6x.js} +1 -1
  110. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-Cvp7vWcy.js → journeyDiagram-5HDEW3XC-DlbpTZHG.js} +1 -1
  111. package/ui-dist/assets/{kanban-definition-HUTT4EX6-C6dURjjK.js → kanban-definition-HUTT4EX6-Cd67S14K.js} +1 -1
  112. package/ui-dist/assets/{layout-DxdOu9MY.js → layout-Dc4YEiCC.js} +1 -1
  113. package/ui-dist/assets/{linear-CRi8RB_7.js → linear-BhRsbD_5.js} +1 -1
  114. package/ui-dist/assets/{map-BlpDrw_l.js → map-CEWybj6z.js} +1 -1
  115. package/ui-dist/assets/{mermaid.core-2Gz9YusH.js → mermaid.core-CqqAFYNg.js} +4 -4
  116. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BmSjJLTI.js → mindmap-definition-LN4V7U3C-CCb-XmEi.js} +1 -1
  117. package/ui-dist/assets/{pegDiagram-2B236MQR-BTUboUw8.js → pegDiagram-2B236MQR-CqUV14VZ.js} +1 -1
  118. package/ui-dist/assets/{pieDiagram-ENE6RG2P-CVPP8SYv.js → pieDiagram-ENE6RG2P-Dn_M_GRM.js} +1 -1
  119. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BG4FmirT.js → quadrantDiagram-ABIIQ3AL-rAEhJDr2.js} +1 -1
  120. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-DFRNo9hF.js → railroadDiagram-RFXS5EU6-iE2o_jkH.js} +1 -1
  121. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-DpnVF9qP.js → requirementDiagram-TGXJPOKE-BFyKBR1h.js} +1 -1
  122. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-D1ETFdSE.js → sankeyDiagram-HTMAVEWB-DRa_2YB6.js} +1 -1
  123. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CNqzB_AL.js → sequenceDiagram-DBY2YBRQ-BG8VIjD7.js} +1 -1
  124. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-DuoMCjdH.js → sizeCapture-X5ZJPWSS-CS1xlbzG.js} +1 -1
  125. package/ui-dist/assets/{stateDiagram-2N3HPSRC-fmHeKL8l.js → stateDiagram-2N3HPSRC-BSl2RUJ1.js} +1 -1
  126. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DUVNSzI8.js +1 -0
  127. package/ui-dist/assets/{swimlanes-5IMT3BWC-hjosaIhT.js → swimlanes-5IMT3BWC-BT8Hrm9J.js} +2 -2
  128. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DfLFj7sh.js +8 -0
  129. package/ui-dist/assets/{timeline-definition-FHXFAJF6-xoMEClc-.js → timeline-definition-FHXFAJF6-CfYIqEuQ.js} +1 -1
  130. package/ui-dist/assets/{vennDiagram-L72KCM5P-sdSXQfX-.js → vennDiagram-L72KCM5P-C8zBH7o9.js} +1 -1
  131. package/ui-dist/assets/{wardleyDiagram-EHGQE667-BbQBSKlD.js → wardleyDiagram-EHGQE667-D5HyFfI9.js} +1 -1
  132. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-BfAtadyK.js → xychartDiagram-FW5EYKEG-CGItJmLx.js} +1 -1
  133. package/ui-dist/index.html +2 -2
  134. package/ui-dist/assets/channel-CHc3ibOz.js +0 -1
  135. package/ui-dist/assets/classDiagram-OUVF2IWQ-9iLZpz3t.js +0 -1
  136. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-9iLZpz3t.js +0 -1
  137. package/ui-dist/assets/index-QrRs-r-4.css +0 -1
  138. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DajAVikh.js +0 -1
  139. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DJjG9OJ-.js +0 -8
@@ -0,0 +1,65 @@
1
+ export const PRODUCT_ANALYTICS_FIXTURE_NOW = "2026-08-04T00:00:00.000Z";
2
+ export const PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS = [
3
+ "11111111-1111-4111-8111-111111111111",
4
+ "11111111-1111-4111-8111-111111111112",
5
+ "11111111-1111-4111-8111-111111111113",
6
+ ];
7
+ export const PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION = {
8
+ installationId: PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[0],
9
+ mode: "anonymous",
10
+ consentVersion: "v1",
11
+ consentEpoch: 1,
12
+ pseudonymousInstallationId: "fixture-installation-hash",
13
+ };
14
+ const EVENT_NAMES = ["human_work_started", "run_succeeded", "output_ready", "work_loop_completed"];
15
+ export function productAnalyticsFixtureEvent(index, overrides = {}) {
16
+ const installationIndex = index % PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.length;
17
+ const eventName = EVENT_NAMES[index % EVENT_NAMES.length];
18
+ const occurredAt = new Date(Date.parse(PRODUCT_ANALYTICS_FIXTURE_NOW) - (index % 3) * 60 * 60 * 1000).toISOString();
19
+ const properties = eventName === "run_succeeded"
20
+ ? { run_kind: "agent", runtime: "local", attempt_kind: "initial" }
21
+ : eventName === "output_ready"
22
+ ? { output_kind: "patch" }
23
+ : eventName === "work_loop_completed"
24
+ ? { work_surface: "issue", origin: "human", review_required: false }
25
+ : { work_surface: "issue", origin: "human" };
26
+ return {
27
+ eventId: `22222222-2222-4${String(index % 10)}22-8222-${String(index).padStart(12, "0")}`,
28
+ eventName,
29
+ schemaVersion: 1,
30
+ occurredAt,
31
+ environment: "production",
32
+ appVersion: "0.6.6-fixture",
33
+ releaseChannel: "stable",
34
+ deploymentMode: "desktop_local",
35
+ actorKind: "user",
36
+ origin: "human",
37
+ pseudonymousInstallationId: `fixture-installation-hash-${installationIndex}`,
38
+ pseudonymousOrgId: "fixture-org-hash",
39
+ pseudonymousWorkId: `fixture-work-${index}`,
40
+ pseudonymousWorkCycleId: `fixture-cycle-${index}`,
41
+ pseudonymousRootRunId: null,
42
+ pseudonymousRunId: `fixture-run-${index}`,
43
+ completionRevision: eventNameIsCompletion(index) ? 1 : null,
44
+ properties,
45
+ confidence: "exact",
46
+ isBackfill: index % 11 === 0,
47
+ ...overrides,
48
+ };
49
+ }
50
+ function eventNameIsCompletion(index) {
51
+ return EVENT_NAMES[index % EVENT_NAMES.length] === "work_loop_completed";
52
+ }
53
+ export function productAnalyticsFixtureDashboardEvents(count = 30) {
54
+ return Array.from({ length: count }, (_, index) => {
55
+ const event = productAnalyticsFixtureEvent(index);
56
+ const installationId = PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[index % PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.length];
57
+ return {
58
+ ...event,
59
+ installationId,
60
+ effectiveAt: event.occurredAt,
61
+ late: false,
62
+ };
63
+ });
64
+ }
65
+ //# sourceMappingURL=product-analytics-fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-analytics-fixture.js","sourceRoot":"","sources":["../../src/services/product-analytics-fixture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=product-analytics-prd-gates.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-analytics-prd-gates.test.d.ts","sourceRoot":"","sources":["../../src/services/product-analytics-prd-gates.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,135 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildProductAnalyticsCollectorDashboard, createProductAnalyticsCollector, InMemoryProductAnalyticsCollectorStore, validateProductAnalyticsCollectorEvent, } from "./product-analytics-collector.js";
3
+ import { PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION, PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS, PRODUCT_ANALYTICS_FIXTURE_NOW, productAnalyticsFixtureDashboardEvents, productAnalyticsFixtureEvent, } from "./product-analytics-fixture.js";
4
+ const SENSITIVE_TOKEN = /(prompt|transcript|title|description|body|content|url|token|secret|password|credential|email|hostname|username)/i;
5
+ describe("product analytics PRD gates", () => {
6
+ it("uses a deterministic fixture and keeps every accepted seed privacy-safe", () => {
7
+ const first = productAnalyticsFixtureEvent(0);
8
+ const second = productAnalyticsFixtureEvent(0);
9
+ expect(first).toEqual(second);
10
+ expect(first.occurredAt).toBe(PRODUCT_ANALYTICS_FIXTURE_NOW);
11
+ const seeds = Array.from({ length: 20 }, (_, index) => productAnalyticsFixtureEvent(index));
12
+ for (const seed of seeds) {
13
+ const serialized = JSON.stringify(seed);
14
+ expect(serialized).not.toMatch(SENSITIVE_TOKEN);
15
+ const result = validateProductAnalyticsCollectorEvent(seed);
16
+ expect(result).not.toHaveProperty("status", "rejected");
17
+ }
18
+ });
19
+ it("proves idempotency, offline-safe retry, revoke races, and shared-user isolation", async () => {
20
+ const store = new InMemoryProductAnalyticsCollectorStore();
21
+ store.setInstallationState(PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION.installationId, {
22
+ consentVersion: "v1",
23
+ consentEpoch: 1,
24
+ revoked: false,
25
+ });
26
+ const collector = createProductAnalyticsCollector(store);
27
+ const payload = productAnalyticsFixtureEvent(1, { pseudonymousInstallationId: "fixture-installation-hash" });
28
+ const first = collector.ingestBatch({
29
+ authorization: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
30
+ events: [payload],
31
+ now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
32
+ });
33
+ const duplicate = collector.ingestBatch({
34
+ authorization: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
35
+ events: [payload],
36
+ now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
37
+ });
38
+ expect(first.accepted).toBe(1);
39
+ expect(duplicate.duplicate).toBe(1);
40
+ const revoked = collector.advanceConsent({
41
+ installationId: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION.installationId,
42
+ consentVersion: "v1",
43
+ consentEpoch: 2,
44
+ revoked: true,
45
+ });
46
+ const stale = collector.ingestBatch({
47
+ authorization: PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
48
+ events: [productAnalyticsFixtureEvent(2)],
49
+ now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
50
+ });
51
+ expect(revoked.revoked).toBe(true);
52
+ expect(stale.rejected[0]?.errorCode).toBe("revoked");
53
+ const sharedUserStore = new InMemoryProductAnalyticsCollectorStore();
54
+ const sharedUserCollector = createProductAnalyticsCollector(sharedUserStore);
55
+ const sharedAuthorization = (installationId) => ({
56
+ ...PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION,
57
+ installationId,
58
+ analyticsSubject: "shared-user-subject",
59
+ pseudonymousInstallationId: null,
60
+ mode: "account_linked",
61
+ });
62
+ for (const installationId of PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.slice(0, 2)) {
63
+ sharedUserStore.setSubjectState(installationId, "shared-user-subject", { consentVersion: "v1", consentEpoch: 1, revoked: false });
64
+ }
65
+ const sharedFirst = sharedUserCollector.ingestBatch({
66
+ authorization: sharedAuthorization(PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[0]),
67
+ events: [productAnalyticsFixtureEvent(10, { eventId: "33333333-3333-4333-8333-333333333333" })],
68
+ now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
69
+ });
70
+ const sharedSecond = sharedUserCollector.ingestBatch({
71
+ authorization: sharedAuthorization(PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS[1]),
72
+ events: [productAnalyticsFixtureEvent(11, { eventId: "33333333-3333-4333-8333-333333333334" })],
73
+ now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
74
+ });
75
+ expect(sharedFirst.accepted).toBe(1);
76
+ expect(sharedSecond.accepted).toBe(1);
77
+ expect(sharedUserCollector.store.listEvents().map((event) => event.installationId).sort()).toEqual([...PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS.slice(0, 2)].sort());
78
+ expect(sharedUserCollector.store.listEvents().every((event) => event.analyticsSubject === "shared-user-subject")).toBe(true);
79
+ });
80
+ it("builds thresholded rollups, installation cohorts, and quality counters", () => {
81
+ const events = productAnalyticsFixtureDashboardEvents(30);
82
+ const dashboard = buildProductAnalyticsCollectorDashboard(events, { privacyThreshold: 2 });
83
+ expect(dashboard.rollups.length).toBeGreaterThan(0);
84
+ expect(dashboard.rollups.every((rollup) => rollup.contributingInstallations >= 2 || rollup.eventCount === null)).toBe(true);
85
+ expect(dashboard.cohorts).toEqual([
86
+ { firstSeenDay: "2026-08-03", installationCount: 2, suppressed: false },
87
+ { firstSeenDay: "2026-08-04", installationCount: null, suppressed: true },
88
+ ]);
89
+ expect(dashboard.quality).toMatchObject({
90
+ acceptedEventCount: 30,
91
+ backfillEventCount: 3,
92
+ lateEventCount: 0,
93
+ missingPseudonymousInstallationCount: 0,
94
+ privacyThreshold: 2,
95
+ });
96
+ const singleton = {
97
+ ...events[0],
98
+ installationId: "44444444-4444-4444-8444-444444444444",
99
+ eventName: "human_work_started",
100
+ occurredAt: "2026-08-05T00:00:00.000Z",
101
+ effectiveAt: "2026-08-04T00:00:00.000Z",
102
+ late: true,
103
+ };
104
+ const recomputed = buildProductAnalyticsCollectorDashboard([...events, singleton], { privacyThreshold: 2 });
105
+ const suppressed = recomputed.rollups.find((rollup) => rollup.day === "2026-08-04" && rollup.eventName === "human_work_started");
106
+ expect(suppressed).toMatchObject({ contributingInstallations: 1, eventCount: null, suppressed: true });
107
+ expect(recomputed.quality.lateEventCount).toBe(1);
108
+ expect(recomputed).toEqual(buildProductAnalyticsCollectorDashboard([singleton, ...events], { privacyThreshold: 2 }));
109
+ const dashboardJson = JSON.stringify(recomputed);
110
+ for (const installationId of PRODUCT_ANALYTICS_FIXTURE_INSTALLATIONS) {
111
+ expect(dashboardJson).not.toContain(installationId);
112
+ }
113
+ });
114
+ it("keeps the deterministic ingest benchmark bounded", () => {
115
+ const store = new InMemoryProductAnalyticsCollectorStore();
116
+ store.setInstallationState(PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION.installationId, { consentVersion: "v1", consentEpoch: 1, revoked: false });
117
+ const collector = createProductAnalyticsCollector(store);
118
+ const events = Array.from({ length: 100 }, (_, index) => productAnalyticsFixtureEvent(index));
119
+ const benchmarkAuthorization = { ...PRODUCT_ANALYTICS_FIXTURE_AUTHORIZATION, pseudonymousInstallationId: null };
120
+ const startedAt = performance.now();
121
+ let accepted = 0;
122
+ for (let offset = 0; offset < events.length; offset += 25) {
123
+ accepted += collector.ingestBatch({
124
+ authorization: benchmarkAuthorization,
125
+ events: events.slice(offset, offset + 25),
126
+ now: new Date(PRODUCT_ANALYTICS_FIXTURE_NOW),
127
+ }).accepted;
128
+ }
129
+ const elapsedMs = performance.now() - startedAt;
130
+ console.info(`[product-analytics-benchmark] events=${events.length} accepted=${accepted} elapsed_ms=${elapsedMs.toFixed(2)}`);
131
+ expect(accepted).toBe(100);
132
+ expect(elapsedMs).toBeLessThan(500);
133
+ });
134
+ });
135
+ //# sourceMappingURL=product-analytics-prd-gates.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-analytics-prd-gates.test.js","sourceRoot":"","sources":["../../src/services/product-analytics-prd-gates.test.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ export type ProductAnalyticsEnvironment = "production" | "development" | "test";
14
14
  export type ProductAnalyticsReleaseChannel = "development" | "preview" | "canary" | "stable";
15
15
  export type ProductAnalyticsDeploymentMode = "desktop_local" | "self_hosted" | "remote_server";
16
16
  type AnalyticsProperties = Record<string, ProductAnalyticsPropertyValue>;
17
+ export declare const PRODUCT_ANALYTICS_EVENT_PROPERTY_ALLOWLIST: Record<ProductAnalyticsEventName, ReadonlySet<string>>;
17
18
  export type RecordProductAnalyticsEventInput = {
18
19
  orgId?: string | null;
19
20
  eventName: ProductAnalyticsEventName;
@@ -59,11 +60,11 @@ export declare function enqueueProductAnalyticsEvent(db: Db, input: {
59
60
  leaseExpiresAt: Date | null;
60
61
  attemptCount: number;
61
62
  installationId: string;
63
+ consentVersion: string;
62
64
  consentEpoch: number;
63
65
  deliveryMode: string;
64
66
  consentScope: string;
65
67
  consentedLocalUserId: string | null;
66
- consentVersion: string;
67
68
  nextAttemptAt: Date;
68
69
  deliveredAt: Date | null;
69
70
  deadLetteredAt: Date | null;
@@ -90,11 +91,11 @@ export declare function ensureProductAnalyticsWorkCycle(db: Db, input: EnsurePro
90
91
  completedAt: Date | null;
91
92
  reviewDecision: string | null;
92
93
  actorId: string | null;
94
+ completionRevision: number;
95
+ invalidatedAt: Date | null;
93
96
  workSurface: string;
94
97
  workId: string;
95
98
  workCycleId: string;
96
- completionRevision: number;
97
- invalidatedAt: Date | null;
98
99
  outputReadyAt: Date | null;
99
100
  rootRunIds: string[];
100
101
  }>;
@@ -152,7 +153,7 @@ export declare function setProductAnalyticsInstallationMode(db: Db, installation
152
153
  createdAt: Date;
153
154
  updatedAt: Date;
154
155
  }>;
155
- export declare function reconcileProductAnalyticsInstallationMode(db: Db, installationId: string): Promise<{
156
+ export declare function reconcileProductAnalyticsInstallationMode(db: Db, installationId: string, preferredMode?: "off" | "anonymous" | "account_linked"): Promise<{
156
157
  id: string;
157
158
  installationId: string;
158
159
  installationSecretHash: string;
@@ -199,9 +200,9 @@ export declare function recordProductAnalyticsConsent(db: Db, input: {
199
200
  decidedAt: Date;
200
201
  localUserId: string | null;
201
202
  installationId: string;
203
+ consentEpoch: number;
202
204
  decision: string;
203
205
  policyVersion: string;
204
- consentEpoch: number;
205
206
  decidedByLocalUserId: string | null;
206
207
  uploadFromAt: Date | null;
207
208
  }>;
@@ -243,6 +244,7 @@ export declare function claimProductAnalyticsOutboxBatch(db: Db, input: {
243
244
  installationId: string;
244
245
  deliveryMode: string;
245
246
  consentScope: string;
247
+ consentedLocalUserId: string | null;
246
248
  consentEpoch: number;
247
249
  events: {
248
250
  eventId: string;
@@ -285,12 +287,22 @@ export declare function acknowledgeProductAnalyticsOutboxClaim(db: Db, input: {
285
287
  installationSecret: string;
286
288
  eventIds: string[];
287
289
  claimToken: string;
290
+ consentedLocalUserId?: string | null;
288
291
  delivered?: boolean;
289
292
  errorCode?: string;
290
293
  }): Promise<{
291
294
  updatedCount: number;
292
295
  state: string;
293
296
  }>;
297
+ export declare function cleanupProductAnalyticsRetention(db: Db, input: {
298
+ installationId: string;
299
+ retentionDays?: number;
300
+ now?: Date;
301
+ }): Promise<{
302
+ deletedEvents: number;
303
+ droppedDueToRetention: number;
304
+ cutoff: string;
305
+ }>;
294
306
  export declare function pseudonymizeProductAnalyticsId(secret: string, value: string): string;
295
307
  export declare function buildProductAnalyticsExportPayload(secret: string, event: Record<string, unknown>): {
296
308
  eventId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"product-analytics.d.ts","sourceRoot":"","sources":["../../src/services/product-analytics.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"product-analytics.d.ts","sourceRoot":"","sources":["../../src/services/product-analytics.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { productAnalyticsConsentLedger, productAnalyticsEvents, productAnalyticsInstallations, productAnalyticsOutbox, productAnalyticsWorkCycles, } from "@rudderhq/db";
1
+ import { productAnalyticsConsentLedger, productAnalyticsEvents, productAnalyticsInstallations, productAnalyticsOutbox, productAnalyticsWorkCycleRevisions, productAnalyticsWorkCycles, } from "@rudderhq/db";
2
2
  import { and, asc, desc, eq, gte, inArray, isNull, lt, lte, or, sql } from "drizzle-orm";
3
3
  import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
4
4
  import { forbidden, unauthorized, unprocessable } from "../errors.js";
@@ -44,7 +44,9 @@ const ACTOR_TYPE_SET = new Set(["human", "agent", "system", "automation"]);
44
44
  // focused on content-bearing names so safe dimensions such as creation_path do
45
45
  // not get rejected by a substring match.
46
46
  const SENSITIVE_PROPERTY_KEY = /(prompt|transcript|title|description|body|content|url|token|secret|password|credential|email|hostname|username)/i;
47
- const EVENT_PROPERTY_ALLOWLIST = {
47
+ const PRODUCT_ANALYTICS_OUTBOX_MAX_ROWS = 10_000;
48
+ const PRODUCT_ANALYTICS_OUTBOX_MAX_BYTES = 32 * 1024 * 1024;
49
+ export const PRODUCT_ANALYTICS_EVENT_PROPERTY_ALLOWLIST = {
48
50
  organization_created: new Set([
49
51
  "creation_path",
50
52
  "template_kind",
@@ -81,7 +83,7 @@ function assertBoundedText(value, field, maxLength) {
81
83
  }
82
84
  }
83
85
  function validateProperties(eventName, properties) {
84
- const allowed = EVENT_PROPERTY_ALLOWLIST[eventName];
86
+ const allowed = PRODUCT_ANALYTICS_EVENT_PROPERTY_ALLOWLIST[eventName];
85
87
  for (const [key, value] of Object.entries(properties)) {
86
88
  if (!allowed.has(key) || SENSITIVE_PROPERTY_KEY.test(key)) {
87
89
  throw unprocessable(`Product analytics property is not allowed: ${key}`);
@@ -181,10 +183,23 @@ export async function recordProductAnalyticsEvent(db, input) {
181
183
  return rows[0] ?? null;
182
184
  }
183
185
  export async function enqueueProductAnalyticsEvent(db, input) {
184
- const [installation] = await db.select({ mode: productAnalyticsInstallations.mode }).from(productAnalyticsInstallations)
186
+ const [event] = await db.select({ environment: productAnalyticsEvents.environment }).from(productAnalyticsEvents)
187
+ .where(eq(productAnalyticsEvents.id, input.eventId)).limit(1);
188
+ if (!event || event.environment !== "production")
189
+ return null;
190
+ const [installation] = await db.select({ mode: productAnalyticsInstallations.mode, state: productAnalyticsInstallations.state }).from(productAnalyticsInstallations)
185
191
  .where(eq(productAnalyticsInstallations.installationId, input.installationId)).limit(1);
186
192
  if (!installation || installation.mode === "off")
187
193
  return null;
194
+ const [backlog] = await db.select({ count: sql `count(*)`, bytes: sql `coalesce(sum(length(${productAnalyticsEvents.properties}::text)), 0)` })
195
+ .from(productAnalyticsOutbox)
196
+ .innerJoin(productAnalyticsEvents, eq(productAnalyticsOutbox.eventId, productAnalyticsEvents.id))
197
+ .where(and(eq(productAnalyticsOutbox.installationId, input.installationId), inArray(productAnalyticsOutbox.state, ["pending", "claimed", "retry_wait"])));
198
+ if (Number(backlog?.count ?? 0) >= PRODUCT_ANALYTICS_OUTBOX_MAX_ROWS || Number(backlog?.bytes ?? 0) >= PRODUCT_ANALYTICS_OUTBOX_MAX_BYTES) {
199
+ await db.update(productAnalyticsInstallations).set({ state: { ...(installation.state ?? {}), coverageGap: true }, updatedAt: new Date() })
200
+ .where(eq(productAnalyticsInstallations.installationId, input.installationId));
201
+ return null;
202
+ }
188
203
  const scope = installation.mode === "account_linked" ? "account_linked_user" : "anonymous_installation";
189
204
  const conditions = [eq(productAnalyticsConsentLedger.installationId, input.installationId), eq(productAnalyticsConsentLedger.scope, scope), eq(productAnalyticsConsentLedger.decision, "granted")];
190
205
  conditions.push(scope === "account_linked_user" && input.localUserId ? eq(productAnalyticsConsentLedger.localUserId, input.localUserId) : isNull(productAnalyticsConsentLedger.localUserId));
@@ -247,6 +262,12 @@ export async function completeProductAnalyticsWorkCycle(db, input) {
247
262
  if (lockedCycle.state === "completed" && lockedCycle.invalidatedAt === null) {
248
263
  return { event: null, revision: lockedCycle.completionRevision };
249
264
  }
265
+ const rootRunRows = await tx.select({ rootRunId: productAnalyticsEvents.rootRunId }).from(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.orgId, input.orgId), eq(productAnalyticsEvents.workCycleId, input.workCycleId), sql `${productAnalyticsEvents.rootRunId} is not null`));
266
+ const rootRunIds = [...new Set([
267
+ ...(lockedCycle.rootRunIds ?? []),
268
+ ...rootRunRows.map((row) => row.rootRunId).filter((value) => Boolean(value)),
269
+ ...(input.rootRunId ? [input.rootRunId] : []),
270
+ ])];
250
271
  const revision = (lockedCycle.completionRevision ?? 0) + 1;
251
272
  const event = await recordProductAnalyticsEvent(tx, {
252
273
  orgId: input.orgId,
@@ -269,8 +290,16 @@ export async function completeProductAnalyticsWorkCycle(db, input) {
269
290
  });
270
291
  await tx
271
292
  .update(productAnalyticsWorkCycles)
272
- .set({ state: "completed", completionRevision: revision, completedAt: new Date(), invalidatedAt: null, updatedAt: new Date() })
293
+ .set({ state: "completed", completionRevision: revision, completedAt: new Date(), invalidatedAt: null, rootRunIds, updatedAt: new Date() })
273
294
  .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
295
+ await tx.insert(productAnalyticsWorkCycleRevisions).values({
296
+ orgId: input.orgId,
297
+ workCycleId: input.workCycleId,
298
+ completionRevision: revision,
299
+ completionEventId: event?.id,
300
+ completedAt: new Date(),
301
+ updatedAt: new Date(),
302
+ }).onConflictDoNothing();
274
303
  return { event, revision };
275
304
  });
276
305
  }
@@ -297,6 +326,12 @@ export async function invalidateProductAnalyticsWorkCycle(db, input) {
297
326
  dedupeKey: `work_loop_invalidated:${input.workCycleId}:${input.completionRevision}:${input.reasonCode}`,
298
327
  properties: { reason_code: input.reasonCode },
299
328
  });
329
+ await tx.update(productAnalyticsWorkCycleRevisions).set({
330
+ invalidatedAt: input.occurredAt ?? new Date(),
331
+ invalidationReasonCode: input.reasonCode,
332
+ invalidationEventId: event?.id,
333
+ updatedAt: new Date(),
334
+ }).where(and(eq(productAnalyticsWorkCycleRevisions.orgId, input.orgId), eq(productAnalyticsWorkCycleRevisions.workCycleId, input.workCycleId), eq(productAnalyticsWorkCycleRevisions.completionRevision, input.completionRevision)));
300
335
  await tx.update(productAnalyticsWorkCycles).set({ state: "invalidated", invalidatedAt: input.occurredAt ?? new Date(), updatedAt: new Date() })
301
336
  .where(and(eq(productAnalyticsWorkCycles.orgId, input.orgId), eq(productAnalyticsWorkCycles.workCycleId, input.workCycleId)));
302
337
  return event;
@@ -335,7 +370,7 @@ export async function setProductAnalyticsInstallationMode(db, installationId, mo
335
370
  .where(eq(productAnalyticsInstallations.installationId, installationId)).returning();
336
371
  return row ?? null;
337
372
  }
338
- export async function reconcileProductAnalyticsInstallationMode(db, installationId) {
373
+ export async function reconcileProductAnalyticsInstallationMode(db, installationId, preferredMode) {
339
374
  const consent = await db.select({ scope: productAnalyticsConsentLedger.scope, decision: productAnalyticsConsentLedger.decision })
340
375
  .from(productAnalyticsConsentLedger)
341
376
  .where(eq(productAnalyticsConsentLedger.installationId, installationId))
@@ -345,9 +380,9 @@ export async function reconcileProductAnalyticsInstallationMode(db, installation
345
380
  if (!latestByScope.has(row.scope))
346
381
  latestByScope.set(row.scope, row.decision);
347
382
  }
348
- const mode = latestByScope.get("account_linked_user") === "granted"
383
+ const mode = preferredMode ?? (latestByScope.get("account_linked_user") === "granted"
349
384
  ? "account_linked"
350
- : latestByScope.get("anonymous_installation") === "granted" ? "anonymous" : "off";
385
+ : latestByScope.get("anonymous_installation") === "granted" ? "anonymous" : "off");
351
386
  return setProductAnalyticsInstallationMode(db, installationId, mode);
352
387
  }
353
388
  export async function getProductAnalyticsInstallationState(db, installationId) {
@@ -470,6 +505,30 @@ export async function claimProductAnalyticsOutboxBatch(db, input) {
470
505
  });
471
506
  throw error;
472
507
  }
508
+ // Keep anonymous exporter output available to the local Privacy & Telemetry page.
509
+ // Account-linked payloads are deliberately never retained in installation-wide
510
+ // state because another signed-in user may later read the settings page.
511
+ const [installation] = await db.select({ state: productAnalyticsInstallations.state })
512
+ .from(productAnalyticsInstallations)
513
+ .where(eq(productAnalyticsInstallations.installationId, input.installationId))
514
+ .limit(1);
515
+ if (installation) {
516
+ const nextState = { ...(installation.state ?? {}) };
517
+ if (input.deliveryMode === "anonymous") {
518
+ nextState.lastPayloadAt = new Date().toISOString();
519
+ nextState.lastPayload = payloads.slice(-20);
520
+ nextState.lastPayloadMode = "anonymous";
521
+ }
522
+ else {
523
+ delete nextState.lastPayloadAt;
524
+ delete nextState.lastPayload;
525
+ delete nextState.lastPayloadMode;
526
+ }
527
+ await db.update(productAnalyticsInstallations).set({
528
+ state: nextState,
529
+ updatedAt: new Date(),
530
+ }).where(eq(productAnalyticsInstallations.installationId, input.installationId));
531
+ }
473
532
  const first = rows[0];
474
533
  if (!first.claimToken)
475
534
  throw unprocessable("Product analytics outbox claim token is missing");
@@ -478,6 +537,7 @@ export async function claimProductAnalyticsOutboxBatch(db, input) {
478
537
  installationId: first.installationId,
479
538
  deliveryMode: first.deliveryMode,
480
539
  consentScope: first.consentScope,
540
+ consentedLocalUserId: first.consentedLocalUserId,
481
541
  consentEpoch: first.consentEpoch,
482
542
  events: payloads,
483
543
  };
@@ -496,7 +556,7 @@ export async function acknowledgeProductAnalyticsOutbox(db, input) {
496
556
  attemptCount: sql `${productAnalyticsOutbox.attemptCount} + 1`,
497
557
  deliveredAt: delivered ? now : null,
498
558
  deadLetteredAt: deadLettered ? now : null,
499
- nextAttemptAt: delivered || deadLettered ? now : new Date(now.getTime() + Math.min(60 * 60_000, 2 ** Math.min(maxAttemptCount, 6) * 60_000)),
559
+ nextAttemptAt: delivered || deadLettered ? now : new Date(now.getTime() + Math.round(Math.min(60 * 60_000, 2 ** Math.min(maxAttemptCount, 6) * 60_000) * (0.8 + Math.random() * 0.4))),
500
560
  lastErrorCode: input.errorCode ?? null,
501
561
  claimToken: null,
502
562
  leaseExpiresAt: null,
@@ -511,7 +571,11 @@ export async function acknowledgeProductAnalyticsOutboxClaim(db, input) {
511
571
  deliveryMode: productAnalyticsOutbox.deliveryMode,
512
572
  consentEpoch: productAnalyticsOutbox.consentEpoch,
513
573
  consentedLocalUserId: productAnalyticsOutbox.consentedLocalUserId,
514
- }).from(productAnalyticsOutbox).where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.claimToken, input.claimToken), inArray(productAnalyticsOutbox.eventId, input.eventIds)));
574
+ }).from(productAnalyticsOutbox).where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.claimToken, input.claimToken), inArray(productAnalyticsOutbox.eventId, input.eventIds), input.consentedLocalUserId === undefined
575
+ ? undefined
576
+ : input.consentedLocalUserId
577
+ ? eq(productAnalyticsOutbox.consentedLocalUserId, input.consentedLocalUserId)
578
+ : isNull(productAnalyticsOutbox.consentedLocalUserId)));
515
579
  if (rows.length === 0)
516
580
  return { updatedCount: 0, state: input.delivered === false ? "retry_wait" : "delivered" };
517
581
  const first = rows[0];
@@ -527,6 +591,23 @@ export async function acknowledgeProductAnalyticsOutboxClaim(db, input) {
527
591
  consentedLocalUserId: first.consentedLocalUserId,
528
592
  });
529
593
  }
594
+ export async function cleanupProductAnalyticsRetention(db, input) {
595
+ const now = input.now ?? new Date();
596
+ const retentionDays = Math.min(Math.max(Math.trunc(input.retentionDays ?? 90), 1), 3650);
597
+ const cutoff = new Date(now.getTime() - retentionDays * 24 * 60 * 60 * 1000);
598
+ const delivered = await db.select({ eventId: productAnalyticsOutbox.eventId }).from(productAnalyticsOutbox)
599
+ .where(and(eq(productAnalyticsOutbox.installationId, input.installationId), eq(productAnalyticsOutbox.state, "delivered"), lt(productAnalyticsOutbox.updatedAt, cutoff)));
600
+ if (delivered.length === 0)
601
+ return { deletedEvents: 0, droppedDueToRetention: 0, cutoff: cutoff.toISOString() };
602
+ const eventIds = delivered.map((row) => row.eventId);
603
+ await db.delete(productAnalyticsOutbox).where(inArray(productAnalyticsOutbox.eventId, eventIds));
604
+ const deleted = await db.delete(productAnalyticsEvents).where(and(eq(productAnalyticsEvents.installationId, input.installationId), inArray(productAnalyticsEvents.id, eventIds))).returning({ id: productAnalyticsEvents.id });
605
+ await db.update(productAnalyticsInstallations).set({
606
+ state: { retentionCleanupAt: now.toISOString(), droppedDueToRetention: 0 },
607
+ updatedAt: now,
608
+ }).where(eq(productAnalyticsInstallations.installationId, input.installationId));
609
+ return { deletedEvents: deleted.length, droppedDueToRetention: 0, cutoff: cutoff.toISOString() };
610
+ }
530
611
  export function pseudonymizeProductAnalyticsId(secret, value) {
531
612
  return createHmac("sha256", secret).update(value).digest("hex");
532
613
  }
@@ -598,10 +679,11 @@ export function productAnalyticsService(db) {
598
679
  .select({ count: sql `count(distinct ${productAnalyticsEvents.workCycleId} || ':' || ${productAnalyticsEvents.completionRevision})` })
599
680
  .from(productAnalyticsEvents)
600
681
  .where(and(where, eq(productAnalyticsEvents.eventName, "work_loop_completed"), eq(productAnalyticsEvents.origin, "human"), eq(productAnalyticsEvents.environment, "production"), sql `not exists (
601
- select 1 from ${productAnalyticsWorkCycles} invalidated
682
+ select 1 from ${productAnalyticsWorkCycleRevisions} invalidated
602
683
  where invalidated.org_id = ${productAnalyticsEvents.orgId}
603
684
  and invalidated.work_cycle_id = ${productAnalyticsEvents.workCycleId}
604
- and invalidated.state = 'invalidated'
685
+ and invalidated.completion_revision = ${productAnalyticsEvents.completionRevision}
686
+ and invalidated.invalidated_at is not null
605
687
  )`)),
606
688
  db
607
689
  .select({ count: sql `count(*)` })