@camstack/addon-model-studio 1.1.116 → 1.1.119

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 (21) hide show
  1. package/dist/{MotionZonesSettings-ONStonmD.mjs → MotionZonesSettings-C1B7ek0q.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-JjT72U5m.mjs → PrivacyMaskSettings-D5Bn4NKK.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-Bda9WHrf.mjs → SceneMonitorEditor-BhIufhCl.mjs} +3 -3
  4. package/dist/_stub.js +11 -11
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-yVuxM2oy.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-BBQxiWvp.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.js-CISC_XrP.mjs +25 -0
  7. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-B4jedMw4.mjs +26 -0
  8. package/dist/addon-model-studio.css +1 -1
  9. package/dist/{hostInit-BbyxT-51.mjs → hostInit-DiV4K66F.mjs} +3 -3
  10. package/dist/model-studio.addon.js +201 -14
  11. package/dist/model-studio.addon.mjs +201 -14
  12. package/dist/{player-overlays-DdeySlRR.mjs → player-overlays-0xs1Erjz.mjs} +1 -1
  13. package/dist/remoteEntry.js +1 -1
  14. package/dist/{responsive-CbWjxAoZ.mjs → responsive-DPVjfYqA.mjs} +1 -1
  15. package/dist/{square-COPzuHgN.mjs → square-Bd-ghxRh.mjs} +1 -1
  16. package/dist/{trash-2-CL_ANR0b.mjs → trash-2-BVelhdRT.mjs} +1 -1
  17. package/dist/{use-device-snapshot-TQqx6JFy.mjs → use-device-snapshot-Bw55QpDD.mjs} +1 -1
  18. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-vfKMEWga.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-B-64r32q.mjs} +1 -1
  19. package/package.json +1 -1
  20. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.js-ZNPquRb4.mjs +0 -25
  21. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-nsY-xdN2.mjs +0 -26
@@ -523,7 +523,7 @@ async function importScryptedCatalogModel(input, deps) {
523
523
  }
524
524
  }
525
525
  //#endregion
526
- //#region ../types/dist/event-category-CnLqLOKs.mjs
526
+ //#region ../types/dist/event-category-DAXzJeTX.mjs
527
527
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
528
528
  EventCategory["SystemBoot"] = "system.boot";
529
529
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1115,7 +1115,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1115
1115
  * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
1116
1116
  */
1117
1117
  EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
1118
- EventCategory["FrigateLiveEvent"] = "frigate.live-event";
1119
1118
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
1120
1119
  /**
1121
1120
  * Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
@@ -13146,7 +13145,7 @@ DeviceType.Camera;
13146
13145
  var SourceInfoSchema = object({
13147
13146
  /** Live dispatch key — mutable when the source system allows rename. */
13148
13147
  id: string(),
13149
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'frigate'. */
13148
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
13150
13149
  system: string(),
13151
13150
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
13152
13151
  * Used to detect rename when `id` changes. */
@@ -13633,7 +13632,7 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13633
13632
  * - `device-management.router.ts` (deleted in Phase 2)
13634
13633
  * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
13635
13634
  *
13636
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
13635
+ * All device provider addons (rtsp, onvif, …) are hub-local: they may
13637
13636
  * fork into separate processes but never run on remote cluster agents. Therefore:
13638
13637
  * - No nodeId routing needed — this is a pure hub singleton.
13639
13638
  * - The hub's DeviceRegistry is the single source of truth for all live devices.
@@ -17649,7 +17648,7 @@ var NcRuleInputSchema = object({
17649
17648
  * The measured seven-person arrival on device 590 spans 110 s with every
17650
17649
  * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
17651
17650
  * idle cutoff holds it as one and ends it when the arrival actually ends.
17652
- * 30 is Frigate's shipped value for the same decision.
17651
+ * 30 s is a widely shipped value for the same decision.
17653
17652
  *
17654
17653
  * ### What it replaces
17655
17654
  *
@@ -19691,6 +19690,13 @@ var ZoneCrossingSchema = object({
19691
19690
  /** Zone display name at crossing time (falls back to the id). */
19692
19691
  zoneName: string().optional()
19693
19692
  });
19693
+ /** One zone a box was stamped with, and how much of the BOX fell inside it. */
19694
+ var ZoneOverlapSchema = object({
19695
+ zoneId: string(),
19696
+ zoneName: string().optional(),
19697
+ /** Percentage of the BOX area inside the zone, 0–100. */
19698
+ overlapPct: number()
19699
+ });
19694
19700
  var ObjectEventSchema = object({
19695
19701
  ...BaseEventFields,
19696
19702
  kind: literal("object"),
@@ -19715,6 +19721,35 @@ var ObjectEventSchema = object({
19715
19721
  bbox: BoundingBoxSchema.optional(),
19716
19722
  /** Heavy JSON — omitted in slim projection. */
19717
19723
  zones: array(string()).readonly().optional(),
19724
+ /**
19725
+ * How far inside each stamped zone the box actually was, as a percentage of
19726
+ * the BOX area.
19727
+ *
19728
+ * Membership alone cannot express "at least a third of it inside", which is
19729
+ * the bar an operator argues with — and the number already existed: the
19730
+ * engine computed it, the pipeline logged it, and then discarded it. It
19731
+ * matters most on the events that carry no track, where a rule has nothing
19732
+ * else to judge by.
19733
+ */
19734
+ zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
19735
+ /**
19736
+ * Present when this event describes a detection the TRACKER refused, so it
19737
+ * has no track and never will.
19738
+ *
19739
+ * A zone-scoped immediate rule asks "is there an animal in the flowerbed
19740
+ * NOW?"; tracking answers a different question ("is this the same subject as
19741
+ * before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
19742
+ * dog at 0.7729, 100% inside a watched zone, was refused three times in six
19743
+ * seconds with three different candidate ids — seven analysed frames in the
19744
+ * window, the subject in a handful of them, metres apart — and produced no
19745
+ * event at all.
19746
+ *
19747
+ * It is NAMED rather than merely implied by a missing `trackId`: an event
19748
+ * with no track and no reason is indistinguishable from a track whose media
19749
+ * went missing, and a consumer that asks this one for a best shot or a
19750
+ * history is asking for something that never existed.
19751
+ */
19752
+ spawnRefused: string().optional(),
19718
19753
  /** Omitted in slim projection. */
19719
19754
  state: TrackStateSchema.optional(),
19720
19755
  /**
@@ -20788,6 +20823,60 @@ var NativeCropResultSchema = object({
20788
20823
  */
20789
20824
  tier: _enum(["native", "ram-fullframe"]).optional()
20790
20825
  });
20826
+ /**
20827
+ * What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
20828
+ * kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
20829
+ *
20830
+ * - `keyFrame` — the track's clean best FULL frame at native width (capped by
20831
+ * the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
20832
+ * from, and what the detail plane's full-frame rung cuts from.
20833
+ * - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
20834
+ * window, cut by the runner at uncapped native resolution.
20835
+ *
20836
+ * The runner never interprets `trackId`; it is an opaque key the hub owns.
20837
+ */
20838
+ var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
20839
+ /**
20840
+ * Why a park did NOT happen. Every one is logged on the runner with
20841
+ * `tags: { deviceId }` and the handle's measured age, because a handle already
20842
+ * gone at park time IS the defect the 150–500 ms window exists to prevent —
20843
+ * never a reason to retry in a loop.
20844
+ *
20845
+ * - `handle-gone` — the native surface no longer resolves this handle (never
20846
+ * registered, past the lease bound, or the worker released it). `detail`
20847
+ * carries the runner's own miss reason.
20848
+ * - `encode-failed` — pixels resolved and the JPEG encode threw.
20849
+ */
20850
+ var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
20851
+ /** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
20852
+ var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
20853
+ parked: literal(true),
20854
+ /** The node holding the parcel — what a later retrieval must be pinned to. */
20855
+ nodeId: string(),
20856
+ width: number().int().positive(),
20857
+ height: number().int().positive(),
20858
+ /** JPEG bytes the parcel costs on the node. */
20859
+ bytes: number().int().nonnegative(),
20860
+ /** Whether this park replaced a parcel already held for the key. */
20861
+ replaced: boolean()
20862
+ }), object({
20863
+ parked: literal(false),
20864
+ nodeId: string(),
20865
+ reason: ParkRefusalSchema,
20866
+ /** Age of the handle at the park, on the runner's clock; null if unknown. */
20867
+ handleAgeMs: number().nullable(),
20868
+ detail: string().optional()
20869
+ })]);
20870
+ /** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
20871
+ var ParkedTrackFrameSchema = object({
20872
+ jpeg: string(),
20873
+ width: number().int().positive(),
20874
+ height: number().int().positive(),
20875
+ /** The frame instant the parcel shows (the caller's clock, echoed back). */
20876
+ timestamp: number(),
20877
+ /** When the node stored it (the node's clock). */
20878
+ parkedAt: number()
20879
+ });
20791
20880
  /** Parent detection context passed to `runDetailSubtree` — the crop's
20792
20881
  * originating detection, in FRAME-space coordinates. Reuses
20793
20882
  * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
@@ -21177,7 +21266,7 @@ var RunnerCameraConfigSchema = object({
21177
21266
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
21178
21267
  * analyzer providers) running on decoded frames in this runner.
21179
21268
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
21180
- * analytics, Frigate motion). The provider emits ProviderMotion
21269
+ * analytics). The provider emits ProviderMotion
21181
21270
  * events; the orchestrator forwards to `reportMotion`.
21182
21271
  */
21183
21272
  motionSources: MotionSourcesSchema.default(["analyzer"]),
@@ -21419,7 +21508,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
21419
21508
  className: string(),
21420
21509
  /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
21421
21510
  sourceDeviceId: number()
21422
- }), RunStatelessStepResultSchema, { kind: "mutation" });
21511
+ }), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
21512
+ deviceId: number(),
21513
+ /** Opaque to the runner — the hub's track id. */
21514
+ trackId: string(),
21515
+ kind: ParkedFrameKindSchema,
21516
+ handle: FrameHandleSchema,
21517
+ /** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
21518
+ bbox: NativeCropBboxSchema,
21519
+ /** Width cap for a full-frame park (the `keyFrame` native cap). */
21520
+ maxWidth: number().int().positive().optional(),
21521
+ /** The frame instant (caller's clock) — orders parks for one key. */
21522
+ timestamp: number()
21523
+ }), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
21524
+ deviceId: number(),
21525
+ trackId: string(),
21526
+ kind: ParkedFrameKindSchema
21527
+ }), ParkedTrackFrameSchema.nullable()), method(object({
21528
+ deviceId: number(),
21529
+ trackId: string()
21530
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" });
21423
21531
  var CameraPipelineConfigSchema = object({
21424
21532
  engine: PipelineEngineChoiceSchema.optional(),
21425
21533
  steps: array(PipelineStepInputSchema).readonly(),
@@ -24348,7 +24456,7 @@ DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _v
24348
24456
  *
24349
24457
  * Implementations:
24350
24458
  * - Each camera-providing addon registers this cap natively per
24351
- * device (Reolink, Hikvision, RTSP, Frigate, ONVIF self).
24459
+ * device (Reolink, Hikvision, RTSP, ONVIF self).
24352
24460
  * - Returns a snapshot from the persisted device config blob — no
24353
24461
  * extra round-trip to the camera, no probing.
24354
24462
  * - The cap is admin-only (`auth: 'admin'`) — credentials must not
@@ -25734,7 +25842,7 @@ var AvailableIntegrationTypeSchema = object({
25734
25842
  * Which integration-marker cap the addon declared, so the wizard can
25735
25843
  * branch on CAP — never on addon name. `device-adoption` integrations
25736
25844
  * (Home Assistant, …) route through the broker step (Approach A);
25737
- * `device-provider` integrations (Reolink/Frigate/ONVIF) keep the
25845
+ * `device-provider` integrations (Reolink/ONVIF) keep the
25738
25846
  * legacy config → discovery flow.
25739
25847
  */
25740
25848
  kind: _enum(["device-adoption", "device-provider"]),
@@ -28291,13 +28399,31 @@ var RecordingManifestSchema = object({
28291
28399
  */
28292
28400
  playbackEndpoints: array(string())
28293
28401
  });
28294
- /** Recording storage usage for one camera (bytes on disk across all its
28295
- * profiles/subtrees/locations on this node). */
28402
+ /**
28403
+ * Recording storage usage for one camera — what the ARCHIVE holds for it,
28404
+ * across every profile and every resolvable location on this node.
28405
+ *
28406
+ * ## Archive vs residency, which is the whole point of this shape (D418)
28407
+ *
28408
+ * `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
28409
+ * the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
28410
+ * this camera writes to, so the two can be added up and compared.
28411
+ *
28412
+ * They were not always. Until D418 this row came from the recorder's in-RAM
28413
+ * `RecordingIndex`, which since D287 holds only the live tail plus the hours
28414
+ * somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
28415
+ * 60.5 GB and "oldest: three hours ago" on the same page as the volume row
28416
+ * that said otherwise, and two people read it as retention eating recent
28417
+ * footage. Residency is still reported, under `residentBytes` /
28418
+ * `residentOldestMs`, which answer a different question and say so.
28419
+ */
28296
28420
  var RecordingDeviceUsageSchema = object({
28297
28421
  deviceId: number(),
28422
+ /** Bytes the ARCHIVE holds for this camera on this node's resolvable
28423
+ * recordings locations. */
28298
28424
  usedBytes: number(),
28299
28425
  /**
28300
- * Start of this camera's OLDEST indexed segment, across every profile and
28426
+ * Start of this camera's OLDEST ARCHIVED segment, across every profile and
28301
28427
  * location — the "Oldest footage" column in Recordings → Storage, and the
28302
28428
  * only honest answer to "is retention actually holding?" per camera.
28303
28429
  *
@@ -28307,7 +28433,24 @@ var RecordingDeviceUsageSchema = object({
28307
28433
  * (types) and the addon ship on different trains, and the addon is usually
28308
28434
  * the later of the two.
28309
28435
  */
28310
- oldestMs: number().nullable().optional()
28436
+ oldestMs: number().nullable().optional(),
28437
+ /** Segments the archive holds for this camera — the "M" beside `usedBytes`.
28438
+ * OPTIONAL for the same train-skew reason as `oldestMs`. */
28439
+ segments: number().optional(),
28440
+ /**
28441
+ * RESIDENCY, not the archive: bytes the recorder process currently holds in
28442
+ * its RAM index — the live tail plus the hours a reader hydrated.
28443
+ *
28444
+ * It answers "what can be served without touching the disk", never "how much
28445
+ * footage this camera has". A UI that renders it must label it as such, and
28446
+ * must never total it as storage. OPTIONAL: a recorder predating D418 omits
28447
+ * it, and so does a node whose ledger could not answer.
28448
+ */
28449
+ residentBytes: number().optional(),
28450
+ /** The residency counterpart of `oldestMs` — the oldest segment currently in
28451
+ * RAM, which on a healthy recorder is minutes ago and means nothing about
28452
+ * retention. `null` = nothing resident. */
28453
+ residentOldestMs: number().nullable().optional()
28311
28454
  });
28312
28455
  /** Recording storage usage + capacity for one storage location. */
28313
28456
  var RecordingLocationUsageSchema = object({
@@ -28345,6 +28488,17 @@ var RecordingStorageUsageSchema = object({
28345
28488
  totalUsedBytes: number(),
28346
28489
  devices: array(RecordingDeviceUsageSchema),
28347
28490
  /**
28491
+ * WHERE `devices[]` came from, so an empty table is never mistaken for empty
28492
+ * disks (D418).
28493
+ *
28494
+ * `archive` = the durable hour ledger answered. `unavailable` = this node
28495
+ * declared no ledger and the recorder REFUSED rather than answering the
28496
+ * question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
28497
+ * zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
28498
+ * and its rows are residency.
28499
+ */
28500
+ devicesRead: _enum(["archive", "unavailable"]).optional(),
28501
+ /**
28348
28502
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
28349
28503
  * of the payload without it: the hub serves this method through a BAKED
28350
28504
  * router, so a hub whose framework train predates a change to this array
@@ -34732,6 +34886,24 @@ Object.freeze({
34732
34886
  addonId: null,
34733
34887
  access: "view"
34734
34888
  },
34889
+ "pipelineRunner.getParkedTrackFrame": {
34890
+ capName: "pipeline-runner",
34891
+ capScope: "system",
34892
+ addonId: null,
34893
+ access: "view"
34894
+ },
34895
+ "pipelineRunner.parkTrackFrame": {
34896
+ capName: "pipeline-runner",
34897
+ capScope: "system",
34898
+ addonId: null,
34899
+ access: "create"
34900
+ },
34901
+ "pipelineRunner.releaseParkedTrackFrames": {
34902
+ capName: "pipeline-runner",
34903
+ capScope: "system",
34904
+ addonId: null,
34905
+ access: "create"
34906
+ },
34735
34907
  "pipelineRunner.reportMotion": {
34736
34908
  capName: "pipeline-runner",
34737
34909
  capScope: "system",
@@ -38009,6 +38181,21 @@ Object.freeze({
38009
38181
  form: "single",
38010
38182
  optional: false
38011
38183
  }],
38184
+ "pipelineRunner.getParkedTrackFrame": [{
38185
+ name: "deviceId",
38186
+ form: "single",
38187
+ optional: false
38188
+ }],
38189
+ "pipelineRunner.parkTrackFrame": [{
38190
+ name: "deviceId",
38191
+ form: "single",
38192
+ optional: false
38193
+ }],
38194
+ "pipelineRunner.releaseParkedTrackFrames": [{
38195
+ name: "deviceId",
38196
+ form: "single",
38197
+ optional: false
38198
+ }],
38012
38199
  "pipelineRunner.reportMotion": [{
38013
38200
  name: "deviceId",
38014
38201
  form: "single",
@@ -38700,7 +38887,7 @@ var PreviewTextField = string().max(200);
38700
38887
  * Whether the notification's preview is a STILL or a short animation.
38701
38888
  *
38702
38889
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
38703
- * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
38890
+ * rule)"* — the other rules already offer an animated preview, and a ten-hour
38704
38891
  * night reads better as three seconds of motion than as one frame of it. Both
38705
38892
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
38706
38893
  * simply applies it to a dozen frames sampled across the render and assembles
@@ -502,7 +502,7 @@ async function importScryptedCatalogModel(input, deps) {
502
502
  }
503
503
  }
504
504
  //#endregion
505
- //#region ../types/dist/event-category-CnLqLOKs.mjs
505
+ //#region ../types/dist/event-category-DAXzJeTX.mjs
506
506
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
507
507
  EventCategory["SystemBoot"] = "system.boot";
508
508
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1094,7 +1094,6 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1094
1094
  * issue. Payload `PipelineAnalyticsPlateGalleryChangedPayload`.
1095
1095
  */
1096
1096
  EventCategory["PipelineAnalyticsPlateGalleryChanged"] = "pipeline-analytics.plate-gallery-changed";
1097
- EventCategory["FrigateLiveEvent"] = "frigate.live-event";
1098
1097
  EventCategory["CameraStreamsProfileSlotsChanged"] = "camera-streams.onProfileSlotsChanged";
1099
1098
  /**
1100
1099
  * Stream-broker health watchdog. Per-broker (deviceId/profile) emission.
@@ -13125,7 +13124,7 @@ DeviceType.Camera;
13125
13124
  var SourceInfoSchema = object({
13126
13125
  /** Live dispatch key — mutable when the source system allows rename. */
13127
13126
  id: string(),
13128
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'frigate'. */
13127
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
13129
13128
  system: string(),
13130
13129
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
13131
13130
  * Used to detect rename when `id` changes. */
@@ -13612,7 +13611,7 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
13612
13611
  * - `device-management.router.ts` (deleted in Phase 2)
13613
13612
  * - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
13614
13613
  *
13615
- * All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
13614
+ * All device provider addons (rtsp, onvif, …) are hub-local: they may
13616
13615
  * fork into separate processes but never run on remote cluster agents. Therefore:
13617
13616
  * - No nodeId routing needed — this is a pure hub singleton.
13618
13617
  * - The hub's DeviceRegistry is the single source of truth for all live devices.
@@ -17628,7 +17627,7 @@ var NcRuleInputSchema = object({
17628
17627
  * The measured seven-person arrival on device 590 spans 110 s with every
17629
17628
  * internal gap under 30 s. A 12 s fixed window cuts it into three groups; an
17630
17629
  * idle cutoff holds it as one and ends it when the arrival actually ends.
17631
- * 30 is Frigate's shipped value for the same decision.
17630
+ * 30 s is a widely shipped value for the same decision.
17632
17631
  *
17633
17632
  * ### What it replaces
17634
17633
  *
@@ -19670,6 +19669,13 @@ var ZoneCrossingSchema = object({
19670
19669
  /** Zone display name at crossing time (falls back to the id). */
19671
19670
  zoneName: string().optional()
19672
19671
  });
19672
+ /** One zone a box was stamped with, and how much of the BOX fell inside it. */
19673
+ var ZoneOverlapSchema = object({
19674
+ zoneId: string(),
19675
+ zoneName: string().optional(),
19676
+ /** Percentage of the BOX area inside the zone, 0–100. */
19677
+ overlapPct: number()
19678
+ });
19673
19679
  var ObjectEventSchema = object({
19674
19680
  ...BaseEventFields,
19675
19681
  kind: literal("object"),
@@ -19694,6 +19700,35 @@ var ObjectEventSchema = object({
19694
19700
  bbox: BoundingBoxSchema.optional(),
19695
19701
  /** Heavy JSON — omitted in slim projection. */
19696
19702
  zones: array(string()).readonly().optional(),
19703
+ /**
19704
+ * How far inside each stamped zone the box actually was, as a percentage of
19705
+ * the BOX area.
19706
+ *
19707
+ * Membership alone cannot express "at least a third of it inside", which is
19708
+ * the bar an operator argues with — and the number already existed: the
19709
+ * engine computed it, the pipeline logged it, and then discarded it. It
19710
+ * matters most on the events that carry no track, where a rule has nothing
19711
+ * else to judge by.
19712
+ */
19713
+ zoneOverlaps: array(ZoneOverlapSchema).readonly().optional(),
19714
+ /**
19715
+ * Present when this event describes a detection the TRACKER refused, so it
19716
+ * has no track and never will.
19717
+ *
19718
+ * A zone-scoped immediate rule asks "is there an animal in the flowerbed
19719
+ * NOW?"; tracking answers a different question ("is this the same subject as
19720
+ * before?") whose confirmation threshold presumes continuity. On 2026-09-09 a
19721
+ * dog at 0.7729, 100% inside a watched zone, was refused three times in six
19722
+ * seconds with three different candidate ids — seven analysed frames in the
19723
+ * window, the subject in a handful of them, metres apart — and produced no
19724
+ * event at all.
19725
+ *
19726
+ * It is NAMED rather than merely implied by a missing `trackId`: an event
19727
+ * with no track and no reason is indistinguishable from a track whose media
19728
+ * went missing, and a consumer that asks this one for a best shot or a
19729
+ * history is asking for something that never existed.
19730
+ */
19731
+ spawnRefused: string().optional(),
19697
19732
  /** Omitted in slim projection. */
19698
19733
  state: TrackStateSchema.optional(),
19699
19734
  /**
@@ -20767,6 +20802,60 @@ var NativeCropResultSchema = object({
20767
20802
  */
20768
20803
  tier: _enum(["native", "ram-fullframe"]).optional()
20769
20804
  });
20805
+ /**
20806
+ * What a PARKED frame is for — the `kind` half of the `(deviceId, trackId,
20807
+ * kind)` key ([D422](../../../docs/decisions/adr-0422-a-relevant-frame-is-parked-when-it-becomes-relevant.md)).
20808
+ *
20809
+ * - `keyFrame` — the track's clean best FULL frame at native width (capped by
20810
+ * the caller's `maxWidth`). What `keyFrame` + `keyFrameSmall` are written
20811
+ * from, and what the detail plane's full-frame rung cuts from.
20812
+ * - `thumbnail` — the best-shot subject slab: the caller's 16:9 central-square
20813
+ * window, cut by the runner at uncapped native resolution.
20814
+ *
20815
+ * The runner never interprets `trackId`; it is an opaque key the hub owns.
20816
+ */
20817
+ var ParkedFrameKindSchema = _enum(["keyFrame", "thumbnail"]);
20818
+ /**
20819
+ * Why a park did NOT happen. Every one is logged on the runner with
20820
+ * `tags: { deviceId }` and the handle's measured age, because a handle already
20821
+ * gone at park time IS the defect the 150–500 ms window exists to prevent —
20822
+ * never a reason to retry in a loop.
20823
+ *
20824
+ * - `handle-gone` — the native surface no longer resolves this handle (never
20825
+ * registered, past the lease bound, or the worker released it). `detail`
20826
+ * carries the runner's own miss reason.
20827
+ * - `encode-failed` — pixels resolved and the JPEG encode threw.
20828
+ */
20829
+ var ParkRefusalSchema = _enum(["handle-gone", "encode-failed"]);
20830
+ /** Answer to `parkTrackFrame` — a discriminated union, so "not parked" always names why. */
20831
+ var ParkTrackFrameResultSchema = discriminatedUnion("parked", [object({
20832
+ parked: literal(true),
20833
+ /** The node holding the parcel — what a later retrieval must be pinned to. */
20834
+ nodeId: string(),
20835
+ width: number().int().positive(),
20836
+ height: number().int().positive(),
20837
+ /** JPEG bytes the parcel costs on the node. */
20838
+ bytes: number().int().nonnegative(),
20839
+ /** Whether this park replaced a parcel already held for the key. */
20840
+ replaced: boolean()
20841
+ }), object({
20842
+ parked: literal(false),
20843
+ nodeId: string(),
20844
+ reason: ParkRefusalSchema,
20845
+ /** Age of the handle at the park, on the runner's clock; null if unknown. */
20846
+ handleAgeMs: number().nullable(),
20847
+ detail: string().optional()
20848
+ })]);
20849
+ /** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
20850
+ var ParkedTrackFrameSchema = object({
20851
+ jpeg: string(),
20852
+ width: number().int().positive(),
20853
+ height: number().int().positive(),
20854
+ /** The frame instant the parcel shows (the caller's clock, echoed back). */
20855
+ timestamp: number(),
20856
+ /** When the node stored it (the node's clock). */
20857
+ parkedAt: number()
20858
+ });
20770
20859
  /** Parent detection context passed to `runDetailSubtree` — the crop's
20771
20860
  * originating detection, in FRAME-space coordinates. Reuses
20772
20861
  * `NativeCropBboxSchema`'s `{x,y,w,h}` shape (same numeric fields; here
@@ -21156,7 +21245,7 @@ var RunnerCameraConfigSchema = object({
21156
21245
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
21157
21246
  * analyzer providers) running on decoded frames in this runner.
21158
21247
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
21159
- * analytics, Frigate motion). The provider emits ProviderMotion
21248
+ * analytics). The provider emits ProviderMotion
21160
21249
  * events; the orchestrator forwards to `reportMotion`.
21161
21250
  */
21162
21251
  motionSources: MotionSourcesSchema.default(["analyzer"]),
@@ -21398,7 +21487,26 @@ method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mu
21398
21487
  className: string(),
21399
21488
  /** Camera the pixels came from. Diagnostics + log tags ONLY — never routing. */
21400
21489
  sourceDeviceId: number()
21401
- }), RunStatelessStepResultSchema, { kind: "mutation" });
21490
+ }), RunStatelessStepResultSchema, { kind: "mutation" }), method(object({
21491
+ deviceId: number(),
21492
+ /** Opaque to the runner — the hub's track id. */
21493
+ trackId: string(),
21494
+ kind: ParkedFrameKindSchema,
21495
+ handle: FrameHandleSchema,
21496
+ /** Normalized [0,1] window to cut. `{0,0,1,1}` for a full frame. */
21497
+ bbox: NativeCropBboxSchema,
21498
+ /** Width cap for a full-frame park (the `keyFrame` native cap). */
21499
+ maxWidth: number().int().positive().optional(),
21500
+ /** The frame instant (caller's clock) — orders parks for one key. */
21501
+ timestamp: number()
21502
+ }), ParkTrackFrameResultSchema, { kind: "mutation" }), method(object({
21503
+ deviceId: number(),
21504
+ trackId: string(),
21505
+ kind: ParkedFrameKindSchema
21506
+ }), ParkedTrackFrameSchema.nullable()), method(object({
21507
+ deviceId: number(),
21508
+ trackId: string()
21509
+ }), object({ released: number().int().nonnegative() }), { kind: "mutation" });
21402
21510
  var CameraPipelineConfigSchema = object({
21403
21511
  engine: PipelineEngineChoiceSchema.optional(),
21404
21512
  steps: array(PipelineStepInputSchema).readonly(),
@@ -24327,7 +24435,7 @@ DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _v
24327
24435
  *
24328
24436
  * Implementations:
24329
24437
  * - Each camera-providing addon registers this cap natively per
24330
- * device (Reolink, Hikvision, RTSP, Frigate, ONVIF self).
24438
+ * device (Reolink, Hikvision, RTSP, ONVIF self).
24331
24439
  * - Returns a snapshot from the persisted device config blob — no
24332
24440
  * extra round-trip to the camera, no probing.
24333
24441
  * - The cap is admin-only (`auth: 'admin'`) — credentials must not
@@ -25713,7 +25821,7 @@ var AvailableIntegrationTypeSchema = object({
25713
25821
  * Which integration-marker cap the addon declared, so the wizard can
25714
25822
  * branch on CAP — never on addon name. `device-adoption` integrations
25715
25823
  * (Home Assistant, …) route through the broker step (Approach A);
25716
- * `device-provider` integrations (Reolink/Frigate/ONVIF) keep the
25824
+ * `device-provider` integrations (Reolink/ONVIF) keep the
25717
25825
  * legacy config → discovery flow.
25718
25826
  */
25719
25827
  kind: _enum(["device-adoption", "device-provider"]),
@@ -28270,13 +28378,31 @@ var RecordingManifestSchema = object({
28270
28378
  */
28271
28379
  playbackEndpoints: array(string())
28272
28380
  });
28273
- /** Recording storage usage for one camera (bytes on disk across all its
28274
- * profiles/subtrees/locations on this node). */
28381
+ /**
28382
+ * Recording storage usage for one camera — what the ARCHIVE holds for it,
28383
+ * across every profile and every resolvable location on this node.
28384
+ *
28385
+ * ## Archive vs residency, which is the whole point of this shape (D418)
28386
+ *
28387
+ * `usedBytes` / `oldestMs` / `segments` are the durable hour ledger's numbers:
28388
+ * the same quantity `RecordingLocationUsage.usedBytes` reports for the volume
28389
+ * this camera writes to, so the two can be added up and compared.
28390
+ *
28391
+ * They were not always. Until D418 this row came from the recorder's in-RAM
28392
+ * `RecordingIndex`, which since D287 holds only the live tail plus the hours
28393
+ * somebody hydrated — so a hub with 1.9 TB and 3.3 days of footage reported
28394
+ * 60.5 GB and "oldest: three hours ago" on the same page as the volume row
28395
+ * that said otherwise, and two people read it as retention eating recent
28396
+ * footage. Residency is still reported, under `residentBytes` /
28397
+ * `residentOldestMs`, which answer a different question and say so.
28398
+ */
28275
28399
  var RecordingDeviceUsageSchema = object({
28276
28400
  deviceId: number(),
28401
+ /** Bytes the ARCHIVE holds for this camera on this node's resolvable
28402
+ * recordings locations. */
28277
28403
  usedBytes: number(),
28278
28404
  /**
28279
- * Start of this camera's OLDEST indexed segment, across every profile and
28405
+ * Start of this camera's OLDEST ARCHIVED segment, across every profile and
28280
28406
  * location — the "Oldest footage" column in Recordings → Storage, and the
28281
28407
  * only honest answer to "is retention actually holding?" per camera.
28282
28408
  *
@@ -28286,7 +28412,24 @@ var RecordingDeviceUsageSchema = object({
28286
28412
  * (types) and the addon ship on different trains, and the addon is usually
28287
28413
  * the later of the two.
28288
28414
  */
28289
- oldestMs: number().nullable().optional()
28415
+ oldestMs: number().nullable().optional(),
28416
+ /** Segments the archive holds for this camera — the "M" beside `usedBytes`.
28417
+ * OPTIONAL for the same train-skew reason as `oldestMs`. */
28418
+ segments: number().optional(),
28419
+ /**
28420
+ * RESIDENCY, not the archive: bytes the recorder process currently holds in
28421
+ * its RAM index — the live tail plus the hours a reader hydrated.
28422
+ *
28423
+ * It answers "what can be served without touching the disk", never "how much
28424
+ * footage this camera has". A UI that renders it must label it as such, and
28425
+ * must never total it as storage. OPTIONAL: a recorder predating D418 omits
28426
+ * it, and so does a node whose ledger could not answer.
28427
+ */
28428
+ residentBytes: number().optional(),
28429
+ /** The residency counterpart of `oldestMs` — the oldest segment currently in
28430
+ * RAM, which on a healthy recorder is minutes ago and means nothing about
28431
+ * retention. `null` = nothing resident. */
28432
+ residentOldestMs: number().nullable().optional()
28290
28433
  });
28291
28434
  /** Recording storage usage + capacity for one storage location. */
28292
28435
  var RecordingLocationUsageSchema = object({
@@ -28324,6 +28467,17 @@ var RecordingStorageUsageSchema = object({
28324
28467
  totalUsedBytes: number(),
28325
28468
  devices: array(RecordingDeviceUsageSchema),
28326
28469
  /**
28470
+ * WHERE `devices[]` came from, so an empty table is never mistaken for empty
28471
+ * disks (D418).
28472
+ *
28473
+ * `archive` = the durable hour ledger answered. `unavailable` = this node
28474
+ * declared no ledger and the recorder REFUSED rather than answering the
28475
+ * question with the resident index (D295 rule 3) — the rows are UNKNOWN, not
28476
+ * zero, and a UI must say so. OPTIONAL: a recorder predating D418 omits it,
28477
+ * and its rows are residency.
28478
+ */
28479
+ devicesRead: _enum(["archive", "unavailable"]).optional(),
28480
+ /**
28327
28481
  * Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
28328
28482
  * of the payload without it: the hub serves this method through a BAKED
28329
28483
  * router, so a hub whose framework train predates a change to this array
@@ -34711,6 +34865,24 @@ Object.freeze({
34711
34865
  addonId: null,
34712
34866
  access: "view"
34713
34867
  },
34868
+ "pipelineRunner.getParkedTrackFrame": {
34869
+ capName: "pipeline-runner",
34870
+ capScope: "system",
34871
+ addonId: null,
34872
+ access: "view"
34873
+ },
34874
+ "pipelineRunner.parkTrackFrame": {
34875
+ capName: "pipeline-runner",
34876
+ capScope: "system",
34877
+ addonId: null,
34878
+ access: "create"
34879
+ },
34880
+ "pipelineRunner.releaseParkedTrackFrames": {
34881
+ capName: "pipeline-runner",
34882
+ capScope: "system",
34883
+ addonId: null,
34884
+ access: "create"
34885
+ },
34714
34886
  "pipelineRunner.reportMotion": {
34715
34887
  capName: "pipeline-runner",
34716
34888
  capScope: "system",
@@ -37988,6 +38160,21 @@ Object.freeze({
37988
38160
  form: "single",
37989
38161
  optional: false
37990
38162
  }],
38163
+ "pipelineRunner.getParkedTrackFrame": [{
38164
+ name: "deviceId",
38165
+ form: "single",
38166
+ optional: false
38167
+ }],
38168
+ "pipelineRunner.parkTrackFrame": [{
38169
+ name: "deviceId",
38170
+ form: "single",
38171
+ optional: false
38172
+ }],
38173
+ "pipelineRunner.releaseParkedTrackFrames": [{
38174
+ name: "deviceId",
38175
+ form: "single",
38176
+ optional: false
38177
+ }],
37991
38178
  "pipelineRunner.reportMotion": [{
37992
38179
  name: "deviceId",
37993
38180
  form: "single",
@@ -38679,7 +38866,7 @@ var PreviewTextField = string().max(200);
38679
38866
  * Whether the notification's preview is a STILL or a short animation.
38680
38867
  *
38681
38868
  * The operator's ask, verbatim: *"inviato come gif o video (come per le altre
38682
- * rule)"* — his Scrypted advanced-notifier has a `gifRule`, and a ten-hour
38869
+ * rule)"* — the other rules already offer an animated preview, and a ten-hour
38683
38870
  * night reads better as three seconds of motion than as one frame of it. Both
38684
38871
  * modes get the SAME treatment (blurred frame, large centred title); `'gif'`
38685
38872
  * simply applies it to a dozen frames sampled across the render and assembles