@camstack/system 1.2.457 → 1.2.458

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 (89) hide show
  1. package/dist/addon-runner.js +163 -27
  2. package/dist/addon-runner.mjs +193 -57
  3. package/dist/{agent-https-CK9Z4sSR.mjs → agent-https-CQA3W-3e.mjs} +2 -2
  4. package/dist/{agent-https-CoyOZ3pC.js → agent-https-DOELEug8.js} +2 -2
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +2 -2
  10. package/dist/builtins/alerts/alerts.addon.mjs +2 -2
  11. package/dist/builtins/autotrack/index.js +1 -1
  12. package/dist/builtins/autotrack/index.mjs +1 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  15. package/dist/builtins/calendars/calendars.addon.js +1 -1
  16. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  17. package/dist/builtins/camera-grid/index.js +1 -1
  18. package/dist/builtins/camera-grid/index.mjs +1 -1
  19. package/dist/builtins/composer/composer.addon.js +2 -2
  20. package/dist/builtins/composer/composer.addon.mjs +2 -2
  21. package/dist/builtins/console-logging/index.js +1 -1
  22. package/dist/builtins/console-logging/index.mjs +1 -1
  23. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  24. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  25. package/dist/builtins/device-history/device-history.addon.d.ts +13 -0
  26. package/dist/builtins/device-history/device-history.addon.js +296 -233
  27. package/dist/builtins/device-history/device-history.addon.mjs +296 -233
  28. package/dist/builtins/device-history/ingest/producer-ingest.d.ts +11 -1
  29. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  31. package/dist/builtins/hub-forwarder/index.js +1 -1
  32. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  33. package/dist/builtins/keychain/index.js +4 -4
  34. package/dist/builtins/keychain/index.mjs +4 -4
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  37. package/dist/builtins/local-auth/local-auth.addon.js +3 -3
  38. package/dist/builtins/local-auth/local-auth.addon.mjs +3 -3
  39. package/dist/builtins/local-network/local-network.addon.js +2 -2
  40. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  41. package/dist/builtins/loki-logging/index.js +1 -1
  42. package/dist/builtins/loki-logging/index.mjs +1 -1
  43. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  44. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  45. package/dist/builtins/platform-probe/coral-accelerators.d.ts +26 -9
  46. package/dist/builtins/platform-probe/index.js +11 -68
  47. package/dist/builtins/platform-probe/index.mjs +11 -68
  48. package/dist/builtins/record-entries/index.js +1 -1
  49. package/dist/builtins/record-entries/index.mjs +1 -1
  50. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  51. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  52. package/dist/builtins/snapshot/index.js +1 -1
  53. package/dist/builtins/snapshot/index.mjs +1 -1
  54. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  55. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/index.js +1 -1
  57. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  58. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  59. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  60. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  61. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  62. package/dist/builtins/system-config/system-config.addon.js +3 -3
  63. package/dist/builtins/system-config/system-config.addon.mjs +3 -3
  64. package/dist/builtins/winston-logging/index.js +1 -1
  65. package/dist/builtins/winston-logging/index.mjs +1 -1
  66. package/dist/{composition-sources-B_jeZDRJ.mjs → composition-sources-BVguN7VV.mjs} +1 -1
  67. package/dist/{composition-sources-qKyvMAvc.js → composition-sources-DURQNDJ4.js} +1 -1
  68. package/dist/{constants-cxKcZRMi.mjs → constants-FGjnsE2x.mjs} +1 -1
  69. package/dist/{constants-B_X9cWEK.js → constants-Njka8Cv6.js} +1 -1
  70. package/dist/coral-accelerators-Bi_c3Rft.js +175 -0
  71. package/dist/coral-accelerators-DcnZvctM.mjs +162 -0
  72. package/dist/{device-ca-CVK3YcR_.js → device-ca-BrVK_5s3.js} +1 -1
  73. package/dist/{device-ca-BB6nHAL5.mjs → device-ca-lEaAQ9Xi.mjs} +1 -1
  74. package/dist/{dist-C89nqNnn.js → dist-BZQjLPiz.js} +55 -10
  75. package/dist/{dist-Dhux90yB.mjs → dist-B_GRPhnY.mjs} +55 -10
  76. package/dist/file-host-worker.js +1 -1
  77. package/dist/file-host-worker.mjs +1 -1
  78. package/dist/{hub-mdns-ciao-B8olZqqd.mjs → hub-mdns-ciao-Bg2HfTwP.mjs} +2 -2
  79. package/dist/{hub-mdns-ciao-DB1Y9agt.js → hub-mdns-ciao-nce3TXs_.js} +2 -2
  80. package/dist/index.js +12 -35
  81. package/dist/index.mjs +12 -35
  82. package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +7 -3
  83. package/dist/{phase6-declare-gate-BO-_jLh_.js → phase6-declare-gate-Dag1_WR2.js} +1 -1
  84. package/dist/{phase6-declare-gate-x9ZCbQJP.mjs → phase6-declare-gate-Lb-mrVvz.mjs} +1 -1
  85. package/dist/{restore-archive-DyOaPeAr.mjs → restore-archive-C7kLDI_0.mjs} +1 -1
  86. package/dist/{restore-archive-4DOUR5Gu.js → restore-archive-FyfZ8YMC.js} +1 -1
  87. package/dist/{wal-maintenance-DVN-DiVP.js → wal-maintenance-DfSIOj24.js} +0 -0
  88. package/dist/{wal-maintenance-BMiX6UIa.mjs → wal-maintenance-Duejh7Lx.mjs} +0 -0
  89. package/package.json +1 -1
@@ -3,7 +3,239 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-C89nqNnn.js");
6
+ const require_dist = require("../../dist-BZQjLPiz.js");
7
+ //#region src/builtins/device-history/time/bucket-time.ts
8
+ /**
9
+ * Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
10
+ *
11
+ * 5m and 1h buckets are aligned to UTC; a 1d bucket starts at HUB-LOCAL
12
+ * midnight, so a day is 23, 24 or 25 hours long across a DST change (operator
13
+ * answer Q4). Pure: the zone is the process zone (`TZ`), nothing is read from
14
+ * the environment here.
15
+ */
16
+ var FIVE_MINUTES_MS = 5 * 6e4;
17
+ var HOUR_MS = 60 * 6e4;
18
+ var DAY_MS = 24 * HOUR_MS;
19
+ function floorTo(ts, widthMs) {
20
+ return Math.floor(ts / widthMs) * widthMs;
21
+ }
22
+ /** Hub-local midnight at or before `ts`. */
23
+ function startOfLocalDay(ts) {
24
+ const d = new Date(ts);
25
+ d.setHours(0, 0, 0, 0);
26
+ return d.getTime();
27
+ }
28
+ /** The local midnight following the local midnight `dayStart`. */
29
+ function nextLocalDayStart(dayStart) {
30
+ const d = new Date(dayStart);
31
+ d.setDate(d.getDate() + 1);
32
+ d.setHours(0, 0, 0, 0);
33
+ return d.getTime();
34
+ }
35
+ /** Start of the `tier` bucket containing `ts`. */
36
+ function bucketStartOf(tier, ts) {
37
+ if (tier === "5m") return floorTo(ts, FIVE_MINUTES_MS);
38
+ if (tier === "1h") return floorTo(ts, HOUR_MS);
39
+ return startOfLocalDay(ts);
40
+ }
41
+ /** Exclusive end of the `tier` bucket that starts at `start`. */
42
+ function bucketEndOf(tier, start) {
43
+ if (tier === "5m") return start + FIVE_MINUTES_MS;
44
+ if (tier === "1h") return start + HOUR_MS;
45
+ return nextLocalDayStart(start);
46
+ }
47
+ /** Quiet time after a bucket closes before it is rolled up (late writers). */
48
+ var ROLLUP_GRACE_MS = {
49
+ "5m": 3e4,
50
+ "1h": 5 * 6e4,
51
+ "1d": 15 * 6e4
52
+ };
53
+ //#endregion
54
+ //#region src/builtins/device-history/retention/departure-tracker.ts
55
+ var DEPARTED_ABSENCE_MS = 7 * DAY_MS;
56
+ var DepartureTracker = class {
57
+ store;
58
+ registry;
59
+ logger;
60
+ missing = /* @__PURE__ */ new Map();
61
+ constructor(store, registry, logger) {
62
+ this.store = store;
63
+ this.registry = registry;
64
+ this.logger = logger;
65
+ }
66
+ async load() {
67
+ this.missing = await this.store.loadDepartures();
68
+ }
69
+ missingSince(deviceId) {
70
+ return this.missing.get(deviceId);
71
+ }
72
+ /** Forget a device's clock — its series were removed because it was DELETED (D865). */
73
+ async forget(deviceId) {
74
+ await this.store.clearMissing([deviceId]);
75
+ const next = new Map(this.missing);
76
+ next.delete(deviceId);
77
+ this.missing = next;
78
+ }
79
+ /** Feed one SUCCESSFUL listing. A failed read never reaches here. */
80
+ async observe(listed, now) {
81
+ const withSeries = new Set(this.registry.all().flatMap((s) => s.deviceId === null ? [] : [s.deviceId]));
82
+ const back = [...this.missing.keys()].filter((id) => listed.has(id));
83
+ const gone = [...withSeries].filter((id) => !listed.has(id) && !this.missing.has(id));
84
+ try {
85
+ await this.store.clearMissing(back);
86
+ await this.store.markMissing(gone, now);
87
+ } catch (err) {
88
+ this.logger.warn("device-history: departure clock not written", { meta: { error: err instanceof Error ? err.message : String(err) } });
89
+ return;
90
+ }
91
+ const next = new Map(this.missing);
92
+ for (const id of back) next.delete(id);
93
+ for (const id of gone) next.set(id, now);
94
+ this.missing = next;
95
+ }
96
+ };
97
+ //#endregion
98
+ //#region src/builtins/device-history/retention/retention.ts
99
+ var DEFAULT_RETENTION = {
100
+ rawDays: 7,
101
+ fiveMinuteDays: 30,
102
+ hourDays: 365,
103
+ dayDays: null
104
+ };
105
+ /** Cutoff per tier: rows strictly older are deleted. `null` = keep forever. */
106
+ function retentionCutoffs(policy, now) {
107
+ return {
108
+ raw: now - policy.rawDays * DAY_MS,
109
+ "5m": now - policy.fiveMinuteDays * DAY_MS,
110
+ "1h": now - policy.hourDays * DAY_MS,
111
+ "1d": policy.dayDays === null || policy.dayDays <= 0 ? null : now - policy.dayDays * DAY_MS
112
+ };
113
+ }
114
+ var TIERS = [
115
+ "raw",
116
+ "5m",
117
+ "1h",
118
+ "1d"
119
+ ];
120
+ var RETENTION_STEP_MS = {
121
+ raw: HOUR_MS,
122
+ "5m": HOUR_MS,
123
+ "1h": DAY_MS,
124
+ "1d": DAY_MS
125
+ };
126
+ var MAX_PURGES_PER_RUN = 20;
127
+ /** Width of one delete statement's time range, per tier. */
128
+ var SLICE_MS = {
129
+ raw: DAY_MS,
130
+ "5m": DAY_MS,
131
+ "1h": 30 * DAY_MS,
132
+ "1d": 30 * DAY_MS
133
+ };
134
+ /** A bucket's start may precede the series' first point by up to a day. */
135
+ var BUCKET_LEAD_MS = 2 * DAY_MS;
136
+ /** The short tiers hold nothing older than this unless the operator configured more. */
137
+ var SHORT_TIER_LOOKBACK_MS = 400 * DAY_MS;
138
+ var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
139
+ var RetentionJob = class {
140
+ deps;
141
+ appliedUpTo = /* @__PURE__ */ new Map();
142
+ running = null;
143
+ constructor(deps) {
144
+ this.deps = deps;
145
+ }
146
+ run(policy, now) {
147
+ if (this.running !== null) return this.running;
148
+ const run = this.doRun(policy, now).finally(() => {
149
+ this.running = null;
150
+ });
151
+ this.running = run;
152
+ return run;
153
+ }
154
+ async doRun(policy, now) {
155
+ const { registry, counters, logger } = this.deps;
156
+ const cutoffs = retentionCutoffs(policy, now);
157
+ try {
158
+ for (const tier of TIERS) {
159
+ const cutoff = cutoffs[tier];
160
+ if (cutoff === null) continue;
161
+ for (const series of registry.all()) {
162
+ if (series.firstTs - 864e5 >= cutoff) continue;
163
+ if (tier === "raw" && registry.metric(series.key)?.ingest === "buckets") continue;
164
+ const marker = `${tier}:${series.id}`;
165
+ const applied = this.appliedUpTo.get(marker);
166
+ if (applied !== void 0 && cutoff - applied < RETENTION_STEP_MS[tier]) continue;
167
+ const lo = Math.max(0, applied === void 0 ? series.firstTs - BUCKET_LEAD_MS : applied - RETENTION_STEP_MS[tier]);
168
+ counters.addDeleted(tier, await this.deleteSliced(tier, series.id, lo, cutoff - 1));
169
+ this.appliedUpTo.set(marker, cutoff);
170
+ }
171
+ }
172
+ await this.purgeDeparted(now);
173
+ counters.retentionLastRunAt = now;
174
+ } catch (err) {
175
+ logger.warn("device-history: retention pass failed — retried at the next run", { meta: { error: err instanceof Error ? err.message : String(err) } });
176
+ }
177
+ }
178
+ /** Delete `[lo, hiInclusive]` of one tier in time slices, yielding between them. */
179
+ /** Drop the visit markers of a purged series. */
180
+ forgetSeries(seriesId) {
181
+ for (const tier of TIERS) this.appliedUpTo.delete(`${tier}:${seriesId}`);
182
+ }
183
+ async deleteSliced(tier, seriesId, lo, hiInclusive) {
184
+ const yieldNow = this.deps.yieldNow ?? yieldToEventLoop;
185
+ let deleted = 0;
186
+ for (let from = lo; from <= hiInclusive; from += SLICE_MS[tier]) {
187
+ const to = Math.min(from + SLICE_MS[tier] - 1, hiInclusive);
188
+ deleted += await this.deps.store.deleteRange(tier, seriesId, from, to);
189
+ await yieldNow();
190
+ }
191
+ return deleted;
192
+ }
193
+ /** Tier by tier in slices, then the series row (and whatever the slices missed). */
194
+ async purgeSeries(series, now) {
195
+ for (const tier of TIERS) {
196
+ const lo = tier === "raw" || tier === "5m" ? Math.max(series.firstTs - BUCKET_LEAD_MS, now - SHORT_TIER_LOOKBACK_MS) : series.firstTs - BUCKET_LEAD_MS;
197
+ await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
198
+ }
199
+ await this.deps.store.deleteSeries(series.id);
200
+ }
201
+ /**
202
+ * Delete every series of a DELETED device now (D865). Driven by a tombstone
203
+ * through the `device-removal-hook`, never by a listing: the week-long
204
+ * absence clock above remains the backstop for a device removed before
205
+ * tombstones existed.
206
+ */
207
+ async purgeDevice(deviceId, now) {
208
+ const { registry, counters } = this.deps;
209
+ let purged = 0;
210
+ for (const series of registry.all()) {
211
+ if (series.deviceId !== deviceId) continue;
212
+ await this.purgeSeries(series, now);
213
+ registry.remove(series.id);
214
+ counters.purgedSeries += 1;
215
+ purged += 1;
216
+ }
217
+ return purged;
218
+ }
219
+ async purgeDeparted(now) {
220
+ const { registry, departures, counters, logger } = this.deps;
221
+ let purged = 0;
222
+ for (const series of registry.all()) {
223
+ if (purged >= MAX_PURGES_PER_RUN) break;
224
+ if (series.deviceId === null) continue;
225
+ const since = departures.missingSince(series.deviceId);
226
+ if (since === void 0 || now - since < DEPARTED_ABSENCE_MS) continue;
227
+ await this.purgeSeries(series, now);
228
+ registry.remove(series.id);
229
+ counters.purgedSeries += 1;
230
+ purged += 1;
231
+ logger.info("device-history: purged the series of an unregistered device", {
232
+ tags: { deviceId: series.deviceId },
233
+ meta: { key: series.key }
234
+ });
235
+ }
236
+ }
237
+ };
238
+ //#endregion
7
239
  //#region src/builtins/device-history/store/series-registry.ts
8
240
  function subjectIdOf(subject) {
9
241
  return subject.kind === "device" ? `d:${subject.deviceId}` : `n:${subject.nodeId}`;
@@ -735,52 +967,27 @@ var PointWriter = class {
735
967
  }
736
968
  };
737
969
  //#endregion
738
- //#region src/builtins/device-history/time/bucket-time.ts
970
+ //#region src/builtins/device-history/ingest/producer-ingest.ts
971
+ /** One point per series per this many ms, sustained. */
972
+ var RECORD_MIN_INTERVAL_MS = 1e4;
739
973
  /**
740
- * Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
741
- *
742
- * 5m and 1h buckets are aligned to UTC; a 1d bucket starts at HUB-LOCAL
743
- * midnight, so a day is 23, 24 or 25 hours long across a DST change (operator
744
- * answer Q4). Pure: the zone is the process zone (`TZ`), nothing is read from
745
- * the environment here.
974
+ * The slot after accepting a point at `ts`. A point older than the newest
975
+ * accepted one (a late arrival) moves nothing. Otherwise the consumed slot is
976
+ * the later of the one that was due and `ts` itself: an early point uses the
977
+ * due slot (the grid holds, so slack never compounds into a higher rate), a
978
+ * late one restarts the grid at `ts` (no credit is banked for a gap).
746
979
  */
747
- var FIVE_MINUTES_MS = 5 * 6e4;
748
- var HOUR_MS = 60 * 6e4;
749
- var DAY_MS = 24 * HOUR_MS;
750
- function floorTo(ts, widthMs) {
751
- return Math.floor(ts / widthMs) * widthMs;
752
- }
753
- /** Hub-local midnight at or before `ts`. */
754
- function startOfLocalDay(ts) {
755
- const d = new Date(ts);
756
- d.setHours(0, 0, 0, 0);
757
- return d.getTime();
758
- }
759
- /** The local midnight following the local midnight `dayStart`. */
760
- function nextLocalDayStart(dayStart) {
761
- const d = new Date(dayStart);
762
- d.setDate(d.getDate() + 1);
763
- d.setHours(0, 0, 0, 0);
764
- return d.getTime();
765
- }
766
- /** Start of the `tier` bucket containing `ts`. */
767
- function bucketStartOf(tier, ts) {
768
- if (tier === "5m") return floorTo(ts, FIVE_MINUTES_MS);
769
- if (tier === "1h") return floorTo(ts, HOUR_MS);
770
- return startOfLocalDay(ts);
771
- }
772
- /** Exclusive end of the `tier` bucket that starts at `start`. */
773
- function bucketEndOf(tier, start) {
774
- if (tier === "5m") return start + FIVE_MINUTES_MS;
775
- if (tier === "1h") return start + HOUR_MS;
776
- return nextLocalDayStart(start);
980
+ function nextSlot(slot, ts) {
981
+ if (slot === void 0) return {
982
+ lastTs: ts,
983
+ nextDue: ts + RECORD_MIN_INTERVAL_MS
984
+ };
985
+ if (ts < slot.lastTs) return slot;
986
+ return {
987
+ lastTs: ts,
988
+ nextDue: Math.max(slot.nextDue, ts) + RECORD_MIN_INTERVAL_MS
989
+ };
777
990
  }
778
- /** Quiet time after a bucket closes before it is rolled up (late writers). */
779
- var ROLLUP_GRACE_MS = {
780
- "5m": 3e4,
781
- "1h": 5 * 6e4,
782
- "1d": 15 * 6e4
783
- };
784
991
  function addRefusal(refusals, key, reason) {
785
992
  const byReason = refusals.get(key) ?? /* @__PURE__ */ new Map();
786
993
  byReason.set(reason, (byReason.get(reason) ?? 0) + 1);
@@ -798,7 +1005,7 @@ function toResult(accepted, refusals) {
798
1005
  }
799
1006
  var ProducerIngest = class {
800
1007
  deps;
801
- /** Last accepted point time per series, for the sustained-rate bound. */
1008
+ /** Rate state per series identity, for the sustained-rate bound. */
802
1009
  lastAccepted = /* @__PURE__ */ new Map();
803
1010
  constructor(deps) {
804
1011
  this.deps = deps;
@@ -857,13 +1064,13 @@ var ProducerIngest = class {
857
1064
  continue;
858
1065
  }
859
1066
  const identity = identityOf(point.key, subjectIdOf(point.subject), point.scope);
860
- const last = this.lastAccepted.get(identity);
861
- if (last !== void 0 && point.ts >= last && point.ts - last < 1e4) {
1067
+ const slot = this.lastAccepted.get(identity);
1068
+ if (slot !== void 0 && point.ts >= slot.lastTs && point.ts < slot.nextDue - 1e3) {
862
1069
  addRefusal(refusals, point.key, "rate");
863
1070
  counters.refuse("rate");
864
1071
  continue;
865
1072
  }
866
- this.lastAccepted.set(identity, Math.max(last ?? point.ts, point.ts));
1073
+ this.lastAccepted.set(identity, nextSlot(slot, point.ts));
867
1074
  this.deps.writer.enqueue({
868
1075
  key: point.key,
869
1076
  subject: point.subject,
@@ -1324,191 +1531,6 @@ function withoutRaw(tiers) {
1324
1531
  return rollups.length > 0 ? rollups : ["5m"];
1325
1532
  }
1326
1533
  //#endregion
1327
- //#region src/builtins/device-history/retention/departure-tracker.ts
1328
- var DEPARTED_ABSENCE_MS = 7 * DAY_MS;
1329
- var DepartureTracker = class {
1330
- store;
1331
- registry;
1332
- logger;
1333
- missing = /* @__PURE__ */ new Map();
1334
- constructor(store, registry, logger) {
1335
- this.store = store;
1336
- this.registry = registry;
1337
- this.logger = logger;
1338
- }
1339
- async load() {
1340
- this.missing = await this.store.loadDepartures();
1341
- }
1342
- missingSince(deviceId) {
1343
- return this.missing.get(deviceId);
1344
- }
1345
- /** Forget a device's clock — its series were removed because it was DELETED (D865). */
1346
- async forget(deviceId) {
1347
- await this.store.clearMissing([deviceId]);
1348
- const next = new Map(this.missing);
1349
- next.delete(deviceId);
1350
- this.missing = next;
1351
- }
1352
- /** Feed one SUCCESSFUL listing. A failed read never reaches here. */
1353
- async observe(listed, now) {
1354
- const withSeries = new Set(this.registry.all().flatMap((s) => s.deviceId === null ? [] : [s.deviceId]));
1355
- const back = [...this.missing.keys()].filter((id) => listed.has(id));
1356
- const gone = [...withSeries].filter((id) => !listed.has(id) && !this.missing.has(id));
1357
- try {
1358
- await this.store.clearMissing(back);
1359
- await this.store.markMissing(gone, now);
1360
- } catch (err) {
1361
- this.logger.warn("device-history: departure clock not written", { meta: { error: err instanceof Error ? err.message : String(err) } });
1362
- return;
1363
- }
1364
- const next = new Map(this.missing);
1365
- for (const id of back) next.delete(id);
1366
- for (const id of gone) next.set(id, now);
1367
- this.missing = next;
1368
- }
1369
- };
1370
- //#endregion
1371
- //#region src/builtins/device-history/retention/retention.ts
1372
- var DEFAULT_RETENTION = {
1373
- rawDays: 7,
1374
- fiveMinuteDays: 30,
1375
- hourDays: 365,
1376
- dayDays: null
1377
- };
1378
- /** Cutoff per tier: rows strictly older are deleted. `null` = keep forever. */
1379
- function retentionCutoffs(policy, now) {
1380
- return {
1381
- raw: now - policy.rawDays * DAY_MS,
1382
- "5m": now - policy.fiveMinuteDays * DAY_MS,
1383
- "1h": now - policy.hourDays * DAY_MS,
1384
- "1d": policy.dayDays === null || policy.dayDays <= 0 ? null : now - policy.dayDays * DAY_MS
1385
- };
1386
- }
1387
- var TIERS = [
1388
- "raw",
1389
- "5m",
1390
- "1h",
1391
- "1d"
1392
- ];
1393
- var RETENTION_STEP_MS = {
1394
- raw: HOUR_MS,
1395
- "5m": HOUR_MS,
1396
- "1h": DAY_MS,
1397
- "1d": DAY_MS
1398
- };
1399
- var MAX_PURGES_PER_RUN = 20;
1400
- /** Width of one delete statement's time range, per tier. */
1401
- var SLICE_MS = {
1402
- raw: DAY_MS,
1403
- "5m": DAY_MS,
1404
- "1h": 30 * DAY_MS,
1405
- "1d": 30 * DAY_MS
1406
- };
1407
- /** A bucket's start may precede the series' first point by up to a day. */
1408
- var BUCKET_LEAD_MS = 2 * DAY_MS;
1409
- /** The short tiers hold nothing older than this unless the operator configured more. */
1410
- var SHORT_TIER_LOOKBACK_MS = 400 * DAY_MS;
1411
- var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
1412
- var RetentionJob = class {
1413
- deps;
1414
- appliedUpTo = /* @__PURE__ */ new Map();
1415
- running = null;
1416
- constructor(deps) {
1417
- this.deps = deps;
1418
- }
1419
- run(policy, now) {
1420
- if (this.running !== null) return this.running;
1421
- const run = this.doRun(policy, now).finally(() => {
1422
- this.running = null;
1423
- });
1424
- this.running = run;
1425
- return run;
1426
- }
1427
- async doRun(policy, now) {
1428
- const { registry, counters, logger } = this.deps;
1429
- const cutoffs = retentionCutoffs(policy, now);
1430
- try {
1431
- for (const tier of TIERS) {
1432
- const cutoff = cutoffs[tier];
1433
- if (cutoff === null) continue;
1434
- for (const series of registry.all()) {
1435
- if (series.firstTs - 864e5 >= cutoff) continue;
1436
- if (tier === "raw" && registry.metric(series.key)?.ingest === "buckets") continue;
1437
- const marker = `${tier}:${series.id}`;
1438
- const applied = this.appliedUpTo.get(marker);
1439
- if (applied !== void 0 && cutoff - applied < RETENTION_STEP_MS[tier]) continue;
1440
- const lo = Math.max(0, applied === void 0 ? series.firstTs - BUCKET_LEAD_MS : applied - RETENTION_STEP_MS[tier]);
1441
- counters.addDeleted(tier, await this.deleteSliced(tier, series.id, lo, cutoff - 1));
1442
- this.appliedUpTo.set(marker, cutoff);
1443
- }
1444
- }
1445
- await this.purgeDeparted(now);
1446
- counters.retentionLastRunAt = now;
1447
- } catch (err) {
1448
- logger.warn("device-history: retention pass failed — retried at the next run", { meta: { error: err instanceof Error ? err.message : String(err) } });
1449
- }
1450
- }
1451
- /** Delete `[lo, hiInclusive]` of one tier in time slices, yielding between them. */
1452
- /** Drop the visit markers of a purged series. */
1453
- forgetSeries(seriesId) {
1454
- for (const tier of TIERS) this.appliedUpTo.delete(`${tier}:${seriesId}`);
1455
- }
1456
- async deleteSliced(tier, seriesId, lo, hiInclusive) {
1457
- const yieldNow = this.deps.yieldNow ?? yieldToEventLoop;
1458
- let deleted = 0;
1459
- for (let from = lo; from <= hiInclusive; from += SLICE_MS[tier]) {
1460
- const to = Math.min(from + SLICE_MS[tier] - 1, hiInclusive);
1461
- deleted += await this.deps.store.deleteRange(tier, seriesId, from, to);
1462
- await yieldNow();
1463
- }
1464
- return deleted;
1465
- }
1466
- /** Tier by tier in slices, then the series row (and whatever the slices missed). */
1467
- async purgeSeries(series, now) {
1468
- for (const tier of TIERS) {
1469
- const lo = tier === "raw" || tier === "5m" ? Math.max(series.firstTs - BUCKET_LEAD_MS, now - SHORT_TIER_LOOKBACK_MS) : series.firstTs - BUCKET_LEAD_MS;
1470
- await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
1471
- }
1472
- await this.deps.store.deleteSeries(series.id);
1473
- }
1474
- /**
1475
- * Delete every series of a DELETED device now (D865). Driven by a tombstone
1476
- * through the `device-removal-hook`, never by a listing: the week-long
1477
- * absence clock above remains the backstop for a device removed before
1478
- * tombstones existed.
1479
- */
1480
- async purgeDevice(deviceId, now) {
1481
- const { registry, counters } = this.deps;
1482
- let purged = 0;
1483
- for (const series of registry.all()) {
1484
- if (series.deviceId !== deviceId) continue;
1485
- await this.purgeSeries(series, now);
1486
- registry.remove(series.id);
1487
- counters.purgedSeries += 1;
1488
- purged += 1;
1489
- }
1490
- return purged;
1491
- }
1492
- async purgeDeparted(now) {
1493
- const { registry, departures, counters, logger } = this.deps;
1494
- let purged = 0;
1495
- for (const series of registry.all()) {
1496
- if (purged >= MAX_PURGES_PER_RUN) break;
1497
- if (series.deviceId === null) continue;
1498
- const since = departures.missingSince(series.deviceId);
1499
- if (since === void 0 || now - since < DEPARTED_ABSENCE_MS) continue;
1500
- await this.purgeSeries(series, now);
1501
- registry.remove(series.id);
1502
- counters.purgedSeries += 1;
1503
- purged += 1;
1504
- logger.info("device-history: purged the series of an unregistered device", {
1505
- tags: { deviceId: series.deviceId },
1506
- meta: { key: series.key }
1507
- });
1508
- }
1509
- }
1510
- };
1511
- //#endregion
1512
1534
  //#region src/builtins/device-history/store/history-schema.ts
1513
1535
  var HISTORY_NAMESPACE = "device-history";
1514
1536
  var COLLECTIONS = {
@@ -2971,6 +2993,13 @@ var HistoryRuntime = class {
2971
2993
  * are hub capabilities, and a forked runner cannot call them from
2972
2994
  * `onInitialize`. Until then the provider answers "starting" rather than an
2973
2995
  * empty history (an unanswered query is not an empty one, D315).
2996
+ *
2997
+ * Readiness follows the STORE, not the registration: the provider registers at
2998
+ * `onInitialize`, but `ready` is emitted only once the runtime is open in
2999
+ * `onHubReachable` (`autoEmitReadiness` is off). Producers declare their
3000
+ * series on `ready`; the automatic emit at registration sent every one of
3001
+ * them into "device-history is starting" on each boot (`.50`, 1.2.607:
3002
+ * metrics-native and pipeline-analytics).
2974
3003
  */
2975
3004
  var DEFAULT_CONFIG = {
2976
3005
  rawDays: DEFAULT_RETENTION.rawDays,
@@ -2994,9 +3023,40 @@ var DeviceHistoryAddon = class extends require_dist.BaseAddon {
2994
3023
  runtime = null;
2995
3024
  unsubscribe = null;
2996
3025
  removalHook = null;
3026
+ /** This instance's readiness generation (the base class keeps its own private). */
3027
+ readinessGeneration = `device-history-${Date.now().toString(36)}`;
2997
3028
  constructor() {
2998
3029
  super({ ...DEFAULT_CONFIG });
2999
3030
  }
3031
+ /** `ready` is the store being open, emitted by {@link emitStoreReadiness}. */
3032
+ get autoEmitReadiness() {
3033
+ return false;
3034
+ }
3035
+ /** Readiness of both provided caps, on the bare node this runner belongs to. */
3036
+ emitStoreReadiness(state) {
3037
+ const ctx = this.ctxIfReady;
3038
+ if (ctx === null) return;
3039
+ const raw = ctx.kernel?.localNodeId ?? "hub";
3040
+ const nodeId = raw.includes("/") ? raw.split("/")[0] ?? raw : raw;
3041
+ for (const capName of [require_dist.deviceHistoryCapability.name, require_dist.deviceRemovalHookCapability.name]) try {
3042
+ require_dist.emitReadiness(ctx.eventBus, {
3043
+ capName,
3044
+ scope: {
3045
+ type: "node",
3046
+ nodeId
3047
+ },
3048
+ state,
3049
+ generation: this.readinessGeneration,
3050
+ sourceNodeId: nodeId
3051
+ });
3052
+ } catch (err) {
3053
+ ctx.logger.warn("device-history: readiness not emitted", { meta: {
3054
+ capName,
3055
+ state,
3056
+ error: err instanceof Error ? err.message : String(err)
3057
+ } });
3058
+ }
3059
+ }
3000
3060
  retention() {
3001
3061
  const c = this.config;
3002
3062
  return {
@@ -3042,6 +3102,7 @@ var DeviceHistoryAddon = class extends require_dist.BaseAddon {
3042
3102
  setDeviceEnabled: async (input) => ({ enabled: await this.requireRuntime().setEnabled(input.deviceId, input.enabled) }),
3043
3103
  getStatus: async () => this.requireRuntime().status()
3044
3104
  };
3105
+ this.emitStoreReadiness("starting");
3045
3106
  this.ctx.logger.info("Initialized — device-history provider registered (store opens on hub link)");
3046
3107
  const removalHook = {
3047
3108
  onDeviceRemoved: async (input) => {
@@ -3118,6 +3179,7 @@ var DeviceHistoryAddon = class extends require_dist.BaseAddon {
3118
3179
  logger: this.ctx.logger,
3119
3180
  remove: (input) => removalHook.onDeviceRemoved(input)
3120
3181
  }));
3182
+ this.emitStoreReadiness("ready");
3121
3183
  this.ctx.logger.info("device-history: capturing", { meta: {
3122
3184
  series: runtime.status().series,
3123
3185
  metrics: runtime.status().metrics
@@ -3129,6 +3191,7 @@ var DeviceHistoryAddon = class extends require_dist.BaseAddon {
3129
3191
  await this.runtime?.stop();
3130
3192
  }
3131
3193
  async onShutdown() {
3194
+ this.emitStoreReadiness("down");
3132
3195
  await this.stopRuntime();
3133
3196
  this.runtime = null;
3134
3197
  }