@mongrov/analytics 0.6.0 → 0.7.0

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 (213) hide show
  1. package/README.md +23 -10
  2. package/UPGRADING.md +370 -0
  3. package/dist/core/device-events.d.ts +14 -0
  4. package/dist/core/device-events.d.ts.map +1 -0
  5. package/dist/core/device-events.js +13 -0
  6. package/dist/core/device-events.js.map +1 -0
  7. package/dist/core/errors.d.ts +1 -1
  8. package/dist/core/errors.d.ts.map +1 -1
  9. package/dist/core/errors.js.map +1 -1
  10. package/dist/core/extensions.d.ts +21 -4
  11. package/dist/core/extensions.d.ts.map +1 -1
  12. package/dist/core/extensions.js +21 -4
  13. package/dist/core/extensions.js.map +1 -1
  14. package/dist/core/factory.d.ts.map +1 -1
  15. package/dist/core/factory.js +125 -23
  16. package/dist/core/factory.js.map +1 -1
  17. package/dist/core/hooks.js +1 -1
  18. package/dist/core/hooks.js.map +1 -1
  19. package/dist/core/index.d.ts +11 -1
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +14 -0
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/insight.d.ts +44 -0
  24. package/dist/core/insight.d.ts.map +1 -0
  25. package/dist/core/insight.js +62 -0
  26. package/dist/core/insight.js.map +1 -0
  27. package/dist/core/metric_metadata.d.ts +55 -0
  28. package/dist/core/metric_metadata.d.ts.map +1 -1
  29. package/dist/core/metric_metadata.js +35 -7
  30. package/dist/core/metric_metadata.js.map +1 -1
  31. package/dist/core/migrations.d.ts.map +1 -1
  32. package/dist/core/migrations.js +64 -2
  33. package/dist/core/migrations.js.map +1 -1
  34. package/dist/core/retention.d.ts +44 -13
  35. package/dist/core/retention.d.ts.map +1 -1
  36. package/dist/core/retention.js +49 -22
  37. package/dist/core/retention.js.map +1 -1
  38. package/dist/core/schemas.d.ts +73 -2
  39. package/dist/core/schemas.d.ts.map +1 -1
  40. package/dist/core/schemas.js +154 -3
  41. package/dist/core/schemas.js.map +1 -1
  42. package/dist/core/table_metadata.d.ts.map +1 -1
  43. package/dist/core/table_metadata.js +3 -0
  44. package/dist/core/table_metadata.js.map +1 -1
  45. package/dist/core/types.d.ts +45 -10
  46. package/dist/core/types.d.ts.map +1 -1
  47. package/dist/core/warehouse.d.ts +28 -0
  48. package/dist/core/warehouse.d.ts.map +1 -1
  49. package/dist/core/warehouse.js +44 -0
  50. package/dist/core/warehouse.js.map +1 -1
  51. package/dist/core/watermark-cache.d.ts +75 -0
  52. package/dist/core/watermark-cache.d.ts.map +1 -0
  53. package/dist/core/watermark-cache.js +0 -0
  54. package/dist/core/watermark-cache.js.map +1 -0
  55. package/dist/rules/__fakes__/fakeEngine.d.ts +2 -0
  56. package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -1
  57. package/dist/rules/__fakes__/fakeEngine.js +4 -0
  58. package/dist/rules/__fakes__/fakeEngine.js.map +1 -1
  59. package/dist/rules/baseline-reader.d.ts +55 -0
  60. package/dist/rules/baseline-reader.d.ts.map +1 -0
  61. package/dist/rules/baseline-reader.js +111 -0
  62. package/dist/rules/baseline-reader.js.map +1 -0
  63. package/dist/rules/compiler.d.ts +36 -1
  64. package/dist/rules/compiler.d.ts.map +1 -1
  65. package/dist/rules/compiler.js +185 -39
  66. package/dist/rules/compiler.js.map +1 -1
  67. package/dist/rules/defaults/luminx.d.ts.map +1 -1
  68. package/dist/rules/defaults/luminx.js +8 -0
  69. package/dist/rules/defaults/luminx.js.map +1 -1
  70. package/dist/rules/defaults/viva.d.ts.map +1 -1
  71. package/dist/rules/defaults/viva.js +7 -0
  72. package/dist/rules/defaults/viva.js.map +1 -1
  73. package/dist/rules/defaults/yogaring.d.ts.map +1 -1
  74. package/dist/rules/defaults/yogaring.js +7 -0
  75. package/dist/rules/defaults/yogaring.js.map +1 -1
  76. package/dist/rules/defaults/ziva.d.ts.map +1 -1
  77. package/dist/rules/defaults/ziva.js +100 -24
  78. package/dist/rules/defaults/ziva.js.map +1 -1
  79. package/dist/rules/evaluator.d.ts +14 -3
  80. package/dist/rules/evaluator.d.ts.map +1 -1
  81. package/dist/rules/evaluator.js +157 -5
  82. package/dist/rules/evaluator.js.map +1 -1
  83. package/dist/rules/factory.d.ts.map +1 -1
  84. package/dist/rules/factory.js +5 -1
  85. package/dist/rules/factory.js.map +1 -1
  86. package/dist/rules/index.d.ts +2 -1
  87. package/dist/rules/index.d.ts.map +1 -1
  88. package/dist/rules/index.js +2 -1
  89. package/dist/rules/index.js.map +1 -1
  90. package/dist/rules/schema.d.ts +75 -4
  91. package/dist/rules/schema.d.ts.map +1 -1
  92. package/dist/rules/schema.js +44 -0
  93. package/dist/rules/schema.js.map +1 -1
  94. package/dist/rules/types.d.ts +16 -1
  95. package/dist/rules/types.d.ts.map +1 -1
  96. package/dist/rules/validator.d.ts.map +1 -1
  97. package/dist/rules/validator.js +56 -0
  98. package/dist/rules/validator.js.map +1 -1
  99. package/dist/sync/baseline-compute.d.ts +63 -0
  100. package/dist/sync/baseline-compute.d.ts.map +1 -0
  101. package/dist/sync/baseline-compute.js +211 -0
  102. package/dist/sync/baseline-compute.js.map +1 -0
  103. package/dist/sync/events.d.ts +19 -5
  104. package/dist/sync/events.d.ts.map +1 -1
  105. package/dist/sync/events.js +17 -0
  106. package/dist/sync/events.js.map +1 -1
  107. package/dist/sync/factory.d.ts +25 -4
  108. package/dist/sync/factory.d.ts.map +1 -1
  109. package/dist/sync/factory.js +108 -15
  110. package/dist/sync/factory.js.map +1 -1
  111. package/dist/sync/fetcher.d.ts +15 -2
  112. package/dist/sync/fetcher.d.ts.map +1 -1
  113. package/dist/sync/fetcher.js +43 -7
  114. package/dist/sync/fetcher.js.map +1 -1
  115. package/dist/sync/flusher.d.ts +52 -2
  116. package/dist/sync/flusher.d.ts.map +1 -1
  117. package/dist/sync/flusher.js +78 -9
  118. package/dist/sync/flusher.js.map +1 -1
  119. package/dist/sync/index.d.ts +3 -1
  120. package/dist/sync/index.d.ts.map +1 -1
  121. package/dist/sync/index.js +1 -0
  122. package/dist/sync/index.js.map +1 -1
  123. package/dist/sync/mapper/activity.d.ts.map +1 -1
  124. package/dist/sync/mapper/activity.js +2 -1
  125. package/dist/sync/mapper/activity.js.map +1 -1
  126. package/dist/sync/mapper/ring-config.js +1 -1
  127. package/dist/sync/mapper/ring-config.js.map +1 -1
  128. package/dist/sync/mapper/schema.d.ts +411 -0
  129. package/dist/sync/mapper/schema.d.ts.map +1 -0
  130. package/dist/sync/mapper/schema.js +128 -0
  131. package/dist/sync/mapper/schema.js.map +1 -0
  132. package/dist/sync/mapper/sleep.d.ts +58 -13
  133. package/dist/sync/mapper/sleep.d.ts.map +1 -1
  134. package/dist/sync/mapper/sleep.js +141 -44
  135. package/dist/sync/mapper/sleep.js.map +1 -1
  136. package/dist/sync/mapper/types.d.ts +66 -10
  137. package/dist/sync/mapper/types.d.ts.map +1 -1
  138. package/dist/sync/scheduler.d.ts +7 -0
  139. package/dist/sync/scheduler.d.ts.map +1 -1
  140. package/dist/sync/scheduler.js +14 -0
  141. package/dist/sync/scheduler.js.map +1 -1
  142. package/dist/tools/__fakes__/engine.d.ts +2 -0
  143. package/dist/tools/__fakes__/engine.d.ts.map +1 -1
  144. package/dist/tools/__fakes__/engine.js +10 -0
  145. package/dist/tools/__fakes__/engine.js.map +1 -1
  146. package/dist/tools/authorize.d.ts +19 -12
  147. package/dist/tools/authorize.d.ts.map +1 -1
  148. package/dist/tools/authorize.js +31 -40
  149. package/dist/tools/authorize.js.map +1 -1
  150. package/dist/tools/factory.d.ts +2 -0
  151. package/dist/tools/factory.d.ts.map +1 -1
  152. package/dist/tools/factory.js +12 -1
  153. package/dist/tools/factory.js.map +1 -1
  154. package/dist/tools/formatters/copy-guidelines.d.ts +57 -0
  155. package/dist/tools/formatters/copy-guidelines.d.ts.map +1 -0
  156. package/dist/tools/formatters/copy-guidelines.js +110 -0
  157. package/dist/tools/formatters/copy-guidelines.js.map +1 -0
  158. package/dist/tools/formatters/index.d.ts +14 -0
  159. package/dist/tools/formatters/index.d.ts.map +1 -0
  160. package/dist/tools/formatters/index.js +14 -0
  161. package/dist/tools/formatters/index.js.map +1 -0
  162. package/dist/tools/formatters/spo2.d.ts +21 -0
  163. package/dist/tools/formatters/spo2.d.ts.map +1 -0
  164. package/dist/tools/formatters/spo2.js +79 -0
  165. package/dist/tools/formatters/spo2.js.map +1 -0
  166. package/dist/tools/{formatters.d.ts → formatters/text.d.ts} +1 -1
  167. package/dist/tools/formatters/text.d.ts.map +1 -0
  168. package/dist/tools/{formatters.js → formatters/text.js} +1 -1
  169. package/dist/tools/formatters/text.js.map +1 -0
  170. package/dist/tools/impls/activity.d.ts +2 -2
  171. package/dist/tools/impls/activity.js +7 -3
  172. package/dist/tools/impls/activity.js.map +1 -1
  173. package/dist/tools/impls/anomaly.d.ts +2 -2
  174. package/dist/tools/impls/anomaly.js +8 -4
  175. package/dist/tools/impls/anomaly.js.map +1 -1
  176. package/dist/tools/impls/compare.d.ts +2 -2
  177. package/dist/tools/impls/compare.js +8 -4
  178. package/dist/tools/impls/compare.js.map +1 -1
  179. package/dist/tools/impls/hrv.d.ts +2 -2
  180. package/dist/tools/impls/hrv.js +6 -2
  181. package/dist/tools/impls/hrv.js.map +1 -1
  182. package/dist/tools/impls/insights.d.ts +4 -4
  183. package/dist/tools/impls/insights.d.ts.map +1 -1
  184. package/dist/tools/impls/insights.js +10 -3
  185. package/dist/tools/impls/insights.js.map +1 -1
  186. package/dist/tools/impls/sleep.d.ts +2 -2
  187. package/dist/tools/impls/sleep.js +6 -2
  188. package/dist/tools/impls/sleep.js.map +1 -1
  189. package/dist/tools/impls/spo2.d.ts +52 -0
  190. package/dist/tools/impls/spo2.d.ts.map +1 -0
  191. package/dist/tools/impls/spo2.js +65 -0
  192. package/dist/tools/impls/spo2.js.map +1 -0
  193. package/dist/tools/index.d.ts +1 -0
  194. package/dist/tools/index.d.ts.map +1 -1
  195. package/dist/tools/index.js +4 -0
  196. package/dist/tools/index.js.map +1 -1
  197. package/dist/tools/mcp/guard.d.ts +28 -11
  198. package/dist/tools/mcp/guard.d.ts.map +1 -1
  199. package/dist/tools/mcp/guard.js +45 -15
  200. package/dist/tools/mcp/guard.js.map +1 -1
  201. package/dist/tools/mcp/index.d.ts +1 -1
  202. package/dist/tools/mcp/index.d.ts.map +1 -1
  203. package/dist/tools/mcp/index.js +1 -1
  204. package/dist/tools/mcp/index.js.map +1 -1
  205. package/dist/tools/mcp/server.d.ts.map +1 -1
  206. package/dist/tools/mcp/server.js +4 -0
  207. package/dist/tools/mcp/server.js.map +1 -1
  208. package/dist/tools/mcp/transports/http.d.ts.map +1 -1
  209. package/dist/tools/mcp/transports/http.js +4 -0
  210. package/dist/tools/mcp/transports/http.js.map +1 -1
  211. package/package.json +8 -5
  212. package/dist/tools/formatters.d.ts.map +0 -1
  213. package/dist/tools/formatters.js.map +0 -1
@@ -14,18 +14,18 @@
14
14
  * change (bump metadata; add DDL column if new).
15
15
  */
16
16
  export const METRIC_METADATA = Object.freeze({
17
- hrv_ms: { table: 'hrv', column: 'hrv_ms', sampling_minutes: 60, exposure: 'full' },
18
- stress: { table: 'hrv', column: 'stress', sampling_minutes: 60, exposure: 'full' },
17
+ hrv_ms: { table: 'hrv', column: 'hrv_ms', sampling_minutes: 60, exposure: 'full', baselineDailyAggregate: 'avg' },
18
+ stress: { table: 'hrv', column: 'stress', sampling_minutes: 60, exposure: 'full', baselineDailyAggregate: 'avg' },
19
19
  systolic_bp: { table: 'hrv', column: 'systolic_bp', sampling_minutes: 60, exposure: 'collected_only' },
20
20
  diastolic_bp: { table: 'hrv', column: 'diastolic_bp', sampling_minutes: 60, exposure: 'collected_only' },
21
21
  vascular_aging: { table: 'hrv', column: 'vascular_aging', sampling_minutes: 60, exposure: 'collected_only' },
22
- hr_bpm: { table: 'heart_rate', column: 'bpm', sampling_minutes: 10, exposure: 'full' },
23
- spo2: { table: 'spo2', column: 'spo2', sampling_minutes: 30, exposure: 'full' },
24
- temp_c: { table: 'temperature', column: 'temp_c', sampling_minutes: 30, exposure: 'full' },
25
- activity_steps: { table: 'activity', column: 'steps', sampling_minutes: 1, exposure: 'full' },
22
+ hr_bpm: { table: 'heart_rate', column: 'bpm', sampling_minutes: 10, exposure: 'full', baselineDailyAggregate: 'avg' },
23
+ spo2: { table: 'spo2', column: 'spo2', sampling_minutes: 30, exposure: 'full', baselineDailyAggregate: 'avg' },
24
+ temp_c: { table: 'temperature', column: 'temp_c', sampling_minutes: 30, exposure: 'full', baselineDailyAggregate: 'avg' },
25
+ activity_steps: { table: 'activity', column: 'steps', sampling_minutes: 1, exposure: 'full', baselineDailyAggregate: 'sum' },
26
26
  calories: { table: 'activity_bucket', column: 'calories', sampling_minutes: 10, exposure: 'full' },
27
27
  distance_km: { table: 'activity_bucket', column: 'distance_km', sampling_minutes: 10, exposure: 'full' },
28
- sleep_total_minutes: { table: 'sleep_session', column: 'total_minutes', sampling_minutes: 'per_session', exposure: 'full' },
28
+ sleep_total_minutes: { table: 'sleep_session', column: 'total_minutes', sampling_minutes: 'per_session', exposure: 'full', baselineDailyAggregate: 'session' },
29
29
  sleep_score: { table: 'sleep_session', column: 'avg_confidence', sampling_minutes: 'per_session', exposure: 'full' },
30
30
  device_battery: { table: 'device_battery', column: 'battery_pct', sampling_minutes: 240, exposure: 'full' },
31
31
  });
@@ -44,4 +44,32 @@ export function isMetricExposed(id) {
44
44
  export function getExposedMetricIds() {
45
45
  return Object.keys(METRIC_METADATA).filter(isMetricExposed);
46
46
  }
47
+ /**
48
+ * Metric ids that carry a `user_baseline` row — those declaring a
49
+ * `baselineDailyAggregate`. Seven in v0.2.0, which at three windows each
50
+ * (7d / 30d / 90d) is the 21-baselines-per-user figure in the Sprint 5
51
+ * design.
52
+ */
53
+ export function getBaselineMetricIds() {
54
+ return Object.keys(METRIC_METADATA).filter(id => baselineAggregateFor(id) !== undefined);
55
+ }
56
+ /**
57
+ * `baselineDailyAggregate` for a metric, or `undefined` when it carries no
58
+ * baseline. Accessor rather than a direct property read because the
59
+ * `as const` registry narrows each entry to its own literal type, and the
60
+ * field is genuinely absent — not `undefined` — on entries without one.
61
+ */
62
+ export function baselineAggregateFor(id) {
63
+ const entry = METRIC_METADATA[id];
64
+ return entry.baselineDailyAggregate;
65
+ }
66
+ /** Baseline windows computed per metric (Sprint 5 §7). */
67
+ export const BASELINE_WINDOW_DAYS = [7, 30, 90];
68
+ /**
69
+ * Minimum distinct local DAYS before a baseline row is written
70
+ * (principle 27). Counting readings instead of days is the classic
71
+ * mis-implementation: 15 days of 30-minute SpO₂ sampling is 720 readings
72
+ * but still only 15 days, and must NOT populate a baseline.
73
+ */
74
+ export const BASELINE_MIN_DAYS = 20;
47
75
  //# sourceMappingURL=metric_metadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"metric_metadata.js","sourceRoot":"","sources":["../../src/core/metric_metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC;IAC3C,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IAClF,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IAClF,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE;IACtG,YAAY,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE;IACxG,cAAc,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE;IAC5G,MAAM,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IACtF,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IAC/E,MAAM,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IAC1F,cAAc,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE;IAC7F,QAAQ,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IAClG,WAAW,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;IACxG,mBAAmB,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE;IAC3H,WAAW,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE;IACpH,cAAc,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE;CAC3C,CAAC,CAAA;AAInE;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAY;IAC1C,OAAO,eAAe,CAAC,EAAE,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAA;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAQ,MAAM,CAAC,IAAI,CAAC,eAAe,CAAgB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;AAC7E,CAAC"}
1
+ {"version":3,"file":"metric_metadata.js","sourceRoot":"","sources":["../../src/core/metric_metadata.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/core/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAA;AAEzC,MAAM,WAAW,SAAS;IACxB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjE;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAmCzC,CAAA;AAEF,uEAAuE;AACvE,eAAO,MAAM,eAAe,EAAE,MAA0B,CAAA;AAExD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExE;AAWD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,YAAY,EAChB,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,sBAAsB,CAAC,CAyCjC"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/core/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAA;AAEzC,MAAM,WAAW,SAAS;IACxB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjE;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAgHzC,CAAA;AAEF,uEAAuE;AACvE,eAAO,MAAM,eAAe,EAAE,MAA0B,CAAA;AAExD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExE;AAWD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,YAAY,EAChB,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,sBAAsB,CAAC,CAyCjC"}
@@ -20,7 +20,7 @@
20
20
  * `ensureMigrations` is idempotent and safe to call on every attach.
21
21
  */
22
22
  import { AnalyticsError } from './errors';
23
- import { ensureSchemas, LOCAL_SCHEMAS, qualifyDdl, SCHEMAS } from './schemas';
23
+ import { ensureSchemas, insightIndexDdl, LOCAL_SCHEMAS, qualifyDdl, SCHEMAS } from './schemas';
24
24
  /**
25
25
  * Iceberg namespace remote DDL is issued under — remote tables live at
26
26
  * `<secret>.default.<table>` (spec §Attach Protocol). Local DDL stays
@@ -46,7 +46,7 @@ export const MIGRATIONS = Object.freeze([
46
46
  // namespace explicitly — 2-part `zone_x.<table>` fails with
47
47
  // "Schema with name ... not found" on an attached Iceberg catalog.
48
48
  if (catalogs.remote) {
49
- await ensureSchemas(db, `${catalogs.remote}.${REMOTE_NAMESPACE}`, SCHEMAS);
49
+ await ensureSchemas(db, `${catalogs.remote}.${REMOTE_NAMESPACE}`, SCHEMAS, { skipLocalOnly: true });
50
50
  }
51
51
  },
52
52
  },
@@ -66,6 +66,68 @@ export const MIGRATIONS = Object.freeze([
66
66
  }
67
67
  },
68
68
  },
69
+ {
70
+ version: 3,
71
+ name: 'insight v2 (insight_id / metric / kind / dismissed_at)',
72
+ // Fix CO-2: the pre-0.7.0 `insight` table used an `id` PK and lacked
73
+ // the spec's `metric`, `kind`, and `dismissed_at` columns, so the
74
+ // registry contract (queries.ts worthALookInsight) could never match.
75
+ // Recreates the LOCAL table to the spec shape, preserving rows:
76
+ // id → insight_id, kind defaults to 'threshold', severity
77
+ // 'critical' → 'urgent' (spec enum is info|warn|urgent).
78
+ // Idempotent: skips the recreate when `metric` is already present
79
+ // (fresh installs get the new shape from the baseline ensureSchemas).
80
+ // The remote zone gets CREATE IF NOT EXISTS only — the client never
81
+ // destructively rewrites the attached Iceberg catalog; schema
82
+ // evolution of pre-existing remote tables is a server-side operation.
83
+ async up(db, catalogs) {
84
+ const cols = await db.execute(`SELECT column_name FROM information_schema.columns `
85
+ + `WHERE table_catalog = $catalog AND table_name = 'insight'`, { catalog: catalogs.local });
86
+ const hasMetric = cols.some(c => c.column_name === 'metric');
87
+ if (!hasMetric) {
88
+ // Re-entrancy: a prior partially-failed run may have left the
89
+ // scratch table behind (KV only advances after full success).
90
+ await db.execute(`DROP TABLE IF EXISTS ${catalogs.local}.insight_v2;`);
91
+ const tmpDdl = LOCAL_SCHEMAS.insight.replace('CREATE TABLE insight', `CREATE TABLE ${catalogs.local}.insight_v2`);
92
+ await db.execute(tmpDdl);
93
+ // Old rows had no metric column; the package never wrote rows
94
+ // pre-v2 (rules only emitted events), so 'unknown' only tags
95
+ // app-authored legacy rows.
96
+ await db.execute(`INSERT INTO ${catalogs.local}.insight_v2 `
97
+ + `(insight_id, ts, brand, family_id, user_id, rule_id, metric, kind, `
98
+ + `severity, title, body, evidence, acknowledged_at, dismissed_at) `
99
+ + `SELECT id, ts, brand, family_id, user_id, rule_id, 'unknown', 'threshold', `
100
+ + `CASE WHEN severity = 'critical' THEN 'urgent' ELSE severity END, `
101
+ + `title, body, evidence, acknowledged_at, NULL `
102
+ + `FROM ${catalogs.local}.insight;`);
103
+ await db.execute(`DROP TABLE ${catalogs.local}.insight;`);
104
+ await db.execute(`ALTER TABLE ${catalogs.local}.insight_v2 RENAME TO insight;`);
105
+ }
106
+ await db.execute(insightIndexDdl(catalogs.local));
107
+ if (catalogs.remote) {
108
+ await db.execute(qualifyDdl(SCHEMAS.insight, 'insight', `${catalogs.remote}.${REMOTE_NAMESPACE}`));
109
+ }
110
+ },
111
+ },
112
+ {
113
+ version: 4,
114
+ name: 'user_baseline table (Sprint 5 §7)',
115
+ // Shared day-first baseline store for rules, tools, and screens.
116
+ //
117
+ // NOTE ON NUMBERING: the Sprint 5 spec (§33 / tasks T-03) calls this
118
+ // "migration v3". Versions 2 and 3 were already spent on the
119
+ // device_battery table and the insight v2 rebuild before Sprint 5
120
+ // started, so it lands at 4 instead. Same for the spec's "migration v4"
121
+ // (insight.dismissed_at), which is already satisfied — the column ships
122
+ // in the baseline DDL and step 3 backfills it, so no ALTER is needed.
123
+ //
124
+ // Local-only: baselines are derived from local+R2 union views and are
125
+ // cheap to recompute, so there is no value in pushing them to R2 and
126
+ // no correctness story for two devices racing to write the same row.
127
+ async up(db, catalogs) {
128
+ await db.execute(qualifyDdl(LOCAL_SCHEMAS.user_baseline, 'user_baseline', catalogs.local));
129
+ },
130
+ },
69
131
  ]);
70
132
  /** Version of the latest known migration — target for every attach. */
71
133
  export const CURRENT_VERSION = MIGRATIONS.length;
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/core/migrations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/core/migrations.ts"],"names":[],"mappings":"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"}
@@ -9,9 +9,18 @@
9
9
  * that would preserve data wins.
10
10
  *
11
11
  * 2. **Sweep** the warehouse via `runRetentionSweep` — issues one DELETE
12
- * per retention-managed table, guarded by the per-table push watermark
13
- * from `sync_watermark` so rows still awaiting server sync are never
14
- * dropped (spec Fix 9).
12
+ * per retention-managed table in the LOCAL catalog. The client never
13
+ * sweeps the remote R2 zone (principles 17/53): device-side compaction
14
+ * prunes local data only; server-side retention is R2 snapshot
15
+ * expiration, configured externally.
16
+ *
17
+ * Watermark guard (fix CO-1): tables the pusher pushes (`syncable` in
18
+ * `table_metadata.ts`) are bounded by their KVStore push watermark —
19
+ * `ts < LEAST(now() - retention, $pushWatermark)` — so rows still awaiting
20
+ * R2 push are never dropped. No watermark yet (nothing pushed) means the
21
+ * table is skipped entirely. In `'local'` mode there is no push and never
22
+ * will be — data is local-forever by design — so the plain retention
23
+ * cutoff applies without any watermark guard.
15
24
  *
16
25
  * Internal tables have fixed defaults independent of brand/user config:
17
26
  * - `insight` → 90 days
@@ -22,6 +31,7 @@
22
31
  */
23
32
  import type { HybridDuckDB } from './engine';
24
33
  import type { TableName } from './schemas';
34
+ import type { AnalyticsMode } from './types';
25
35
  export interface EffectiveRetentionInput {
26
36
  /** Brand-level default (v0.1.0 active layer) — from `AnalyticsConfig.retention[brand].days`. */
27
37
  brandDefault: number;
@@ -42,9 +52,28 @@ export declare function resolveEffectiveRetention(input: EffectiveRetentionInput
42
52
  export declare const INSIGHT_RETENTION_DAYS = 90;
43
53
  /** Fixed retention for the `tool_call_audit` table — spec §Retention. */
44
54
  export declare const AUDIT_RETENTION_DAYS = 30;
55
+ /**
56
+ * Push-watermark accessor injected by the factory. Resolves the highest
57
+ * `ts` successfully pushed to R2 for `table` — read from the KVStore key
58
+ * the pusher writes (`analytics:watermark:{brand}:{familyId}:{table}:push`,
59
+ * see sync/watermark.ts). `null` means nothing pushed yet.
60
+ */
61
+ export type PushWatermarkAccessor = (table: TableName) => Promise<Date | null>;
45
62
  export interface RetentionSweepInput {
46
63
  /** Effective retention days for sensor tables (see `resolveEffectiveRetention`). */
47
64
  effectiveDays: number;
65
+ /**
66
+ * Attach mode. `'local'` applies plain retention cutoffs — there is no
67
+ * push in local mode, so nothing ever "reaches R2" and the watermark
68
+ * guard would block deletion forever. Defaults to `'r2'`.
69
+ */
70
+ mode?: AnalyticsMode;
71
+ /**
72
+ * Required in r2 mode to sweep syncable tables. Missing accessor or a
73
+ * `null` watermark ⇒ the table is skipped (spec: no data deleted before
74
+ * it reaches R2).
75
+ */
76
+ getPushWatermark?: PushWatermarkAccessor;
48
77
  }
49
78
  export interface RetentionSweepEntry {
50
79
  table: TableName;
@@ -52,25 +81,25 @@ export interface RetentionSweepEntry {
52
81
  }
53
82
  export interface RetentionSweepResult {
54
83
  swept: RetentionSweepEntry[];
84
+ /** Syncable tables left untouched because no push watermark exists yet. */
85
+ skipped: TableName[];
55
86
  }
56
87
  /**
57
- * Delete expired rows across every retention-managed table under `catalog`,
58
- * bounded by each table's push watermark (`sync_watermark.cursor_ts`).
88
+ * Delete expired rows across every retention-managed table under `catalog`
89
+ * (the LOCAL DuckDB catalog never the attached R2 zone).
59
90
  *
60
91
  * SQL shape per table:
61
92
  *
62
93
  * ```sql
94
+ * -- pushed (syncable) tables, r2 mode:
63
95
  * DELETE FROM {catalog}.{table}
64
- * WHERE {tsCol} < LEAST(
65
- * now() - INTERVAL '{days} days',
66
- * (SELECT MAX(cursor_ts) FROM {catalog}.sync_watermark
67
- * WHERE table_name = '{table}')
68
- * );
96
+ * WHERE {tsCol} < LEAST(now() - INTERVAL '{days} days',
97
+ * CAST($pushWatermark AS TIMESTAMP));
98
+ * -- non-pushed tables, and every table in local mode:
99
+ * DELETE FROM {catalog}.{table}
100
+ * WHERE {tsCol} < now() - INTERVAL '{days} days';
69
101
  * ```
70
102
  *
71
- * When no watermark exists, `MAX` returns NULL, `LEAST` becomes NULL and the
72
- * comparison drops to false — safest default (no deletion on unsynced data).
73
- *
74
103
  * Failure at any table surfaces as `AnalyticsError('retention_failed', …)`;
75
104
  * the caller decides whether to swallow-and-log or bubble.
76
105
  */
@@ -80,6 +109,8 @@ interface DeleteSqlInput {
80
109
  table: TableName;
81
110
  tsCol: string;
82
111
  days: number;
112
+ /** Bound by `$pushWatermark` (ISO string param, cast to TIMESTAMP). */
113
+ watermarkBound?: boolean;
83
114
  }
84
115
  /**
85
116
  * Compose the retention DELETE for one table. Extracted so tests can snapshot
@@ -1 +1 @@
1
- {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/core/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAI1C,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAMhF;AA6CD,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,KAAK,CAAA;AACxC,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAItC,MAAM,WAAW,mBAAmB;IAClC,oFAAoF;IACpF,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAsB/B;AAaD,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAS5D"}
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/core/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAI5C,MAAM,WAAW,uBAAuB;IACtC,gGAAgG;IAChG,YAAY,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAMhF;AAgDD,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,KAAK,CAAA;AACxC,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAItC;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;AAE9E,MAAM,WAAW,mBAAmB;IAClC,oFAAoF;IACpF,aAAa,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,qBAAqB,CAAA;CACzC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,mBAAmB,EAAE,CAAA;IAC5B,2EAA2E;IAC3E,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAqC/B;AAaD,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,SAAS,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAO5D"}
@@ -9,9 +9,18 @@
9
9
  * that would preserve data wins.
10
10
  *
11
11
  * 2. **Sweep** the warehouse via `runRetentionSweep` — issues one DELETE
12
- * per retention-managed table, guarded by the per-table push watermark
13
- * from `sync_watermark` so rows still awaiting server sync are never
14
- * dropped (spec Fix 9).
12
+ * per retention-managed table in the LOCAL catalog. The client never
13
+ * sweeps the remote R2 zone (principles 17/53): device-side compaction
14
+ * prunes local data only; server-side retention is R2 snapshot
15
+ * expiration, configured externally.
16
+ *
17
+ * Watermark guard (fix CO-1): tables the pusher pushes (`syncable` in
18
+ * `table_metadata.ts`) are bounded by their KVStore push watermark —
19
+ * `ts < LEAST(now() - retention, $pushWatermark)` — so rows still awaiting
20
+ * R2 push are never dropped. No watermark yet (nothing pushed) means the
21
+ * table is skipped entirely. In `'local'` mode there is no push and never
22
+ * will be — data is local-forever by design — so the plain retention
23
+ * cutoff applies without any watermark guard.
15
24
  *
16
25
  * Internal tables have fixed defaults independent of brand/user config:
17
26
  * - `insight` → 90 days
@@ -21,6 +30,7 @@
21
30
  * are skipped.
22
31
  */
23
32
  import { AnalyticsError } from './errors';
33
+ import { isSyncable } from './table_metadata';
24
34
  /**
25
35
  * Resolve effective retention days per spec §Retention precedence — returns
26
36
  * `max(userOverride ?? 0, familySetting ?? 0, brandDefault)`.
@@ -49,6 +59,11 @@ const TABLE_RETENTION = Object.freeze({
49
59
  insight: { tsColumn: 'ts', kind: 'insight' },
50
60
  sync_watermark: null,
51
61
  tool_call_audit: { tsColumn: 'ts', kind: 'audit' },
62
+ // Never swept: one row per (user, metric, window) that the baseline
63
+ // job UPSERTs in place. There is no growth to reclaim, and deleting
64
+ // by `computed_at` would drop live baselines for any metric whose
65
+ // recompute is stale.
66
+ user_baseline: null,
52
67
  });
53
68
  // -------------------- constants --------------------
54
69
  /** Fixed retention for the `insight` table — spec §Retention. */
@@ -56,44 +71,56 @@ export const INSIGHT_RETENTION_DAYS = 90;
56
71
  /** Fixed retention for the `tool_call_audit` table — spec §Retention. */
57
72
  export const AUDIT_RETENTION_DAYS = 30;
58
73
  /**
59
- * Delete expired rows across every retention-managed table under `catalog`,
60
- * bounded by each table's push watermark (`sync_watermark.cursor_ts`).
74
+ * Delete expired rows across every retention-managed table under `catalog`
75
+ * (the LOCAL DuckDB catalog never the attached R2 zone).
61
76
  *
62
77
  * SQL shape per table:
63
78
  *
64
79
  * ```sql
80
+ * -- pushed (syncable) tables, r2 mode:
81
+ * DELETE FROM {catalog}.{table}
82
+ * WHERE {tsCol} < LEAST(now() - INTERVAL '{days} days',
83
+ * CAST($pushWatermark AS TIMESTAMP));
84
+ * -- non-pushed tables, and every table in local mode:
65
85
  * DELETE FROM {catalog}.{table}
66
- * WHERE {tsCol} < LEAST(
67
- * now() - INTERVAL '{days} days',
68
- * (SELECT MAX(cursor_ts) FROM {catalog}.sync_watermark
69
- * WHERE table_name = '{table}')
70
- * );
86
+ * WHERE {tsCol} < now() - INTERVAL '{days} days';
71
87
  * ```
72
88
  *
73
- * When no watermark exists, `MAX` returns NULL, `LEAST` becomes NULL and the
74
- * comparison drops to false — safest default (no deletion on unsynced data).
75
- *
76
89
  * Failure at any table surfaces as `AnalyticsError('retention_failed', …)`;
77
90
  * the caller decides whether to swallow-and-log or bubble.
78
91
  */
79
92
  export async function runRetentionSweep(db, catalog, input) {
93
+ const mode = input.mode ?? 'r2';
80
94
  const swept = [];
95
+ const skipped = [];
81
96
  const tableNames = Object.keys(TABLE_RETENTION);
82
97
  for (const table of tableNames) {
83
98
  const cfg = TABLE_RETENTION[table];
84
99
  if (!cfg)
85
100
  continue;
86
101
  const days = daysForKind(cfg.kind, input.effectiveDays);
87
- const sql = buildDeleteSql({ catalog, table, tsCol: cfg.tsColumn, days });
102
+ const guarded = mode !== 'local' && isSyncable(table);
103
+ let params;
104
+ if (guarded) {
105
+ const watermark = (await input.getPushWatermark?.(table)) ?? null;
106
+ if (!watermark) {
107
+ // Nothing pushed yet — deleting would lose data that never
108
+ // reached R2. Leave the table alone until the pusher advances.
109
+ skipped.push(table);
110
+ continue;
111
+ }
112
+ params = { pushWatermark: watermark.toISOString() };
113
+ }
114
+ const sql = buildDeleteSql({ catalog, table, tsCol: cfg.tsColumn, days, watermarkBound: guarded });
88
115
  try {
89
- await db.execute(sql);
116
+ await db.execute(sql, params);
90
117
  swept.push({ table, days });
91
118
  }
92
119
  catch (cause) {
93
120
  throw new AnalyticsError('retention_failed', `runRetentionSweep failed on table ${table}`, cause);
94
121
  }
95
122
  }
96
- return { swept };
123
+ return { swept, skipped };
97
124
  }
98
125
  function daysForKind(kind, effective) {
99
126
  switch (kind) {
@@ -110,11 +137,11 @@ function daysForKind(kind, effective) {
110
137
  * the exact SQL issued without a live engine.
111
138
  */
112
139
  export function buildDeleteSql(input) {
113
- const { catalog, table, tsCol, days } = input;
114
- return (`DELETE FROM ${catalog}.${table} `
115
- + `WHERE ${tsCol} < LEAST(`
116
- + `now() - INTERVAL '${days} days', `
117
- + `(SELECT MAX(cursor_ts) FROM ${catalog}.sync_watermark WHERE table_name = '${table}')`
118
- + `);`);
140
+ const { catalog, table, tsCol, days, watermarkBound } = input;
141
+ const cutoff = `now() - INTERVAL '${days} days'`;
142
+ const bound = watermarkBound
143
+ ? `LEAST(${cutoff}, CAST($pushWatermark AS TIMESTAMP))`
144
+ : cutoff;
145
+ return `DELETE FROM ${catalog}.${table} WHERE ${tsCol} < ${bound};`;
119
146
  }
120
147
  //# sourceMappingURL=retention.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/core/retention.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/core/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAGzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAc7C;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAA8B;IACtE,OAAO,IAAI,CAAC,GAAG,CACb,KAAK,CAAC,YAAY,IAAI,CAAC,EACvB,KAAK,CAAC,aAAa,IAAI,CAAC,EACxB,KAAK,CAAC,YAAY,CACnB,CAAA;AACH,CAAC;AAqBD,MAAM,eAAe,GAAuD,MAAM,CAAC,MAAM,CAAC;IACxF,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IACvC,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC9C,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxC,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC/C,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC5C,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IACnD,4EAA4E;IAC5E,mDAAmD;IACnD,aAAa,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE;IACrD,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC/C,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC7C,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAChD,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;IAClD,aAAa,EAAE,IAAI;IACnB,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;IAC5C,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE;IAClD,oEAAoE;IACpE,oEAAoE;IACpE,kEAAkE;IAClE,sBAAsB;IACtB,aAAa,EAAE,IAAI;CACpB,CAAC,CAAA;AAEF,sDAAsD;AAEtD,iEAAiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAA;AACxC,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAwCtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAgB,EAChB,OAAe,EACf,KAA0B;IAE1B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,IAAI,CAAA;IAC/B,MAAM,KAAK,GAA0B,EAAE,CAAA;IACvC,MAAM,OAAO,GAAgB,EAAE,CAAA;IAC/B,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAgB,CAAA;IAC9D,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;QAClC,IAAI,CAAC,GAAG;YAAE,SAAQ;QAElB,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;QACvD,MAAM,OAAO,GAAG,IAAI,KAAK,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;QACrD,IAAI,MAA2C,CAAA;QAC/C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,SAAS,GAAG,CAAC,MAAM,KAAK,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAA;YACjE,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,2DAA2D;gBAC3D,+DAA+D;gBAC/D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBACnB,SAAQ;YACV,CAAC;YACD,MAAM,GAAG,EAAE,aAAa,EAAE,SAAS,CAAC,WAAW,EAAE,EAAE,CAAA;QACrD,CAAC;QAED,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAA;QAClG,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAC7B,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,cAAc,CACtB,kBAAkB,EAClB,qCAAqC,KAAK,EAAE,EAC5C,KAAK,CACN,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AAC3B,CAAC;AAED,SAAS,WAAW,CAAC,IAA4B,EAAE,SAAiB;IAClE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,SAAS,CAAA;QAClB,KAAK,SAAS;YACZ,OAAO,sBAAsB,CAAA;QAC/B,KAAK,OAAO;YACV,OAAO,oBAAoB,CAAA;IAC/B,CAAC;AACH,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAqB;IAClD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,KAAK,CAAA;IAC7D,MAAM,MAAM,GAAG,qBAAqB,IAAI,QAAQ,CAAA;IAChD,MAAM,KAAK,GAAG,cAAc;QAC1B,CAAC,CAAC,SAAS,MAAM,sCAAsC;QACvD,CAAC,CAAC,MAAM,CAAA;IACV,OAAO,eAAe,OAAO,IAAI,KAAK,UAAU,KAAK,MAAM,KAAK,GAAG,CAAA;AACrE,CAAC"}
@@ -23,13 +23,82 @@
23
23
  */
24
24
  import type { HybridDuckDB } from './engine';
25
25
  /** Ordered list of every warehouse table. Order = creation order. */
26
- export declare const TABLE_NAMES: readonly ["hrv", "heart_rate", "spo2", "temperature", "activity", "activity_bucket", "sleep_session", "sleep_stage", "sleep_raw", "device_event", "device_battery", "device_config", "insight", "sync_watermark", "tool_call_audit"];
26
+ export declare const TABLE_NAMES: readonly ["hrv", "heart_rate", "spo2", "temperature", "activity", "activity_bucket", "sleep_session", "sleep_stage", "sleep_raw", "device_event", "device_battery", "device_config", "insight", "sync_watermark", "tool_call_audit", "user_baseline"];
27
27
  export type TableName = (typeof TABLE_NAMES)[number];
28
+ /**
29
+ * Tables that exist only in the local catalog and are never created in the
30
+ * attached R2 zone.
31
+ *
32
+ * `user_baseline` is derived data: it is recomputed from the union views
33
+ * after each sync cycle and is cheap to rebuild, so pushing it would add an
34
+ * Iceberg table nobody reads and invite two devices in the same family to
35
+ * race on the same composite PK.
36
+ *
37
+ * `sync_watermark` is deliberately NOT here, despite being per-device
38
+ * bookkeeping that is never pushed (`TABLE_METADATA.syncable: false`).
39
+ * Excluding it broke the T-18 retention integration cases, which exercise
40
+ * the sweep against the real attached Iceberg catalog and need the table
41
+ * present there. An empty remote copy is harmless; a failing sweep is not.
42
+ */
43
+ export declare const LOCAL_ONLY_TABLES: ReadonlySet<TableName>;
28
44
  /**
29
45
  * Frozen DDL for every warehouse table. Copied verbatim from spec §Table
30
46
  * schema; rewrite happens in `ensureSchemas` at issue time.
31
47
  */
32
48
  export declare const SCHEMAS: Readonly<Record<TableName, string>>;
49
+ /**
50
+ * Lookup index for the `insight` table (spec §Table schema). Local catalog
51
+ * only — Iceberg has no CREATE INDEX; remote-side query pruning comes from
52
+ * the `day(ts)` partition instead. Issued by migration step 3.
53
+ */
54
+ export declare function insightIndexDdl(catalog: string): string;
55
+ /**
56
+ * Sensor tables exposed through a `v_{table}` union view (Sprint 5 §3).
57
+ *
58
+ * Registry queries, rule SQL, and tool SQL all target `v_{table}` and never
59
+ * `local.{table}` or `r2.default.{table}` directly (principle 19). Internal
60
+ * tables are absent deliberately: `insight`, `sync_watermark`,
61
+ * `tool_call_audit`, `user_baseline` and `device_config` are local-authoritative
62
+ * or local-only, so a union would either duplicate rows or read a remote
63
+ * copy that is never authoritative.
64
+ */
65
+ export declare const VIEWED_TABLES: readonly ["hrv", "heart_rate", "spo2", "temperature", "activity", "activity_bucket", "sleep_session", "sleep_stage", "device_event", "device_battery"];
66
+ export type ViewedTable = (typeof VIEWED_TABLES)[number];
67
+ /** Timestamp column the watermark partitions on, per table. */
68
+ export declare function watermarkColumnFor(table: ViewedTable): string;
69
+ /**
70
+ * Build the `CREATE OR REPLACE VIEW v_{table}` DDL for one sensor table.
71
+ *
72
+ * The view is what makes freshness honest: rows the device has flushed
73
+ * locally but not yet pushed to R2 are invisible in `r2.default.{table}`,
74
+ * and rows already pushed exist in BOTH catalogs. The push watermark
75
+ * partitions the two sources exactly —
76
+ *
77
+ * local: rows strictly AFTER the watermark (not yet pushed)
78
+ * remote: everything (pushed)
79
+ *
80
+ * — so the union is complete with no duplicates at the boundary, and a row
81
+ * becomes remote-visible at the same instant it stops being local-visible.
82
+ *
83
+ * The watermark subquery reads `sync_watermark` rather than taking a bound
84
+ * parameter because DuckDB views cannot close over query-time params; the
85
+ * view must resolve the current cursor on every scan. `$brand` /
86
+ * `$family_id` are likewise unavailable inside a view body, so the tenant
87
+ * filter is baked in at creation time from the attach context — which is
88
+ * safe precisely because views are dropped and recreated on every attach
89
+ * (T-06), so a brand switch cannot leave a view scoped to the old tenant.
90
+ *
91
+ * `remoteCatalog` omitted (local mode) produces a local-only view with no
92
+ * UNION, so consumers write one SQL string that works in both modes.
93
+ */
94
+ export declare function generateViewDdl(table: ViewedTable, ctx: {
95
+ brand: string;
96
+ familyId: string;
97
+ localCatalog: string;
98
+ remoteCatalog?: string;
99
+ }): string;
100
+ /** `DROP VIEW IF EXISTS v_{table}` for detach + brand switch. */
101
+ export declare function dropViewDdl(table: ViewedTable): string;
33
102
  /**
34
103
  * Rewrite one base DDL to `CREATE TABLE IF NOT EXISTS <catalog>.<table>`.
35
104
  *
@@ -62,5 +131,7 @@ export declare const LOCAL_SCHEMAS: Readonly<Record<TableName, string>>;
62
131
  * catalog (without). Default is `SCHEMAS` for back-compat with 0.4.x
63
132
  * callers that only wrote to the attached remote.
64
133
  */
65
- export declare function ensureSchemas(db: HybridDuckDB, catalog: string, schemas?: Readonly<Record<TableName, string>>): Promise<void>;
134
+ export declare function ensureSchemas(db: HybridDuckDB, catalog: string, schemas?: Readonly<Record<TableName, string>>, opts?: {
135
+ skipLocalOnly?: boolean;
136
+ }): Promise<void>;
66
137
  //# sourceMappingURL=schemas.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/core/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,qEAAqE;AACrE,eAAO,MAAM,WAAW,sOAgBd,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CA0KtD,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKrF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAI7D,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAW,GACrD,OAAO,CAAC,IAAI,CAAC,CAcf"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/core/schemas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,qEAAqE;AACrE,eAAO,MAAM,WAAW,uPAiBd,CAAA;AAEV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,SAAS,CAEnD,CAAA;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAwMtD,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,wJAWhB,CAAA;AAEV,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACrF,MAAM,CA0BR;AAkBD,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKrF;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAI7D,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAW,EACtD,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,OAAO,CAAC,IAAI,CAAC,CAmBf"}