@camstack/types 1.1.46 → 1.1.48

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.
@@ -143,6 +143,53 @@ export declare const RecordingRetentionSchema: z.ZodObject<{
143
143
  maxSizeGb: z.ZodOptional<z.ZodNumber>;
144
144
  }, z.core.$strip>;
145
145
  export type RecordingRetention = z.infer<typeof RecordingRetentionSchema>;
146
+ /**
147
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
148
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
149
+ * previews at. Five graduated steps; absent on a config = `standard` (the
150
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
151
+ *
152
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
153
+ * Each window's index sidecar carries its own tile dims, so a camera whose
154
+ * preset changed over time renders every historical window at the dims it was
155
+ * written with.
156
+ */
157
+ export declare const ScrubThumbnailPresetSchema: z.ZodEnum<{
158
+ standard: "standard";
159
+ high: "high";
160
+ low: "low";
161
+ max: "max";
162
+ minimal: "minimal";
163
+ }>;
164
+ export type ScrubThumbnailPreset = z.infer<typeof ScrubThumbnailPresetSchema>;
165
+ /** Resolved sprite geometry + encode quality for a scrub-thumbnail preset. */
166
+ export interface ScrubThumbnailGeometry {
167
+ /** Tile width in px (16:9). */
168
+ readonly tileWidth: number;
169
+ /** Tile height in px (16:9). */
170
+ readonly tileHeight: number;
171
+ /** Sheet JPEG quality, 1..100 (higher = better). */
172
+ readonly quality: number;
173
+ }
174
+ /** The default preset when a config omits `scrubThumbnails`. */
175
+ export declare const DEFAULT_SCRUB_THUMBNAIL_PRESET: ScrubThumbnailPreset;
176
+ /**
177
+ * Preset → tile geometry + JPEG quality. Every step is 16:9; the graduated
178
+ * ladder trades sheet size (and remote fetch cost) for scrub crispness.
179
+ * `standard` matches the shipped `sheet-geometry`/`sheet-composer` defaults
180
+ * (480×270 q72), so an unset config is byte-identical to today. The map is
181
+ * strictly monotone in resolution AND quality across the ordered steps.
182
+ */
183
+ export declare const SCRUB_THUMBNAIL_PRESETS: Record<ScrubThumbnailPreset, ScrubThumbnailGeometry>;
184
+ /** Human labels for each preset (resolution-tagged), for settings UIs. */
185
+ export declare const SCRUB_THUMBNAIL_PRESET_LABELS: Record<ScrubThumbnailPreset, string>;
186
+ /** Ordered low→high so a UI can render the ladder in graduated order. */
187
+ export declare const SCRUB_THUMBNAIL_PRESET_ORDER: readonly ScrubThumbnailPreset[];
188
+ /**
189
+ * Resolve the geometry for a recording config's scrub-thumbnail preset,
190
+ * defaulting to `standard` when unset. Pure — no I/O.
191
+ */
192
+ export declare function resolveScrubThumbnailGeometry(preset: ScrubThumbnailPreset | undefined): ScrubThumbnailGeometry;
146
193
  /**
147
194
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
148
195
  *
@@ -224,5 +271,12 @@ export declare const RecordingConfigSchema: z.ZodObject<{
224
271
  maxAgeDays: z.ZodOptional<z.ZodNumber>;
225
272
  maxSizeGb: z.ZodOptional<z.ZodNumber>;
226
273
  }, z.core.$strip>>;
274
+ scrubThumbnails: z.ZodOptional<z.ZodEnum<{
275
+ standard: "standard";
276
+ high: "high";
277
+ low: "low";
278
+ max: "max";
279
+ minimal: "minimal";
280
+ }>>;
227
281
  }, z.core.$strip>;
228
282
  export type RecordingConfig = z.infer<typeof RecordingConfigSchema>;
@@ -98,19 +98,42 @@ export declare class ReadinessRegistry implements IReadinessRegistry {
98
98
  */
99
99
  getSnapshotForTransport(): readonly ReadinessRecord[];
100
100
  /**
101
- * Hydrate the snapshot from an authoritative source. Entries already
102
- * present locally are skipped live deltas (received via the event
103
- * bus subscription) always take precedence over the snapshot. For
104
- * each newly hydrated entry, a one-shot transition is dispatched to
105
- * matching subscriptions so pending `awaitReady` callers unblock
106
- * without having to wait for a fresh event.
101
+ * Hydrate the snapshot from an authoritative source a RECONCILE, not
102
+ * an add-only merge (D8: readiness events are telemetry and may be
103
+ * lost; the on-reconnect/periodic snapshot re-pull is the repair path,
104
+ * so it MUST be able to advance a stale record the 2026-07-17
105
+ * "still awaiting platform-probe forever" wedge was this method
106
+ * skipping every already-seen key).
107
107
  *
108
- * Local `epoch` is reset to 1 per entry consumer-side epoch is
109
- * derived from observed generation transitions, so this mirrors the
110
- * value that would have been assigned had the consumer observed the
111
- * first `ready` event directly.
108
+ * - **Unseen keys** are added (epoch 1 mirrors the value the
109
+ * consumer would have derived from the first observed `ready`).
110
+ * - **Existing keys** are UPDATED when the authoritative record
111
+ * carries a different generation (producer restart / synthetic-down
112
+ * superseded — epoch bumps on a new-generation `ready`, mirroring
113
+ * `ingest`), or a FORWARD state move within the same generation
114
+ * (`starting → ready → down`; within one producer generation a
115
+ * snapshot can only be ahead of a lost delta, never behind it).
116
+ * - **Never a same-generation regress** — a snapshot pulled just
117
+ * before a live delta landed must not un-ready a record (the next
118
+ * reconcile round converges it anyway).
119
+ * - **Never a record this process is authoritative for** — node-scoped
120
+ * records for our own nodeId, and device/global records we emitted,
121
+ * keep local truth (an authority's stale copy of OUR record must
122
+ * not regress us).
123
+ *
124
+ * Every applied entry dispatches a one-shot transition to matching
125
+ * subscriptions so pending `awaitReady` callers unblock without having
126
+ * to wait for a fresh event.
112
127
  */
113
128
  hydrate(records: readonly ReadinessRecord[]): void;
129
+ /**
130
+ * True when THIS process is the origin authority for `record` — a
131
+ * snapshot pulled from elsewhere must never overwrite it:
132
+ * - node-scoped records whose `scope.nodeId` is our own node id
133
+ * (this process — or a child bridged onto our bus — emits them);
134
+ * - device/global-scoped records whose latest transition WE emitted.
135
+ */
136
+ private isLocallyAuthoritative;
114
137
  /** Shallow copy of the full snapshot — mainly for diagnostics/tests. */
115
138
  getAll(): ReadonlyMap<string, ReadinessRecord>;
116
139
  /**
@@ -1,4 +1,4 @@
1
- import { t as EventCategory } from "./event-category-CFZs3jI4.mjs";
1
+ import { t as EventCategory } from "./event-category-H4AVePnn.mjs";
2
2
  import { z } from "zod";
3
3
  //#region src/disposer-chain.ts
4
4
  var DisposerChain = class {
@@ -1487,6 +1487,19 @@ function scopeKey(scope) {
1487
1487
  case "device": return `device:${scope.deviceId}`;
1488
1488
  }
1489
1489
  }
1490
+ /**
1491
+ * Producer transition order within ONE generation: `starting → ready →
1492
+ * down`. Used by `hydrate` to accept only forward moves for
1493
+ * same-generation snapshot records (a snapshot can be ahead of a lost
1494
+ * delta, never behind it, within one producer generation).
1495
+ */
1496
+ function stateRank(state) {
1497
+ switch (state) {
1498
+ case "starting": return 0;
1499
+ case "ready": return 1;
1500
+ case "down": return 2;
1501
+ }
1502
+ }
1490
1503
  function scopesEqual(a, b) {
1491
1504
  if (a.type !== b.type) return false;
1492
1505
  if (a.type === "global" || b.type === "global") return true;
@@ -1537,43 +1550,70 @@ var ReadinessRegistry = class {
1537
1550
  return Array.from(this.snapshot.values());
1538
1551
  }
1539
1552
  /**
1540
- * Hydrate the snapshot from an authoritative source. Entries already
1541
- * present locally are skipped live deltas (received via the event
1542
- * bus subscription) always take precedence over the snapshot. For
1543
- * each newly hydrated entry, a one-shot transition is dispatched to
1544
- * matching subscriptions so pending `awaitReady` callers unblock
1545
- * without having to wait for a fresh event.
1553
+ * Hydrate the snapshot from an authoritative source a RECONCILE, not
1554
+ * an add-only merge (D8: readiness events are telemetry and may be
1555
+ * lost; the on-reconnect/periodic snapshot re-pull is the repair path,
1556
+ * so it MUST be able to advance a stale record the 2026-07-17
1557
+ * "still awaiting platform-probe forever" wedge was this method
1558
+ * skipping every already-seen key).
1559
+ *
1560
+ * - **Unseen keys** are added (epoch 1 — mirrors the value the
1561
+ * consumer would have derived from the first observed `ready`).
1562
+ * - **Existing keys** are UPDATED when the authoritative record
1563
+ * carries a different generation (producer restart / synthetic-down
1564
+ * superseded — epoch bumps on a new-generation `ready`, mirroring
1565
+ * `ingest`), or a FORWARD state move within the same generation
1566
+ * (`starting → ready → down`; within one producer generation a
1567
+ * snapshot can only be ahead of a lost delta, never behind it).
1568
+ * - **Never a same-generation regress** — a snapshot pulled just
1569
+ * before a live delta landed must not un-ready a record (the next
1570
+ * reconcile round converges it anyway).
1571
+ * - **Never a record this process is authoritative for** — node-scoped
1572
+ * records for our own nodeId, and device/global records we emitted,
1573
+ * keep local truth (an authority's stale copy of OUR record must
1574
+ * not regress us).
1546
1575
  *
1547
- * Local `epoch` is reset to 1 per entry — consumer-side epoch is
1548
- * derived from observed generation transitions, so this mirrors the
1549
- * value that would have been assigned had the consumer observed the
1550
- * first `ready` event directly.
1576
+ * Every applied entry dispatches a one-shot transition to matching
1577
+ * subscriptions so pending `awaitReady` callers unblock without having
1578
+ * to wait for a fresh event.
1551
1579
  */
1552
1580
  hydrate(records) {
1553
1581
  const now = this.now();
1554
1582
  for (const record of records) {
1555
1583
  const key = readinessKey(record.capName, record.scope);
1556
- if (this.snapshot.has(key)) continue;
1584
+ const prev = this.snapshot.get(key) ?? null;
1585
+ let epoch;
1586
+ let durationInPrevState;
1587
+ if (prev !== null) {
1588
+ if (this.isLocallyAuthoritative(prev)) continue;
1589
+ const sameGeneration = prev.generation === record.generation;
1590
+ if (sameGeneration && stateRank(record.state) <= stateRank(prev.state)) continue;
1591
+ epoch = !sameGeneration && record.state === "ready" ? prev.epoch + 1 : prev.epoch;
1592
+ durationInPrevState = Math.max(0, now - prev.lastChange);
1593
+ } else {
1594
+ epoch = 1;
1595
+ durationInPrevState = 0;
1596
+ }
1557
1597
  const hydrated = {
1558
1598
  capName: record.capName,
1559
1599
  scope: record.scope,
1560
1600
  state: record.state,
1561
1601
  generation: record.generation,
1562
- epoch: 1,
1602
+ epoch,
1563
1603
  lastChange: now,
1564
1604
  sourceNodeId: record.sourceNodeId
1565
1605
  };
1566
1606
  this.snapshot.set(key, hydrated);
1567
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated, gen=${record.generation.slice(0, 6)})`);
1607
+ if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1568
1608
  const transition = {
1569
1609
  capName: record.capName,
1570
1610
  scope: record.scope,
1571
1611
  state: record.state,
1572
- epoch: 1,
1612
+ epoch,
1573
1613
  generation: record.generation,
1574
1614
  sourceNodeId: "hydrated",
1575
1615
  ts: now,
1576
- durationInPrevState: 0
1616
+ durationInPrevState
1577
1617
  };
1578
1618
  for (const sub of this.subscriptions) {
1579
1619
  if (sub.capName !== record.capName) continue;
@@ -1586,6 +1626,17 @@ var ReadinessRegistry = class {
1586
1626
  }
1587
1627
  }
1588
1628
  }
1629
+ /**
1630
+ * True when THIS process is the origin authority for `record` — a
1631
+ * snapshot pulled from elsewhere must never overwrite it:
1632
+ * - node-scoped records whose `scope.nodeId` is our own node id
1633
+ * (this process — or a child bridged onto our bus — emits them);
1634
+ * - device/global-scoped records whose latest transition WE emitted.
1635
+ */
1636
+ isLocallyAuthoritative(record) {
1637
+ if (record.scope.type === "node") return record.scope.nodeId === this.sourceNodeId;
1638
+ return record.sourceNodeId === this.sourceNodeId;
1639
+ }
1589
1640
  /** Shallow copy of the full snapshot — mainly for diagnostics/tests. */
1590
1641
  getAll() {
1591
1642
  return new Map(this.snapshot);
@@ -2787,7 +2838,12 @@ function createDeviceProxy(api, binding, opts) {
2787
2838
  getRawState: (input) => dispatch("device-ops", "deviceOps", "getRawState", "query", input)
2788
2839
  },
2789
2840
  deviceStatus: { getStatus: (input) => dispatch("device-status", "deviceStatus", "getStatus", "query", input) },
2790
- doorbell: { getStatus: (input) => dispatch("doorbell", "doorbell", "getStatus", "query", input) },
2841
+ doorbell: {
2842
+ getStatus: (input) => dispatch("doorbell", "doorbell", "getStatus", "query", input),
2843
+ getDeviceSettingsContribution: (input) => dispatch("doorbell", "doorbell", "getDeviceSettingsContribution", "query", input),
2844
+ getDeviceLiveContribution: (input) => dispatch("doorbell", "doorbell", "getDeviceLiveContribution", "query", input),
2845
+ applyDeviceSettingsPatch: (input) => dispatch("doorbell", "doorbell", "applyDeviceSettingsPatch", "mutation", input)
2846
+ },
2791
2847
  enumSensor: { getStatus: (input) => dispatch("enum-sensor", "enumSensor", "getStatus", "query", input) },
2792
2848
  eventEmitter: { getStatus: (input) => dispatch("event-emitter", "eventEmitter", "getStatus", "query", input) },
2793
2849
  events: {
@@ -2906,10 +2962,13 @@ function createDeviceProxy(api, binding, opts) {
2906
2962
  getActiveTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getActiveTracks", "query", input),
2907
2963
  getTrack: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrack", "query", input),
2908
2964
  listTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTracks", "query", input),
2965
+ listRecentTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRecentTracks", "query", input),
2909
2966
  clearTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "clearTracks", "mutation", input),
2910
2967
  getMotionEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getMotionEvents", "query", input),
2911
2968
  getObjectEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getObjectEvents", "query", input),
2912
2969
  getAudioEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getAudioEvents", "query", input),
2970
+ listEventKinds: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventKinds", "query", input),
2971
+ getSensorEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getSensorEvents", "query", input),
2913
2972
  getKeyEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEvents", "query", input),
2914
2973
  getEventDensity: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventDensity", "query", input),
2915
2974
  pruneEventsBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEventsBefore", "mutation", input),
@@ -3083,6 +3142,7 @@ function createDeviceProxy(api, binding, opts) {
3083
3142
  setMetadata: (input) => dispatchSystem("deviceManager", "setMetadata", "mutation", input),
3084
3143
  setDisabled: (input) => dispatchSystem("deviceManager", "setDisabled", "mutation", input),
3085
3144
  getDevice: (input) => dispatchSystem("deviceManager", "getDevice", "query", input),
3145
+ getLinkedDevices: (input) => dispatchSystem("deviceManager", "getLinkedDevices", "query", input),
3086
3146
  getStreamSources: (input) => dispatchSystem("deviceManager", "getStreamSources", "query", input),
3087
3147
  getConfigSchema: (input) => dispatchSystem("deviceManager", "getConfigSchema", "query", input),
3088
3148
  getSettingsSchema: (input) => dispatchSystem("deviceManager", "getSettingsSchema", "query", input),
@@ -3103,7 +3163,10 @@ function createDeviceProxy(api, binding, opts) {
3103
3163
  updateDeviceField: (input) => dispatchSystem("deviceManager", "updateDeviceField", "mutation", input),
3104
3164
  updateDeviceFieldsBatch: (input) => dispatchSystem("deviceManager", "updateDeviceFieldsBatch", "mutation", input),
3105
3165
  testField: (input) => dispatchSystem("deviceManager", "testField", "mutation", input),
3106
- getDeviceStatusAggregate: (input) => dispatchSystem("deviceManager", "getDeviceStatusAggregate", "query", input)
3166
+ getDeviceStatusAggregate: (input) => dispatchSystem("deviceManager", "getDeviceStatusAggregate", "query", input),
3167
+ getDeviceSettingsContribution: (input) => dispatchSystem("deviceManager", "getDeviceSettingsContribution", "query", input),
3168
+ getDeviceLiveContribution: (input) => dispatchSystem("deviceManager", "getDeviceLiveContribution", "query", input),
3169
+ applyDeviceSettingsPatch: (input) => dispatchSystem("deviceManager", "applyDeviceSettingsPatch", "mutation", input)
3107
3170
  },
3108
3171
  deviceState: {
3109
3172
  getSnapshot: (input) => dispatchSystem("deviceState", "getSnapshot", "query", input),
@@ -1,4 +1,4 @@
1
- const require_event_category = require("./event-category-BXd6yeJi.js");
1
+ const require_event_category = require("./event-category-AkBNxg_X.js");
2
2
  let zod = require("zod");
3
3
  //#region src/disposer-chain.ts
4
4
  var DisposerChain = class {
@@ -1487,6 +1487,19 @@ function scopeKey(scope) {
1487
1487
  case "device": return `device:${scope.deviceId}`;
1488
1488
  }
1489
1489
  }
1490
+ /**
1491
+ * Producer transition order within ONE generation: `starting → ready →
1492
+ * down`. Used by `hydrate` to accept only forward moves for
1493
+ * same-generation snapshot records (a snapshot can be ahead of a lost
1494
+ * delta, never behind it, within one producer generation).
1495
+ */
1496
+ function stateRank(state) {
1497
+ switch (state) {
1498
+ case "starting": return 0;
1499
+ case "ready": return 1;
1500
+ case "down": return 2;
1501
+ }
1502
+ }
1490
1503
  function scopesEqual(a, b) {
1491
1504
  if (a.type !== b.type) return false;
1492
1505
  if (a.type === "global" || b.type === "global") return true;
@@ -1537,43 +1550,70 @@ var ReadinessRegistry = class {
1537
1550
  return Array.from(this.snapshot.values());
1538
1551
  }
1539
1552
  /**
1540
- * Hydrate the snapshot from an authoritative source. Entries already
1541
- * present locally are skipped live deltas (received via the event
1542
- * bus subscription) always take precedence over the snapshot. For
1543
- * each newly hydrated entry, a one-shot transition is dispatched to
1544
- * matching subscriptions so pending `awaitReady` callers unblock
1545
- * without having to wait for a fresh event.
1553
+ * Hydrate the snapshot from an authoritative source a RECONCILE, not
1554
+ * an add-only merge (D8: readiness events are telemetry and may be
1555
+ * lost; the on-reconnect/periodic snapshot re-pull is the repair path,
1556
+ * so it MUST be able to advance a stale record the 2026-07-17
1557
+ * "still awaiting platform-probe forever" wedge was this method
1558
+ * skipping every already-seen key).
1559
+ *
1560
+ * - **Unseen keys** are added (epoch 1 — mirrors the value the
1561
+ * consumer would have derived from the first observed `ready`).
1562
+ * - **Existing keys** are UPDATED when the authoritative record
1563
+ * carries a different generation (producer restart / synthetic-down
1564
+ * superseded — epoch bumps on a new-generation `ready`, mirroring
1565
+ * `ingest`), or a FORWARD state move within the same generation
1566
+ * (`starting → ready → down`; within one producer generation a
1567
+ * snapshot can only be ahead of a lost delta, never behind it).
1568
+ * - **Never a same-generation regress** — a snapshot pulled just
1569
+ * before a live delta landed must not un-ready a record (the next
1570
+ * reconcile round converges it anyway).
1571
+ * - **Never a record this process is authoritative for** — node-scoped
1572
+ * records for our own nodeId, and device/global records we emitted,
1573
+ * keep local truth (an authority's stale copy of OUR record must
1574
+ * not regress us).
1546
1575
  *
1547
- * Local `epoch` is reset to 1 per entry — consumer-side epoch is
1548
- * derived from observed generation transitions, so this mirrors the
1549
- * value that would have been assigned had the consumer observed the
1550
- * first `ready` event directly.
1576
+ * Every applied entry dispatches a one-shot transition to matching
1577
+ * subscriptions so pending `awaitReady` callers unblock without having
1578
+ * to wait for a fresh event.
1551
1579
  */
1552
1580
  hydrate(records) {
1553
1581
  const now = this.now();
1554
1582
  for (const record of records) {
1555
1583
  const key = readinessKey(record.capName, record.scope);
1556
- if (this.snapshot.has(key)) continue;
1584
+ const prev = this.snapshot.get(key) ?? null;
1585
+ let epoch;
1586
+ let durationInPrevState;
1587
+ if (prev !== null) {
1588
+ if (this.isLocallyAuthoritative(prev)) continue;
1589
+ const sameGeneration = prev.generation === record.generation;
1590
+ if (sameGeneration && stateRank(record.state) <= stateRank(prev.state)) continue;
1591
+ epoch = !sameGeneration && record.state === "ready" ? prev.epoch + 1 : prev.epoch;
1592
+ durationInPrevState = Math.max(0, now - prev.lastChange);
1593
+ } else {
1594
+ epoch = 1;
1595
+ durationInPrevState = 0;
1596
+ }
1557
1597
  const hydrated = {
1558
1598
  capName: record.capName,
1559
1599
  scope: record.scope,
1560
1600
  state: record.state,
1561
1601
  generation: record.generation,
1562
- epoch: 1,
1602
+ epoch,
1563
1603
  lastChange: now,
1564
1604
  sourceNodeId: record.sourceNodeId
1565
1605
  };
1566
1606
  this.snapshot.set(key, hydrated);
1567
- if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated, gen=${record.generation.slice(0, 6)})`);
1607
+ if (this.logger) this.logger.debug(`readiness: ${record.capName} (${scopeKey(record.scope)}) → ${record.state} (hydrated${prev !== null ? " update" : ""}, gen=${record.generation.slice(0, 6)})`);
1568
1608
  const transition = {
1569
1609
  capName: record.capName,
1570
1610
  scope: record.scope,
1571
1611
  state: record.state,
1572
- epoch: 1,
1612
+ epoch,
1573
1613
  generation: record.generation,
1574
1614
  sourceNodeId: "hydrated",
1575
1615
  ts: now,
1576
- durationInPrevState: 0
1616
+ durationInPrevState
1577
1617
  };
1578
1618
  for (const sub of this.subscriptions) {
1579
1619
  if (sub.capName !== record.capName) continue;
@@ -1586,6 +1626,17 @@ var ReadinessRegistry = class {
1586
1626
  }
1587
1627
  }
1588
1628
  }
1629
+ /**
1630
+ * True when THIS process is the origin authority for `record` — a
1631
+ * snapshot pulled from elsewhere must never overwrite it:
1632
+ * - node-scoped records whose `scope.nodeId` is our own node id
1633
+ * (this process — or a child bridged onto our bus — emits them);
1634
+ * - device/global-scoped records whose latest transition WE emitted.
1635
+ */
1636
+ isLocallyAuthoritative(record) {
1637
+ if (record.scope.type === "node") return record.scope.nodeId === this.sourceNodeId;
1638
+ return record.sourceNodeId === this.sourceNodeId;
1639
+ }
1589
1640
  /** Shallow copy of the full snapshot — mainly for diagnostics/tests. */
1590
1641
  getAll() {
1591
1642
  return new Map(this.snapshot);
@@ -2787,7 +2838,12 @@ function createDeviceProxy(api, binding, opts) {
2787
2838
  getRawState: (input) => dispatch("device-ops", "deviceOps", "getRawState", "query", input)
2788
2839
  },
2789
2840
  deviceStatus: { getStatus: (input) => dispatch("device-status", "deviceStatus", "getStatus", "query", input) },
2790
- doorbell: { getStatus: (input) => dispatch("doorbell", "doorbell", "getStatus", "query", input) },
2841
+ doorbell: {
2842
+ getStatus: (input) => dispatch("doorbell", "doorbell", "getStatus", "query", input),
2843
+ getDeviceSettingsContribution: (input) => dispatch("doorbell", "doorbell", "getDeviceSettingsContribution", "query", input),
2844
+ getDeviceLiveContribution: (input) => dispatch("doorbell", "doorbell", "getDeviceLiveContribution", "query", input),
2845
+ applyDeviceSettingsPatch: (input) => dispatch("doorbell", "doorbell", "applyDeviceSettingsPatch", "mutation", input)
2846
+ },
2791
2847
  enumSensor: { getStatus: (input) => dispatch("enum-sensor", "enumSensor", "getStatus", "query", input) },
2792
2848
  eventEmitter: { getStatus: (input) => dispatch("event-emitter", "eventEmitter", "getStatus", "query", input) },
2793
2849
  events: {
@@ -2906,10 +2962,13 @@ function createDeviceProxy(api, binding, opts) {
2906
2962
  getActiveTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getActiveTracks", "query", input),
2907
2963
  getTrack: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrack", "query", input),
2908
2964
  listTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTracks", "query", input),
2965
+ listRecentTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRecentTracks", "query", input),
2909
2966
  clearTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "clearTracks", "mutation", input),
2910
2967
  getMotionEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getMotionEvents", "query", input),
2911
2968
  getObjectEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getObjectEvents", "query", input),
2912
2969
  getAudioEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getAudioEvents", "query", input),
2970
+ listEventKinds: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventKinds", "query", input),
2971
+ getSensorEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getSensorEvents", "query", input),
2913
2972
  getKeyEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEvents", "query", input),
2914
2973
  getEventDensity: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventDensity", "query", input),
2915
2974
  pruneEventsBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEventsBefore", "mutation", input),
@@ -3083,6 +3142,7 @@ function createDeviceProxy(api, binding, opts) {
3083
3142
  setMetadata: (input) => dispatchSystem("deviceManager", "setMetadata", "mutation", input),
3084
3143
  setDisabled: (input) => dispatchSystem("deviceManager", "setDisabled", "mutation", input),
3085
3144
  getDevice: (input) => dispatchSystem("deviceManager", "getDevice", "query", input),
3145
+ getLinkedDevices: (input) => dispatchSystem("deviceManager", "getLinkedDevices", "query", input),
3086
3146
  getStreamSources: (input) => dispatchSystem("deviceManager", "getStreamSources", "query", input),
3087
3147
  getConfigSchema: (input) => dispatchSystem("deviceManager", "getConfigSchema", "query", input),
3088
3148
  getSettingsSchema: (input) => dispatchSystem("deviceManager", "getSettingsSchema", "query", input),
@@ -3103,7 +3163,10 @@ function createDeviceProxy(api, binding, opts) {
3103
3163
  updateDeviceField: (input) => dispatchSystem("deviceManager", "updateDeviceField", "mutation", input),
3104
3164
  updateDeviceFieldsBatch: (input) => dispatchSystem("deviceManager", "updateDeviceFieldsBatch", "mutation", input),
3105
3165
  testField: (input) => dispatchSystem("deviceManager", "testField", "mutation", input),
3106
- getDeviceStatusAggregate: (input) => dispatchSystem("deviceManager", "getDeviceStatusAggregate", "query", input)
3166
+ getDeviceStatusAggregate: (input) => dispatchSystem("deviceManager", "getDeviceStatusAggregate", "query", input),
3167
+ getDeviceSettingsContribution: (input) => dispatchSystem("deviceManager", "getDeviceSettingsContribution", "query", input),
3168
+ getDeviceLiveContribution: (input) => dispatchSystem("deviceManager", "getDeviceLiveContribution", "query", input),
3169
+ applyDeviceSettingsPatch: (input) => dispatchSystem("deviceManager", "applyDeviceSettingsPatch", "mutation", input)
3107
3170
  },
3108
3171
  deviceState: {
3109
3172
  getSnapshot: (input) => dispatchSystem("deviceState", "getSnapshot", "query", input),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.1.46",
3
+ "version": "1.1.48",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",