@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
@@ -1,4 +1,236 @@
1
- import { B as HISTORY_CATALOG, O as DEVICE_HISTORY_ENERGY_KEY, Oa as EventCategory, Tt as armRemovalReconcile, di as HISTORY_DEFAULT_MIN_INTERVAL_MS, ei as DeviceType, in as deviceRemovalHookCapability, ni as BaseAddon, nn as deviceHistoryCapability } from "../../dist-Dhux90yB.mjs";
1
+ import { B as HISTORY_CATALOG, Ji as emitReadiness, O as DEVICE_HISTORY_ENERGY_KEY, Oa as EventCategory, Tt as armRemovalReconcile, di as HISTORY_DEFAULT_MIN_INTERVAL_MS, ei as DeviceType, in as deviceRemovalHookCapability, ni as BaseAddon, nn as deviceHistoryCapability } from "../../dist-B_GRPhnY.mjs";
2
+ //#region src/builtins/device-history/time/bucket-time.ts
3
+ /**
4
+ * Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
5
+ *
6
+ * 5m and 1h buckets are aligned to UTC; a 1d bucket starts at HUB-LOCAL
7
+ * midnight, so a day is 23, 24 or 25 hours long across a DST change (operator
8
+ * answer Q4). Pure: the zone is the process zone (`TZ`), nothing is read from
9
+ * the environment here.
10
+ */
11
+ var FIVE_MINUTES_MS = 5 * 6e4;
12
+ var HOUR_MS = 60 * 6e4;
13
+ var DAY_MS = 24 * HOUR_MS;
14
+ function floorTo(ts, widthMs) {
15
+ return Math.floor(ts / widthMs) * widthMs;
16
+ }
17
+ /** Hub-local midnight at or before `ts`. */
18
+ function startOfLocalDay(ts) {
19
+ const d = new Date(ts);
20
+ d.setHours(0, 0, 0, 0);
21
+ return d.getTime();
22
+ }
23
+ /** The local midnight following the local midnight `dayStart`. */
24
+ function nextLocalDayStart(dayStart) {
25
+ const d = new Date(dayStart);
26
+ d.setDate(d.getDate() + 1);
27
+ d.setHours(0, 0, 0, 0);
28
+ return d.getTime();
29
+ }
30
+ /** Start of the `tier` bucket containing `ts`. */
31
+ function bucketStartOf(tier, ts) {
32
+ if (tier === "5m") return floorTo(ts, FIVE_MINUTES_MS);
33
+ if (tier === "1h") return floorTo(ts, HOUR_MS);
34
+ return startOfLocalDay(ts);
35
+ }
36
+ /** Exclusive end of the `tier` bucket that starts at `start`. */
37
+ function bucketEndOf(tier, start) {
38
+ if (tier === "5m") return start + FIVE_MINUTES_MS;
39
+ if (tier === "1h") return start + HOUR_MS;
40
+ return nextLocalDayStart(start);
41
+ }
42
+ /** Quiet time after a bucket closes before it is rolled up (late writers). */
43
+ var ROLLUP_GRACE_MS = {
44
+ "5m": 3e4,
45
+ "1h": 5 * 6e4,
46
+ "1d": 15 * 6e4
47
+ };
48
+ //#endregion
49
+ //#region src/builtins/device-history/retention/departure-tracker.ts
50
+ var DEPARTED_ABSENCE_MS = 7 * DAY_MS;
51
+ var DepartureTracker = class {
52
+ store;
53
+ registry;
54
+ logger;
55
+ missing = /* @__PURE__ */ new Map();
56
+ constructor(store, registry, logger) {
57
+ this.store = store;
58
+ this.registry = registry;
59
+ this.logger = logger;
60
+ }
61
+ async load() {
62
+ this.missing = await this.store.loadDepartures();
63
+ }
64
+ missingSince(deviceId) {
65
+ return this.missing.get(deviceId);
66
+ }
67
+ /** Forget a device's clock — its series were removed because it was DELETED (D865). */
68
+ async forget(deviceId) {
69
+ await this.store.clearMissing([deviceId]);
70
+ const next = new Map(this.missing);
71
+ next.delete(deviceId);
72
+ this.missing = next;
73
+ }
74
+ /** Feed one SUCCESSFUL listing. A failed read never reaches here. */
75
+ async observe(listed, now) {
76
+ const withSeries = new Set(this.registry.all().flatMap((s) => s.deviceId === null ? [] : [s.deviceId]));
77
+ const back = [...this.missing.keys()].filter((id) => listed.has(id));
78
+ const gone = [...withSeries].filter((id) => !listed.has(id) && !this.missing.has(id));
79
+ try {
80
+ await this.store.clearMissing(back);
81
+ await this.store.markMissing(gone, now);
82
+ } catch (err) {
83
+ this.logger.warn("device-history: departure clock not written", { meta: { error: err instanceof Error ? err.message : String(err) } });
84
+ return;
85
+ }
86
+ const next = new Map(this.missing);
87
+ for (const id of back) next.delete(id);
88
+ for (const id of gone) next.set(id, now);
89
+ this.missing = next;
90
+ }
91
+ };
92
+ //#endregion
93
+ //#region src/builtins/device-history/retention/retention.ts
94
+ var DEFAULT_RETENTION = {
95
+ rawDays: 7,
96
+ fiveMinuteDays: 30,
97
+ hourDays: 365,
98
+ dayDays: null
99
+ };
100
+ /** Cutoff per tier: rows strictly older are deleted. `null` = keep forever. */
101
+ function retentionCutoffs(policy, now) {
102
+ return {
103
+ raw: now - policy.rawDays * DAY_MS,
104
+ "5m": now - policy.fiveMinuteDays * DAY_MS,
105
+ "1h": now - policy.hourDays * DAY_MS,
106
+ "1d": policy.dayDays === null || policy.dayDays <= 0 ? null : now - policy.dayDays * DAY_MS
107
+ };
108
+ }
109
+ var TIERS = [
110
+ "raw",
111
+ "5m",
112
+ "1h",
113
+ "1d"
114
+ ];
115
+ var RETENTION_STEP_MS = {
116
+ raw: HOUR_MS,
117
+ "5m": HOUR_MS,
118
+ "1h": DAY_MS,
119
+ "1d": DAY_MS
120
+ };
121
+ var MAX_PURGES_PER_RUN = 20;
122
+ /** Width of one delete statement's time range, per tier. */
123
+ var SLICE_MS = {
124
+ raw: DAY_MS,
125
+ "5m": DAY_MS,
126
+ "1h": 30 * DAY_MS,
127
+ "1d": 30 * DAY_MS
128
+ };
129
+ /** A bucket's start may precede the series' first point by up to a day. */
130
+ var BUCKET_LEAD_MS = 2 * DAY_MS;
131
+ /** The short tiers hold nothing older than this unless the operator configured more. */
132
+ var SHORT_TIER_LOOKBACK_MS = 400 * DAY_MS;
133
+ var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
134
+ var RetentionJob = class {
135
+ deps;
136
+ appliedUpTo = /* @__PURE__ */ new Map();
137
+ running = null;
138
+ constructor(deps) {
139
+ this.deps = deps;
140
+ }
141
+ run(policy, now) {
142
+ if (this.running !== null) return this.running;
143
+ const run = this.doRun(policy, now).finally(() => {
144
+ this.running = null;
145
+ });
146
+ this.running = run;
147
+ return run;
148
+ }
149
+ async doRun(policy, now) {
150
+ const { registry, counters, logger } = this.deps;
151
+ const cutoffs = retentionCutoffs(policy, now);
152
+ try {
153
+ for (const tier of TIERS) {
154
+ const cutoff = cutoffs[tier];
155
+ if (cutoff === null) continue;
156
+ for (const series of registry.all()) {
157
+ if (series.firstTs - 864e5 >= cutoff) continue;
158
+ if (tier === "raw" && registry.metric(series.key)?.ingest === "buckets") continue;
159
+ const marker = `${tier}:${series.id}`;
160
+ const applied = this.appliedUpTo.get(marker);
161
+ if (applied !== void 0 && cutoff - applied < RETENTION_STEP_MS[tier]) continue;
162
+ const lo = Math.max(0, applied === void 0 ? series.firstTs - BUCKET_LEAD_MS : applied - RETENTION_STEP_MS[tier]);
163
+ counters.addDeleted(tier, await this.deleteSliced(tier, series.id, lo, cutoff - 1));
164
+ this.appliedUpTo.set(marker, cutoff);
165
+ }
166
+ }
167
+ await this.purgeDeparted(now);
168
+ counters.retentionLastRunAt = now;
169
+ } catch (err) {
170
+ logger.warn("device-history: retention pass failed — retried at the next run", { meta: { error: err instanceof Error ? err.message : String(err) } });
171
+ }
172
+ }
173
+ /** Delete `[lo, hiInclusive]` of one tier in time slices, yielding between them. */
174
+ /** Drop the visit markers of a purged series. */
175
+ forgetSeries(seriesId) {
176
+ for (const tier of TIERS) this.appliedUpTo.delete(`${tier}:${seriesId}`);
177
+ }
178
+ async deleteSliced(tier, seriesId, lo, hiInclusive) {
179
+ const yieldNow = this.deps.yieldNow ?? yieldToEventLoop;
180
+ let deleted = 0;
181
+ for (let from = lo; from <= hiInclusive; from += SLICE_MS[tier]) {
182
+ const to = Math.min(from + SLICE_MS[tier] - 1, hiInclusive);
183
+ deleted += await this.deps.store.deleteRange(tier, seriesId, from, to);
184
+ await yieldNow();
185
+ }
186
+ return deleted;
187
+ }
188
+ /** Tier by tier in slices, then the series row (and whatever the slices missed). */
189
+ async purgeSeries(series, now) {
190
+ for (const tier of TIERS) {
191
+ const lo = tier === "raw" || tier === "5m" ? Math.max(series.firstTs - BUCKET_LEAD_MS, now - SHORT_TIER_LOOKBACK_MS) : series.firstTs - BUCKET_LEAD_MS;
192
+ await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
193
+ }
194
+ await this.deps.store.deleteSeries(series.id);
195
+ }
196
+ /**
197
+ * Delete every series of a DELETED device now (D865). Driven by a tombstone
198
+ * through the `device-removal-hook`, never by a listing: the week-long
199
+ * absence clock above remains the backstop for a device removed before
200
+ * tombstones existed.
201
+ */
202
+ async purgeDevice(deviceId, now) {
203
+ const { registry, counters } = this.deps;
204
+ let purged = 0;
205
+ for (const series of registry.all()) {
206
+ if (series.deviceId !== deviceId) continue;
207
+ await this.purgeSeries(series, now);
208
+ registry.remove(series.id);
209
+ counters.purgedSeries += 1;
210
+ purged += 1;
211
+ }
212
+ return purged;
213
+ }
214
+ async purgeDeparted(now) {
215
+ const { registry, departures, counters, logger } = this.deps;
216
+ let purged = 0;
217
+ for (const series of registry.all()) {
218
+ if (purged >= MAX_PURGES_PER_RUN) break;
219
+ if (series.deviceId === null) continue;
220
+ const since = departures.missingSince(series.deviceId);
221
+ if (since === void 0 || now - since < DEPARTED_ABSENCE_MS) continue;
222
+ await this.purgeSeries(series, now);
223
+ registry.remove(series.id);
224
+ counters.purgedSeries += 1;
225
+ purged += 1;
226
+ logger.info("device-history: purged the series of an unregistered device", {
227
+ tags: { deviceId: series.deviceId },
228
+ meta: { key: series.key }
229
+ });
230
+ }
231
+ }
232
+ };
233
+ //#endregion
2
234
  //#region src/builtins/device-history/store/series-registry.ts
3
235
  function subjectIdOf(subject) {
4
236
  return subject.kind === "device" ? `d:${subject.deviceId}` : `n:${subject.nodeId}`;
@@ -730,52 +962,27 @@ var PointWriter = class {
730
962
  }
731
963
  };
732
964
  //#endregion
733
- //#region src/builtins/device-history/time/bucket-time.ts
965
+ //#region src/builtins/device-history/ingest/producer-ingest.ts
966
+ /** One point per series per this many ms, sustained. */
967
+ var RECORD_MIN_INTERVAL_MS = 1e4;
734
968
  /**
735
- * Bucket arithmetic for the three rollup tiers (D850, plan §4/§5.5).
736
- *
737
- * 5m and 1h buckets are aligned to UTC; a 1d bucket starts at HUB-LOCAL
738
- * midnight, so a day is 23, 24 or 25 hours long across a DST change (operator
739
- * answer Q4). Pure: the zone is the process zone (`TZ`), nothing is read from
740
- * the environment here.
969
+ * The slot after accepting a point at `ts`. A point older than the newest
970
+ * accepted one (a late arrival) moves nothing. Otherwise the consumed slot is
971
+ * the later of the one that was due and `ts` itself: an early point uses the
972
+ * due slot (the grid holds, so slack never compounds into a higher rate), a
973
+ * late one restarts the grid at `ts` (no credit is banked for a gap).
741
974
  */
742
- var FIVE_MINUTES_MS = 5 * 6e4;
743
- var HOUR_MS = 60 * 6e4;
744
- var DAY_MS = 24 * HOUR_MS;
745
- function floorTo(ts, widthMs) {
746
- return Math.floor(ts / widthMs) * widthMs;
747
- }
748
- /** Hub-local midnight at or before `ts`. */
749
- function startOfLocalDay(ts) {
750
- const d = new Date(ts);
751
- d.setHours(0, 0, 0, 0);
752
- return d.getTime();
753
- }
754
- /** The local midnight following the local midnight `dayStart`. */
755
- function nextLocalDayStart(dayStart) {
756
- const d = new Date(dayStart);
757
- d.setDate(d.getDate() + 1);
758
- d.setHours(0, 0, 0, 0);
759
- return d.getTime();
760
- }
761
- /** Start of the `tier` bucket containing `ts`. */
762
- function bucketStartOf(tier, ts) {
763
- if (tier === "5m") return floorTo(ts, FIVE_MINUTES_MS);
764
- if (tier === "1h") return floorTo(ts, HOUR_MS);
765
- return startOfLocalDay(ts);
766
- }
767
- /** Exclusive end of the `tier` bucket that starts at `start`. */
768
- function bucketEndOf(tier, start) {
769
- if (tier === "5m") return start + FIVE_MINUTES_MS;
770
- if (tier === "1h") return start + HOUR_MS;
771
- return nextLocalDayStart(start);
975
+ function nextSlot(slot, ts) {
976
+ if (slot === void 0) return {
977
+ lastTs: ts,
978
+ nextDue: ts + RECORD_MIN_INTERVAL_MS
979
+ };
980
+ if (ts < slot.lastTs) return slot;
981
+ return {
982
+ lastTs: ts,
983
+ nextDue: Math.max(slot.nextDue, ts) + RECORD_MIN_INTERVAL_MS
984
+ };
772
985
  }
773
- /** Quiet time after a bucket closes before it is rolled up (late writers). */
774
- var ROLLUP_GRACE_MS = {
775
- "5m": 3e4,
776
- "1h": 5 * 6e4,
777
- "1d": 15 * 6e4
778
- };
779
986
  function addRefusal(refusals, key, reason) {
780
987
  const byReason = refusals.get(key) ?? /* @__PURE__ */ new Map();
781
988
  byReason.set(reason, (byReason.get(reason) ?? 0) + 1);
@@ -793,7 +1000,7 @@ function toResult(accepted, refusals) {
793
1000
  }
794
1001
  var ProducerIngest = class {
795
1002
  deps;
796
- /** Last accepted point time per series, for the sustained-rate bound. */
1003
+ /** Rate state per series identity, for the sustained-rate bound. */
797
1004
  lastAccepted = /* @__PURE__ */ new Map();
798
1005
  constructor(deps) {
799
1006
  this.deps = deps;
@@ -852,13 +1059,13 @@ var ProducerIngest = class {
852
1059
  continue;
853
1060
  }
854
1061
  const identity = identityOf(point.key, subjectIdOf(point.subject), point.scope);
855
- const last = this.lastAccepted.get(identity);
856
- if (last !== void 0 && point.ts >= last && point.ts - last < 1e4) {
1062
+ const slot = this.lastAccepted.get(identity);
1063
+ if (slot !== void 0 && point.ts >= slot.lastTs && point.ts < slot.nextDue - 1e3) {
857
1064
  addRefusal(refusals, point.key, "rate");
858
1065
  counters.refuse("rate");
859
1066
  continue;
860
1067
  }
861
- this.lastAccepted.set(identity, Math.max(last ?? point.ts, point.ts));
1068
+ this.lastAccepted.set(identity, nextSlot(slot, point.ts));
862
1069
  this.deps.writer.enqueue({
863
1070
  key: point.key,
864
1071
  subject: point.subject,
@@ -1319,191 +1526,6 @@ function withoutRaw(tiers) {
1319
1526
  return rollups.length > 0 ? rollups : ["5m"];
1320
1527
  }
1321
1528
  //#endregion
1322
- //#region src/builtins/device-history/retention/departure-tracker.ts
1323
- var DEPARTED_ABSENCE_MS = 7 * DAY_MS;
1324
- var DepartureTracker = class {
1325
- store;
1326
- registry;
1327
- logger;
1328
- missing = /* @__PURE__ */ new Map();
1329
- constructor(store, registry, logger) {
1330
- this.store = store;
1331
- this.registry = registry;
1332
- this.logger = logger;
1333
- }
1334
- async load() {
1335
- this.missing = await this.store.loadDepartures();
1336
- }
1337
- missingSince(deviceId) {
1338
- return this.missing.get(deviceId);
1339
- }
1340
- /** Forget a device's clock — its series were removed because it was DELETED (D865). */
1341
- async forget(deviceId) {
1342
- await this.store.clearMissing([deviceId]);
1343
- const next = new Map(this.missing);
1344
- next.delete(deviceId);
1345
- this.missing = next;
1346
- }
1347
- /** Feed one SUCCESSFUL listing. A failed read never reaches here. */
1348
- async observe(listed, now) {
1349
- const withSeries = new Set(this.registry.all().flatMap((s) => s.deviceId === null ? [] : [s.deviceId]));
1350
- const back = [...this.missing.keys()].filter((id) => listed.has(id));
1351
- const gone = [...withSeries].filter((id) => !listed.has(id) && !this.missing.has(id));
1352
- try {
1353
- await this.store.clearMissing(back);
1354
- await this.store.markMissing(gone, now);
1355
- } catch (err) {
1356
- this.logger.warn("device-history: departure clock not written", { meta: { error: err instanceof Error ? err.message : String(err) } });
1357
- return;
1358
- }
1359
- const next = new Map(this.missing);
1360
- for (const id of back) next.delete(id);
1361
- for (const id of gone) next.set(id, now);
1362
- this.missing = next;
1363
- }
1364
- };
1365
- //#endregion
1366
- //#region src/builtins/device-history/retention/retention.ts
1367
- var DEFAULT_RETENTION = {
1368
- rawDays: 7,
1369
- fiveMinuteDays: 30,
1370
- hourDays: 365,
1371
- dayDays: null
1372
- };
1373
- /** Cutoff per tier: rows strictly older are deleted. `null` = keep forever. */
1374
- function retentionCutoffs(policy, now) {
1375
- return {
1376
- raw: now - policy.rawDays * DAY_MS,
1377
- "5m": now - policy.fiveMinuteDays * DAY_MS,
1378
- "1h": now - policy.hourDays * DAY_MS,
1379
- "1d": policy.dayDays === null || policy.dayDays <= 0 ? null : now - policy.dayDays * DAY_MS
1380
- };
1381
- }
1382
- var TIERS = [
1383
- "raw",
1384
- "5m",
1385
- "1h",
1386
- "1d"
1387
- ];
1388
- var RETENTION_STEP_MS = {
1389
- raw: HOUR_MS,
1390
- "5m": HOUR_MS,
1391
- "1h": DAY_MS,
1392
- "1d": DAY_MS
1393
- };
1394
- var MAX_PURGES_PER_RUN = 20;
1395
- /** Width of one delete statement's time range, per tier. */
1396
- var SLICE_MS = {
1397
- raw: DAY_MS,
1398
- "5m": DAY_MS,
1399
- "1h": 30 * DAY_MS,
1400
- "1d": 30 * DAY_MS
1401
- };
1402
- /** A bucket's start may precede the series' first point by up to a day. */
1403
- var BUCKET_LEAD_MS = 2 * DAY_MS;
1404
- /** The short tiers hold nothing older than this unless the operator configured more. */
1405
- var SHORT_TIER_LOOKBACK_MS = 400 * DAY_MS;
1406
- var yieldToEventLoop = () => new Promise((resolve) => setImmediate(resolve));
1407
- var RetentionJob = class {
1408
- deps;
1409
- appliedUpTo = /* @__PURE__ */ new Map();
1410
- running = null;
1411
- constructor(deps) {
1412
- this.deps = deps;
1413
- }
1414
- run(policy, now) {
1415
- if (this.running !== null) return this.running;
1416
- const run = this.doRun(policy, now).finally(() => {
1417
- this.running = null;
1418
- });
1419
- this.running = run;
1420
- return run;
1421
- }
1422
- async doRun(policy, now) {
1423
- const { registry, counters, logger } = this.deps;
1424
- const cutoffs = retentionCutoffs(policy, now);
1425
- try {
1426
- for (const tier of TIERS) {
1427
- const cutoff = cutoffs[tier];
1428
- if (cutoff === null) continue;
1429
- for (const series of registry.all()) {
1430
- if (series.firstTs - 864e5 >= cutoff) continue;
1431
- if (tier === "raw" && registry.metric(series.key)?.ingest === "buckets") continue;
1432
- const marker = `${tier}:${series.id}`;
1433
- const applied = this.appliedUpTo.get(marker);
1434
- if (applied !== void 0 && cutoff - applied < RETENTION_STEP_MS[tier]) continue;
1435
- const lo = Math.max(0, applied === void 0 ? series.firstTs - BUCKET_LEAD_MS : applied - RETENTION_STEP_MS[tier]);
1436
- counters.addDeleted(tier, await this.deleteSliced(tier, series.id, lo, cutoff - 1));
1437
- this.appliedUpTo.set(marker, cutoff);
1438
- }
1439
- }
1440
- await this.purgeDeparted(now);
1441
- counters.retentionLastRunAt = now;
1442
- } catch (err) {
1443
- logger.warn("device-history: retention pass failed — retried at the next run", { meta: { error: err instanceof Error ? err.message : String(err) } });
1444
- }
1445
- }
1446
- /** Delete `[lo, hiInclusive]` of one tier in time slices, yielding between them. */
1447
- /** Drop the visit markers of a purged series. */
1448
- forgetSeries(seriesId) {
1449
- for (const tier of TIERS) this.appliedUpTo.delete(`${tier}:${seriesId}`);
1450
- }
1451
- async deleteSliced(tier, seriesId, lo, hiInclusive) {
1452
- const yieldNow = this.deps.yieldNow ?? yieldToEventLoop;
1453
- let deleted = 0;
1454
- for (let from = lo; from <= hiInclusive; from += SLICE_MS[tier]) {
1455
- const to = Math.min(from + SLICE_MS[tier] - 1, hiInclusive);
1456
- deleted += await this.deps.store.deleteRange(tier, seriesId, from, to);
1457
- await yieldNow();
1458
- }
1459
- return deleted;
1460
- }
1461
- /** Tier by tier in slices, then the series row (and whatever the slices missed). */
1462
- async purgeSeries(series, now) {
1463
- for (const tier of TIERS) {
1464
- const lo = tier === "raw" || tier === "5m" ? Math.max(series.firstTs - BUCKET_LEAD_MS, now - SHORT_TIER_LOOKBACK_MS) : series.firstTs - BUCKET_LEAD_MS;
1465
- await this.deleteSliced(tier, series.id, Math.max(0, lo), now + DAY_MS);
1466
- }
1467
- await this.deps.store.deleteSeries(series.id);
1468
- }
1469
- /**
1470
- * Delete every series of a DELETED device now (D865). Driven by a tombstone
1471
- * through the `device-removal-hook`, never by a listing: the week-long
1472
- * absence clock above remains the backstop for a device removed before
1473
- * tombstones existed.
1474
- */
1475
- async purgeDevice(deviceId, now) {
1476
- const { registry, counters } = this.deps;
1477
- let purged = 0;
1478
- for (const series of registry.all()) {
1479
- if (series.deviceId !== deviceId) continue;
1480
- await this.purgeSeries(series, now);
1481
- registry.remove(series.id);
1482
- counters.purgedSeries += 1;
1483
- purged += 1;
1484
- }
1485
- return purged;
1486
- }
1487
- async purgeDeparted(now) {
1488
- const { registry, departures, counters, logger } = this.deps;
1489
- let purged = 0;
1490
- for (const series of registry.all()) {
1491
- if (purged >= MAX_PURGES_PER_RUN) break;
1492
- if (series.deviceId === null) continue;
1493
- const since = departures.missingSince(series.deviceId);
1494
- if (since === void 0 || now - since < DEPARTED_ABSENCE_MS) continue;
1495
- await this.purgeSeries(series, now);
1496
- registry.remove(series.id);
1497
- counters.purgedSeries += 1;
1498
- purged += 1;
1499
- logger.info("device-history: purged the series of an unregistered device", {
1500
- tags: { deviceId: series.deviceId },
1501
- meta: { key: series.key }
1502
- });
1503
- }
1504
- }
1505
- };
1506
- //#endregion
1507
1529
  //#region src/builtins/device-history/store/history-schema.ts
1508
1530
  var HISTORY_NAMESPACE = "device-history";
1509
1531
  var COLLECTIONS = {
@@ -2966,6 +2988,13 @@ var HistoryRuntime = class {
2966
2988
  * are hub capabilities, and a forked runner cannot call them from
2967
2989
  * `onInitialize`. Until then the provider answers "starting" rather than an
2968
2990
  * empty history (an unanswered query is not an empty one, D315).
2991
+ *
2992
+ * Readiness follows the STORE, not the registration: the provider registers at
2993
+ * `onInitialize`, but `ready` is emitted only once the runtime is open in
2994
+ * `onHubReachable` (`autoEmitReadiness` is off). Producers declare their
2995
+ * series on `ready`; the automatic emit at registration sent every one of
2996
+ * them into "device-history is starting" on each boot (`.50`, 1.2.607:
2997
+ * metrics-native and pipeline-analytics).
2969
2998
  */
2970
2999
  var DEFAULT_CONFIG = {
2971
3000
  rawDays: DEFAULT_RETENTION.rawDays,
@@ -2989,9 +3018,40 @@ var DeviceHistoryAddon = class extends BaseAddon {
2989
3018
  runtime = null;
2990
3019
  unsubscribe = null;
2991
3020
  removalHook = null;
3021
+ /** This instance's readiness generation (the base class keeps its own private). */
3022
+ readinessGeneration = `device-history-${Date.now().toString(36)}`;
2992
3023
  constructor() {
2993
3024
  super({ ...DEFAULT_CONFIG });
2994
3025
  }
3026
+ /** `ready` is the store being open, emitted by {@link emitStoreReadiness}. */
3027
+ get autoEmitReadiness() {
3028
+ return false;
3029
+ }
3030
+ /** Readiness of both provided caps, on the bare node this runner belongs to. */
3031
+ emitStoreReadiness(state) {
3032
+ const ctx = this.ctxIfReady;
3033
+ if (ctx === null) return;
3034
+ const raw = ctx.kernel?.localNodeId ?? "hub";
3035
+ const nodeId = raw.includes("/") ? raw.split("/")[0] ?? raw : raw;
3036
+ for (const capName of [deviceHistoryCapability.name, deviceRemovalHookCapability.name]) try {
3037
+ emitReadiness(ctx.eventBus, {
3038
+ capName,
3039
+ scope: {
3040
+ type: "node",
3041
+ nodeId
3042
+ },
3043
+ state,
3044
+ generation: this.readinessGeneration,
3045
+ sourceNodeId: nodeId
3046
+ });
3047
+ } catch (err) {
3048
+ ctx.logger.warn("device-history: readiness not emitted", { meta: {
3049
+ capName,
3050
+ state,
3051
+ error: err instanceof Error ? err.message : String(err)
3052
+ } });
3053
+ }
3054
+ }
2995
3055
  retention() {
2996
3056
  const c = this.config;
2997
3057
  return {
@@ -3037,6 +3097,7 @@ var DeviceHistoryAddon = class extends BaseAddon {
3037
3097
  setDeviceEnabled: async (input) => ({ enabled: await this.requireRuntime().setEnabled(input.deviceId, input.enabled) }),
3038
3098
  getStatus: async () => this.requireRuntime().status()
3039
3099
  };
3100
+ this.emitStoreReadiness("starting");
3040
3101
  this.ctx.logger.info("Initialized — device-history provider registered (store opens on hub link)");
3041
3102
  const removalHook = {
3042
3103
  onDeviceRemoved: async (input) => {
@@ -3113,6 +3174,7 @@ var DeviceHistoryAddon = class extends BaseAddon {
3113
3174
  logger: this.ctx.logger,
3114
3175
  remove: (input) => removalHook.onDeviceRemoved(input)
3115
3176
  }));
3177
+ this.emitStoreReadiness("ready");
3116
3178
  this.ctx.logger.info("device-history: capturing", { meta: {
3117
3179
  series: runtime.status().series,
3118
3180
  metrics: runtime.status().metrics
@@ -3124,6 +3186,7 @@ var DeviceHistoryAddon = class extends BaseAddon {
3124
3186
  await this.runtime?.stop();
3125
3187
  }
3126
3188
  async onShutdown() {
3189
+ this.emitStoreReadiness("down");
3127
3190
  await this.stopRuntime();
3128
3191
  this.runtime = null;
3129
3192
  }
@@ -21,6 +21,16 @@ import type { HistoryStore } from "../store/history-store.js";
21
21
  import { type SeriesRegistry } from "../store/series-registry.js";
22
22
  /** One point per series per this many ms, sustained. */
23
23
  export declare const RECORD_MIN_INTERVAL_MS: number;
24
+ /**
25
+ * How early a point may land against its series' next slot. A producer on a
26
+ * `setInterval` at exactly {@link RECORD_MIN_INTERVAL_MS} stamps `Date.now()`
27
+ * on each tick, and a tick lands a few ms EARLY as often as late; a bare
28
+ * `ts - last < interval` refused every early tick's whole batch (`.50`:
29
+ * `accepted: 0, refused: { rate: 141 }`). The slack is borrowed from the slot
30
+ * grid, never added to the rate: the next slot advances from the slot this
31
+ * point consumed, so a faster producer is still held to one point per interval.
32
+ */
33
+ export declare const RECORD_RATE_SLACK_MS: number;
24
34
  /** A point or bucket may be at most this far in the future (clock skew). */
25
35
  export declare const FUTURE_TOLERANCE_MS: number;
26
36
  export interface DeclareSeriesInput {
@@ -68,7 +78,7 @@ export interface IngestDeps {
68
78
  }
69
79
  export declare class ProducerIngest {
70
80
  private readonly deps;
71
- /** Last accepted point time per series, for the sustained-rate bound. */
81
+ /** Rate state per series identity, for the sustained-rate bound. */
72
82
  private readonly lastAccepted;
73
83
  constructor(deps: IngestDeps);
74
84
  /** A purged series leaves no rate-limit state behind. */
@@ -3,7 +3,7 @@ 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
7
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
8
8
  const require_prefix_range = require("../../prefix-range-D_KueXbQ.js");
9
9
  const require_prefix_walk = require("../../prefix-walk-Dv6MLZlM.js");
@@ -1,4 +1,4 @@
1
- import { $r as DeviceRole, Ea as userError, F as DeviceStatusSchema, I as DeviceTombstoneSchema, It as canonicalIconKey, Ki as defineRecordKinds, Kt as createDeviceExtensionProvider, Mi as WELL_KNOWN_TAB_MAP, Nt as buildStreamParamsConfigSchema, Oa as EventCategory, Pn as legacyAutoLinkIds, Qi as isDeviceConfigCap, Qn as parseStreamParamsFormPatch, Qr as DeviceFeature, R as FieldClaimModeSchema, Rn as locationIconKey, Sa as sleep$1, Sr as siteZoneKey, T as DAY_NAMES, Ta as transformSecretWrite, Vi as collectZodSecretPaths, Y as MINUTES_PER_DAY, Yn as normalizeUnit, Zt as defineExtensionSlotProvider, an as deviceStateCapability, ba as secretTreeFromPaths, c as CAMERA_SWITCH_ORDER, cn as enumerateItemArrayFields, ct as STREAM_PROFILE_META, ea as isKeychainStoredField, ei as DeviceType, fn as expandContainer, gr as runtimeStatePolicyFor, ki as SECRET_CLEAR, ln as enumerateSchemaFields, na as isSecretMarker, ni as BaseAddon, on as deviceStatusCapability, r as ALL_CAPABILITY_DEFINITIONS, ra as isSecretMarkerLike, rn as deviceManagerCapability, ta as isSecretClear, ti as errMsg, tn as deviceExtensionCapability, u as CAP_NAMES_WITH_STATUS, va as resolveSecretMarkers, vn as formatMinutes, wa as toSecretFieldView, z as FieldClaimSchema } from "../../dist-Dhux90yB.mjs";
1
+ import { $r as DeviceRole, Ea as userError, F as DeviceStatusSchema, I as DeviceTombstoneSchema, It as canonicalIconKey, Ki as defineRecordKinds, Kt as createDeviceExtensionProvider, Mi as WELL_KNOWN_TAB_MAP, Nt as buildStreamParamsConfigSchema, Oa as EventCategory, Pn as legacyAutoLinkIds, Qi as isDeviceConfigCap, Qn as parseStreamParamsFormPatch, Qr as DeviceFeature, R as FieldClaimModeSchema, Rn as locationIconKey, Sa as sleep$1, Sr as siteZoneKey, T as DAY_NAMES, Ta as transformSecretWrite, Vi as collectZodSecretPaths, Y as MINUTES_PER_DAY, Yn as normalizeUnit, Zt as defineExtensionSlotProvider, an as deviceStateCapability, ba as secretTreeFromPaths, c as CAMERA_SWITCH_ORDER, cn as enumerateItemArrayFields, ct as STREAM_PROFILE_META, ea as isKeychainStoredField, ei as DeviceType, fn as expandContainer, gr as runtimeStatePolicyFor, ki as SECRET_CLEAR, ln as enumerateSchemaFields, na as isSecretMarker, ni as BaseAddon, on as deviceStatusCapability, r as ALL_CAPABILITY_DEFINITIONS, ra as isSecretMarkerLike, rn as deviceManagerCapability, ta as isSecretClear, ti as errMsg, tn as deviceExtensionCapability, u as CAP_NAMES_WITH_STATUS, va as resolveSecretMarkers, vn as formatMinutes, wa as toSecretFieldView, z as FieldClaimSchema } from "../../dist-B_GRPhnY.mjs";
2
2
  import { t as require_read_all_rows } from "../../read-all-rows-TaPqdWNF.mjs";
3
3
  import { t as prefixRange } from "../../prefix-range-Dl0znowN.mjs";
4
4
  import { t as loadPrefixed } from "../../prefix-walk-DAOBSyH1.mjs";
@@ -3,7 +3,7 @@ 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
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { ni as BaseAddon, zn as logDestinationCapability } from "../../dist-Dhux90yB.mjs";
1
+ import { ni as BaseAddon, zn as logDestinationCapability } from "../../dist-B_GRPhnY.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -3,11 +3,11 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-C89nqNnn.js");
6
+ const require_dist = require("../../dist-BZQjLPiz.js");
7
7
  const require_read_all_rows$1 = require("../../read-all-rows-CVoO1ONM.js");
8
- const require_device_ca = require("../../device-ca-CVK3YcR_.js");
9
- const require_phase6_declare_gate = require("../../phase6-declare-gate-BO-_jLh_.js");
10
- const require_constants = require("../../constants-B_X9cWEK.js");
8
+ const require_device_ca = require("../../device-ca-BrVK_5s3.js");
9
+ const require_phase6_declare_gate = require("../../phase6-declare-gate-Dag1_WR2.js");
10
+ const require_constants = require("../../constants-Njka8Cv6.js");
11
11
  const require_platform_key_source = require("../../platform-key-source-Di7HrlGR.js");
12
12
  let node_crypto = require("node:crypto");
13
13
  let _camstack_types_node = require("@camstack/types/node");