@camstack/addon-matter-broker 0.2.89 → 0.2.91

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 (3) hide show
  1. package/dist/addon.js +205 -18
  2. package/dist/addon.mjs +205 -18
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13,7 +13,7 @@ let node_stream_promises = require("node:stream/promises");
13
13
  let node_net = require("node:net");
14
14
  let node_dgram = require("node:dgram");
15
15
  node_dgram = require_esm.__toESM(node_dgram, 1);
16
- //#region ../types/dist/event-category-CnLqLOKs.mjs
16
+ //#region ../types/dist/event-category-DAXzJeTX.mjs
17
17
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
18
18
  EventCategory["SystemBoot"] = "system.boot";
19
19
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -605,7 +605,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
605
605
  * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
606
606
  */
607
607
  EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
608
- EventCategory["FrigateLiveEvent"] = "frigate.live-event";
609
608
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
610
609
  /**
611
610
  * Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
@@ -12688,7 +12687,7 @@ DeviceType.Camera;
12688
12687
  var SourceInfoSchema = object({
12689
12688
  /** Live dispatch key — mutable when the source system allows rename. */
12690
12689
  id: string$2(),
12691
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'frigate'. */
12690
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
12692
12691
  system: string$2(),
12693
12692
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
12694
12693
  * Used to detect rename when `id` changes. */
@@ -12701,7 +12700,7 @@ var SourceInfoSchema = object({
12701
12700
  /**
12702
12701
  * Build the synthetic SourceInfo every existing provider falls back to
12703
12702
  * when no upstream value has been persisted. Keeps non-migrated providers
12704
- * (Reolink / Hikvision / ONVIF / Frigate / RTSP) functional without code
12703
+ * (Reolink / Hikvision / ONVIF / RTSP) functional without code
12705
12704
  * changes — `id` reuses the CamStack stableId, `system` reuses the addon
12706
12705
  * id. Real upstream identity replaces this once a provider migrates and
12707
12706
  * calls `updateSourceInfo()` with concrete values.
@@ -13380,7 +13379,7 @@ var deviceProviderCapability = {
13380
13379
  * - `device-management.router.ts` (deleted in Phase 2)
13381
13380
  * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
13382
13381
  *
13383
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
13382
+ * All device provider addons (rtsp, onvif, …) are hub-local: they may
13384
13383
  * fork into separate processes but never run on remote cluster agents. Therefore:
13385
13384
  * - No nodeId routing needed — this is a pure hub singleton.
13386
13385
  * - The hub's DeviceRegistry is the single source of truth for all live devices.
@@ -17428,7 +17427,7 @@ var NcRuleInputSchema = object({
17428
17427
  * The measured seven-person arrival on device 590 spans 110 s with every
17429
17428
  * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
17430
17429
  * idle cutoff holds it as one and ends it when the arrival actually ends.
17431
- * 30 is Frigate's shipped value for the same decision.
17430
+ * 30 s is a widely shipped value for the same decision.
17432
17431
  *
17433
17432
  * ### What it replaces
17434
17433
  *
@@ -19510,6 +19509,13 @@ var ZoneCrossingSchema = object({
19510
19509
  /** Zone display name at crossing time (falls back to the id). */
19511
19510
  zoneName: string$2().optional()
19512
19511
  });
19512
+ /** One zone a box was stamped with, and how much of the BOX fell inside it. */
19513
+ var ZoneOverlapSchema = object({
19514
+ zoneId: string$2(),
19515
+ zoneName: string$2().optional(),
19516
+ /** Percentage of the BOX area inside the zone, 0–100. */
19517
+ overlapPct: number()
19518
+ });
19513
19519
  var ObjectEventSchema = object({
19514
19520
  ...BaseEventFields,
19515
19521
  kind: literal("object"),
@@ -19534,6 +19540,35 @@ var ObjectEventSchema = object({
19534
19540
  bbox: BoundingBoxSchema.optional(),
19535
19541
  /** Heavy JSON — omitted in slim projection. */
19536
19542
  zones: array(string$2()).readonly().optional(),
19543
+ /**
19544
+ * How far inside each stamped zone the box actually was, as a percentage of
19545
+ * the BOX area.
19546
+ *
19547
+ * Membership alone cannot express "at least a third of it inside", which is
19548
+ * the bar an operator argues with — and the number already existed: the
19549
+ * engine computed it, the pipeline logged it, and then discarded it. It
19550
+ * matters most on the events that carry no track, where a rule has nothing
19551
+ * else to judge by.
19552
+ */
19553
+ zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
19554
+ /**
19555
+ * Present when this event describes a detection the TRACKER refused, so it
19556
+ * has no track and never will.
19557
+ *
19558
+ * A zone-scoped immediate rule asks "is there an animal in the flowerbed
19559
+ * NOW?"; tracking answers a different question ("is this the same subject as
19560
+ * before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
19561
+ * dog at 0.7729, 100% inside a watched zone, was refused three times in six
19562
+ * seconds with three different candidate ids — seven analysed frames in the
19563
+ * window, the subject in a handful of them, metres apart — and produced no
19564
+ * event at all.
19565
+ *
19566
+ * It is NAMED rather than merely implied by a missing `trackId`: an event
19567
+ * with no track and no reason is indistinguishable from a track whose media
19568
+ * went missing, and a consumer that asks this one for a best shot or a
19569
+ * history is asking for something that never existed.
19570
+ */
19571
+ spawnRefused: string$2().optional(),
19537
19572
  /** Omitted in slim projection. */
19538
19573
  state: TrackStateSchema.optional(),
19539
19574
  /**
@@ -20607,6 +20642,60 @@ var NativeCropResultSchema = object({
20607
20642
  */
20608
20643
  tier: _enum(["native", "ram-fullframe"]).optional()
20609
20644
  });
20645
+ /**
20646
+ * What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
20647
+ * kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
20648
+ *
20649
+ * - `keyFrame` — the track's clean best FULL frame at native width (capped by
20650
+ * the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
20651
+ * from, and what the detail plane's full-frame rung cuts from.
20652
+ * - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
20653
+ * window, cut by the runner at uncapped native resolution.
20654
+ *
20655
+ * The runner never interprets `trackId`; it is an opaque key the hub owns.
20656
+ */
20657
+ var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
20658
+ /**
20659
+ * Why a park did NOT happen. Every one is logged on the runner with
20660
+ * `tags: { deviceId }` and the handle's measured age, because a handle already
20661
+ * gone at park time IS the defect the 150–500 ms window exists to prevent —
20662
+ * never a reason to retry in a loop.
20663
+ *
20664
+ * - `handle-gone` — the native surface no longer resolves this handle (never
20665
+ * registered, past the lease bound, or the worker released it). `detail`
20666
+ * carries the runner's own miss reason.
20667
+ * - `encode-failed` — pixels resolved and the JPEG encode threw.
20668
+ */
20669
+ var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
20670
+ /** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
20671
+ var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
20672
+ parked: literal(true),
20673
+ /** The node holding the parcel — what a later retrieval must be pinned to. */
20674
+ nodeId: string$2(),
20675
+ width: number().int().positive(),
20676
+ height: number().int().positive(),
20677
+ /** JPEG bytes the parcel costs on the node. */
20678
+ bytes: number().int().nonnegative(),
20679
+ /** Whether this park replaced a parcel already held for the key. */
20680
+ replaced: boolean()
20681
+ }), object({
20682
+ parked: literal(false),
20683
+ nodeId: string$2(),
20684
+ reason: ParkRefusalSchema,
20685
+ /** Age of the handle at the park, on the runner's clock; null if unknown. */
20686
+ handleAgeMs: number().nullable(),
20687
+ detail: string$2().optional()
20688
+ })]);
20689
+ /** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
20690
+ var ParkedTrackFrameSchema = object({
20691
+ jpeg: string$2(),
20692
+ width: number().int().positive(),
20693
+ height: number().int().positive(),
20694
+ /** The frame instant the parcel shows (the caller's clock, echoed back). */
20695
+ timestamp: number(),
20696
+ /** When the node stored it (the node's clock). */
20697
+ parkedAt: number()
20698
+ });
20610
20699
  /** Parent detection context passed to `runDetailSubtree` — the crop's
20611
20700
  * originating detection, in FRAME-space coordinates. Reuses
20612
20701
  * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
@@ -20996,7 +21085,7 @@ var RunnerCameraConfigSchema = object({
20996
21085
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
20997
21086
  * analyzer providers) running on decoded frames in this runner.
20998
21087
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
20999
- * analytics, Frigate motion). The provider emits ProviderMotion
21088
+ * analytics). The provider emits ProviderMotion
21000
21089
  * events; the orchestrator forwards to `reportMotion`.
21001
21090
  */
21002
21091
  motionSources: MotionSourcesSchema.default(["analyzer"]),
@@ -21238,7 +21327,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
21238
21327
  className: string$2(),
21239
21328
  /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
21240
21329
  sourceDeviceId: number()
21241
- }), RunStatelessStepResultSchema, { kind: "mutation" });
21330
+ }), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
21331
+ deviceId: number(),
21332
+ /** Opaque to the runner — the hub's track id. */
21333
+ trackId: string$2(),
21334
+ kind: ParkedFrameKindSchema,
21335
+ handle: FrameHandleSchema,
21336
+ /** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
21337
+ bbox: NativeCropBboxSchema,
21338
+ /** Width cap for a full-frame park (the `keyFrame` native cap). */
21339
+ maxWidth: number().int().positive().optional(),
21340
+ /** The frame instant (caller's clock) — orders parks for one key. */
21341
+ timestamp: number()
21342
+ }), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
21343
+ deviceId: number(),
21344
+ trackId: string$2(),
21345
+ kind: ParkedFrameKindSchema
21346
+ }), ParkedTrackFrameSchema.nullable()), method(object({
21347
+ deviceId: number(),
21348
+ trackId: string$2()
21349
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" });
21242
21350
  var CameraPipelineConfigSchema = object({
21243
21351
  engine: PipelineEngineChoiceSchema.optional(),
21244
21352
  steps: array(PipelineStepInputSchema).readonly(),
@@ -24308,8 +24416,8 @@ onStatusChanged: { data: object({
24308
24416
  /**
24309
24417
  * Runtime-state slice — every provider that registers this cap
24310
24418
  * stores the same shape under `device.runtimeState[battery]`.
24311
- * Cross-provider uniformity: a Reolink Argus, a Frigate sensor
24312
- * proxy, an ONVIF battery cam all read/write the same keys.
24419
+ * Cross-provider uniformity: a Reolink Argus, a sensor proxy,
24420
+ * an ONVIF battery cam all read/write the same keys.
24313
24421
  * Consumers (BatteryBadge, snapshot wrapper sleep gate) read once
24314
24422
  * via `device.runtimeState.getCapState('battery')` regardless of
24315
24423
  * the underlying driver.
@@ -24459,7 +24567,7 @@ var buttonCapability = {
24459
24567
  *
24460
24568
  * Implementations:
24461
24569
  * - Each camera-providing addon registers this cap natively per
24462
- * device (Reolink, Hikvision, RTSP, Frigate, ONVIF self).
24570
+ * device (Reolink, Hikvision, RTSP, ONVIF self).
24463
24571
  * - Returns a snapshot from the persisted device config blob — no
24464
24572
  * extra round-trip to the camera, no probing.
24465
24573
  * - The cap is admin-only (`auth: 'admin'`) — credentials must not
@@ -26551,7 +26659,7 @@ var AvailableIntegrationTypeSchema = object({
26551
26659
  * Which integration-marker cap the addon declared, so the wizard can
26552
26660
  * branch on CAP — never on addon name. `device-adoption` integrations
26553
26661
  * (Home Assistant, …) route through the broker step (Approach A);
26554
- * `device-provider` integrations (Reolink/Frigate/ONVIF) keep the
26662
+ * `device-provider` integrations (Reolink/ONVIF) keep the
26555
26663
  * legacy config → discovery flow.
26556
26664
  */
26557
26665
  kind: _enum(["device-adoption", "device-provider"]),
@@ -29975,13 +30083,31 @@ var RecordingManifestSchema = object({
29975
30083
  */
29976
30084
  playbackEndpoints: array(string$2())
29977
30085
  });
29978
- /** Recording storage usage for one camera (bytes on disk across all its
29979
- * profiles/subtrees/locations on this node). */
30086
+ /**
30087
+ * Recording storage usage for one camera — what the ARCHIVE holds for it,
30088
+ * across every profile and every resolvable location on this node.
30089
+ *
30090
+ * ## Archive vs residency, which is the whole point of this shape (D418)
30091
+ *
30092
+ * `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
30093
+ * the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
30094
+ * this camera writes to, so the two can be added up and compared.
30095
+ *
30096
+ * They were not always. Until D418 this row came from the recorder's in-RAM
30097
+ * `RecordingIndex`, which since D287 holds only the live tail plus the hours
30098
+ * somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
30099
+ * 60.5 GB and "oldest: three hours ago" on the same page as the volume row
30100
+ * that said otherwise, and two people read it as retention eating recent
30101
+ * footage. Residency is still reported, under `residentBytes` /
30102
+ * `residentOldestMs`, which answer a different question and say so.
30103
+ */
29980
30104
  var RecordingDeviceUsageSchema = object({
29981
30105
  deviceId: number(),
30106
+ /** Bytes the ARCHIVE holds for this camera on this node's resolvable
30107
+ * recordings locations. */
29982
30108
  usedBytes: number(),
29983
30109
  /**
29984
- * Start of this camera's OLDEST indexed segment, across every profile and
30110
+ * Start of this camera's OLDEST ARCHIVED segment, across every profile and
29985
30111
  * location — the "Oldest footage" column in Recordings → Storage, and the
29986
30112
  * only honest answer to "is retention actually holding?" per camera.
29987
30113
  *
@@ -29991,7 +30117,24 @@ var RecordingDeviceUsageSchema = object({
29991
30117
  * (types) and the addon ship on different trains, and the addon is usually
29992
30118
  * the later of the two.
29993
30119
  */
29994
- oldestMs: number().nullable().optional()
30120
+ oldestMs: number().nullable().optional(),
30121
+ /** Segments the archive holds for this camera — the "M" beside `usedBytes`.
30122
+ * OPTIONAL for the same train-skew reason as `oldestMs`. */
30123
+ segments: number().optional(),
30124
+ /**
30125
+ * RESIDENCY, not the archive: bytes the recorder process currently holds in
30126
+ * its RAM index — the live tail plus the hours a reader hydrated.
30127
+ *
30128
+ * It answers "what can be served without touching the disk", never "how much
30129
+ * footage this camera has". A UI that renders it must label it as such, and
30130
+ * must never total it as storage. OPTIONAL: a recorder predating D418 omits
30131
+ * it, and so does a node whose ledger could not answer.
30132
+ */
30133
+ residentBytes: number().optional(),
30134
+ /** The residency counterpart of `oldestMs` — the oldest segment currently in
30135
+ * RAM, which on a healthy recorder is minutes ago and means nothing about
30136
+ * retention. `null` = nothing resident. */
30137
+ residentOldestMs: number().nullable().optional()
29995
30138
  });
29996
30139
  /** Recording storage usage + capacity for one storage location. */
29997
30140
  var RecordingLocationUsageSchema = object({
@@ -30029,6 +30172,17 @@ var RecordingStorageUsageSchema = object({
30029
30172
  totalUsedBytes: number(),
30030
30173
  devices: array(RecordingDeviceUsageSchema),
30031
30174
  /**
30175
+ * WHERE `devices[]` came from, so an empty table is never mistaken for empty
30176
+ * disks (D418).
30177
+ *
30178
+ * `archive` = the durable hour ledger answered. `unavailable` = this node
30179
+ * declared no ledger and the recorder REFUSED rather than answering the
30180
+ * question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
30181
+ * zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
30182
+ * and its rows are residency.
30183
+ */
30184
+ devicesRead: _enum(["archive", "unavailable"]).optional(),
30185
+ /**
30032
30186
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
30033
30187
  * of the payload without it: the hub serves this method through a BAKED
30034
30188
  * router, so a hub whose framework train predates a change to this array
@@ -33908,7 +34062,7 @@ function toDeviceSummary(device, addonId) {
33908
34062
  };
33909
34063
  }
33910
34064
  /**
33911
- * Base class for device-provider addons (rtsp, onvif, frigate).
34065
+ * Base class for device-provider addons (rtsp, onvif).
33912
34066
  *
33913
34067
  * Provides default implementations for the common device-provider cap
33914
34068
  * methods (`start`, `stop`, `getStatus`, `getDevices`, `supportsDiscovery`,
@@ -38564,6 +38718,24 @@ Object.freeze({
38564
38718
  addonId: null,
38565
38719
  access: "view"
38566
38720
  },
38721
+ "pipelineRunner.getParkedTrackFrame": {
38722
+ capName: "pipeline-runner",
38723
+ capScope: "system",
38724
+ addonId: null,
38725
+ access: "view"
38726
+ },
38727
+ "pipelineRunner.parkTrackFrame": {
38728
+ capName: "pipeline-runner",
38729
+ capScope: "system",
38730
+ addonId: null,
38731
+ access: "create"
38732
+ },
38733
+ "pipelineRunner.releaseParkedTrackFrames": {
38734
+ capName: "pipeline-runner",
38735
+ capScope: "system",
38736
+ addonId: null,
38737
+ access: "create"
38738
+ },
38567
38739
  "pipelineRunner.reportMotion": {
38568
38740
  capName: "pipeline-runner",
38569
38741
  capScope: "system",
@@ -41841,6 +42013,21 @@ Object.freeze({
41841
42013
  form: "single",
41842
42014
  optional: false
41843
42015
  }],
42016
+ "pipelineRunner.getParkedTrackFrame": [{
42017
+ name: "deviceId",
42018
+ form: "single",
42019
+ optional: false
42020
+ }],
42021
+ "pipelineRunner.parkTrackFrame": [{
42022
+ name: "deviceId",
42023
+ form: "single",
42024
+ optional: false
42025
+ }],
42026
+ "pipelineRunner.releaseParkedTrackFrames": [{
42027
+ name: "deviceId",
42028
+ form: "single",
42029
+ optional: false
42030
+ }],
41844
42031
  "pipelineRunner.reportMotion": [{
41845
42032
  name: "deviceId",
41846
42033
  form: "single",
@@ -42532,7 +42719,7 @@ var PreviewTextField = string$2().max(200);
42532
42719
  * Whether the notification's preview is a STILL or a short animation.
42533
42720
  *
42534
42721
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
42535
- * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
42722
+ * rule)"* — the other rules already offer an animated preview, and a ten-hour
42536
42723
  * night reads better as three seconds of motion than as one frame of it. Both
42537
42724
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
42538
42725
  * simply applies it to a dozen frames sampled across the render and assembles
package/dist/addon.mjs CHANGED
@@ -11,7 +11,7 @@ import { networkInterfaces, tmpdir, uptime } from "node:os";
11
11
  import { finished } from "node:stream/promises";
12
12
  import { createConnection, createServer as createServer$2 } from "node:net";
13
13
  import * as dgram from "node:dgram";
14
- //#region ../types/dist/event-category-CnLqLOKs.mjs
14
+ //#region ../types/dist/event-category-DAXzJeTX.mjs
15
15
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
16
16
  EventCategory["SystemBoot"] = "system.boot";
17
17
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -603,7 +603,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
603
603
  * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
604
604
  */
605
605
  EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
606
- EventCategory["FrigateLiveEvent"] = "frigate.live-event";
607
606
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
608
607
  /**
609
608
  * Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
@@ -12686,7 +12685,7 @@ DeviceType.Camera;
12686
12685
  var SourceInfoSchema = object({
12687
12686
  /** Live dispatch key — mutable when the source system allows rename. */
12688
12687
  id: string$2(),
12689
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'frigate'. */
12688
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
12690
12689
  system: string$2(),
12691
12690
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
12692
12691
  * Used to detect rename when `id` changes. */
@@ -12699,7 +12698,7 @@ var SourceInfoSchema = object({
12699
12698
  /**
12700
12699
  * Build the synthetic SourceInfo every existing provider falls back to
12701
12700
  * when no upstream value has been persisted. Keeps non-migrated providers
12702
- * (Reolink / Hikvision / ONVIF / Frigate / RTSP) functional without code
12701
+ * (Reolink / Hikvision / ONVIF / RTSP) functional without code
12703
12702
  * changes — `id` reuses the CamStack stableId, `system` reuses the addon
12704
12703
  * id. Real upstream identity replaces this once a provider migrates and
12705
12704
  * calls `updateSourceInfo()` with concrete values.
@@ -13378,7 +13377,7 @@ var deviceProviderCapability = {
13378
13377
  * - `device-management.router.ts` (deleted in Phase 2)
13379
13378
  * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
13380
13379
  *
13381
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
13380
+ * All device provider addons (rtsp, onvif, …) are hub-local: they may
13382
13381
  * fork into separate processes but never run on remote cluster agents. Therefore:
13383
13382
  * - No nodeId routing needed — this is a pure hub singleton.
13384
13383
  * - The hub's DeviceRegistry is the single source of truth for all live devices.
@@ -17426,7 +17425,7 @@ var NcRuleInputSchema = object({
17426
17425
  * The measured seven-person arrival on device 590 spans 110 s with every
17427
17426
  * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
17428
17427
  * idle cutoff holds it as one and ends it when the arrival actually ends.
17429
- * 30 is Frigate's shipped value for the same decision.
17428
+ * 30 s is a widely shipped value for the same decision.
17430
17429
  *
17431
17430
  * ### What it replaces
17432
17431
  *
@@ -19508,6 +19507,13 @@ var ZoneCrossingSchema = object({
19508
19507
  /** Zone display name at crossing time (falls back to the id). */
19509
19508
  zoneName: string$2().optional()
19510
19509
  });
19510
+ /** One zone a box was stamped with, and how much of the BOX fell inside it. */
19511
+ var ZoneOverlapSchema = object({
19512
+ zoneId: string$2(),
19513
+ zoneName: string$2().optional(),
19514
+ /** Percentage of the BOX area inside the zone, 0–100. */
19515
+ overlapPct: number()
19516
+ });
19511
19517
  var ObjectEventSchema = object({
19512
19518
  ...BaseEventFields,
19513
19519
  kind: literal("object"),
@@ -19532,6 +19538,35 @@ var ObjectEventSchema = object({
19532
19538
  bbox: BoundingBoxSchema.optional(),
19533
19539
  /** Heavy JSON — omitted in slim projection. */
19534
19540
  zones: array(string$2()).readonly().optional(),
19541
+ /**
19542
+ * How far inside each stamped zone the box actually was, as a percentage of
19543
+ * the BOX area.
19544
+ *
19545
+ * Membership alone cannot express "at least a third of it inside", which is
19546
+ * the bar an operator argues with — and the number already existed: the
19547
+ * engine computed it, the pipeline logged it, and then discarded it. It
19548
+ * matters most on the events that carry no track, where a rule has nothing
19549
+ * else to judge by.
19550
+ */
19551
+ zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
19552
+ /**
19553
+ * Present when this event describes a detection the TRACKER refused, so it
19554
+ * has no track and never will.
19555
+ *
19556
+ * A zone-scoped immediate rule asks "is there an animal in the flowerbed
19557
+ * NOW?"; tracking answers a different question ("is this the same subject as
19558
+ * before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
19559
+ * dog at 0.7729, 100% inside a watched zone, was refused three times in six
19560
+ * seconds with three different candidate ids — seven analysed frames in the
19561
+ * window, the subject in a handful of them, metres apart — and produced no
19562
+ * event at all.
19563
+ *
19564
+ * It is NAMED rather than merely implied by a missing `trackId`: an event
19565
+ * with no track and no reason is indistinguishable from a track whose media
19566
+ * went missing, and a consumer that asks this one for a best shot or a
19567
+ * history is asking for something that never existed.
19568
+ */
19569
+ spawnRefused: string$2().optional(),
19535
19570
  /** Omitted in slim projection. */
19536
19571
  state: TrackStateSchema.optional(),
19537
19572
  /**
@@ -20605,6 +20640,60 @@ var NativeCropResultSchema = object({
20605
20640
  */
20606
20641
  tier: _enum(["native", "ram-fullframe"]).optional()
20607
20642
  });
20643
+ /**
20644
+ * What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
20645
+ * kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
20646
+ *
20647
+ * - `keyFrame` — the track's clean best FULL frame at native width (capped by
20648
+ * the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
20649
+ * from, and what the detail plane's full-frame rung cuts from.
20650
+ * - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
20651
+ * window, cut by the runner at uncapped native resolution.
20652
+ *
20653
+ * The runner never interprets `trackId`; it is an opaque key the hub owns.
20654
+ */
20655
+ var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
20656
+ /**
20657
+ * Why a park did NOT happen. Every one is logged on the runner with
20658
+ * `tags: { deviceId }` and the handle's measured age, because a handle already
20659
+ * gone at park time IS the defect the 150–500 ms window exists to prevent —
20660
+ * never a reason to retry in a loop.
20661
+ *
20662
+ * - `handle-gone` — the native surface no longer resolves this handle (never
20663
+ * registered, past the lease bound, or the worker released it). `detail`
20664
+ * carries the runner's own miss reason.
20665
+ * - `encode-failed` — pixels resolved and the JPEG encode threw.
20666
+ */
20667
+ var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
20668
+ /** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
20669
+ var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
20670
+ parked: literal(true),
20671
+ /** The node holding the parcel — what a later retrieval must be pinned to. */
20672
+ nodeId: string$2(),
20673
+ width: number().int().positive(),
20674
+ height: number().int().positive(),
20675
+ /** JPEG bytes the parcel costs on the node. */
20676
+ bytes: number().int().nonnegative(),
20677
+ /** Whether this park replaced a parcel already held for the key. */
20678
+ replaced: boolean()
20679
+ }), object({
20680
+ parked: literal(false),
20681
+ nodeId: string$2(),
20682
+ reason: ParkRefusalSchema,
20683
+ /** Age of the handle at the park, on the runner's clock; null if unknown. */
20684
+ handleAgeMs: number().nullable(),
20685
+ detail: string$2().optional()
20686
+ })]);
20687
+ /** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
20688
+ var ParkedTrackFrameSchema = object({
20689
+ jpeg: string$2(),
20690
+ width: number().int().positive(),
20691
+ height: number().int().positive(),
20692
+ /** The frame instant the parcel shows (the caller's clock, echoed back). */
20693
+ timestamp: number(),
20694
+ /** When the node stored it (the node's clock). */
20695
+ parkedAt: number()
20696
+ });
20608
20697
  /** Parent detection context passed to `runDetailSubtree` — the crop's
20609
20698
  * originating detection, in FRAME-space coordinates. Reuses
20610
20699
  * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
@@ -20994,7 +21083,7 @@ var RunnerCameraConfigSchema = object({
20994
21083
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
20995
21084
  * analyzer providers) running on decoded frames in this runner.
20996
21085
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
20997
- * analytics, Frigate motion). The provider emits ProviderMotion
21086
+ * analytics). The provider emits ProviderMotion
20998
21087
  * events; the orchestrator forwards to `reportMotion`.
20999
21088
  */
21000
21089
  motionSources: MotionSourcesSchema.default(["analyzer"]),
@@ -21236,7 +21325,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
21236
21325
  className: string$2(),
21237
21326
  /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
21238
21327
  sourceDeviceId: number()
21239
- }), RunStatelessStepResultSchema, { kind: "mutation" });
21328
+ }), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
21329
+ deviceId: number(),
21330
+ /** Opaque to the runner — the hub's track id. */
21331
+ trackId: string$2(),
21332
+ kind: ParkedFrameKindSchema,
21333
+ handle: FrameHandleSchema,
21334
+ /** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
21335
+ bbox: NativeCropBboxSchema,
21336
+ /** Width cap for a full-frame park (the `keyFrame` native cap). */
21337
+ maxWidth: number().int().positive().optional(),
21338
+ /** The frame instant (caller's clock) — orders parks for one key. */
21339
+ timestamp: number()
21340
+ }), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
21341
+ deviceId: number(),
21342
+ trackId: string$2(),
21343
+ kind: ParkedFrameKindSchema
21344
+ }), ParkedTrackFrameSchema.nullable()), method(object({
21345
+ deviceId: number(),
21346
+ trackId: string$2()
21347
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" });
21240
21348
  var CameraPipelineConfigSchema = object({
21241
21349
  engine: PipelineEngineChoiceSchema.optional(),
21242
21350
  steps: array(PipelineStepInputSchema).readonly(),
@@ -24306,8 +24414,8 @@ onStatusChanged: { data: object({
24306
24414
  /**
24307
24415
  * Runtime-state slice — every provider that registers this cap
24308
24416
  * stores the same shape under `device.runtimeState[battery]`.
24309
- * Cross-provider uniformity: a Reolink Argus, a Frigate sensor
24310
- * proxy, an ONVIF battery cam all read/write the same keys.
24417
+ * Cross-provider uniformity: a Reolink Argus, a sensor proxy,
24418
+ * an ONVIF battery cam all read/write the same keys.
24311
24419
  * Consumers (BatteryBadge, snapshot wrapper sleep gate) read once
24312
24420
  * via `device.runtimeState.getCapState('battery')` regardless of
24313
24421
  * the underlying driver.
@@ -24457,7 +24565,7 @@ var buttonCapability = {
24457
24565
  *
24458
24566
  * Implementations:
24459
24567
  * - Each camera-providing addon registers this cap natively per
24460
- * device (Reolink, Hikvision, RTSP, Frigate, ONVIF self).
24568
+ * device (Reolink, Hikvision, RTSP, ONVIF self).
24461
24569
  * - Returns a snapshot from the persisted device config blob — no
24462
24570
  * extra round-trip to the camera, no probing.
24463
24571
  * - The cap is admin-only (`auth: 'admin'`) — credentials must not
@@ -26549,7 +26657,7 @@ var AvailableIntegrationTypeSchema = object({
26549
26657
  * Which integration-marker cap the addon declared, so the wizard can
26550
26658
  * branch on CAP — never on addon name. `device-adoption` integrations
26551
26659
  * (Home Assistant, …) route through the broker step (Approach A);
26552
- * `device-provider` integrations (Reolink/Frigate/ONVIF) keep the
26660
+ * `device-provider` integrations (Reolink/ONVIF) keep the
26553
26661
  * legacy config → discovery flow.
26554
26662
  */
26555
26663
  kind: _enum(["device-adoption", "device-provider"]),
@@ -29973,13 +30081,31 @@ var RecordingManifestSchema = object({
29973
30081
  */
29974
30082
  playbackEndpoints: array(string$2())
29975
30083
  });
29976
- /** Recording storage usage for one camera (bytes on disk across all its
29977
- * profiles/subtrees/locations on this node). */
30084
+ /**
30085
+ * Recording storage usage for one camera — what the ARCHIVE holds for it,
30086
+ * across every profile and every resolvable location on this node.
30087
+ *
30088
+ * ## Archive vs residency, which is the whole point of this shape (D418)
30089
+ *
30090
+ * `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
30091
+ * the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
30092
+ * this camera writes to, so the two can be added up and compared.
30093
+ *
30094
+ * They were not always. Until D418 this row came from the recorder's in-RAM
30095
+ * `RecordingIndex`, which since D287 holds only the live tail plus the hours
30096
+ * somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
30097
+ * 60.5 GB and "oldest: three hours ago" on the same page as the volume row
30098
+ * that said otherwise, and two people read it as retention eating recent
30099
+ * footage. Residency is still reported, under `residentBytes` /
30100
+ * `residentOldestMs`, which answer a different question and say so.
30101
+ */
29978
30102
  var RecordingDeviceUsageSchema = object({
29979
30103
  deviceId: number(),
30104
+ /** Bytes the ARCHIVE holds for this camera on this node's resolvable
30105
+ * recordings locations. */
29980
30106
  usedBytes: number(),
29981
30107
  /**
29982
- * Start of this camera's OLDEST indexed segment, across every profile and
30108
+ * Start of this camera's OLDEST ARCHIVED segment, across every profile and
29983
30109
  * location — the "Oldest footage" column in Recordings → Storage, and the
29984
30110
  * only honest answer to "is retention actually holding?" per camera.
29985
30111
  *
@@ -29989,7 +30115,24 @@ var RecordingDeviceUsageSchema = object({
29989
30115
  * (types) and the addon ship on different trains, and the addon is usually
29990
30116
  * the later of the two.
29991
30117
  */
29992
- oldestMs: number().nullable().optional()
30118
+ oldestMs: number().nullable().optional(),
30119
+ /** Segments the archive holds for this camera — the "M" beside `usedBytes`.
30120
+ * OPTIONAL for the same train-skew reason as `oldestMs`. */
30121
+ segments: number().optional(),
30122
+ /**
30123
+ * RESIDENCY, not the archive: bytes the recorder process currently holds in
30124
+ * its RAM index — the live tail plus the hours a reader hydrated.
30125
+ *
30126
+ * It answers "what can be served without touching the disk", never "how much
30127
+ * footage this camera has". A UI that renders it must label it as such, and
30128
+ * must never total it as storage. OPTIONAL: a recorder predating D418 omits
30129
+ * it, and so does a node whose ledger could not answer.
30130
+ */
30131
+ residentBytes: number().optional(),
30132
+ /** The residency counterpart of `oldestMs` — the oldest segment currently in
30133
+ * RAM, which on a healthy recorder is minutes ago and means nothing about
30134
+ * retention. `null` = nothing resident. */
30135
+ residentOldestMs: number().nullable().optional()
29993
30136
  });
29994
30137
  /** Recording storage usage + capacity for one storage location. */
29995
30138
  var RecordingLocationUsageSchema = object({
@@ -30027,6 +30170,17 @@ var RecordingStorageUsageSchema = object({
30027
30170
  totalUsedBytes: number(),
30028
30171
  devices: array(RecordingDeviceUsageSchema),
30029
30172
  /**
30173
+ * WHERE `devices[]` came from, so an empty table is never mistaken for empty
30174
+ * disks (D418).
30175
+ *
30176
+ * `archive` = the durable hour ledger answered. `unavailable` = this node
30177
+ * declared no ledger and the recorder REFUSED rather than answering the
30178
+ * question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
30179
+ * zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
30180
+ * and its rows are residency.
30181
+ */
30182
+ devicesRead: _enum(["archive", "unavailable"]).optional(),
30183
+ /**
30030
30184
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
30031
30185
  * of the payload without it: the hub serves this method through a BAKED
30032
30186
  * router, so a hub whose framework train predates a change to this array
@@ -33906,7 +34060,7 @@ function toDeviceSummary(device, addonId) {
33906
34060
  };
33907
34061
  }
33908
34062
  /**
33909
- * Base class for device-provider addons (rtsp, onvif, frigate).
34063
+ * Base class for device-provider addons (rtsp, onvif).
33910
34064
  *
33911
34065
  * Provides default implementations for the common device-provider cap
33912
34066
  * methods (`start`, `stop`, `getStatus`, `getDevices`, `supportsDiscovery`,
@@ -38562,6 +38716,24 @@ Object.freeze({
38562
38716
  addonId: null,
38563
38717
  access: "view"
38564
38718
  },
38719
+ "pipelineRunner.getParkedTrackFrame": {
38720
+ capName: "pipeline-runner",
38721
+ capScope: "system",
38722
+ addonId: null,
38723
+ access: "view"
38724
+ },
38725
+ "pipelineRunner.parkTrackFrame": {
38726
+ capName: "pipeline-runner",
38727
+ capScope: "system",
38728
+ addonId: null,
38729
+ access: "create"
38730
+ },
38731
+ "pipelineRunner.releaseParkedTrackFrames": {
38732
+ capName: "pipeline-runner",
38733
+ capScope: "system",
38734
+ addonId: null,
38735
+ access: "create"
38736
+ },
38565
38737
  "pipelineRunner.reportMotion": {
38566
38738
  capName: "pipeline-runner",
38567
38739
  capScope: "system",
@@ -41839,6 +42011,21 @@ Object.freeze({
41839
42011
  form: "single",
41840
42012
  optional: false
41841
42013
  }],
42014
+ "pipelineRunner.getParkedTrackFrame": [{
42015
+ name: "deviceId",
42016
+ form: "single",
42017
+ optional: false
42018
+ }],
42019
+ "pipelineRunner.parkTrackFrame": [{
42020
+ name: "deviceId",
42021
+ form: "single",
42022
+ optional: false
42023
+ }],
42024
+ "pipelineRunner.releaseParkedTrackFrames": [{
42025
+ name: "deviceId",
42026
+ form: "single",
42027
+ optional: false
42028
+ }],
41842
42029
  "pipelineRunner.reportMotion": [{
41843
42030
  name: "deviceId",
41844
42031
  form: "single",
@@ -42530,7 +42717,7 @@ var PreviewTextField = string$2().max(200);
42530
42717
  * Whether the notification's preview is a STILL or a short animation.
42531
42718
  *
42532
42719
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
42533
- * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
42720
+ * rule)"* — the other rules already offer an animated preview, and a ten-hour
42534
42721
  * night reads better as three seconds of motion than as one frame of it. Both
42535
42722
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
42536
42723
  * simply applies it to a dozen frames sampled across the render and assembles
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.89",
3
+ "version": "0.2.91",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",