camstack 1.2.67 → 1.2.69

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-Keu5TDO7.js
23637
- var require_dist_Keu5TDO7 = __commonJS({
23638
- "../system/dist/dist-Keu5TDO7.js"(exports) {
23636
+ // ../system/dist/dist-B29Skpzo.js
23637
+ var require_dist_B29Skpzo = __commonJS({
23638
+ "../system/dist/dist-B29Skpzo.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -26274,6 +26274,20 @@ var require_dist_Keu5TDO7 = __commonJS({
26274
26274
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
26275
26275
  */
26276
26276
  rowsReconciled: zod.z.number().int().nonnegative().optional(),
26277
+ /**
26278
+ * Rows this run FORGOT because the file they name is not on disk.
26279
+ *
26280
+ * The mover derived the path from the row's own fields and `stat`ed it; an
26281
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
26282
+ * and the durable row is dropped through the same channel eviction uses. It
26283
+ * is reported for the same reason `rowsReconciled` is: this is a durable
26284
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
26285
+ * the same failure as one that quietly skips them (D295).
26286
+ *
26287
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
26288
+ * ledger claimed 5.65 GB of footage that no longer existed.
26289
+ */
26290
+ rowsForgotten: zod.z.number().int().nonnegative().optional(),
26277
26291
  startedAt: zod.z.number(),
26278
26292
  finishedAt: zod.z.number().nullable(),
26279
26293
  error: zod.z.string().nullable()
@@ -26481,6 +26495,71 @@ var require_dist_Keu5TDO7 = __commonJS({
26481
26495
  segments: zod.z.number().int().nonnegative(),
26482
26496
  bytes: zod.z.number().int().nonnegative()
26483
26497
  }).nullable();
26498
+ var LedgerWalkInputSchema = zod.z.object({
26499
+ locationId: zod.z.string().min(1),
26500
+ /** Forget the confirmed-absent rows, rather than only counting them. */
26501
+ apply: zod.z.boolean().optional(),
26502
+ /** Narrow to one camera. */
26503
+ deviceId: zod.z.number().int().positive().optional(),
26504
+ /** Narrow to these recording profiles; empty/absent = every profile. */
26505
+ profiles: zod.z.array(zod.z.string().min(1)).optional()
26506
+ });
26507
+ var LedgerWalkRefusalSchema = zod.z.enum([
26508
+ "location-unknown",
26509
+ "source-writable",
26510
+ "no-ledger",
26511
+ "archive-unreadable",
26512
+ "anchor-absent",
26513
+ "anchor-unreadable",
26514
+ "anchor-moved"
26515
+ ]);
26516
+ zod.z.enum([
26517
+ "live-tail",
26518
+ "listing-error",
26519
+ "path-mismatch",
26520
+ "durable-refused"
26521
+ ]);
26522
+ var LedgerWalkSkipCountsSchema = zod.z.object({
26523
+ "live-tail": zod.z.number().int().nonnegative(),
26524
+ "listing-error": zod.z.number().int().nonnegative(),
26525
+ "path-mismatch": zod.z.number().int().nonnegative(),
26526
+ "durable-refused": zod.z.number().int().nonnegative()
26527
+ });
26528
+ var LedgerWalkDeviceReportSchema = zod.z.object({
26529
+ deviceId: zod.z.number().int(),
26530
+ hoursWalked: zod.z.number().int().nonnegative(),
26531
+ hoursMissing: zod.z.number().int().nonnegative(),
26532
+ ghostSegments: zod.z.number().int().nonnegative(),
26533
+ ghostBytes: zod.z.number().int().nonnegative(),
26534
+ forgottenSegments: zod.z.number().int().nonnegative(),
26535
+ orphanFiles: zod.z.number().int().nonnegative()
26536
+ });
26537
+ var LedgerWalkReportSchema = zod.z.object({
26538
+ locationId: zod.z.string(),
26539
+ applied: zod.z.boolean(),
26540
+ refused: LedgerWalkRefusalSchema.nullable(),
26541
+ archiveSegments: zod.z.number().int().nonnegative().nullable(),
26542
+ archiveBytes: zod.z.number().int().nonnegative().nullable(),
26543
+ hoursClaimed: zod.z.number().int().nonnegative(),
26544
+ hoursWalked: zod.z.number().int().nonnegative(),
26545
+ hoursMissing: zod.z.number().int().nonnegative(),
26546
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
26547
+ listings: zod.z.number().int().nonnegative(),
26548
+ segmentsClaimed: zod.z.number().int().nonnegative(),
26549
+ ghostSegments: zod.z.number().int().nonnegative(),
26550
+ ghostBytes: zod.z.number().int().nonnegative(),
26551
+ ghostHoursWhole: zod.z.number().int().nonnegative(),
26552
+ forgottenSegments: zod.z.number().int().nonnegative(),
26553
+ forgottenBytes: zod.z.number().int().nonnegative(),
26554
+ /** Files under a claimed hour that no durable row names. Never deleted. */
26555
+ orphanFiles: zod.z.number().int().nonnegative(),
26556
+ orphanSample: zod.z.array(zod.z.string()).readonly(),
26557
+ hoursSkipped: zod.z.number().int().nonnegative(),
26558
+ skippedByReason: LedgerWalkSkipCountsSchema,
26559
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
26560
+ bounded: zod.z.boolean(),
26561
+ byDevice: zod.z.array(LedgerWalkDeviceReportSchema).readonly()
26562
+ });
26484
26563
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
26485
26564
  var RelocatableMediaCountInputSchema = zod.z.object({
26486
26565
  toLocationId: zod.z.string().min(1),
@@ -36998,13 +37077,15 @@ var require_dist_Keu5TDO7 = __commonJS({
36998
37077
  groups: zod.z.array(AnalyticsGroupRecordSchema).readonly(),
36999
37078
  nextCursor: zod.z.string().nullable()
37000
37079
  });
37080
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
37081
+ var KEY_EVENTS_MAX_LIMIT = 200;
37001
37082
  var KeyEventQueryInput = zod.z.object({
37002
37083
  deviceId: zod.z.number(),
37003
37084
  /** Window lower bound (track firstSeen ≥ since). */
37004
37085
  since: zod.z.number(),
37005
37086
  /** Window upper bound (track firstSeen ≤ until). */
37006
37087
  until: zod.z.number(),
37007
- limit: zod.z.number().int().min(1).max(200).default(50),
37088
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
37008
37089
  /** Drop tracks scoring below this importance. */
37009
37090
  minImportance: zod.z.number().min(0).max(1).optional(),
37010
37091
  /** Restrict to a single class (e.g. 'person'). */
@@ -37026,6 +37107,21 @@ var require_dist_Keu5TDO7 = __commonJS({
37026
37107
  ...TrackFlagFields,
37027
37108
  ...TrackRetrainFields
37028
37109
  });
37110
+ var KeyEventBatchQueryInput = zod.z.object({
37111
+ deviceIds: zod.z.array(zod.z.number()).min(1).max(200),
37112
+ since: zod.z.number(),
37113
+ until: zod.z.number(),
37114
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
37115
+ * across the set, which would let a busy camera starve a quiet one of its
37116
+ * rows and change what the merged feed contains. */
37117
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
37118
+ minImportance: zod.z.number().min(0).max(1).optional(),
37119
+ classFilter: zod.z.string().optional()
37120
+ });
37121
+ var KeyEventsForDeviceSchema = zod.z.object({
37122
+ deviceId: zod.z.number(),
37123
+ events: zod.z.array(KeyEventSchema).readonly()
37124
+ });
37029
37125
  zod.z.object({
37030
37126
  trackId: zod.z.string(),
37031
37127
  className: zod.z.string(),
@@ -37094,6 +37190,25 @@ var require_dist_Keu5TDO7 = __commonJS({
37094
37190
  totalBytes: zod.z.number().int(),
37095
37191
  devices: zod.z.array(EventStoreDeviceFootprintSchema).readonly()
37096
37192
  });
37193
+ var EventMediaKindFootprintSchema = zod.z.object({
37194
+ kind: MediaFileKindEnum,
37195
+ /** Media rows of this kind. */
37196
+ rows: zod.z.number().int(),
37197
+ /** Bytes on disk held by those rows. */
37198
+ bytes: zod.z.number().int()
37199
+ });
37200
+ var EventMediaKindBreakdownSchema = zod.z.object({
37201
+ /** Every media row in scope, from one unfiltered aggregate. */
37202
+ totalRows: zod.z.number().int(),
37203
+ /** Every media byte in scope, from that same aggregate. */
37204
+ totalBytes: zod.z.number().int(),
37205
+ /** Per-kind footprint, ordered by bytes descending. */
37206
+ kinds: zod.z.array(EventMediaKindFootprintSchema).readonly(),
37207
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
37208
+ unaccountedRows: zod.z.number().int(),
37209
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
37210
+ unaccountedBytes: zod.z.number().int()
37211
+ });
37097
37212
  var EventPruneCountsSchema = zod.z.object({
37098
37213
  motion: zod.z.number().int(),
37099
37214
  object: zod.z.number().int(),
@@ -37301,6 +37416,21 @@ var require_dist_Keu5TDO7 = __commonJS({
37301
37416
  * scored on-read (no write). Degrades to `[]` on error.
37302
37417
  */
37303
37418
  getKeyEvents: method(KeyEventQueryInput, zod.z.array(KeyEventSchema).readonly()),
37419
+ /**
37420
+ * The same ranking, for a SET of cameras, in one round trip.
37421
+ *
37422
+ * The Detection Intelligence events feed asks this of every selected
37423
+ * camera and re-asks on a 30s timer. Fanned out client-side that is N
37424
+ * round trips — browser → hub → post-analysis — for N independent,
37425
+ * already-indexed store queries. Batched, the queries are unchanged and
37426
+ * run concurrently INSIDE the owner; only the transport collapses.
37427
+ *
37428
+ * Deliberately per-device rather than pre-merged: `limit` stays per
37429
+ * camera (a total would let a busy camera starve a quiet one), and a
37430
+ * caller that renders one camera's lane needs to know which camera a row
37431
+ * came from. `getKeyEvents` stays for single-device callers.
37432
+ */
37433
+ getKeyEventsBatch: method(KeyEventBatchQueryInput, zod.z.array(KeyEventsForDeviceSchema).readonly()),
37304
37434
  /** Server-side bucketed event counts for the 24-hour timeline.
37305
37435
  * Returns one entry per non-empty bucket; empty buckets are omitted. */
37306
37436
  getEventDensity: method(zod.z.object({
@@ -37449,6 +37579,22 @@ var require_dist_Keu5TDO7 = __commonJS({
37449
37579
  auth: "admin"
37450
37580
  }),
37451
37581
  /**
37582
+ * The same media footprint, broken down by {@link MediaFileKind} instead of
37583
+ * by camera — fleet-wide, or for one camera with `deviceId`.
37584
+ *
37585
+ * Separate from {@link getEventStoreFootprint} rather than a field on it:
37586
+ * the two answer different questions on different axes, the per-camera one
37587
+ * is what the management table renders on every open, and nothing should
37588
+ * pay for a per-kind pass to draw it. See
37589
+ * {@link EventMediaKindBreakdownSchema} for why `unaccounted*` exists —
37590
+ * `kinds` is enumerated, `totalBytes` is not, and the gap must be visible
37591
+ * to anyone sizing a deletion against it.
37592
+ */
37593
+ getEventMediaFootprintByKind: method(zod.z.object({ deviceId: zod.z.number().int().optional() }), EventMediaKindBreakdownSchema, {
37594
+ kind: "query",
37595
+ auth: "admin"
37596
+ }),
37597
+ /**
37452
37598
  * Cluster-wide prune of events older than `olderThanMs` (exclusive) across
37453
37599
  * every camera, deleting each event's media in lockstep. Logged to the
37454
37600
  * events ops-log with `reason` (default `'retention'`). Returns the summed
@@ -40047,6 +40193,20 @@ var require_dist_Keu5TDO7 = __commonJS({
40047
40193
  listProviders: method(zod.z.void(), zod.z.array(ProviderListEntrySchema).readonly()),
40048
40194
  testConfig: method(zod.z.object({
40049
40195
  providerId: zod.z.string(),
40196
+ /**
40197
+ * The location this config is an UNSAVED edit of, when there is one.
40198
+ *
40199
+ * `listLocations` replaces every declared secret with the redaction
40200
+ * sentinel, so the edit modal's form state holds the sentinel for any
40201
+ * credential the operator did not retype — and posting that here
40202
+ * without a way to resolve it makes the provider try to authenticate
40203
+ * as `__camstack_redacted__` and report the operator's own working
40204
+ * password as wrong. Given this id, the orchestrator restores each
40205
+ * sentinel from the stored config (same rule as `upsertLocation`)
40206
+ * before dispatching. Omitted by the "Add location" wizard, where
40207
+ * every value was typed just now and nothing is stored yet.
40208
+ */
40209
+ locationId: zod.z.string().optional(),
40050
40210
  config: zod.z.record(zod.z.string(), zod.z.unknown())
40051
40211
  }), zod.z.object({
40052
40212
  ok: zod.z.boolean(),
@@ -47215,6 +47375,23 @@ var require_dist_Keu5TDO7 = __commonJS({
47215
47375
  kind: "query",
47216
47376
  auth: "admin"
47217
47377
  }),
47378
+ /**
47379
+ * Does this location's LEDGER tell the truth about its disk? (D319)
47380
+ *
47381
+ * One `readdir` per claimed hour, diffed both ways: durable rows whose file
47382
+ * is not there, and files no durable row names. `apply` defaults to FALSE —
47383
+ * the report is the product, and the dry run is how an operator
47384
+ * sanity-checks the destructive run before authorising it.
47385
+ *
47386
+ * REFUSES a source that is still a write target: a listing of a live
47387
+ * location is a lower bound, which is not the strong evidence that lets
47388
+ * this pass forget without a budget. A live location is reconciled by the
47389
+ * mover, under D318's bound.
47390
+ */
47391
+ reconcileLedgerAgainstDisk: method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
47392
+ kind: "mutation",
47393
+ auth: "admin"
47394
+ }),
47218
47395
  /** Cancel a running or queued relocate job. A queued job never runs. */
47219
47396
  cancelRelocateJob: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
47220
47397
  kind: "mutation",
@@ -47512,6 +47689,10 @@ var require_dist_Keu5TDO7 = __commonJS({
47512
47689
  monitors: zod.z.array(SceneMonitorSchema),
47513
47690
  lastFetchedAt: zod.z.number()
47514
47691
  });
47692
+ var SceneMonitorStatusForDeviceSchema = zod.z.object({
47693
+ deviceId: zod.z.number(),
47694
+ status: SceneMonitorStatusSchema.nullable()
47695
+ });
47515
47696
  var sceneMonitorCapability = {
47516
47697
  name: "scene-monitor",
47517
47698
  scope: "device",
@@ -47521,6 +47702,22 @@ var require_dist_Keu5TDO7 = __commonJS({
47521
47702
  deviceTypes: [DeviceType.Camera],
47522
47703
  methods: {
47523
47704
  listScenes: method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
47705
+ /**
47706
+ * The same answer, for a SET of cameras, in one round trip.
47707
+ *
47708
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
47709
+ * poll behind the push slice. Fanned out client-side that was one query
47710
+ * per camera — 29 round trips through the browser, the hub and the
47711
+ * post-analysis runner every 30 seconds to read an in-memory map the
47712
+ * owner had already merged. The work is unchanged (`statusFor` per
47713
+ * device, all in-process at the owner); what collapses is the transport.
47714
+ *
47715
+ * A camera that cannot answer still gets a row, with `status: null` —
47716
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
47717
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
47718
+ * tell which two are missing, or that any are.
47719
+ */
47720
+ listScenesBatch: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(SceneMonitorStatusForDeviceSchema).readonly()),
47524
47721
  createScene: method(zod.z.object({
47525
47722
  deviceId: zod.z.number(),
47526
47723
  label: zod.z.string(),
@@ -49321,6 +49518,11 @@ var require_dist_Keu5TDO7 = __commonJS({
49321
49518
  * than as repeated tracks/events. */
49322
49519
  stationaryObjects: zod.z.array(StationaryObjectSchema).readonly().optional()
49323
49520
  });
49521
+ var CameraOccupancySnapshotForDeviceSchema = zod.z.object({
49522
+ deviceId: zod.z.number(),
49523
+ read: zod.z.enum(["read", "unreadable"]),
49524
+ snapshot: CameraOccupancySnapshotSchema.nullable()
49525
+ });
49324
49526
  var HistoryResolutionEnum = zod.z.enum([
49325
49527
  "minute",
49326
49528
  "5min",
@@ -49350,6 +49552,20 @@ var require_dist_Keu5TDO7 = __commonJS({
49350
49552
  * (no inference result emitted since boot or since binding was
49351
49553
  * activated). */
49352
49554
  getCurrentSnapshot: method(zod.z.object({ deviceId: zod.z.number() }), CameraOccupancySnapshotSchema.nullable()),
49555
+ /**
49556
+ * The same snapshot, for a SET of cameras, in one round trip.
49557
+ *
49558
+ * The Events page's Stationary section polls this every 15s for every
49559
+ * selected camera. Fanned out client-side that is one query per camera to
49560
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
49561
+ * costs everything. Batched, N transports become one and the per-device
49562
+ * work is unchanged.
49563
+ *
49564
+ * Every requested deviceId gets a row, tagged `read` — see
49565
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
49566
+ * not answer for is `'unreadable'`, never an empty reading.
49567
+ */
49568
+ getCurrentSnapshotBatch: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(CameraOccupancySnapshotForDeviceSchema).readonly()),
49353
49569
  /** Time-series object count inside one zone. `className` optional —
49354
49570
  * omit to count every class in the zone. */
49355
49571
  getZoneHistory: method(zod.z.object({
@@ -53273,6 +53489,12 @@ var require_dist_Keu5TDO7 = __commonJS({
53273
53489
  addonId: null,
53274
53490
  access: "view"
53275
53491
  },
53492
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
53493
+ capName: "pipeline-analytics",
53494
+ capScope: "device",
53495
+ addonId: null,
53496
+ access: "view"
53497
+ },
53276
53498
  "pipelineAnalytics.getEventStoreFootprint": {
53277
53499
  capName: "pipeline-analytics",
53278
53500
  capScope: "device",
@@ -53291,6 +53513,12 @@ var require_dist_Keu5TDO7 = __commonJS({
53291
53513
  addonId: null,
53292
53514
  access: "view"
53293
53515
  },
53516
+ "pipelineAnalytics.getKeyEventsBatch": {
53517
+ capName: "pipeline-analytics",
53518
+ capScope: "device",
53519
+ addonId: null,
53520
+ access: "view"
53521
+ },
53294
53522
  "pipelineAnalytics.getMotionEvents": {
53295
53523
  capName: "pipeline-analytics",
53296
53524
  capScope: "device",
@@ -54467,6 +54695,12 @@ var require_dist_Keu5TDO7 = __commonJS({
54467
54695
  addonId: null,
54468
54696
  access: "view"
54469
54697
  },
54698
+ "recording.reconcileLedgerAgainstDisk": {
54699
+ capName: "recording",
54700
+ capScope: "system",
54701
+ addonId: null,
54702
+ access: "create"
54703
+ },
54470
54704
  "recording.refreshStorageLocationsForMigration": {
54471
54705
  capName: "recording",
54472
54706
  capScope: "system",
@@ -54593,6 +54827,12 @@ var require_dist_Keu5TDO7 = __commonJS({
54593
54827
  addonId: null,
54594
54828
  access: "view"
54595
54829
  },
54830
+ "sceneMonitor.listScenesBatch": {
54831
+ capName: "scene-monitor",
54832
+ capScope: "device",
54833
+ addonId: null,
54834
+ access: "view"
54835
+ },
54596
54836
  "sceneMonitor.recheckNow": {
54597
54837
  capName: "scene-monitor",
54598
54838
  capScope: "device",
@@ -55949,6 +56189,12 @@ var require_dist_Keu5TDO7 = __commonJS({
55949
56189
  addonId: null,
55950
56190
  access: "view"
55951
56191
  },
56192
+ "zoneAnalytics.getCurrentSnapshotBatch": {
56193
+ capName: "zone-analytics",
56194
+ capScope: "device",
56195
+ addonId: null,
56196
+ access: "view"
56197
+ },
55952
56198
  "zoneAnalytics.getUnzonedHistory": {
55953
56199
  capName: "zone-analytics",
55954
56200
  capScope: "device",
@@ -56938,6 +57184,11 @@ var require_dist_Keu5TDO7 = __commonJS({
56938
57184
  form: "single",
56939
57185
  optional: false
56940
57186
  }],
57187
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
57188
+ name: "deviceId",
57189
+ form: "single",
57190
+ optional: true
57191
+ }],
56941
57192
  "pipelineAnalytics.getGroup": [{
56942
57193
  name: "deviceId",
56943
57194
  form: "single",
@@ -56948,6 +57199,11 @@ var require_dist_Keu5TDO7 = __commonJS({
56948
57199
  form: "single",
56949
57200
  optional: false
56950
57201
  }],
57202
+ "pipelineAnalytics.getKeyEventsBatch": [{
57203
+ name: "deviceIds",
57204
+ form: "array",
57205
+ optional: false
57206
+ }],
56951
57207
  "pipelineAnalytics.getMotionEvents": [{
56952
57208
  name: "deviceId",
56953
57209
  form: "single",
@@ -57388,6 +57644,11 @@ var require_dist_Keu5TDO7 = __commonJS({
57388
57644
  form: "single",
57389
57645
  optional: false
57390
57646
  }],
57647
+ "recording.reconcileLedgerAgainstDisk": [{
57648
+ name: "deviceId",
57649
+ form: "single",
57650
+ optional: true
57651
+ }],
57391
57652
  "recording.relocateFootage": [{
57392
57653
  name: "deviceId",
57393
57654
  form: "single",
@@ -57453,6 +57714,11 @@ var require_dist_Keu5TDO7 = __commonJS({
57453
57714
  form: "single",
57454
57715
  optional: false
57455
57716
  }],
57717
+ "sceneMonitor.listScenesBatch": [{
57718
+ name: "deviceIds",
57719
+ form: "array",
57720
+ optional: false
57721
+ }],
57456
57722
  "sceneMonitor.recheckNow": [{
57457
57723
  name: "deviceId",
57458
57724
  form: "single",
@@ -57714,6 +57980,11 @@ var require_dist_Keu5TDO7 = __commonJS({
57714
57980
  form: "single",
57715
57981
  optional: false
57716
57982
  }],
57983
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
57984
+ name: "deviceIds",
57985
+ form: "array",
57986
+ optional: false
57987
+ }],
57717
57988
  "zoneAnalytics.getUnzonedHistory": [{
57718
57989
  name: "deviceId",
57719
57990
  form: "single",
@@ -59147,7 +59418,7 @@ var require_alerts_addon = __commonJS({
59147
59418
  [Symbol.toStringTag]: { value: "Module" }
59148
59419
  });
59149
59420
  require_chunk_Cek0wNdY();
59150
- var require_dist10 = require_dist_Keu5TDO7();
59421
+ var require_dist10 = require_dist_B29Skpzo();
59151
59422
  function selectExpired(alerts, cutoffMs) {
59152
59423
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59153
59424
  }
@@ -59966,7 +60237,7 @@ var require_console_logging = __commonJS({
59966
60237
  [Symbol.toStringTag]: { value: "Module" }
59967
60238
  });
59968
60239
  require_chunk_Cek0wNdY();
59969
- var require_dist10 = require_dist_Keu5TDO7();
60240
+ var require_dist10 = require_dist_B29Skpzo();
59970
60241
  var require_formatter = require_formatter_DqAKDlvN();
59971
60242
  var LEVEL_RANK = {
59972
60243
  debug: 0,
@@ -60060,7 +60331,7 @@ var require_core_blocks_addon = __commonJS({
60060
60331
  "use strict";
60061
60332
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60062
60333
  var require_chunk = require_chunk_Cek0wNdY();
60063
- var require_dist10 = require_dist_Keu5TDO7();
60334
+ var require_dist10 = require_dist_B29Skpzo();
60064
60335
  var node_crypto = __require("crypto");
60065
60336
  var node_fs_promises = __require("fs/promises");
60066
60337
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -60957,11 +61228,11 @@ var require_core_blocks = __commonJS({
60957
61228
  }
60958
61229
  });
60959
61230
 
60960
- // ../system/dist/retired-settings-keys-BBohF-eC.js
60961
- var require_retired_settings_keys_BBohF_eC = __commonJS({
60962
- "../system/dist/retired-settings-keys-BBohF-eC.js"(exports) {
61231
+ // ../system/dist/retired-settings-keys-CRL0qOnU.js
61232
+ var require_retired_settings_keys_CRL0qOnU = __commonJS({
61233
+ "../system/dist/retired-settings-keys-CRL0qOnU.js"(exports) {
60963
61234
  "use strict";
60964
- var require_dist10 = require_dist_Keu5TDO7();
61235
+ var require_dist10 = require_dist_B29Skpzo();
60965
61236
  function settingsStoreIsAuthoritativeHere(env) {
60966
61237
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
60967
61238
  return raw === "" || raw === "hub";
@@ -63175,8 +63446,8 @@ var require_device_manager_addon = __commonJS({
63175
63446
  [Symbol.toStringTag]: { value: "Module" }
63176
63447
  });
63177
63448
  require_chunk_Cek0wNdY();
63178
- var require_dist10 = require_dist_Keu5TDO7();
63179
- var require_retired_settings_keys = require_retired_settings_keys_BBohF_eC();
63449
+ var require_dist10 = require_dist_B29Skpzo();
63450
+ var require_retired_settings_keys = require_retired_settings_keys_CRL0qOnU();
63180
63451
  var node_crypto = __require("crypto");
63181
63452
  var _camstack_types_node = require_node();
63182
63453
  var JOB_HISTORY = 20;
@@ -67989,7 +68260,7 @@ var require_hub_forwarder = __commonJS({
67989
68260
  [Symbol.toStringTag]: { value: "Module" }
67990
68261
  });
67991
68262
  require_chunk_Cek0wNdY();
67992
- var require_dist10 = require_dist_Keu5TDO7();
68263
+ var require_dist10 = require_dist_B29Skpzo();
67993
68264
  var require_formatter = require_formatter_DqAKDlvN();
67994
68265
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
67995
68266
  var HubForwarderDestination = class {
@@ -68126,7 +68397,7 @@ var require_liveness_monitor_addon = __commonJS({
68126
68397
  "use strict";
68127
68398
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68128
68399
  require_chunk_Cek0wNdY();
68129
- var require_dist10 = require_dist_Keu5TDO7();
68400
+ var require_dist10 = require_dist_B29Skpzo();
68130
68401
  var NO_DEVICES = "liveness:no-devices";
68131
68402
  var ALL_OFFLINE = "liveness:all-devices-offline";
68132
68403
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68316,7 +68587,7 @@ var require_local_auth_addon = __commonJS({
68316
68587
  [Symbol.toStringTag]: { value: "Module" }
68317
68588
  });
68318
68589
  var require_chunk = require_chunk_Cek0wNdY();
68319
- var require_dist10 = require_dist_Keu5TDO7();
68590
+ var require_dist10 = require_dist_B29Skpzo();
68320
68591
  var node_crypto = __require("crypto");
68321
68592
  node_crypto = require_chunk.__toESM(node_crypto);
68322
68593
  var crypto$1 = __require("crypto");
@@ -76129,7 +76400,7 @@ var require_loki_logging = __commonJS({
76129
76400
  [Symbol.toStringTag]: { value: "Module" }
76130
76401
  });
76131
76402
  require_chunk_Cek0wNdY();
76132
- var require_dist10 = require_dist_Keu5TDO7();
76403
+ var require_dist10 = require_dist_B29Skpzo();
76133
76404
  function sanitizeLabelName(raw) {
76134
76405
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76135
76406
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -76694,7 +76965,7 @@ var require_native_metrics_addon = __commonJS({
76694
76965
  [Symbol.toStringTag]: { value: "Module" }
76695
76966
  });
76696
76967
  var require_chunk = require_chunk_Cek0wNdY();
76697
- var require_dist10 = require_dist_Keu5TDO7();
76968
+ var require_dist10 = require_dist_B29Skpzo();
76698
76969
  var node_fs_promises = __require("fs/promises");
76699
76970
  var node_child_process = __require("child_process");
76700
76971
  var node_util = __require("util");
@@ -79316,7 +79587,7 @@ var require_filesystem_storage_addon = __commonJS({
79316
79587
  [Symbol.toStringTag]: { value: "Module" }
79317
79588
  });
79318
79589
  var require_chunk = require_chunk_Cek0wNdY();
79319
- var require_dist10 = require_dist_Keu5TDO7();
79590
+ var require_dist10 = require_dist_B29Skpzo();
79320
79591
  var node_crypto = __require("crypto");
79321
79592
  var node_fs_promises = __require("fs/promises");
79322
79593
  var node_path = __require("path");
@@ -80432,8 +80703,8 @@ var require_sqlite_settings_addon = __commonJS({
80432
80703
  [Symbol.toStringTag]: { value: "Module" }
80433
80704
  });
80434
80705
  var require_chunk = require_chunk_Cek0wNdY();
80435
- var require_dist10 = require_dist_Keu5TDO7();
80436
- var require_retired_settings_keys = require_retired_settings_keys_BBohF_eC();
80706
+ var require_dist10 = require_dist_B29Skpzo();
80707
+ var require_retired_settings_keys = require_retired_settings_keys_CRL0qOnU();
80437
80708
  var node_crypto = __require("crypto");
80438
80709
  var node_fs = __require("fs");
80439
80710
  var node_module = __require("module");
@@ -82812,7 +83083,7 @@ var require_storage_orchestrator_addon = __commonJS({
82812
83083
  [Symbol.toStringTag]: { value: "Module" }
82813
83084
  });
82814
83085
  var require_chunk = require_chunk_Cek0wNdY();
82815
- var require_dist10 = require_dist_Keu5TDO7();
83086
+ var require_dist10 = require_dist_B29Skpzo();
82816
83087
  var node_crypto = __require("crypto");
82817
83088
  var node_fs_promises = __require("fs/promises");
82818
83089
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -83795,10 +84066,21 @@ var require_storage_orchestrator_addon = __commonJS({
83795
84066
  declareCollection: async (input) => (await engine()).declareCollection(input)
83796
84067
  };
83797
84068
  }
83798
- var EMPTY_SECRET_KEYS = /* @__PURE__ */ new Set();
84069
+ var FRAMEWORK_CONFIG_KEYS = /* @__PURE__ */ new Set([
84070
+ "basePath",
84071
+ "readOnly",
84072
+ "maxUsedGb",
84073
+ "minFreePercent"
84074
+ ]);
83799
84075
  function secretKeysOfProviderInfo(info) {
83800
84076
  return require_dist10.collectSecretConfigKeys(info.configSchema);
83801
84077
  }
84078
+ function secretKeysForConfig(config, declared) {
84079
+ if (declared !== void 0) return declared;
84080
+ const out = /* @__PURE__ */ new Set();
84081
+ for (const key of Object.keys(config)) if (!FRAMEWORK_CONFIG_KEYS.has(key)) out.add(key);
84082
+ return out;
84083
+ }
83802
84084
  function redactLocationConfig(config, secretKeys) {
83803
84085
  if (secretKeys.size === 0) return config;
83804
84086
  let touched = false;
@@ -84784,7 +85066,7 @@ var require_storage_orchestrator_addon = __commonJS({
84784
85066
  listLocationDeclarations: async () => service.listDeclarations(),
84785
85067
  upsertLocation: async (input) => {
84786
85068
  const stored = service.getLocationById(input.id);
84787
- const config = restoreRedactedSecrets(input.config, stored?.config, this.secretKeysFor(input.providerId));
85069
+ const config = restoreRedactedSecrets(input.config, stored?.config, this.secretKeysFor(input.providerId, stored?.config ?? {}));
84788
85070
  const saved = service.upsertLocation(config === input.config ? input : {
84789
85071
  ...input,
84790
85072
  config
@@ -84817,11 +85099,14 @@ var require_storage_orchestrator_addon = __commonJS({
84817
85099
  } });
84818
85100
  });
84819
85101
  },
84820
- testConfig: async ({ providerId, config }) => {
85102
+ testConfig: async ({ providerId, locationId, config }) => {
85103
+ const stored = locationId === void 0 ? void 0 : service.getLocationById(locationId);
85104
+ const base = stored?.providerId === providerId ? stored.config : void 0;
85105
+ const resolved = restoreRedactedSecrets(config, base, this.secretKeysFor(providerId, base ?? {}));
84821
85106
  const providers = getProviders();
84822
85107
  for (const p of providers) try {
84823
85108
  if ((await p.getProviderInfo()).providerId !== providerId) continue;
84824
- return p.testLocation({ config });
85109
+ return p.testLocation({ config: resolved });
84825
85110
  } catch (err) {
84826
85111
  return {
84827
85112
  ok: false,
@@ -85141,13 +85426,21 @@ var require_storage_orchestrator_addon = __commonJS({
85141
85426
  }
85142
85427
  return out;
85143
85428
  }
85144
- /** Declared secret config keys for a provider; empty when unknown. */
85145
- secretKeysFor(providerId) {
85146
- return this.secretKeysByProvider.get(providerId) ?? EMPTY_SECRET_KEYS;
85429
+ /**
85430
+ * What the provider's own schema declared secret, or `undefined` when this
85431
+ * node has never heard from that provider. The distinction is the whole
85432
+ * point — see `secretKeysForConfig`, which fails CLOSED on `undefined`.
85433
+ */
85434
+ declaredSecretKeys(providerId) {
85435
+ return this.secretKeysByProvider.get(providerId);
85436
+ }
85437
+ /** Which keys of a given stored config must never leave this process. */
85438
+ secretKeysFor(providerId, config) {
85439
+ return secretKeysForConfig(config, this.declaredSecretKeys(providerId));
85147
85440
  }
85148
85441
  /** A location with its provider's declared secrets replaced by the sentinel. */
85149
85442
  redacted(location) {
85150
- return redactLocation(location, this.secretKeysFor(location.providerId));
85443
+ return redactLocation(location, this.secretKeysFor(location.providerId, location.config));
85151
85444
  }
85152
85445
  /**
85153
85446
  * Is this location backed by a provider that serves a genuine local
@@ -85387,7 +85680,7 @@ var require_system_config_addon = __commonJS({
85387
85680
  [Symbol.toStringTag]: { value: "Module" }
85388
85681
  });
85389
85682
  require_chunk_Cek0wNdY();
85390
- var require_dist10 = require_dist_Keu5TDO7();
85683
+ var require_dist10 = require_dist_B29Skpzo();
85391
85684
  var SECTION_TITLES = {
85392
85685
  server: "Server",
85393
85686
  auth: "Authentication"
@@ -103448,7 +103741,7 @@ var require_winston_logging = __commonJS({
103448
103741
  [Symbol.toStringTag]: { value: "Module" }
103449
103742
  });
103450
103743
  var require_chunk = require_chunk_Cek0wNdY();
103451
- var require_dist10 = require_dist_Keu5TDO7();
103744
+ var require_dist10 = require_dist_B29Skpzo();
103452
103745
  var require_formatter = require_formatter_DqAKDlvN();
103453
103746
  var node_path = __require("path");
103454
103747
  node_path = require_chunk.__toESM(node_path);
@@ -105391,9 +105684,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105391
105684
  }
105392
105685
  });
105393
105686
 
105394
- // ../types/dist/sleep-C3AniWy-.js
105395
- var require_sleep_C3AniWy = __commonJS({
105396
- "../types/dist/sleep-C3AniWy-.js"(exports) {
105687
+ // ../types/dist/sleep-DIg3xuEw.js
105688
+ var require_sleep_DIg3xuEw = __commonJS({
105689
+ "../types/dist/sleep-DIg3xuEw.js"(exports) {
105397
105690
  "use strict";
105398
105691
  var require_event_category = require_event_category_BaEgqJNv();
105399
105692
  var zod = require_zod();
@@ -108076,6 +108369,7 @@ var require_sleep_C3AniWy = __commonJS({
108076
108369
  listEventKindsBatch: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventKindsBatch", "query", input),
108077
108370
  getSensorEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getSensorEvents", "query", input),
108078
108371
  getKeyEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEvents", "query", input),
108372
+ getKeyEventsBatch: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEventsBatch", "query", input),
108079
108373
  getEventDensity: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventDensity", "query", input),
108080
108374
  pruneEventsBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEventsBefore", "mutation", input),
108081
108375
  pruneTracksBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneTracksBefore", "mutation", input),
@@ -108084,6 +108378,7 @@ var require_sleep_C3AniWy = __commonJS({
108084
108378
  deleteTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteTracks", "mutation", input),
108085
108379
  setTrackFlags: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "setTrackFlags", "mutation", input),
108086
108380
  getEventStoreFootprint: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventStoreFootprint", "query", input),
108381
+ getEventMediaFootprintByKind: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventMediaFootprintByKind", "query", input),
108087
108382
  pruneEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEvents", "mutation", input),
108088
108383
  deleteDeviceEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "deleteDeviceEvents", "mutation", input),
108089
108384
  pauseForStorageMigration: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pauseForStorageMigration", "mutation", input),
@@ -108156,6 +108451,7 @@ var require_sleep_C3AniWy = __commonJS({
108156
108451
  reboot: { reboot: (input) => dispatch("reboot", "reboot", "reboot", "mutation", input) },
108157
108452
  sceneMonitor: {
108158
108453
  listScenes: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenes", "query", input),
108454
+ listScenesBatch: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenesBatch", "query", input),
108159
108455
  createScene: (input) => dispatch("scene-monitor", "sceneMonitor", "createScene", "mutation", input),
108160
108456
  updateScene: (input) => dispatch("scene-monitor", "sceneMonitor", "updateScene", "mutation", input),
108161
108457
  deleteScene: (input) => dispatch("scene-monitor", "sceneMonitor", "deleteScene", "mutation", input),
@@ -108240,6 +108536,7 @@ var require_sleep_C3AniWy = __commonJS({
108240
108536
  },
108241
108537
  zoneAnalytics: {
108242
108538
  getCurrentSnapshot: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCurrentSnapshot", "query", input),
108539
+ getCurrentSnapshotBatch: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCurrentSnapshotBatch", "query", input),
108243
108540
  getZoneHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getZoneHistory", "query", input),
108244
108541
  getCameraHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCameraHistory", "query", input),
108245
108542
  getUnzonedHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getUnzonedHistory", "query", input)
@@ -109060,7 +109357,7 @@ var require_addon = __commonJS({
109060
109357
  "use strict";
109061
109358
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
109062
109359
  var require_event_category = require_event_category_BaEgqJNv();
109063
- var require_sleep = require_sleep_C3AniWy();
109360
+ var require_sleep = require_sleep_DIg3xuEw();
109064
109361
  var require_err_msg = require_err_msg_COpsHMw2();
109065
109362
  var CAP_INPUT_DEFAULTS = Object.freeze({
109066
109363
  "addons": { "getLogs": { "limit": 100 } },
@@ -109170,6 +109467,7 @@ var require_addon = __commonJS({
109170
109467
  "pipeline-analytics": {
109171
109468
  "getAudioEvents": { "limit": 1e3 },
109172
109469
  "getKeyEvents": { "limit": 50 },
109470
+ "getKeyEventsBatch": { "limit": 50 },
109173
109471
  "getMotionEvents": { "limit": 1e3 },
109174
109472
  "getObjectEvents": { "limit": 1e3 },
109175
109473
  "getSensorEvents": { "limit": 1e3 },
@@ -115942,12 +116240,12 @@ var require_dist2 = __commonJS({
115942
116240
  }
115943
116241
  });
115944
116242
 
115945
- // ../system/dist/manifest-system-deps-DYv4ZPo2.js
115946
- var require_manifest_system_deps_DYv4ZPo2 = __commonJS({
115947
- "../system/dist/manifest-system-deps-DYv4ZPo2.js"(exports) {
116243
+ // ../system/dist/manifest-system-deps-DBje540e.js
116244
+ var require_manifest_system_deps_DBje540e = __commonJS({
116245
+ "../system/dist/manifest-system-deps-DBje540e.js"(exports) {
115948
116246
  "use strict";
115949
116247
  var require_chunk = require_chunk_Cek0wNdY();
115950
- require_dist_Keu5TDO7();
116248
+ require_dist_B29Skpzo();
115951
116249
  var node_crypto = __require("crypto");
115952
116250
  node_crypto = require_chunk.__toESM(node_crypto);
115953
116251
  var _camstack_types_node = require_node();
@@ -127976,7 +128274,7 @@ var require_dist3 = __commonJS({
127976
128274
  "use strict";
127977
128275
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
127978
128276
  var require_chunk = require_chunk_Cek0wNdY();
127979
- var require_dist10 = require_dist_Keu5TDO7();
128277
+ var require_dist10 = require_dist_B29Skpzo();
127980
128278
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
127981
128279
  require_alerts();
127982
128280
  var require_formatter = require_formatter_DqAKDlvN();
@@ -128002,7 +128300,7 @@ var require_dist3 = __commonJS({
128002
128300
  var require_builtins_winston_logging_index = require_winston_logging();
128003
128301
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
128004
128302
  var require_tls$1 = require_tls_BxQlomxd();
128005
- var require_manifest_system_deps = require_manifest_system_deps_DYv4ZPo2();
128303
+ var require_manifest_system_deps = require_manifest_system_deps_DBje540e();
128006
128304
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
128007
128305
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
128008
128306
  var zod = require_zod();
@@ -133890,19 +134188,37 @@ var require_dist3 = __commonJS({
133890
134188
  if (!isRecord$1(parsed) || !("value" in parsed)) return parsed;
133891
134189
  return parsed.value;
133892
134190
  }
134191
+ var PREFIX_PAGE_ROWS = 1e3;
134192
+ var PREFIX_PAGE_BUDGET = 512;
133893
134193
  async function loadPrefixed(door, collection, prefix) {
133894
134194
  const range = require_builtins_sqlite_storage_sqlite_settings_addon.prefixRange(prefix);
133895
- const rows = await door.query({
133896
- collection,
133897
- ...range !== null ? { filter: { whereBetween: { id: [range.lo, range.hi] } } } : {}
133898
- });
133899
134195
  const result = {};
133900
- for (const row of rows) {
133901
- if (range !== null && (row.id < range.lo || row.id >= range.hi)) continue;
133902
- if (!row.id.startsWith(prefix)) continue;
133903
- result[row.id.slice(prefix.length)] = unwrapValue(row.data);
134196
+ let cursor = range?.lo ?? null;
134197
+ for (let page = 0; ; page++) {
134198
+ if (page >= PREFIX_PAGE_BUDGET) throw new Error(`settings door: reading "${prefix}" from "${collection}" exceeded ${PREFIX_PAGE_BUDGET} pages of ${PREFIX_PAGE_ROWS} rows. Refusing to return the part that was read \u2014 a partial scope read is indistinguishable from an empty one.`);
134199
+ const rows = await door.query({
134200
+ collection,
134201
+ filter: {
134202
+ ...range !== null && cursor !== null ? { whereBetween: { id: [cursor, range.hi] } } : {},
134203
+ orderBy: {
134204
+ field: "id",
134205
+ direction: "asc"
134206
+ },
134207
+ limit: PREFIX_PAGE_ROWS
134208
+ }
134209
+ });
134210
+ for (const row of rows) {
134211
+ if (range !== null && (row.id < range.lo || row.id >= range.hi)) continue;
134212
+ if (!row.id.startsWith(prefix)) continue;
134213
+ result[row.id.slice(prefix.length)] = unwrapValue(row.data);
134214
+ }
134215
+ if (rows.length < PREFIX_PAGE_ROWS) return result;
134216
+ const last = rows[rows.length - 1];
134217
+ if (last === void 0) return result;
134218
+ if (range === null) throw new Error(`settings door: prefix "${prefix}" on "${collection}" cannot be expressed as an id range, and the unfiltered read filled a page \u2014 the answer would be partial. Use an ASCII, non-empty prefix.`);
134219
+ if (last.id === cursor) throw new Error(`settings door: reading "${prefix}" from "${collection}" made no progress at id "${last.id}" \u2014 ids under a prefix must be unique.`);
134220
+ cursor = last.id;
133904
134221
  }
133905
- return result;
133906
134222
  }
133907
134223
  async function replacePrefixed(door, collection, prefix, values, wrap3) {
133908
134224
  const range = require_builtins_sqlite_storage_sqlite_settings_addon.prefixRange(prefix);
@@ -208826,7 +209142,7 @@ var require_dist4 = __commonJS({
208826
209142
  "use strict";
208827
209143
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
208828
209144
  var require_event_category = require_event_category_BaEgqJNv();
208829
- var require_sleep = require_sleep_C3AniWy();
209145
+ var require_sleep = require_sleep_DIg3xuEw();
208830
209146
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
208831
209147
  var require_enums2 = require_enums();
208832
209148
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -210173,6 +210489,20 @@ var require_dist4 = __commonJS({
210173
210489
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
210174
210490
  */
210175
210491
  rowsReconciled: zod.z.number().int().nonnegative().optional(),
210492
+ /**
210493
+ * Rows this run FORGOT because the file they name is not on disk.
210494
+ *
210495
+ * The mover derived the path from the row's own fields and `stat`ed it; an
210496
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
210497
+ * and the durable row is dropped through the same channel eviction uses. It
210498
+ * is reported for the same reason `rowsReconciled` is: this is a durable
210499
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
210500
+ * the same failure as one that quietly skips them (D295).
210501
+ *
210502
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
210503
+ * ledger claimed 5.65 GB of footage that no longer existed.
210504
+ */
210505
+ rowsForgotten: zod.z.number().int().nonnegative().optional(),
210176
210506
  startedAt: zod.z.number(),
210177
210507
  finishedAt: zod.z.number().nullable(),
210178
210508
  error: zod.z.string().nullable()
@@ -210380,6 +210710,71 @@ var require_dist4 = __commonJS({
210380
210710
  segments: zod.z.number().int().nonnegative(),
210381
210711
  bytes: zod.z.number().int().nonnegative()
210382
210712
  }).nullable();
210713
+ var LedgerWalkInputSchema = zod.z.object({
210714
+ locationId: zod.z.string().min(1),
210715
+ /** Forget the confirmed-absent rows, rather than only counting them. */
210716
+ apply: zod.z.boolean().optional(),
210717
+ /** Narrow to one camera. */
210718
+ deviceId: zod.z.number().int().positive().optional(),
210719
+ /** Narrow to these recording profiles; empty/absent = every profile. */
210720
+ profiles: zod.z.array(zod.z.string().min(1)).optional()
210721
+ });
210722
+ var LedgerWalkRefusalSchema = zod.z.enum([
210723
+ "location-unknown",
210724
+ "source-writable",
210725
+ "no-ledger",
210726
+ "archive-unreadable",
210727
+ "anchor-absent",
210728
+ "anchor-unreadable",
210729
+ "anchor-moved"
210730
+ ]);
210731
+ var LedgerWalkSkipReasonSchema = zod.z.enum([
210732
+ "live-tail",
210733
+ "listing-error",
210734
+ "path-mismatch",
210735
+ "durable-refused"
210736
+ ]);
210737
+ var LedgerWalkSkipCountsSchema = zod.z.object({
210738
+ "live-tail": zod.z.number().int().nonnegative(),
210739
+ "listing-error": zod.z.number().int().nonnegative(),
210740
+ "path-mismatch": zod.z.number().int().nonnegative(),
210741
+ "durable-refused": zod.z.number().int().nonnegative()
210742
+ });
210743
+ var LedgerWalkDeviceReportSchema = zod.z.object({
210744
+ deviceId: zod.z.number().int(),
210745
+ hoursWalked: zod.z.number().int().nonnegative(),
210746
+ hoursMissing: zod.z.number().int().nonnegative(),
210747
+ ghostSegments: zod.z.number().int().nonnegative(),
210748
+ ghostBytes: zod.z.number().int().nonnegative(),
210749
+ forgottenSegments: zod.z.number().int().nonnegative(),
210750
+ orphanFiles: zod.z.number().int().nonnegative()
210751
+ });
210752
+ var LedgerWalkReportSchema = zod.z.object({
210753
+ locationId: zod.z.string(),
210754
+ applied: zod.z.boolean(),
210755
+ refused: LedgerWalkRefusalSchema.nullable(),
210756
+ archiveSegments: zod.z.number().int().nonnegative().nullable(),
210757
+ archiveBytes: zod.z.number().int().nonnegative().nullable(),
210758
+ hoursClaimed: zod.z.number().int().nonnegative(),
210759
+ hoursWalked: zod.z.number().int().nonnegative(),
210760
+ hoursMissing: zod.z.number().int().nonnegative(),
210761
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
210762
+ listings: zod.z.number().int().nonnegative(),
210763
+ segmentsClaimed: zod.z.number().int().nonnegative(),
210764
+ ghostSegments: zod.z.number().int().nonnegative(),
210765
+ ghostBytes: zod.z.number().int().nonnegative(),
210766
+ ghostHoursWhole: zod.z.number().int().nonnegative(),
210767
+ forgottenSegments: zod.z.number().int().nonnegative(),
210768
+ forgottenBytes: zod.z.number().int().nonnegative(),
210769
+ /** Files under a claimed hour that no durable row names. Never deleted. */
210770
+ orphanFiles: zod.z.number().int().nonnegative(),
210771
+ orphanSample: zod.z.array(zod.z.string()).readonly(),
210772
+ hoursSkipped: zod.z.number().int().nonnegative(),
210773
+ skippedByReason: LedgerWalkSkipCountsSchema,
210774
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
210775
+ bounded: zod.z.boolean(),
210776
+ byDevice: zod.z.array(LedgerWalkDeviceReportSchema).readonly()
210777
+ });
210383
210778
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
210384
210779
  var RelocatableMediaCountInputSchema = zod.z.object({
210385
210780
  toLocationId: zod.z.string().min(1),
@@ -222879,13 +223274,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222879
223274
  groups: zod.z.array(AnalyticsGroupRecordSchema).readonly(),
222880
223275
  nextCursor: zod.z.string().nullable()
222881
223276
  });
223277
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
223278
+ var KEY_EVENTS_MAX_LIMIT = 200;
222882
223279
  var KeyEventQueryInput = zod.z.object({
222883
223280
  deviceId: zod.z.number(),
222884
223281
  /** Window lower bound (track firstSeen ≥ since). */
222885
223282
  since: zod.z.number(),
222886
223283
  /** Window upper bound (track firstSeen ≤ until). */
222887
223284
  until: zod.z.number(),
222888
- limit: zod.z.number().int().min(1).max(200).default(50),
223285
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
222889
223286
  /** Drop tracks scoring below this importance. */
222890
223287
  minImportance: zod.z.number().min(0).max(1).optional(),
222891
223288
  /** Restrict to a single class (e.g. 'person'). */
@@ -222907,6 +223304,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222907
223304
  ...TrackFlagFields,
222908
223305
  ...TrackRetrainFields
222909
223306
  });
223307
+ var KeyEventBatchQueryInput = zod.z.object({
223308
+ deviceIds: zod.z.array(zod.z.number()).min(1).max(200),
223309
+ since: zod.z.number(),
223310
+ until: zod.z.number(),
223311
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
223312
+ * across the set, which would let a busy camera starve a quiet one of its
223313
+ * rows and change what the merged feed contains. */
223314
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
223315
+ minImportance: zod.z.number().min(0).max(1).optional(),
223316
+ classFilter: zod.z.string().optional()
223317
+ });
223318
+ var KeyEventsForDeviceSchema = zod.z.object({
223319
+ deviceId: zod.z.number(),
223320
+ events: zod.z.array(KeyEventSchema).readonly()
223321
+ });
222910
223322
  var TrackedDetectionSchema = zod.z.object({
222911
223323
  trackId: zod.z.string(),
222912
223324
  className: zod.z.string(),
@@ -222975,6 +223387,25 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222975
223387
  totalBytes: zod.z.number().int(),
222976
223388
  devices: zod.z.array(EventStoreDeviceFootprintSchema).readonly()
222977
223389
  });
223390
+ var EventMediaKindFootprintSchema = zod.z.object({
223391
+ kind: MediaFileKindEnum,
223392
+ /** Media rows of this kind. */
223393
+ rows: zod.z.number().int(),
223394
+ /** Bytes on disk held by those rows. */
223395
+ bytes: zod.z.number().int()
223396
+ });
223397
+ var EventMediaKindBreakdownSchema = zod.z.object({
223398
+ /** Every media row in scope, from one unfiltered aggregate. */
223399
+ totalRows: zod.z.number().int(),
223400
+ /** Every media byte in scope, from that same aggregate. */
223401
+ totalBytes: zod.z.number().int(),
223402
+ /** Per-kind footprint, ordered by bytes descending. */
223403
+ kinds: zod.z.array(EventMediaKindFootprintSchema).readonly(),
223404
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
223405
+ unaccountedRows: zod.z.number().int(),
223406
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
223407
+ unaccountedBytes: zod.z.number().int()
223408
+ });
222978
223409
  var EventPruneCountsSchema = zod.z.object({
222979
223410
  motion: zod.z.number().int(),
222980
223411
  object: zod.z.number().int(),
@@ -223182,6 +223613,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223182
223613
  * scored on-read (no write). Degrades to `[]` on error.
223183
223614
  */
223184
223615
  getKeyEvents: require_sleep.method(KeyEventQueryInput, zod.z.array(KeyEventSchema).readonly()),
223616
+ /**
223617
+ * The same ranking, for a SET of cameras, in one round trip.
223618
+ *
223619
+ * The Detection Intelligence events feed asks this of every selected
223620
+ * camera and re-asks on a 30s timer. Fanned out client-side that is N
223621
+ * round trips — browser → hub → post-analysis — for N independent,
223622
+ * already-indexed store queries. Batched, the queries are unchanged and
223623
+ * run concurrently INSIDE the owner; only the transport collapses.
223624
+ *
223625
+ * Deliberately per-device rather than pre-merged: `limit` stays per
223626
+ * camera (a total would let a busy camera starve a quiet one), and a
223627
+ * caller that renders one camera's lane needs to know which camera a row
223628
+ * came from. `getKeyEvents` stays for single-device callers.
223629
+ */
223630
+ getKeyEventsBatch: require_sleep.method(KeyEventBatchQueryInput, zod.z.array(KeyEventsForDeviceSchema).readonly()),
223185
223631
  /** Server-side bucketed event counts for the 24-hour timeline.
223186
223632
  * Returns one entry per non-empty bucket; empty buckets are omitted. */
223187
223633
  getEventDensity: require_sleep.method(zod.z.object({
@@ -223330,6 +223776,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223330
223776
  auth: "admin"
223331
223777
  }),
223332
223778
  /**
223779
+ * The same media footprint, broken down by {@link MediaFileKind} instead of
223780
+ * by camera — fleet-wide, or for one camera with `deviceId`.
223781
+ *
223782
+ * Separate from {@link getEventStoreFootprint} rather than a field on it:
223783
+ * the two answer different questions on different axes, the per-camera one
223784
+ * is what the management table renders on every open, and nothing should
223785
+ * pay for a per-kind pass to draw it. See
223786
+ * {@link EventMediaKindBreakdownSchema} for why `unaccounted*` exists —
223787
+ * `kinds` is enumerated, `totalBytes` is not, and the gap must be visible
223788
+ * to anyone sizing a deletion against it.
223789
+ */
223790
+ getEventMediaFootprintByKind: require_sleep.method(zod.z.object({ deviceId: zod.z.number().int().optional() }), EventMediaKindBreakdownSchema, {
223791
+ kind: "query",
223792
+ auth: "admin"
223793
+ }),
223794
+ /**
223333
223795
  * Cluster-wide prune of events older than `olderThanMs` (exclusive) across
223334
223796
  * every camera, deleting each event's media in lockstep. Logged to the
223335
223797
  * events ops-log with `reason` (default `'retention'`). Returns the summed
@@ -226092,6 +226554,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
226092
226554
  listProviders: require_sleep.method(zod.z.void(), zod.z.array(ProviderListEntrySchema).readonly()),
226093
226555
  testConfig: require_sleep.method(zod.z.object({
226094
226556
  providerId: zod.z.string(),
226557
+ /**
226558
+ * The location this config is an UNSAVED edit of, when there is one.
226559
+ *
226560
+ * `listLocations` replaces every declared secret with the redaction
226561
+ * sentinel, so the edit modal's form state holds the sentinel for any
226562
+ * credential the operator did not retype — and posting that here
226563
+ * without a way to resolve it makes the provider try to authenticate
226564
+ * as `__camstack_redacted__` and report the operator's own working
226565
+ * password as wrong. Given this id, the orchestrator restores each
226566
+ * sentinel from the stored config (same rule as `upsertLocation`)
226567
+ * before dispatching. Omitted by the "Add location" wizard, where
226568
+ * every value was typed just now and nothing is stored yet.
226569
+ */
226570
+ locationId: zod.z.string().optional(),
226095
226571
  config: zod.z.record(zod.z.string(), zod.z.unknown())
226096
226572
  }), zod.z.object({
226097
226573
  ok: zod.z.boolean(),
@@ -233269,6 +233745,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233269
233745
  kind: "query",
233270
233746
  auth: "admin"
233271
233747
  }),
233748
+ /**
233749
+ * Does this location's LEDGER tell the truth about its disk? (D319)
233750
+ *
233751
+ * One `readdir` per claimed hour, diffed both ways: durable rows whose file
233752
+ * is not there, and files no durable row names. `apply` defaults to FALSE —
233753
+ * the report is the product, and the dry run is how an operator
233754
+ * sanity-checks the destructive run before authorising it.
233755
+ *
233756
+ * REFUSES a source that is still a write target: a listing of a live
233757
+ * location is a lower bound, which is not the strong evidence that lets
233758
+ * this pass forget without a budget. A live location is reconciled by the
233759
+ * mover, under D318's bound.
233760
+ */
233761
+ reconcileLedgerAgainstDisk: require_sleep.method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
233762
+ kind: "mutation",
233763
+ auth: "admin"
233764
+ }),
233272
233765
  /** Cancel a running or queued relocate job. A queued job never runs. */
233273
233766
  cancelRelocateJob: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
233274
233767
  kind: "mutation",
@@ -233583,6 +234076,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233583
234076
  monitors: zod.z.array(SceneMonitorSchema),
233584
234077
  lastFetchedAt: zod.z.number()
233585
234078
  });
234079
+ var SceneMonitorStatusForDeviceSchema = zod.z.object({
234080
+ deviceId: zod.z.number(),
234081
+ status: SceneMonitorStatusSchema.nullable()
234082
+ });
233586
234083
  var sceneMonitorCapability = {
233587
234084
  name: "scene-monitor",
233588
234085
  scope: "device",
@@ -233592,6 +234089,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233592
234089
  deviceTypes: [require_sleep.DeviceType.Camera],
233593
234090
  methods: {
233594
234091
  listScenes: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
234092
+ /**
234093
+ * The same answer, for a SET of cameras, in one round trip.
234094
+ *
234095
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
234096
+ * poll behind the push slice. Fanned out client-side that was one query
234097
+ * per camera — 29 round trips through the browser, the hub and the
234098
+ * post-analysis runner every 30 seconds to read an in-memory map the
234099
+ * owner had already merged. The work is unchanged (`statusFor` per
234100
+ * device, all in-process at the owner); what collapses is the transport.
234101
+ *
234102
+ * A camera that cannot answer still gets a row, with `status: null` —
234103
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
234104
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
234105
+ * tell which two are missing, or that any are.
234106
+ */
234107
+ listScenesBatch: require_sleep.method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(SceneMonitorStatusForDeviceSchema).readonly()),
233595
234108
  createScene: require_sleep.method(zod.z.object({
233596
234109
  deviceId: zod.z.number(),
233597
234110
  label: zod.z.string(),
@@ -235393,6 +235906,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
235393
235906
  * than as repeated tracks/events. */
235394
235907
  stationaryObjects: zod.z.array(StationaryObjectSchema).readonly().optional()
235395
235908
  });
235909
+ var CameraOccupancySnapshotForDeviceSchema = zod.z.object({
235910
+ deviceId: zod.z.number(),
235911
+ read: zod.z.enum(["read", "unreadable"]),
235912
+ snapshot: CameraOccupancySnapshotSchema.nullable()
235913
+ });
235396
235914
  var HistoryResolutionEnum = zod.z.enum([
235397
235915
  "minute",
235398
235916
  "5min",
@@ -235422,6 +235940,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
235422
235940
  * (no inference result emitted since boot or since binding was
235423
235941
  * activated). */
235424
235942
  getCurrentSnapshot: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), CameraOccupancySnapshotSchema.nullable()),
235943
+ /**
235944
+ * The same snapshot, for a SET of cameras, in one round trip.
235945
+ *
235946
+ * The Events page's Stationary section polls this every 15s for every
235947
+ * selected camera. Fanned out client-side that is one query per camera to
235948
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
235949
+ * costs everything. Batched, N transports become one and the per-device
235950
+ * work is unchanged.
235951
+ *
235952
+ * Every requested deviceId gets a row, tagged `read` — see
235953
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
235954
+ * not answer for is `'unreadable'`, never an empty reading.
235955
+ */
235956
+ getCurrentSnapshotBatch: require_sleep.method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(CameraOccupancySnapshotForDeviceSchema).readonly()),
235425
235957
  /** Time-series object count inside one zone. `className` optional —
235426
235958
  * omit to count every class in the zone. */
235427
235959
  getZoneHistory: require_sleep.method(zod.z.object({
@@ -242263,6 +242795,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242263
242795
  addonId: null,
242264
242796
  access: "view"
242265
242797
  },
242798
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
242799
+ capName: "pipeline-analytics",
242800
+ capScope: "device",
242801
+ addonId: null,
242802
+ access: "view"
242803
+ },
242266
242804
  "pipelineAnalytics.getEventStoreFootprint": {
242267
242805
  capName: "pipeline-analytics",
242268
242806
  capScope: "device",
@@ -242281,6 +242819,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242281
242819
  addonId: null,
242282
242820
  access: "view"
242283
242821
  },
242822
+ "pipelineAnalytics.getKeyEventsBatch": {
242823
+ capName: "pipeline-analytics",
242824
+ capScope: "device",
242825
+ addonId: null,
242826
+ access: "view"
242827
+ },
242284
242828
  "pipelineAnalytics.getMotionEvents": {
242285
242829
  capName: "pipeline-analytics",
242286
242830
  capScope: "device",
@@ -243457,6 +244001,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243457
244001
  addonId: null,
243458
244002
  access: "view"
243459
244003
  },
244004
+ "recording.reconcileLedgerAgainstDisk": {
244005
+ capName: "recording",
244006
+ capScope: "system",
244007
+ addonId: null,
244008
+ access: "create"
244009
+ },
243460
244010
  "recording.refreshStorageLocationsForMigration": {
243461
244011
  capName: "recording",
243462
244012
  capScope: "system",
@@ -243583,6 +244133,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243583
244133
  addonId: null,
243584
244134
  access: "view"
243585
244135
  },
244136
+ "sceneMonitor.listScenesBatch": {
244137
+ capName: "scene-monitor",
244138
+ capScope: "device",
244139
+ addonId: null,
244140
+ access: "view"
244141
+ },
243586
244142
  "sceneMonitor.recheckNow": {
243587
244143
  capName: "scene-monitor",
243588
244144
  capScope: "device",
@@ -244939,6 +245495,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244939
245495
  addonId: null,
244940
245496
  access: "view"
244941
245497
  },
245498
+ "zoneAnalytics.getCurrentSnapshotBatch": {
245499
+ capName: "zone-analytics",
245500
+ capScope: "device",
245501
+ addonId: null,
245502
+ access: "view"
245503
+ },
244942
245504
  "zoneAnalytics.getUnzonedHistory": {
244943
245505
  capName: "zone-analytics",
244944
245506
  capScope: "device",
@@ -246186,6 +246748,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246186
246748
  form: "single",
246187
246749
  optional: false
246188
246750
  }],
246751
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
246752
+ name: "deviceId",
246753
+ form: "single",
246754
+ optional: true
246755
+ }],
246189
246756
  "pipelineAnalytics.getGroup": [{
246190
246757
  name: "deviceId",
246191
246758
  form: "single",
@@ -246196,6 +246763,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246196
246763
  form: "single",
246197
246764
  optional: false
246198
246765
  }],
246766
+ "pipelineAnalytics.getKeyEventsBatch": [{
246767
+ name: "deviceIds",
246768
+ form: "array",
246769
+ optional: false
246770
+ }],
246199
246771
  "pipelineAnalytics.getMotionEvents": [{
246200
246772
  name: "deviceId",
246201
246773
  form: "single",
@@ -246636,6 +247208,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246636
247208
  form: "single",
246637
247209
  optional: false
246638
247210
  }],
247211
+ "recording.reconcileLedgerAgainstDisk": [{
247212
+ name: "deviceId",
247213
+ form: "single",
247214
+ optional: true
247215
+ }],
246639
247216
  "recording.relocateFootage": [{
246640
247217
  name: "deviceId",
246641
247218
  form: "single",
@@ -246701,6 +247278,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246701
247278
  form: "single",
246702
247279
  optional: false
246703
247280
  }],
247281
+ "sceneMonitor.listScenesBatch": [{
247282
+ name: "deviceIds",
247283
+ form: "array",
247284
+ optional: false
247285
+ }],
246704
247286
  "sceneMonitor.recheckNow": [{
246705
247287
  name: "deviceId",
246706
247288
  form: "single",
@@ -246962,6 +247544,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246962
247544
  form: "single",
246963
247545
  optional: false
246964
247546
  }],
247547
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
247548
+ name: "deviceIds",
247549
+ form: "array",
247550
+ optional: false
247551
+ }],
246965
247552
  "zoneAnalytics.getUnzonedHistory": [{
246966
247553
  name: "deviceId",
246967
247554
  form: "single",
@@ -247114,6 +247701,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247114
247701
  "recording.readGopBytes",
247115
247702
  "recording.readSegmentBytes",
247116
247703
  "recording.readWindowBytes",
247704
+ "recording.reconcileLedgerAgainstDisk",
247117
247705
  "recording.relocateFootage",
247118
247706
  "recording.renderClip",
247119
247707
  "recording.renderGif",
@@ -247999,6 +248587,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247999
248587
  relocateFootage: (input) => dispatch("recording", "relocateFootage", "mutation", input),
248000
248588
  listRelocateJobs: (input) => dispatch("recording", "listRelocateJobs", "query", input),
248001
248589
  getRelocateResidue: (input) => dispatch("recording", "getRelocateResidue", "query", input),
248590
+ reconcileLedgerAgainstDisk: (input) => dispatch("recording", "reconcileLedgerAgainstDisk", "mutation", input),
248002
248591
  cancelRelocateJob: (input) => dispatch("recording", "cancelRelocateJob", "mutation", input),
248003
248592
  planStorageRebalance: (input) => dispatch("recording", "planStorageRebalance", "query", input),
248004
248593
  startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input)
@@ -250959,6 +251548,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250959
251548
  exports.CameraMetricsSchema = CameraMetricsSchema;
250960
251549
  exports.CameraMetricsWithDeviceIdSchema = CameraMetricsWithDeviceIdSchema;
250961
251550
  exports.CameraMotionStatusSchema = CameraMotionStatusSchema;
251551
+ exports.CameraOccupancySnapshotForDeviceSchema = CameraOccupancySnapshotForDeviceSchema;
250962
251552
  exports.CameraRecordingModeSchema = CameraRecordingModeSchema;
250963
251553
  exports.CameraRecordingStatusSchema = CameraRecordingStatusSchema;
250964
251554
  exports.CameraSourceStatusSchema = CameraSourceStatusSchema;
@@ -251209,6 +251799,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251209
251799
  exports.LabelTierSchema = LabelTierSchema;
251210
251800
  exports.LawnMowerActivitySchema = LawnMowerActivitySchema;
251211
251801
  exports.LawnMowerControlStatusSchema = LawnMowerControlStatusSchema;
251802
+ exports.LedgerWalkDeviceReportSchema = LedgerWalkDeviceReportSchema;
251803
+ exports.LedgerWalkInputSchema = LedgerWalkInputSchema;
251804
+ exports.LedgerWalkRefusalSchema = LedgerWalkRefusalSchema;
251805
+ exports.LedgerWalkReportSchema = LedgerWalkReportSchema;
251806
+ exports.LedgerWalkSkipCountsSchema = LedgerWalkSkipCountsSchema;
251807
+ exports.LedgerWalkSkipReasonSchema = LedgerWalkSkipReasonSchema;
251212
251808
  exports.LinkedDeviceSchema = LinkedDeviceSchema;
251213
251809
  exports.LinkedDevicesModeSchema = LinkedDevicesModeSchema;
251214
251810
  exports.ListGroupsPageSchema = ListGroupsPageSchema;
@@ -251291,6 +251887,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251291
251887
  exports.MaskShapeKindSchema = MaskShapeKindSchema;
251292
251888
  exports.MaskShapeSchema = MaskShapeSchema;
251293
251889
  exports.MediaFileInfoSchema = MediaFileInfoSchema;
251890
+ exports.MediaFileKindEnum = MediaFileKindEnum;
251294
251891
  exports.MediaFileRefSchema = MediaFileRefSchema;
251295
251892
  exports.MediaFileSchema = MediaFileSchema;
251296
251893
  exports.MediaPlayerRepeatSchema = MediaPlayerRepeatSchema;
@@ -251629,6 +252226,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251629
252226
  exports.SceneConfirmSchema = SceneConfirmSchema;
251630
252227
  exports.SceneMonitorSchema = SceneMonitorSchema;
251631
252228
  exports.SceneMonitorStateSchema = SceneMonitorStateSchema;
252229
+ exports.SceneMonitorStatusForDeviceSchema = SceneMonitorStatusForDeviceSchema;
251632
252230
  exports.SceneMonitorStatusSchema = SceneMonitorStatusSchema;
251633
252231
  exports.SceneReferenceSchema = SceneReferenceSchema;
251634
252232
  exports.SceneUnavailableSchema = SceneUnavailableSchema;
@@ -410449,6 +411047,7 @@ var require_cap_providers = __commonJS({
410449
411047
  exports.buildSettingsStoreGateway = buildSettingsStoreGateway;
410450
411048
  exports.buildNetworkQualityProvider = buildNetworkQualityProvider;
410451
411049
  exports.buildToastProvider = buildToastProvider;
411050
+ exports.computeTopologyPass = computeTopologyPass;
410452
411051
  exports.computeTopology = computeTopology;
410453
411052
  exports.createNodeRootPackageLookup = createNodeRootPackageLookup;
410454
411053
  exports.buildNodesProvider = buildNodesProvider;
@@ -410597,9 +411196,19 @@ var require_cap_providers = __commonJS({
410597
411196
  }
410598
411197
  return ips;
410599
411198
  }
411199
+ async function computeTopologyPass(agentRegistry, addonRegistry, getNodeRootPackage) {
411200
+ const [nodes, liveNodes] = await computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage);
411201
+ return { nodes, liveNodes };
411202
+ }
410600
411203
  async function computeTopology(agentRegistry, addonRegistry, getNodeRootPackage) {
410601
- const nodes = await agentRegistry.listNodes();
410602
- const history = await agentRegistry.getClusterNodeHistory();
411204
+ const [nodes] = await computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage);
411205
+ return nodes;
411206
+ }
411207
+ async function computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage) {
411208
+ const [nodes, history] = await Promise.all([
411209
+ agentRegistry.listNodes(),
411210
+ agentRegistry.getClusterNodeHistory()
411211
+ ]);
410603
411212
  const historyById = new Map(history.map((h) => [h.id, h]));
410604
411213
  const liveIds = new Set(nodes.map((n) => n.info.id));
410605
411214
  const allAddons = addonRegistry?.listAddons() ?? [];
@@ -410754,7 +411363,7 @@ var require_cap_providers = __commonJS({
410754
411363
  categories: [],
410755
411364
  rootPackage: null
410756
411365
  }));
410757
- return [...liveNodes, ...offlineNodes];
411366
+ return [[...liveNodes, ...offlineNodes], nodes];
410758
411367
  }
410759
411368
  function createNodeRootPackageLookup(moleculer, serverUpdate) {
410760
411369
  return (nodeId, isHub) => {
@@ -420480,6 +421089,19 @@ var require_agent_registry_service = __commonJS({
420480
421089
  addonIds: [...entry.agentAddons ?? []]
420481
421090
  };
420482
421091
  }
421092
+ function toSubProcess(p) {
421093
+ return {
421094
+ pid: p.pid ?? 0,
421095
+ name: p.name ?? "",
421096
+ command: "moleculer-service",
421097
+ state: p.state ?? "running",
421098
+ cpuPercent: p.cpuPercent ?? 0,
421099
+ memoryRss: p.memoryRss ?? 0,
421100
+ uptimeSeconds: p.uptimeSeconds ?? 0,
421101
+ addonIds: p.addonIds ?? [],
421102
+ groupId: p.groupId ?? null
421103
+ };
421104
+ }
420483
421105
  var AgentRegistryService = class {
420484
421106
  eventBus;
420485
421107
  moleculer;
@@ -421006,107 +421628,113 @@ var require_agent_registry_service = __commonJS({
421006
421628
  return toNodeLiveness(this.moleculer.broker.registry.getNodeList({ onlyAvailable: false }));
421007
421629
  }
421008
421630
  async listNodes() {
421009
- let hubProcesses = [];
421631
+ const registry = this.moleculer.broker.registry;
421632
+ const nodes = registry?.getNodeList?.({ onlyAvailable: false }) ?? [];
421633
+ const remoteTargets = nodes.filter((node) => typeof node.id === "string" && node.id !== "hub" && !node.id.includes("/"));
421634
+ const [hubEntry, remoteEntries] = await Promise.all([
421635
+ this.listHubProcesses().then((procs) => this.buildHubEntry(procs)),
421636
+ Promise.all(remoteTargets.map((node) => node.available ? (
421637
+ // Online: one wave of `$agent.status` + `$process.list`.
421638
+ this.buildRemoteEntry(node.id)
421639
+ ) : (
421640
+ // Offline: skip the RPC fan-out entirely (the node is unreachable —
421641
+ // those calls would just time out) and surface a minimal,
421642
+ // honestly-degraded row instead.
421643
+ Promise.resolve(this.buildOfflineEntry(node))
421644
+ )))
421645
+ ]);
421646
+ return [hubEntry, ...remoteEntries.filter((entry) => entry !== null)];
421647
+ }
421648
+ /** The hub's own runner subprocesses (`$process.list`, hub-local). */
421649
+ async listHubProcesses() {
421010
421650
  try {
421011
421651
  const processes = await this.broker.call("$process.list");
421012
- hubProcesses = processes.map((p) => ({
421013
- pid: p.pid ?? 0,
421014
- name: p.name ?? "",
421015
- command: "moleculer-service",
421016
- state: p.state ?? "running",
421017
- cpuPercent: p.cpuPercent ?? 0,
421018
- memoryRss: p.memoryRss ?? 0,
421019
- uptimeSeconds: p.uptimeSeconds ?? 0,
421020
- addonIds: p.addonIds ?? [],
421021
- groupId: p.groupId ?? null
421022
- }));
421652
+ return processes.map(toSubProcess);
421023
421653
  } catch {
421654
+ return [];
421024
421655
  }
421025
- const hubEntry = await this.buildHubEntry(hubProcesses);
421026
- const remoteEntries = [];
421027
- const registry = this.moleculer.broker.registry;
421028
- const nodes = registry?.getNodeList?.({ onlyAvailable: false }) ?? [];
421029
- for (const node of nodes) {
421030
- const nodeId = node.id;
421031
- if (typeof nodeId !== "string" || nodeId === "hub" || nodeId.includes("/"))
421032
- continue;
421033
- if (!node.available) {
421034
- remoteEntries.push(this.buildOfflineEntry(node));
421035
- continue;
421036
- }
421037
- try {
421038
- const status = await this.broker.call("$agent.status", {}, {
421039
- nodeID: nodeId,
421040
- timeout: 5e3
421041
- });
421042
- let subProcesses = [];
421043
- try {
421044
- const processes = await this.broker.call("$process.list", {}, {
421045
- nodeID: nodeId,
421046
- timeout: 5e3
421047
- });
421048
- subProcesses = processes.map((p) => ({
421049
- pid: p.pid ?? 0,
421050
- name: p.name ?? "",
421051
- command: "moleculer-service",
421052
- state: p.state ?? "running",
421053
- cpuPercent: p.cpuPercent ?? 0,
421054
- memoryRss: p.memoryRss ?? 0,
421055
- uptimeSeconds: p.uptimeSeconds ?? 0,
421056
- addonIds: p.addonIds ?? [],
421057
- groupId: p.groupId ?? null
421058
- }));
421059
- } catch {
421060
- subProcesses = status.addons?.map((a) => ({
421061
- pid: 0,
421062
- name: a.id ?? "",
421063
- command: "moleculer-service",
421064
- state: a.status ?? "running",
421065
- cpuPercent: 0,
421066
- memoryRss: 0,
421067
- uptimeSeconds: 0
421068
- })) ?? [];
421069
- }
421070
- const agentAddons = status.addons?.map((a) => a.id) ?? [];
421071
- const hostname = typeof status.hostname === "string" ? status.hostname : null;
421072
- const agentName = typeof status.name === "string" ? status.name : nodeId;
421073
- remoteEntries.push({
421074
- info: {
421075
- id: nodeId,
421076
- name: agentName,
421077
- hostname: hostname ?? nodeId,
421078
- capabilities: [],
421079
- platform: status.platform ?? "unknown",
421080
- arch: status.arch ?? "unknown",
421081
- cpuCores: status.cpuCores ?? 0,
421082
- memoryMB: status.totalMemoryMB ?? 0,
421083
- cpuModel: status.cpuModel
421084
- },
421085
- localIps: Array.isArray(status.localIps) ? status.localIps : [],
421086
- status: {
421087
- activeCameras: 0,
421088
- cpuPercent: status.cpuPercent ?? 0,
421089
- memoryPercent: status.memoryPercent ?? 0,
421090
- fps: {},
421091
- errors: []
421092
- },
421093
- connectedSince: typeof status.uptime === "number" ? Date.now() - status.uptime * 1e3 : Date.now(),
421094
- isHub: false,
421095
- subProcesses,
421096
- agentAddons
421097
- });
421098
- } catch {
421099
- }
421100
- }
421101
- await this.snapshotOnlineNodes([hubEntry, ...remoteEntries]);
421102
- return [hubEntry, ...remoteEntries];
421103
421656
  }
421104
421657
  /**
421105
- * Best-effort upsert of every online entry's descriptor into the history
421106
- * store. Never throws the store swallows its own errors and this awaits
421107
- * `allSettled` so a storage hiccup can never fail `listNodes()`.
421658
+ * One online agent's row: `$agent.status` for the descriptor + live metrics,
421659
+ * `$process.list` for real sub-process stats. `null` when the node has no
421660
+ * `$agent` service (it is then simply absent from the list, as before).
421661
+ *
421662
+ * The two calls are ONE wave, not two: `$process.list` never reads anything
421663
+ * from `$agent.status` — only its FALLBACK does, and the fallback is applied
421664
+ * after both have settled.
421665
+ */
421666
+ async buildRemoteEntry(nodeId) {
421667
+ const [statusResult, processResult] = await Promise.allSettled([
421668
+ this.broker.call("$agent.status", {}, { nodeID: nodeId, timeout: 5e3 }),
421669
+ this.broker.call("$process.list", {}, { nodeID: nodeId, timeout: 5e3 })
421670
+ ]);
421671
+ if (statusResult.status === "rejected")
421672
+ return null;
421673
+ const status = statusResult.value;
421674
+ const subProcesses = processResult.status === "fulfilled" ? processResult.value.map(toSubProcess) : (
421675
+ // Fall back to the addon list from $agent.status (no stats)
421676
+ status.addons?.map((a) => ({
421677
+ pid: 0,
421678
+ name: a.id ?? "",
421679
+ command: "moleculer-service",
421680
+ state: a.status ?? "running",
421681
+ cpuPercent: 0,
421682
+ memoryRss: 0,
421683
+ uptimeSeconds: 0
421684
+ })) ?? []
421685
+ );
421686
+ const agentAddons = status.addons?.map((a) => a.id) ?? [];
421687
+ const hostname = typeof status.hostname === "string" ? status.hostname : null;
421688
+ const agentName = typeof status.name === "string" ? status.name : nodeId;
421689
+ return {
421690
+ info: {
421691
+ id: nodeId,
421692
+ name: agentName,
421693
+ hostname: hostname ?? nodeId,
421694
+ capabilities: [],
421695
+ platform: status.platform ?? "unknown",
421696
+ arch: status.arch ?? "unknown",
421697
+ cpuCores: status.cpuCores ?? 0,
421698
+ memoryMB: status.totalMemoryMB ?? 0,
421699
+ cpuModel: status.cpuModel
421700
+ },
421701
+ localIps: Array.isArray(status.localIps) ? status.localIps : [],
421702
+ status: {
421703
+ activeCameras: 0,
421704
+ cpuPercent: status.cpuPercent ?? 0,
421705
+ memoryPercent: status.memoryPercent ?? 0,
421706
+ fps: {},
421707
+ errors: []
421708
+ },
421709
+ connectedSince: typeof status.uptime === "number" ? Date.now() - status.uptime * 1e3 : Date.now(),
421710
+ isHub: false,
421711
+ subProcesses,
421712
+ agentAddons
421713
+ };
421714
+ }
421715
+ /**
421716
+ * A1 (R2 capture point): snapshot every ONLINE node's static descriptor +
421717
+ * last-known addon roster into the durable, routing-blind history store.
421718
+ * This is the ONLY place cpuModel/cores/memory/localIps are resolved from a
421719
+ * live `$agent.status`, so it is where an offline row's descriptor must be
421720
+ * captured while the node is still reachable. Offline entries are NOT
421721
+ * re-snapshotted — their `lastActive` is stamped on `$node.disconnected`
421722
+ * instead. This is a separate durable store, never a `knownAgents`-style
421723
+ * shadow of `HubNodeRegistry` (which stays the ephemeral live cap authority).
421724
+ *
421725
+ * **It belongs to the PERIODIC PASS, not to a read** (D317). It used to run
421726
+ * at the tail of `listNodes()`, which made every operator poll of
421727
+ * `nodes.topology` perform one durable read-back — and, past the five-minute
421728
+ * heartbeat, one durable write — per online node, in a phase that had to
421729
+ * finish before the history read could even start. Measured on the live hub
421730
+ * on 2026-08-31: one settings-store round trip cost 14.8–18.3 s and
421731
+ * `nodes.topology` cost exactly two of them, 30.5 s. `TopologyEmitterService`
421732
+ * already recomputes this picture every 30 s; the capture is its work.
421733
+ *
421734
+ * Never throws — the store swallows its own errors and this awaits
421735
+ * `allSettled` so a storage hiccup can never fail the pass.
421108
421736
  */
421109
- async snapshotOnlineNodes(entries) {
421737
+ async captureNodeHistory(entries) {
421110
421738
  if (!this.historyStore)
421111
421739
  return;
421112
421740
  const online = entries.filter((e) => e.isOnline !== false);
@@ -423756,7 +424384,7 @@ var require_topology_emitter_service = __commonJS({
423756
424384
  return;
423757
424385
  this.emitting = true;
423758
424386
  try {
423759
- const nodes = await (0, cap_providers_1.computeTopology)(this.agentRegistry, this.addonRegistry, this.getNodeRootPackage);
424387
+ const { nodes, liveNodes } = await (0, cap_providers_1.computeTopologyPass)(this.agentRegistry, this.addonRegistry, this.getNodeRootPackage);
423760
424388
  this.eventBus.emit({
423761
424389
  id: (0, node_crypto_1.randomUUID)(),
423762
424390
  timestamp: /* @__PURE__ */ new Date(),
@@ -423764,6 +424392,7 @@ var require_topology_emitter_service = __commonJS({
423764
424392
  category: types_1.EventCategory.ClusterTopologySnapshot,
423765
424393
  data: { nodes, timestamp: Date.now() }
423766
424394
  });
424395
+ await this.agentRegistry.captureNodeHistory(liveNodes);
423767
424396
  } catch {
423768
424397
  } finally {
423769
424398
  this.emitting = false;