camstack 1.2.68 → 1.2.70

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-CCIBlacf.js
23637
- var require_dist_CCIBlacf = __commonJS({
23638
- "../system/dist/dist-CCIBlacf.js"(exports) {
23636
+ // ../system/dist/dist-BhE8zNfY.js
23637
+ var require_dist_BhE8zNfY = __commonJS({
23638
+ "../system/dist/dist-BhE8zNfY.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_CCIBlacf = __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_CCIBlacf = __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_CCIBlacf = __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_CCIBlacf = __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(),
@@ -37190,6 +37286,39 @@ var require_dist_CCIBlacf = __commonJS({
37190
37286
  /** Present when the pass ended by throwing. */
37191
37287
  error: zod.z.string().nullable()
37192
37288
  });
37289
+ var MediaReclaimStartResultSchema = zod.z.object({
37290
+ started: zod.z.boolean(),
37291
+ /** True when a pass was already running; the new request is ignored. */
37292
+ alreadyRunning: zod.z.boolean()
37293
+ });
37294
+ var MediaReclaimInputSchema = zod.z.object({
37295
+ mode: zod.z.enum(["report", "reclaim"]).default("report"),
37296
+ scopes: zod.z.array(zod.z.enum(["motion-still", "track-filmstrip"])).min(1).optional(),
37297
+ deviceIds: zod.z.array(zod.z.number().int()).min(1).optional(),
37298
+ restart: zod.z.boolean().optional(),
37299
+ pageSize: zod.z.number().int().min(50).max(5e3).optional(),
37300
+ maxRowsPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
37301
+ maxReclaimPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
37302
+ maxBytesPerRun: zod.z.number().int().min(1).optional(),
37303
+ budgetMinutes: zod.z.number().int().min(1).max(720).optional(),
37304
+ throttleBytesPerSec: zod.z.number().int().min(64 * 1024).optional(),
37305
+ graceMinutes: zod.z.number().int().min(1).max(10080).optional()
37306
+ });
37307
+ var MediaReclaimStatusSchema = zod.z.object({
37308
+ running: zod.z.boolean(),
37309
+ mode: zod.z.enum(["report", "reclaim"]).nullable(),
37310
+ totalExamined: zod.z.number(),
37311
+ totalEligible: zod.z.number(),
37312
+ totalReclaimed: zod.z.number(),
37313
+ totalBytesReclaimed: zod.z.number(),
37314
+ totalRefused: zod.z.number(),
37315
+ /** Device+scope windows finished in this pass. */
37316
+ devicesDone: zod.z.number(),
37317
+ complete: zod.z.boolean().nullable(),
37318
+ startedAtMs: zod.z.number().nullable(),
37319
+ finishedAtMs: zod.z.number().nullable(),
37320
+ error: zod.z.string().nullable()
37321
+ });
37193
37322
  var ReplayFrameInputSchema = zod.z.object({
37194
37323
  timestamp: zod.z.number(),
37195
37324
  frame: PipelineRunResultBridge
@@ -37320,6 +37449,21 @@ var require_dist_CCIBlacf = __commonJS({
37320
37449
  * scored on-read (no write). Degrades to `[]` on error.
37321
37450
  */
37322
37451
  getKeyEvents: method(KeyEventQueryInput, zod.z.array(KeyEventSchema).readonly()),
37452
+ /**
37453
+ * The same ranking, for a SET of cameras, in one round trip.
37454
+ *
37455
+ * The Detection Intelligence events feed asks this of every selected
37456
+ * camera and re-asks on a 30s timer. Fanned out client-side that is N
37457
+ * round trips — browser → hub → post-analysis — for N independent,
37458
+ * already-indexed store queries. Batched, the queries are unchanged and
37459
+ * run concurrently INSIDE the owner; only the transport collapses.
37460
+ *
37461
+ * Deliberately per-device rather than pre-merged: `limit` stays per
37462
+ * camera (a total would let a busy camera starve a quiet one), and a
37463
+ * caller that renders one camera's lane needs to know which camera a row
37464
+ * came from. `getKeyEvents` stays for single-device callers.
37465
+ */
37466
+ getKeyEventsBatch: method(KeyEventBatchQueryInput, zod.z.array(KeyEventsForDeviceSchema).readonly()),
37323
37467
  /** Server-side bucketed event counts for the 24-hour timeline.
37324
37468
  * Returns one entry per non-empty bucket; empty buckets are omitted. */
37325
37469
  getEventDensity: method(zod.z.object({
@@ -37598,6 +37742,20 @@ var require_dist_CCIBlacf = __commonJS({
37598
37742
  auth: "admin"
37599
37743
  }),
37600
37744
  /**
37745
+ * Reclaim the standing debug-media backlog (motion-visit stills + track
37746
+ * filmstrips). DETACHED: returns `{ started }` immediately. The pass is
37747
+ * tens of minutes; awaiting it is a timeout, not a verdict. Dry-run unless
37748
+ * `mode: 'reclaim'`. Poll `getMediaReclaimStatus`.
37749
+ */
37750
+ reclaimDebugMedia: method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
37751
+ kind: "mutation",
37752
+ auth: "admin"
37753
+ }),
37754
+ getMediaReclaimStatus: method(zod.z.object({}), MediaReclaimStatusSchema, {
37755
+ kind: "query",
37756
+ auth: "admin"
37757
+ }),
37758
+ /**
37601
37759
  * The CHEAP QUESTION, asked before any media moves: how big is the dataset
37602
37760
  * the marked (`markForTrain`) tracks would produce?
37603
37761
  *
@@ -47264,6 +47422,23 @@ var require_dist_CCIBlacf = __commonJS({
47264
47422
  kind: "query",
47265
47423
  auth: "admin"
47266
47424
  }),
47425
+ /**
47426
+ * Does this location's LEDGER tell the truth about its disk? (D319)
47427
+ *
47428
+ * One `readdir` per claimed hour, diffed both ways: durable rows whose file
47429
+ * is not there, and files no durable row names. `apply` defaults to FALSE —
47430
+ * the report is the product, and the dry run is how an operator
47431
+ * sanity-checks the destructive run before authorising it.
47432
+ *
47433
+ * REFUSES a source that is still a write target: a listing of a live
47434
+ * location is a lower bound, which is not the strong evidence that lets
47435
+ * this pass forget without a budget. A live location is reconciled by the
47436
+ * mover, under D318's bound.
47437
+ */
47438
+ reconcileLedgerAgainstDisk: method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
47439
+ kind: "mutation",
47440
+ auth: "admin"
47441
+ }),
47267
47442
  /** Cancel a running or queued relocate job. A queued job never runs. */
47268
47443
  cancelRelocateJob: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
47269
47444
  kind: "mutation",
@@ -47561,6 +47736,10 @@ var require_dist_CCIBlacf = __commonJS({
47561
47736
  monitors: zod.z.array(SceneMonitorSchema),
47562
47737
  lastFetchedAt: zod.z.number()
47563
47738
  });
47739
+ var SceneMonitorStatusForDeviceSchema = zod.z.object({
47740
+ deviceId: zod.z.number(),
47741
+ status: SceneMonitorStatusSchema.nullable()
47742
+ });
47564
47743
  var sceneMonitorCapability = {
47565
47744
  name: "scene-monitor",
47566
47745
  scope: "device",
@@ -47570,6 +47749,22 @@ var require_dist_CCIBlacf = __commonJS({
47570
47749
  deviceTypes: [DeviceType.Camera],
47571
47750
  methods: {
47572
47751
  listScenes: method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
47752
+ /**
47753
+ * The same answer, for a SET of cameras, in one round trip.
47754
+ *
47755
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
47756
+ * poll behind the push slice. Fanned out client-side that was one query
47757
+ * per camera — 29 round trips through the browser, the hub and the
47758
+ * post-analysis runner every 30 seconds to read an in-memory map the
47759
+ * owner had already merged. The work is unchanged (`statusFor` per
47760
+ * device, all in-process at the owner); what collapses is the transport.
47761
+ *
47762
+ * A camera that cannot answer still gets a row, with `status: null` —
47763
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
47764
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
47765
+ * tell which two are missing, or that any are.
47766
+ */
47767
+ listScenesBatch: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(SceneMonitorStatusForDeviceSchema).readonly()),
47573
47768
  createScene: method(zod.z.object({
47574
47769
  deviceId: zod.z.number(),
47575
47770
  label: zod.z.string(),
@@ -49370,6 +49565,11 @@ var require_dist_CCIBlacf = __commonJS({
49370
49565
  * than as repeated tracks/events. */
49371
49566
  stationaryObjects: zod.z.array(StationaryObjectSchema).readonly().optional()
49372
49567
  });
49568
+ var CameraOccupancySnapshotForDeviceSchema = zod.z.object({
49569
+ deviceId: zod.z.number(),
49570
+ read: zod.z.enum(["read", "unreadable"]),
49571
+ snapshot: CameraOccupancySnapshotSchema.nullable()
49572
+ });
49373
49573
  var HistoryResolutionEnum = zod.z.enum([
49374
49574
  "minute",
49375
49575
  "5min",
@@ -49399,6 +49599,20 @@ var require_dist_CCIBlacf = __commonJS({
49399
49599
  * (no inference result emitted since boot or since binding was
49400
49600
  * activated). */
49401
49601
  getCurrentSnapshot: method(zod.z.object({ deviceId: zod.z.number() }), CameraOccupancySnapshotSchema.nullable()),
49602
+ /**
49603
+ * The same snapshot, for a SET of cameras, in one round trip.
49604
+ *
49605
+ * The Events page's Stationary section polls this every 15s for every
49606
+ * selected camera. Fanned out client-side that is one query per camera to
49607
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
49608
+ * costs everything. Batched, N transports become one and the per-device
49609
+ * work is unchanged.
49610
+ *
49611
+ * Every requested deviceId gets a row, tagged `read` — see
49612
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
49613
+ * not answer for is `'unreadable'`, never an empty reading.
49614
+ */
49615
+ getCurrentSnapshotBatch: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(CameraOccupancySnapshotForDeviceSchema).readonly()),
49402
49616
  /** Time-series object count inside one zone. `className` optional —
49403
49617
  * omit to count every class in the zone. */
49404
49618
  getZoneHistory: method(zod.z.object({
@@ -53346,6 +53560,18 @@ var require_dist_CCIBlacf = __commonJS({
53346
53560
  addonId: null,
53347
53561
  access: "view"
53348
53562
  },
53563
+ "pipelineAnalytics.getKeyEventsBatch": {
53564
+ capName: "pipeline-analytics",
53565
+ capScope: "device",
53566
+ addonId: null,
53567
+ access: "view"
53568
+ },
53569
+ "pipelineAnalytics.getMediaReclaimStatus": {
53570
+ capName: "pipeline-analytics",
53571
+ capScope: "device",
53572
+ addonId: null,
53573
+ access: "view"
53574
+ },
53349
53575
  "pipelineAnalytics.getMotionEvents": {
53350
53576
  capName: "pipeline-analytics",
53351
53577
  capScope: "device",
@@ -53520,6 +53746,12 @@ var require_dist_CCIBlacf = __commonJS({
53520
53746
  addonId: null,
53521
53747
  access: "create"
53522
53748
  },
53749
+ "pipelineAnalytics.reclaimDebugMedia": {
53750
+ capName: "pipeline-analytics",
53751
+ capScope: "device",
53752
+ addonId: null,
53753
+ access: "create"
53754
+ },
53523
53755
  "pipelineAnalytics.reconcileFromDisk": {
53524
53756
  capName: "pipeline-analytics",
53525
53757
  capScope: "device",
@@ -54522,6 +54754,12 @@ var require_dist_CCIBlacf = __commonJS({
54522
54754
  addonId: null,
54523
54755
  access: "view"
54524
54756
  },
54757
+ "recording.reconcileLedgerAgainstDisk": {
54758
+ capName: "recording",
54759
+ capScope: "system",
54760
+ addonId: null,
54761
+ access: "create"
54762
+ },
54525
54763
  "recording.refreshStorageLocationsForMigration": {
54526
54764
  capName: "recording",
54527
54765
  capScope: "system",
@@ -54648,6 +54886,12 @@ var require_dist_CCIBlacf = __commonJS({
54648
54886
  addonId: null,
54649
54887
  access: "view"
54650
54888
  },
54889
+ "sceneMonitor.listScenesBatch": {
54890
+ capName: "scene-monitor",
54891
+ capScope: "device",
54892
+ addonId: null,
54893
+ access: "view"
54894
+ },
54651
54895
  "sceneMonitor.recheckNow": {
54652
54896
  capName: "scene-monitor",
54653
54897
  capScope: "device",
@@ -56004,6 +56248,12 @@ var require_dist_CCIBlacf = __commonJS({
56004
56248
  addonId: null,
56005
56249
  access: "view"
56006
56250
  },
56251
+ "zoneAnalytics.getCurrentSnapshotBatch": {
56252
+ capName: "zone-analytics",
56253
+ capScope: "device",
56254
+ addonId: null,
56255
+ access: "view"
56256
+ },
56007
56257
  "zoneAnalytics.getUnzonedHistory": {
56008
56258
  capName: "zone-analytics",
56009
56259
  capScope: "device",
@@ -57008,6 +57258,11 @@ var require_dist_CCIBlacf = __commonJS({
57008
57258
  form: "single",
57009
57259
  optional: false
57010
57260
  }],
57261
+ "pipelineAnalytics.getKeyEventsBatch": [{
57262
+ name: "deviceIds",
57263
+ form: "array",
57264
+ optional: false
57265
+ }],
57011
57266
  "pipelineAnalytics.getMotionEvents": [{
57012
57267
  name: "deviceId",
57013
57268
  form: "single",
@@ -57113,6 +57368,11 @@ var require_dist_CCIBlacf = __commonJS({
57113
57368
  form: "single",
57114
57369
  optional: true
57115
57370
  }],
57371
+ "pipelineAnalytics.reclaimDebugMedia": [{
57372
+ name: "deviceIds",
57373
+ form: "array",
57374
+ optional: true
57375
+ }],
57116
57376
  "pipelineAnalytics.reconcileFromDisk": [{
57117
57377
  name: "deviceId",
57118
57378
  form: "single",
@@ -57448,6 +57708,11 @@ var require_dist_CCIBlacf = __commonJS({
57448
57708
  form: "single",
57449
57709
  optional: false
57450
57710
  }],
57711
+ "recording.reconcileLedgerAgainstDisk": [{
57712
+ name: "deviceId",
57713
+ form: "single",
57714
+ optional: true
57715
+ }],
57451
57716
  "recording.relocateFootage": [{
57452
57717
  name: "deviceId",
57453
57718
  form: "single",
@@ -57513,6 +57778,11 @@ var require_dist_CCIBlacf = __commonJS({
57513
57778
  form: "single",
57514
57779
  optional: false
57515
57780
  }],
57781
+ "sceneMonitor.listScenesBatch": [{
57782
+ name: "deviceIds",
57783
+ form: "array",
57784
+ optional: false
57785
+ }],
57516
57786
  "sceneMonitor.recheckNow": [{
57517
57787
  name: "deviceId",
57518
57788
  form: "single",
@@ -57774,6 +58044,11 @@ var require_dist_CCIBlacf = __commonJS({
57774
58044
  form: "single",
57775
58045
  optional: false
57776
58046
  }],
58047
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
58048
+ name: "deviceIds",
58049
+ form: "array",
58050
+ optional: false
58051
+ }],
57777
58052
  "zoneAnalytics.getUnzonedHistory": [{
57778
58053
  name: "deviceId",
57779
58054
  form: "single",
@@ -59207,7 +59482,7 @@ var require_alerts_addon = __commonJS({
59207
59482
  [Symbol.toStringTag]: { value: "Module" }
59208
59483
  });
59209
59484
  require_chunk_Cek0wNdY();
59210
- var require_dist10 = require_dist_CCIBlacf();
59485
+ var require_dist10 = require_dist_BhE8zNfY();
59211
59486
  function selectExpired(alerts, cutoffMs) {
59212
59487
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59213
59488
  }
@@ -60026,7 +60301,7 @@ var require_console_logging = __commonJS({
60026
60301
  [Symbol.toStringTag]: { value: "Module" }
60027
60302
  });
60028
60303
  require_chunk_Cek0wNdY();
60029
- var require_dist10 = require_dist_CCIBlacf();
60304
+ var require_dist10 = require_dist_BhE8zNfY();
60030
60305
  var require_formatter = require_formatter_DqAKDlvN();
60031
60306
  var LEVEL_RANK = {
60032
60307
  debug: 0,
@@ -60120,7 +60395,7 @@ var require_core_blocks_addon = __commonJS({
60120
60395
  "use strict";
60121
60396
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60122
60397
  var require_chunk = require_chunk_Cek0wNdY();
60123
- var require_dist10 = require_dist_CCIBlacf();
60398
+ var require_dist10 = require_dist_BhE8zNfY();
60124
60399
  var node_crypto = __require("crypto");
60125
60400
  var node_fs_promises = __require("fs/promises");
60126
60401
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -61017,11 +61292,11 @@ var require_core_blocks = __commonJS({
61017
61292
  }
61018
61293
  });
61019
61294
 
61020
- // ../system/dist/retired-settings-keys-BxV3e2Km.js
61021
- var require_retired_settings_keys_BxV3e2Km = __commonJS({
61022
- "../system/dist/retired-settings-keys-BxV3e2Km.js"(exports) {
61295
+ // ../system/dist/retired-settings-keys-CqDbI-vK.js
61296
+ var require_retired_settings_keys_CqDbI_vK = __commonJS({
61297
+ "../system/dist/retired-settings-keys-CqDbI-vK.js"(exports) {
61023
61298
  "use strict";
61024
- var require_dist10 = require_dist_CCIBlacf();
61299
+ var require_dist10 = require_dist_BhE8zNfY();
61025
61300
  function settingsStoreIsAuthoritativeHere(env) {
61026
61301
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
61027
61302
  return raw === "" || raw === "hub";
@@ -63235,8 +63510,8 @@ var require_device_manager_addon = __commonJS({
63235
63510
  [Symbol.toStringTag]: { value: "Module" }
63236
63511
  });
63237
63512
  require_chunk_Cek0wNdY();
63238
- var require_dist10 = require_dist_CCIBlacf();
63239
- var require_retired_settings_keys = require_retired_settings_keys_BxV3e2Km();
63513
+ var require_dist10 = require_dist_BhE8zNfY();
63514
+ var require_retired_settings_keys = require_retired_settings_keys_CqDbI_vK();
63240
63515
  var node_crypto = __require("crypto");
63241
63516
  var _camstack_types_node = require_node();
63242
63517
  var JOB_HISTORY = 20;
@@ -64214,6 +64489,9 @@ var require_device_manager_addon = __commonJS({
64214
64489
  for (const f of features) if (DEVICE_FEATURE_VALUES.has(f)) out.push(f);
64215
64490
  return out;
64216
64491
  }
64492
+ function persistedIsCamera(type) {
64493
+ return type === require_dist10.DeviceType.Camera;
64494
+ }
64217
64495
  function buildSourceInfoFromConfig(persistedConfig, stableId, addonId) {
64218
64496
  const id = typeof persistedConfig["entityId"] === "string" ? persistedConfig["entityId"] : void 0;
64219
64497
  const system = typeof persistedConfig["system"] === "string" ? persistedConfig["system"] : void 0;
@@ -64360,7 +64638,7 @@ var require_device_manager_addon = __commonJS({
64360
64638
  online: pctx.host.resolveDeviceOnline(m.id, pctx.registry !== null),
64361
64639
  probed: pctx.host.resolveDeviceProbed(m.id),
64362
64640
  features: persistedFeatures(m.features),
64363
- isCamera: persistedType === require_dist10.DeviceType.Camera,
64641
+ isCamera: persistedIsCamera(persistedType),
64364
64642
  config: persistedConfig ?? {},
64365
64643
  metadata,
64366
64644
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
@@ -64405,7 +64683,7 @@ var require_device_manager_addon = __commonJS({
64405
64683
  online: pctx.host.resolveDeviceOnline(deviceId, true),
64406
64684
  probed: pctx.host.resolveDeviceProbed(deviceId),
64407
64685
  features: persistedFeatures(m.features),
64408
- isCamera: false,
64686
+ isCamera: persistedIsCamera(m.type),
64409
64687
  config: persistedConfig ?? {},
64410
64688
  metadata,
64411
64689
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
@@ -64453,7 +64731,7 @@ var require_device_manager_addon = __commonJS({
64453
64731
  online: pctx.host.resolveDeviceOnline(m.id, pctx.registry !== null),
64454
64732
  probed: pctx.host.resolveDeviceProbed(m.id),
64455
64733
  features: persistedFeatures(m.features),
64456
- isCamera: false,
64734
+ isCamera: persistedIsCamera(m.type),
64457
64735
  config: persistedConfig,
64458
64736
  metadata,
64459
64737
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
@@ -68049,7 +68327,7 @@ var require_hub_forwarder = __commonJS({
68049
68327
  [Symbol.toStringTag]: { value: "Module" }
68050
68328
  });
68051
68329
  require_chunk_Cek0wNdY();
68052
- var require_dist10 = require_dist_CCIBlacf();
68330
+ var require_dist10 = require_dist_BhE8zNfY();
68053
68331
  var require_formatter = require_formatter_DqAKDlvN();
68054
68332
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
68055
68333
  var HubForwarderDestination = class {
@@ -68186,7 +68464,7 @@ var require_liveness_monitor_addon = __commonJS({
68186
68464
  "use strict";
68187
68465
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68188
68466
  require_chunk_Cek0wNdY();
68189
- var require_dist10 = require_dist_CCIBlacf();
68467
+ var require_dist10 = require_dist_BhE8zNfY();
68190
68468
  var NO_DEVICES = "liveness:no-devices";
68191
68469
  var ALL_OFFLINE = "liveness:all-devices-offline";
68192
68470
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68376,7 +68654,7 @@ var require_local_auth_addon = __commonJS({
68376
68654
  [Symbol.toStringTag]: { value: "Module" }
68377
68655
  });
68378
68656
  var require_chunk = require_chunk_Cek0wNdY();
68379
- var require_dist10 = require_dist_CCIBlacf();
68657
+ var require_dist10 = require_dist_BhE8zNfY();
68380
68658
  var node_crypto = __require("crypto");
68381
68659
  node_crypto = require_chunk.__toESM(node_crypto);
68382
68660
  var crypto$1 = __require("crypto");
@@ -76189,7 +76467,7 @@ var require_loki_logging = __commonJS({
76189
76467
  [Symbol.toStringTag]: { value: "Module" }
76190
76468
  });
76191
76469
  require_chunk_Cek0wNdY();
76192
- var require_dist10 = require_dist_CCIBlacf();
76470
+ var require_dist10 = require_dist_BhE8zNfY();
76193
76471
  function sanitizeLabelName(raw) {
76194
76472
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76195
76473
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -76754,7 +77032,7 @@ var require_native_metrics_addon = __commonJS({
76754
77032
  [Symbol.toStringTag]: { value: "Module" }
76755
77033
  });
76756
77034
  var require_chunk = require_chunk_Cek0wNdY();
76757
- var require_dist10 = require_dist_CCIBlacf();
77035
+ var require_dist10 = require_dist_BhE8zNfY();
76758
77036
  var node_fs_promises = __require("fs/promises");
76759
77037
  var node_child_process = __require("child_process");
76760
77038
  var node_util = __require("util");
@@ -79376,7 +79654,7 @@ var require_filesystem_storage_addon = __commonJS({
79376
79654
  [Symbol.toStringTag]: { value: "Module" }
79377
79655
  });
79378
79656
  var require_chunk = require_chunk_Cek0wNdY();
79379
- var require_dist10 = require_dist_CCIBlacf();
79657
+ var require_dist10 = require_dist_BhE8zNfY();
79380
79658
  var node_crypto = __require("crypto");
79381
79659
  var node_fs_promises = __require("fs/promises");
79382
79660
  var node_path = __require("path");
@@ -80492,8 +80770,8 @@ var require_sqlite_settings_addon = __commonJS({
80492
80770
  [Symbol.toStringTag]: { value: "Module" }
80493
80771
  });
80494
80772
  var require_chunk = require_chunk_Cek0wNdY();
80495
- var require_dist10 = require_dist_CCIBlacf();
80496
- var require_retired_settings_keys = require_retired_settings_keys_BxV3e2Km();
80773
+ var require_dist10 = require_dist_BhE8zNfY();
80774
+ var require_retired_settings_keys = require_retired_settings_keys_CqDbI_vK();
80497
80775
  var node_crypto = __require("crypto");
80498
80776
  var node_fs = __require("fs");
80499
80777
  var node_module = __require("module");
@@ -80529,6 +80807,14 @@ var require_sqlite_settings_addon = __commonJS({
80529
80807
  var SQLITE_PROFILE_REPORT_INTERVAL_MS = 6e4;
80530
80808
  var SQLITE_PROFILE_REPORT_FLOOR_MS = 250;
80531
80809
  var SQLITE_PROFILE_TOP_N = 5;
80810
+ var SQLITE_ENGINE_STORM_WALL_SHARE = 0.5;
80811
+ function sqliteEngineStorm(stats, windowMs) {
80812
+ if (windowMs <= 0) return null;
80813
+ const top = stats[0];
80814
+ if (top === void 0) return null;
80815
+ if (top.totalMs / windowMs < 0.5) return null;
80816
+ return top;
80817
+ }
80532
80818
  function rowsOf(value) {
80533
80819
  if (Array.isArray(value)) return value.length;
80534
80820
  if (value === void 0 || value === null) return 0;
@@ -80663,21 +80949,39 @@ var require_sqlite_settings_addon = __commonJS({
80663
80949
  const stats = this.drain();
80664
80950
  const busyMs = stats.reduce((acc, s) => acc + s.totalMs, 0);
80665
80951
  if (busyMs < this.reportFloorMs) return;
80952
+ const busyPct = Math.round(busyMs / windowMs * 100);
80953
+ const calls = stats.reduce((acc, s) => acc + s.calls, 0);
80954
+ const top = stats.slice(0, this.topN).map((s) => ({
80955
+ op: s.op,
80956
+ collection: s.collection,
80957
+ ...s.owner === void 0 ? {} : { owner: s.owner },
80958
+ ...s.shape === void 0 ? {} : { shape: s.shape },
80959
+ calls: s.calls,
80960
+ totalMs: Math.round(s.totalMs),
80961
+ maxMs: Math.round(s.maxMs),
80962
+ rows: s.rows
80963
+ }));
80666
80964
  this.logger.info("sqlite engine busy", { meta: {
80667
80965
  busyMs: Math.round(busyMs),
80668
80966
  windowMs: Math.round(windowMs),
80669
- busyPct: Math.round(busyMs / windowMs * 100),
80670
- calls: stats.reduce((acc, s) => acc + s.calls, 0),
80671
- top: stats.slice(0, this.topN).map((s) => ({
80672
- op: s.op,
80673
- collection: s.collection,
80674
- ...s.owner === void 0 ? {} : { owner: s.owner },
80675
- ...s.shape === void 0 ? {} : { shape: s.shape },
80676
- calls: s.calls,
80677
- totalMs: Math.round(s.totalMs),
80678
- maxMs: Math.round(s.maxMs),
80679
- rows: s.rows
80680
- }))
80967
+ busyPct,
80968
+ calls,
80969
+ top
80970
+ } });
80971
+ const storm = sqliteEngineStorm(stats, windowMs);
80972
+ if (storm !== null) this.logger.warn("sqlite engine storm", { meta: {
80973
+ busyMs: Math.round(busyMs),
80974
+ windowMs: Math.round(windowMs),
80975
+ busyPct,
80976
+ wallPct: Math.round(storm.totalMs / windowMs * 100),
80977
+ op: storm.op,
80978
+ collection: storm.collection,
80979
+ ...storm.owner === void 0 ? {} : { owner: storm.owner },
80980
+ ...storm.shape === void 0 ? {} : { shape: storm.shape },
80981
+ calls: storm.calls,
80982
+ totalMs: Math.round(storm.totalMs),
80983
+ maxMs: Math.round(storm.maxMs),
80984
+ rows: storm.rows
80681
80985
  } });
80682
80986
  }
80683
80987
  start() {
@@ -82757,6 +83061,7 @@ var require_sqlite_settings_addon = __commonJS({
82757
83061
  return this.vectorIndex;
82758
83062
  }
82759
83063
  };
83064
+ exports.SQLITE_ENGINE_STORM_WALL_SHARE = SQLITE_ENGINE_STORM_WALL_SHARE;
82760
83065
  exports.SQLITE_JOURNAL_SIZE_LIMIT_BYTES = SQLITE_JOURNAL_SIZE_LIMIT_BYTES;
82761
83066
  exports.SQLITE_MMAP_CEILING_BYTES = SQLITE_MMAP_CEILING_BYTES;
82762
83067
  exports.SQLITE_MMAP_FLOOR_BYTES = SQLITE_MMAP_FLOOR_BYTES;
@@ -82776,6 +83081,7 @@ var require_sqlite_settings_addon = __commonJS({
82776
83081
  exports.WAL_MAINTENANCE_INTERVAL_MS = WAL_MAINTENANCE_INTERVAL_MS;
82777
83082
  exports.WalMaintenance = WalMaintenance;
82778
83083
  exports.prefixRange = prefixRange;
83084
+ exports.sqliteEngineStorm = sqliteEngineStorm;
82779
83085
  exports.sqliteMmapSizeFor = sqliteMmapSizeFor;
82780
83086
  exports.sqlitePageCacheKibFor = sqlitePageCacheKibFor;
82781
83087
  }
@@ -82840,6 +83146,7 @@ var require_sqlite_storage = __commonJS({
82840
83146
  exports.DeviceStore$1 = DeviceStore;
82841
83147
  exports.FilesystemStorageAddon = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageAddon;
82842
83148
  exports.FilesystemStorageProvider = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageProvider;
83149
+ exports.SQLITE_ENGINE_STORM_WALL_SHARE = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_ENGINE_STORM_WALL_SHARE;
82843
83150
  exports.SQLITE_JOURNAL_SIZE_LIMIT_BYTES = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_JOURNAL_SIZE_LIMIT_BYTES;
82844
83151
  exports.SQLITE_MMAP_CEILING_BYTES = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_MMAP_CEILING_BYTES;
82845
83152
  exports.SQLITE_MMAP_FLOOR_BYTES = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_MMAP_FLOOR_BYTES;
@@ -82858,6 +83165,7 @@ var require_sqlite_storage = __commonJS({
82858
83165
  exports.WAL_MAINTENANCE_INTERVAL_MS = require_builtins_sqlite_storage_sqlite_settings_addon.WAL_MAINTENANCE_INTERVAL_MS;
82859
83166
  exports.WalMaintenance = require_builtins_sqlite_storage_sqlite_settings_addon.WalMaintenance;
82860
83167
  exports.default = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageAddon;
83168
+ exports.sqliteEngineStorm = require_builtins_sqlite_storage_sqlite_settings_addon.sqliteEngineStorm;
82861
83169
  exports.sqliteMmapSizeFor = require_builtins_sqlite_storage_sqlite_settings_addon.sqliteMmapSizeFor;
82862
83170
  exports.sqlitePageCacheKibFor = require_builtins_sqlite_storage_sqlite_settings_addon.sqlitePageCacheKibFor;
82863
83171
  }
@@ -82872,7 +83180,7 @@ var require_storage_orchestrator_addon = __commonJS({
82872
83180
  [Symbol.toStringTag]: { value: "Module" }
82873
83181
  });
82874
83182
  var require_chunk = require_chunk_Cek0wNdY();
82875
- var require_dist10 = require_dist_CCIBlacf();
83183
+ var require_dist10 = require_dist_BhE8zNfY();
82876
83184
  var node_crypto = __require("crypto");
82877
83185
  var node_fs_promises = __require("fs/promises");
82878
83186
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -83056,17 +83364,13 @@ var require_storage_orchestrator_addon = __commonJS({
83056
83364
  "recordings",
83057
83365
  "recordingsLow",
83058
83366
  "eventMedia",
83059
- "backups",
83060
- "galleryMedia"
83367
+ "backups"
83061
83368
  ];
83062
83369
  var MOVER_CLASSES = [
83063
83370
  "recordings",
83064
83371
  "recordingsLow",
83065
- "eventMedia",
83066
- "galleryMedia"
83372
+ "eventMedia"
83067
83373
  ];
83068
- var BLOCKING_ONLY_CLASSES = ["galleryMedia"];
83069
- var GALLERY_FORBIDDEN_NEIGHBOUR_TYPES = ["recordings", "recordingsLow"];
83070
83374
  var MOVE_LANES = ["media", "footage"];
83071
83375
  var PARTICIPANTS = [
83072
83376
  "pipeline",
@@ -83091,6 +83395,7 @@ var require_storage_orchestrator_addon = __commonJS({
83091
83395
  this.deps = deps;
83092
83396
  }
83093
83397
  async plan(input) {
83398
+ refuseRetiredGalleryClass(input.destinations);
83094
83399
  const mode = input.mode ?? "blocking";
83095
83400
  const moves = [];
83096
83401
  const findings = [];
@@ -83104,8 +83409,6 @@ var require_storage_orchestrator_addon = __commonJS({
83104
83409
  if (target.type !== storageClass) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${storageClass}"`);
83105
83410
  if (source.id === target.id) throw new Error(`Storage location "${targetId}" is already the "${storageClass}" default`);
83106
83411
  if (!MOVER_CLASSES.includes(storageClass)) throw new Error(`No mover owns "${storageClass}" \u2014 the migration can repoint its default but cannot move its bytes. Move it by hand, then repoint the default with upsertLocation.`);
83107
- if (mode === "nonBlocking" && BLOCKING_ONLY_CLASSES.includes(storageClass)) throw new Error(`"${storageClass}" is a single-cardinality storage class: it can never exist at two locations at once, so it cannot be repointed first and drained behind. Use mode "blocking".`);
83108
- await this.refuseGalleryOntoRecordingsVolume(storageClass, targetId);
83109
83412
  moves.push({
83110
83413
  storageClass,
83111
83414
  fromLocationId: source.id,
@@ -83115,11 +83418,6 @@ var require_storage_orchestrator_addon = __commonJS({
83115
83418
  error: null,
83116
83419
  progress: null
83117
83420
  });
83118
- if (BLOCKING_ONLY_CLASSES.includes(storageClass)) findings.push({
83119
- code: "blockingOnly",
83120
- storageClass,
83121
- message: `"${storageClass}" is single-cardinality: this move is stop-the-world for its whole duration, in every mode. It is a few KB per enrolled sample, which is why that is acceptable.`
83122
- });
83123
83421
  findings.push(...await this.deviceFindings(storageClass, source.id, target.id));
83124
83422
  }
83125
83423
  if (moves.length === 0) throw new Error("select at least one storage class");
@@ -83154,29 +83452,6 @@ var require_storage_orchestrator_addon = __commonJS({
83154
83452
  findings
83155
83453
  };
83156
83454
  }
83157
- /**
83158
- * HARD refusal: `galleryMedia` onto a volume a recordings class lives on.
83159
- *
83160
- * This is the one same-device case that is a refusal rather than a finding,
83161
- * because its consequence is silent and arrives months later — the gallery is
83162
- * wiped by the next footage wipe, quota eviction or recordings disk swap, and
83163
- * nothing at that moment points back at this migration.
83164
- *
83165
- * It can only refuse what it can SEE. `deviceKeyOf` is a realpath, so it
83166
- * catches a symlink, a bind mount and a literally-shared directory — and
83167
- * misses two distinct directories on one filesystem, and any location pinned
83168
- * to another node. Those come back `null`/distinct and produce a
83169
- * `deviceIdentityUnknown` finding instead.
83170
- */
83171
- async refuseGalleryOntoRecordingsVolume(storageClass, targetId) {
83172
- if (storageClass !== "galleryMedia") return;
83173
- const targetKey = await this.deviceKey(targetId);
83174
- if (targetKey === null) return;
83175
- for (const type of GALLERY_FORBIDDEN_NEIGHBOUR_TYPES) for (const neighbour of this.deps.locations.listLocations({ type })) {
83176
- if (await this.deviceKey(neighbour.id) !== targetKey) continue;
83177
- throw new Error(`Storage location "${targetId}" shares a volume with "${neighbour.id}" (${type}). galleryMedia is deliberately NOT on the recordings volume: enrolment images survive every retention sweep, so they must also survive a footage wipe, a quota eviction and a recordings disk swap. Choose a destination on another volume.`);
83178
- }
83179
- }
83180
83455
  /** Same-device observations that are reported, not refused. */
83181
83456
  async deviceFindings(storageClass, sourceId, targetId) {
83182
83457
  if (this.deps.deviceKeyOf === void 0) return [];
@@ -83308,7 +83583,7 @@ var require_storage_orchestrator_addon = __commonJS({
83308
83583
  if (laneOf(storageClass) === "media") {
83309
83584
  const count = await unanswerable(this.deps.participants.analytics.residue({
83310
83585
  toLocationId: target.id,
83311
- mode: storageClass === "galleryMedia" ? "gallery" : "move"
83586
+ mode: "move"
83312
83587
  }));
83313
83588
  if (count !== null && count.rows === 0) continue;
83314
83589
  out.push({
@@ -83588,9 +83863,7 @@ var require_storage_orchestrator_addon = __commonJS({
83588
83863
  *
83589
83864
  * Two lanes — the recorder's footage engine and post-analysis's media engine
83590
83865
  * — run in parallel with each other and strictly FIFO within themselves.
83591
- * `galleryMedia` shares the media lane with `eventMedia` because it shares
83592
- * the same `MediaRelocateEngine`, which refuses a second running job; starting
83593
- * both would fail the second one instantly.
83866
+ * `eventMedia` is the only class on the media lane.
83594
83867
  */
83595
83868
  async startOrResumeMoves(job) {
83596
83869
  if (job.cancelRequested) return;
@@ -83720,7 +83993,7 @@ var require_storage_orchestrator_addon = __commonJS({
83720
83993
  const input2 = {
83721
83994
  toLocationId: move.toLocationId,
83722
83995
  throttleMbps: job.throttleMbps,
83723
- mode: move.storageClass === "galleryMedia" ? "gallery" : "move"
83996
+ mode: "move"
83724
83997
  };
83725
83998
  return (unleased ? await this.deps.participants.analytics.startDrain(input2) : await this.deps.participants.analytics.startMove({
83726
83999
  ...input2,
@@ -83781,7 +84054,11 @@ var require_storage_orchestrator_addon = __commonJS({
83781
84054
  return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
83782
84055
  }
83783
84056
  function laneOf(storageClass) {
83784
- return storageClass === "eventMedia" || storageClass === "galleryMedia" ? "media" : "footage";
84057
+ return storageClass === "eventMedia" ? "media" : "footage";
84058
+ }
84059
+ function refuseRetiredGalleryClass(destinations) {
84060
+ if (destinations.galleryMedia === void 0) return;
84061
+ throw new Error("galleryMedia is no longer a storage class. Enrolled gallery lives in the pipeline-analytics addon data directory, not a location the operator places.");
83785
84062
  }
83786
84063
  function runsUnleased(job) {
83787
84064
  if (job.phase === "sealing" || job.phase === "draining") return true;
@@ -84052,14 +84329,13 @@ var require_storage_orchestrator_addon = __commonJS({
84052
84329
  /**
84053
84330
  * Shout about a persisted row that is BOTH the type default and disabled.
84054
84331
  *
84055
- * `upsertLocation` cannot produce this (`resolveEnabled` force-enables a
84056
- * default) and neither can `setDefaultLocations` but hydrate writes rows
84057
- * straight into the map without either, so a hand-edited store, a partial
84058
- * migration or a row written by another build can. It matters because every
84059
- * bare-type ref resolves through {@link getDefaultLocation}: on such a row
84332
+ * `upsertLocation` can now produce this (enabled is the write set; `isDefault`
84333
+ * is store residue). Hydrate also writes rows straight into the map, so a
84334
+ * hand-edited store or a row written by another build can too. It matters
84335
+ * because every bare-type ref still resolves through {@link getDefaultLocation}
84336
+ * until Stage 5 of the enabled-write-set design: on such a row
84060
84337
  * `storage.write({ location: 'eventMedia' })` and the migration coordinator's
84061
- * drain destination both point at a disk the operator turned off. That is the
84062
- * one thing this model must never do silently.
84338
+ * drain destination both point at a disk the operator turned off.
84063
84339
  *
84064
84340
  * Reported, never repaired: flipping an operator's flag back on at boot would
84065
84341
  * be the system overruling the switch instead of obeying it. The coordinator
@@ -84344,6 +84620,7 @@ var require_storage_orchestrator_addon = __commonJS({
84344
84620
  createdAt: existing?.createdAt ?? now,
84345
84621
  updatedAt: now
84346
84622
  };
84623
+ refuseLastWriteTargetDisabled(existing, next, this.locations.values());
84347
84624
  const demoted = [];
84348
84625
  if (next.isDefault) {
84349
84626
  for (const [otherId, otherLoc] of this.locations) if (otherLoc.type === next.type && otherLoc.id !== next.id && otherLoc.isDefault) {
@@ -84638,9 +84915,20 @@ var require_storage_orchestrator_addon = __commonJS({
84638
84915
  if (existing) return {};
84639
84916
  return { enabled: input.isDefault || !typeAlreadyHasLocation };
84640
84917
  }
84641
- if (input.isDefault && requested === false) return { enabled: true };
84642
84918
  return { enabled: requested };
84643
84919
  }
84920
+ var OPTIONAL_WRITE_TYPES = /* @__PURE__ */ new Set(["backups"]);
84921
+ function refuseLastWriteTargetDisabled(existing, next, locations) {
84922
+ if (OPTIONAL_WRITE_TYPES.has(next.type)) return;
84923
+ if (next.enabled !== false) return;
84924
+ if (existing === void 0) return;
84925
+ if (existing.enabled === false) return;
84926
+ for (const loc of locations) {
84927
+ if (loc.id === next.id || loc.type !== next.type) continue;
84928
+ if (loc.enabled !== false) return;
84929
+ }
84930
+ throw new Error(`Cannot disable "${next.id}": it is the last enabled location of type "${next.type}". Enable another location of this type first.`);
84931
+ }
84644
84932
  var DEFAULT_MAX_ROUNDS = 8;
84645
84933
  var DEFAULT_HYSTERESIS_PERCENT = 1;
84646
84934
  var StoragePressureManager = class {
@@ -85469,7 +85757,7 @@ var require_system_config_addon = __commonJS({
85469
85757
  [Symbol.toStringTag]: { value: "Module" }
85470
85758
  });
85471
85759
  require_chunk_Cek0wNdY();
85472
- var require_dist10 = require_dist_CCIBlacf();
85760
+ var require_dist10 = require_dist_BhE8zNfY();
85473
85761
  var SECTION_TITLES = {
85474
85762
  server: "Server",
85475
85763
  auth: "Authentication"
@@ -103530,7 +103818,7 @@ var require_winston_logging = __commonJS({
103530
103818
  [Symbol.toStringTag]: { value: "Module" }
103531
103819
  });
103532
103820
  var require_chunk = require_chunk_Cek0wNdY();
103533
- var require_dist10 = require_dist_CCIBlacf();
103821
+ var require_dist10 = require_dist_BhE8zNfY();
103534
103822
  var require_formatter = require_formatter_DqAKDlvN();
103535
103823
  var node_path = __require("path");
103536
103824
  node_path = require_chunk.__toESM(node_path);
@@ -105473,9 +105761,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105473
105761
  }
105474
105762
  });
105475
105763
 
105476
- // ../types/dist/sleep-D5821NGq.js
105477
- var require_sleep_D5821NGq = __commonJS({
105478
- "../types/dist/sleep-D5821NGq.js"(exports) {
105764
+ // ../types/dist/sleep-CqVyhcl-.js
105765
+ var require_sleep_CqVyhcl = __commonJS({
105766
+ "../types/dist/sleep-CqVyhcl-.js"(exports) {
105479
105767
  "use strict";
105480
105768
  var require_event_category = require_event_category_BaEgqJNv();
105481
105769
  var zod = require_zod();
@@ -108158,6 +108446,7 @@ var require_sleep_D5821NGq = __commonJS({
108158
108446
  listEventKindsBatch: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventKindsBatch", "query", input),
108159
108447
  getSensorEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getSensorEvents", "query", input),
108160
108448
  getKeyEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEvents", "query", input),
108449
+ getKeyEventsBatch: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEventsBatch", "query", input),
108161
108450
  getEventDensity: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventDensity", "query", input),
108162
108451
  pruneEventsBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEventsBefore", "mutation", input),
108163
108452
  pruneTracksBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneTracksBefore", "mutation", input),
@@ -108181,6 +108470,8 @@ var require_sleep_D5821NGq = __commonJS({
108181
108470
  listRelocateMediaJobs: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRelocateMediaJobs", "query", input),
108182
108471
  cancelRelocateMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "cancelRelocateMedia", "mutation", input),
108183
108472
  listOpsLog: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listOpsLog", "query", input),
108473
+ reclaimDebugMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "reclaimDebugMedia", "mutation", input),
108474
+ getMediaReclaimStatus: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getMediaReclaimStatus", "query", input),
108184
108475
  getTrainingExportSummary: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrainingExportSummary", "query", input),
108185
108476
  getTrainingExportUrl: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrainingExportUrl", "query", input),
108186
108477
  listRetrainStaging: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRetrainStaging", "query", input),
@@ -108239,6 +108530,7 @@ var require_sleep_D5821NGq = __commonJS({
108239
108530
  reboot: { reboot: (input) => dispatch("reboot", "reboot", "reboot", "mutation", input) },
108240
108531
  sceneMonitor: {
108241
108532
  listScenes: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenes", "query", input),
108533
+ listScenesBatch: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenesBatch", "query", input),
108242
108534
  createScene: (input) => dispatch("scene-monitor", "sceneMonitor", "createScene", "mutation", input),
108243
108535
  updateScene: (input) => dispatch("scene-monitor", "sceneMonitor", "updateScene", "mutation", input),
108244
108536
  deleteScene: (input) => dispatch("scene-monitor", "sceneMonitor", "deleteScene", "mutation", input),
@@ -108323,6 +108615,7 @@ var require_sleep_D5821NGq = __commonJS({
108323
108615
  },
108324
108616
  zoneAnalytics: {
108325
108617
  getCurrentSnapshot: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCurrentSnapshot", "query", input),
108618
+ getCurrentSnapshotBatch: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCurrentSnapshotBatch", "query", input),
108326
108619
  getZoneHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getZoneHistory", "query", input),
108327
108620
  getCameraHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCameraHistory", "query", input),
108328
108621
  getUnzonedHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getUnzonedHistory", "query", input)
@@ -109143,7 +109436,7 @@ var require_addon = __commonJS({
109143
109436
  "use strict";
109144
109437
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
109145
109438
  var require_event_category = require_event_category_BaEgqJNv();
109146
- var require_sleep = require_sleep_D5821NGq();
109439
+ var require_sleep = require_sleep_CqVyhcl();
109147
109440
  var require_err_msg = require_err_msg_COpsHMw2();
109148
109441
  var CAP_INPUT_DEFAULTS = Object.freeze({
109149
109442
  "addons": { "getLogs": { "limit": 100 } },
@@ -109253,11 +109546,13 @@ var require_addon = __commonJS({
109253
109546
  "pipeline-analytics": {
109254
109547
  "getAudioEvents": { "limit": 1e3 },
109255
109548
  "getKeyEvents": { "limit": 50 },
109549
+ "getKeyEventsBatch": { "limit": 50 },
109256
109550
  "getMotionEvents": { "limit": 1e3 },
109257
109551
  "getObjectEvents": { "limit": 1e3 },
109258
109552
  "getSensorEvents": { "limit": 1e3 },
109259
109553
  "listGroups": { "limit": 40 },
109260
109554
  "listRecentTracks": { "limit": 200 },
109555
+ "reclaimDebugMedia": { "mode": "report" },
109261
109556
  "searchObjectEvents": {
109262
109557
  "limit": 50,
109263
109558
  "minScore": 0.2
@@ -116025,12 +116320,12 @@ var require_dist2 = __commonJS({
116025
116320
  }
116026
116321
  });
116027
116322
 
116028
- // ../system/dist/manifest-system-deps-alrkBKVQ.js
116029
- var require_manifest_system_deps_alrkBKVQ = __commonJS({
116030
- "../system/dist/manifest-system-deps-alrkBKVQ.js"(exports) {
116323
+ // ../system/dist/manifest-system-deps-uflHvHDB.js
116324
+ var require_manifest_system_deps_uflHvHDB = __commonJS({
116325
+ "../system/dist/manifest-system-deps-uflHvHDB.js"(exports) {
116031
116326
  "use strict";
116032
116327
  var require_chunk = require_chunk_Cek0wNdY();
116033
- require_dist_CCIBlacf();
116328
+ require_dist_BhE8zNfY();
116034
116329
  var node_crypto = __require("crypto");
116035
116330
  node_crypto = require_chunk.__toESM(node_crypto);
116036
116331
  var _camstack_types_node = require_node();
@@ -123315,6 +123610,93 @@ var require_manifest_system_deps_alrkBKVQ = __commonJS({
123315
123610
  }
123316
123611
  await deps.installPythonRequirements(reqAbs);
123317
123612
  }
123613
+ var DISTRO_PACKAGE_ALLOWLIST = {
123614
+ apt: ["smbclient"],
123615
+ apk: ["samba-client"],
123616
+ dnf: ["samba-client"]
123617
+ };
123618
+ function packageIsAllowlisted(manager, packageName) {
123619
+ if (manager === "brew") return true;
123620
+ if (packageName.length === 0) return false;
123621
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9+._-]*$/.test(packageName)) return false;
123622
+ return DISTRO_PACKAGE_ALLOWLIST[manager].includes(packageName);
123623
+ }
123624
+ function decidePackageInstall(input) {
123625
+ if (input.manager === "brew") return { kind: "local" };
123626
+ if (!packageIsAllowlisted(input.manager, input.packageName)) return {
123627
+ kind: "refuse",
123628
+ reason: "not-allowlisted"
123629
+ };
123630
+ if (!input.helperAvailable) return {
123631
+ kind: "refuse",
123632
+ reason: "helper-unavailable"
123633
+ };
123634
+ return { kind: "helper" };
123635
+ }
123636
+ function pkgdSocketPath() {
123637
+ return process.env["CAMSTACK_PKGD_SOCKET"] ?? "/run/camstack/pkgd.sock";
123638
+ }
123639
+ function privilegedHelperAvailable(socketPath = pkgdSocketPath()) {
123640
+ return (0, node_fs.existsSync)(socketPath);
123641
+ }
123642
+ function requestPrivilegedInstall(req, socketPath = pkgdSocketPath(), timeoutMs = 360 * 1e3) {
123643
+ return new Promise((resolve) => {
123644
+ const socket = (0, node_net.createConnection)({ path: socketPath });
123645
+ const chunks = [];
123646
+ const finish = (result) => {
123647
+ socket.destroy();
123648
+ resolve(result);
123649
+ };
123650
+ const timer = setTimeout(() => {
123651
+ finish({
123652
+ ok: false,
123653
+ error: `privileged helper timed out after ${timeoutMs}ms`
123654
+ });
123655
+ }, timeoutMs);
123656
+ socket.on("connect", () => {
123657
+ socket.write(`${JSON.stringify({
123658
+ v: 1,
123659
+ ...req
123660
+ })}
123661
+ `);
123662
+ });
123663
+ socket.on("data", (data) => {
123664
+ chunks.push(data);
123665
+ const text = Buffer.concat(chunks).toString("utf8");
123666
+ const nl = text.indexOf("\n");
123667
+ if (nl === -1) return;
123668
+ clearTimeout(timer);
123669
+ try {
123670
+ const parsed = JSON.parse(text.slice(0, nl));
123671
+ finish({
123672
+ ok: parsed.ok === true,
123673
+ ...parsed.error !== void 0 ? { error: parsed.error } : {}
123674
+ });
123675
+ } catch (err) {
123676
+ finish({
123677
+ ok: false,
123678
+ error: err instanceof Error ? err.message : String(err)
123679
+ });
123680
+ }
123681
+ });
123682
+ socket.on("error", (err) => {
123683
+ clearTimeout(timer);
123684
+ finish({
123685
+ ok: false,
123686
+ error: err.message
123687
+ });
123688
+ });
123689
+ socket.on("end", () => {
123690
+ if (chunks.length === 0) {
123691
+ clearTimeout(timer);
123692
+ finish({
123693
+ ok: false,
123694
+ error: "privileged helper closed without a reply"
123695
+ });
123696
+ }
123697
+ });
123698
+ });
123699
+ }
123318
123700
  var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
123319
123701
  var INSTALL_TIMEOUT_MS = 300 * 1e3;
123320
123702
  var PROBE_TIMEOUT_MS = 1e4;
@@ -123325,12 +123707,16 @@ var require_manifest_system_deps_alrkBKVQ = __commonJS({
123325
123707
  const detectManager = options.detectManager ?? defaultDetectManager;
123326
123708
  const run = options.run ?? defaultRun;
123327
123709
  const platform = options.platform ?? process.platform;
123710
+ const helperAvailable = options.helperAvailable ?? (async () => privilegedHelperAvailable());
123711
+ const requestInstall = options.requestInstall ?? ((req) => requestPrivilegedInstall(req));
123328
123712
  for (const dep of deps) try {
123329
123713
  await satisfyOne(dep, declaration.id, {
123330
123714
  hasBinary,
123331
123715
  detectManager,
123332
123716
  run,
123333
- platform
123717
+ platform,
123718
+ helperAvailable,
123719
+ requestInstall
123334
123720
  }, logger);
123335
123721
  } catch (err) {
123336
123722
  logger.warn("system dependency step failed", { meta: {
@@ -123368,15 +123754,43 @@ var require_manifest_system_deps_alrkBKVQ = __commonJS({
123368
123754
  } });
123369
123755
  return;
123370
123756
  }
123371
- const plan = planFor(manager, packageName);
123757
+ const decision = decidePackageInstall({
123758
+ manager,
123759
+ packageName,
123760
+ helperAvailable: await rt.helperAvailable()
123761
+ });
123762
+ if (decision.kind === "refuse") {
123763
+ const message = decision.reason === "helper-unavailable" ? "system dependency missing and the privileged helper is not running \u2014 apt cannot run as uid 1001" : "system dependency missing and the package is not on the helper allowlist";
123764
+ logger.warn(message, { meta: {
123765
+ addonId,
123766
+ binary: dep.binary,
123767
+ manager,
123768
+ package: packageName,
123769
+ reason: decision.reason,
123770
+ ...dep.reason !== void 0 ? { reason: dep.reason } : {}
123771
+ } });
123772
+ return;
123773
+ }
123372
123774
  logger.info("installing system dependency", { meta: {
123373
123775
  addonId,
123374
123776
  binary: dep.binary,
123375
123777
  manager,
123376
- package: packageName
123778
+ package: packageName,
123779
+ via: decision.kind
123377
123780
  } });
123378
123781
  try {
123379
- for (const [command, args] of plan.steps) await rt.run(command, args);
123782
+ if (decision.kind === "helper") {
123783
+ const result = await rt.requestInstall({
123784
+ addonId,
123785
+ binary: dep.binary,
123786
+ manager,
123787
+ packageName
123788
+ });
123789
+ if (!result.ok) throw new Error(result.error ?? "privileged helper refused");
123790
+ } else {
123791
+ const plan = planFor(manager, packageName);
123792
+ for (const [command, args] of plan.steps) await rt.run(command, args);
123793
+ }
123380
123794
  } catch (err) {
123381
123795
  logger.warn("system dependency install failed \u2014 the addon loads, its capability must refuse", { meta: {
123382
123796
  addonId,
@@ -128059,7 +128473,7 @@ var require_dist3 = __commonJS({
128059
128473
  "use strict";
128060
128474
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
128061
128475
  var require_chunk = require_chunk_Cek0wNdY();
128062
- var require_dist10 = require_dist_CCIBlacf();
128476
+ var require_dist10 = require_dist_BhE8zNfY();
128063
128477
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
128064
128478
  require_alerts();
128065
128479
  var require_formatter = require_formatter_DqAKDlvN();
@@ -128085,7 +128499,7 @@ var require_dist3 = __commonJS({
128085
128499
  var require_builtins_winston_logging_index = require_winston_logging();
128086
128500
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
128087
128501
  var require_tls$1 = require_tls_BxQlomxd();
128088
- var require_manifest_system_deps = require_manifest_system_deps_alrkBKVQ();
128502
+ var require_manifest_system_deps = require_manifest_system_deps_uflHvHDB();
128089
128503
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
128090
128504
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
128091
128505
  var zod = require_zod();
@@ -133973,19 +134387,37 @@ var require_dist3 = __commonJS({
133973
134387
  if (!isRecord$1(parsed) || !("value" in parsed)) return parsed;
133974
134388
  return parsed.value;
133975
134389
  }
134390
+ var PREFIX_PAGE_ROWS = 1e3;
134391
+ var PREFIX_PAGE_BUDGET = 512;
133976
134392
  async function loadPrefixed(door, collection, prefix) {
133977
134393
  const range = require_builtins_sqlite_storage_sqlite_settings_addon.prefixRange(prefix);
133978
- const rows = await door.query({
133979
- collection,
133980
- ...range !== null ? { filter: { whereBetween: { id: [range.lo, range.hi] } } } : {}
133981
- });
133982
134394
  const result = {};
133983
- for (const row of rows) {
133984
- if (range !== null && (row.id < range.lo || row.id >= range.hi)) continue;
133985
- if (!row.id.startsWith(prefix)) continue;
133986
- result[row.id.slice(prefix.length)] = unwrapValue(row.data);
134395
+ let cursor = range?.lo ?? null;
134396
+ for (let page = 0; ; page++) {
134397
+ 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.`);
134398
+ const rows = await door.query({
134399
+ collection,
134400
+ filter: {
134401
+ ...range !== null && cursor !== null ? { whereBetween: { id: [cursor, range.hi] } } : {},
134402
+ orderBy: {
134403
+ field: "id",
134404
+ direction: "asc"
134405
+ },
134406
+ limit: PREFIX_PAGE_ROWS
134407
+ }
134408
+ });
134409
+ for (const row of rows) {
134410
+ if (range !== null && (row.id < range.lo || row.id >= range.hi)) continue;
134411
+ if (!row.id.startsWith(prefix)) continue;
134412
+ result[row.id.slice(prefix.length)] = unwrapValue(row.data);
134413
+ }
134414
+ if (rows.length < PREFIX_PAGE_ROWS) return result;
134415
+ const last = rows[rows.length - 1];
134416
+ if (last === void 0) return result;
134417
+ 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.`);
134418
+ 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.`);
134419
+ cursor = last.id;
133987
134420
  }
133988
- return result;
133989
134421
  }
133990
134422
  async function replacePrefixed(door, collection, prefix, values, wrap3) {
133991
134423
  const range = require_builtins_sqlite_storage_sqlite_settings_addon.prefixRange(prefix);
@@ -208909,7 +209341,7 @@ var require_dist4 = __commonJS({
208909
209341
  "use strict";
208910
209342
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
208911
209343
  var require_event_category = require_event_category_BaEgqJNv();
208912
- var require_sleep = require_sleep_D5821NGq();
209344
+ var require_sleep = require_sleep_CqVyhcl();
208913
209345
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
208914
209346
  var require_enums2 = require_enums();
208915
209347
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -210256,6 +210688,20 @@ var require_dist4 = __commonJS({
210256
210688
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
210257
210689
  */
210258
210690
  rowsReconciled: zod.z.number().int().nonnegative().optional(),
210691
+ /**
210692
+ * Rows this run FORGOT because the file they name is not on disk.
210693
+ *
210694
+ * The mover derived the path from the row's own fields and `stat`ed it; an
210695
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
210696
+ * and the durable row is dropped through the same channel eviction uses. It
210697
+ * is reported for the same reason `rowsReconciled` is: this is a durable
210698
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
210699
+ * the same failure as one that quietly skips them (D295).
210700
+ *
210701
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
210702
+ * ledger claimed 5.65 GB of footage that no longer existed.
210703
+ */
210704
+ rowsForgotten: zod.z.number().int().nonnegative().optional(),
210259
210705
  startedAt: zod.z.number(),
210260
210706
  finishedAt: zod.z.number().nullable(),
210261
210707
  error: zod.z.string().nullable()
@@ -210463,6 +210909,71 @@ var require_dist4 = __commonJS({
210463
210909
  segments: zod.z.number().int().nonnegative(),
210464
210910
  bytes: zod.z.number().int().nonnegative()
210465
210911
  }).nullable();
210912
+ var LedgerWalkInputSchema = zod.z.object({
210913
+ locationId: zod.z.string().min(1),
210914
+ /** Forget the confirmed-absent rows, rather than only counting them. */
210915
+ apply: zod.z.boolean().optional(),
210916
+ /** Narrow to one camera. */
210917
+ deviceId: zod.z.number().int().positive().optional(),
210918
+ /** Narrow to these recording profiles; empty/absent = every profile. */
210919
+ profiles: zod.z.array(zod.z.string().min(1)).optional()
210920
+ });
210921
+ var LedgerWalkRefusalSchema = zod.z.enum([
210922
+ "location-unknown",
210923
+ "source-writable",
210924
+ "no-ledger",
210925
+ "archive-unreadable",
210926
+ "anchor-absent",
210927
+ "anchor-unreadable",
210928
+ "anchor-moved"
210929
+ ]);
210930
+ var LedgerWalkSkipReasonSchema = zod.z.enum([
210931
+ "live-tail",
210932
+ "listing-error",
210933
+ "path-mismatch",
210934
+ "durable-refused"
210935
+ ]);
210936
+ var LedgerWalkSkipCountsSchema = zod.z.object({
210937
+ "live-tail": zod.z.number().int().nonnegative(),
210938
+ "listing-error": zod.z.number().int().nonnegative(),
210939
+ "path-mismatch": zod.z.number().int().nonnegative(),
210940
+ "durable-refused": zod.z.number().int().nonnegative()
210941
+ });
210942
+ var LedgerWalkDeviceReportSchema = zod.z.object({
210943
+ deviceId: zod.z.number().int(),
210944
+ hoursWalked: zod.z.number().int().nonnegative(),
210945
+ hoursMissing: zod.z.number().int().nonnegative(),
210946
+ ghostSegments: zod.z.number().int().nonnegative(),
210947
+ ghostBytes: zod.z.number().int().nonnegative(),
210948
+ forgottenSegments: zod.z.number().int().nonnegative(),
210949
+ orphanFiles: zod.z.number().int().nonnegative()
210950
+ });
210951
+ var LedgerWalkReportSchema = zod.z.object({
210952
+ locationId: zod.z.string(),
210953
+ applied: zod.z.boolean(),
210954
+ refused: LedgerWalkRefusalSchema.nullable(),
210955
+ archiveSegments: zod.z.number().int().nonnegative().nullable(),
210956
+ archiveBytes: zod.z.number().int().nonnegative().nullable(),
210957
+ hoursClaimed: zod.z.number().int().nonnegative(),
210958
+ hoursWalked: zod.z.number().int().nonnegative(),
210959
+ hoursMissing: zod.z.number().int().nonnegative(),
210960
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
210961
+ listings: zod.z.number().int().nonnegative(),
210962
+ segmentsClaimed: zod.z.number().int().nonnegative(),
210963
+ ghostSegments: zod.z.number().int().nonnegative(),
210964
+ ghostBytes: zod.z.number().int().nonnegative(),
210965
+ ghostHoursWhole: zod.z.number().int().nonnegative(),
210966
+ forgottenSegments: zod.z.number().int().nonnegative(),
210967
+ forgottenBytes: zod.z.number().int().nonnegative(),
210968
+ /** Files under a claimed hour that no durable row names. Never deleted. */
210969
+ orphanFiles: zod.z.number().int().nonnegative(),
210970
+ orphanSample: zod.z.array(zod.z.string()).readonly(),
210971
+ hoursSkipped: zod.z.number().int().nonnegative(),
210972
+ skippedByReason: LedgerWalkSkipCountsSchema,
210973
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
210974
+ bounded: zod.z.boolean(),
210975
+ byDevice: zod.z.array(LedgerWalkDeviceReportSchema).readonly()
210976
+ });
210466
210977
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
210467
210978
  var RelocatableMediaCountInputSchema = zod.z.object({
210468
210979
  toLocationId: zod.z.string().min(1),
@@ -222962,13 +223473,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222962
223473
  groups: zod.z.array(AnalyticsGroupRecordSchema).readonly(),
222963
223474
  nextCursor: zod.z.string().nullable()
222964
223475
  });
223476
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
223477
+ var KEY_EVENTS_MAX_LIMIT = 200;
222965
223478
  var KeyEventQueryInput = zod.z.object({
222966
223479
  deviceId: zod.z.number(),
222967
223480
  /** Window lower bound (track firstSeen ≥ since). */
222968
223481
  since: zod.z.number(),
222969
223482
  /** Window upper bound (track firstSeen ≤ until). */
222970
223483
  until: zod.z.number(),
222971
- limit: zod.z.number().int().min(1).max(200).default(50),
223484
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
222972
223485
  /** Drop tracks scoring below this importance. */
222973
223486
  minImportance: zod.z.number().min(0).max(1).optional(),
222974
223487
  /** Restrict to a single class (e.g. 'person'). */
@@ -222990,6 +223503,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222990
223503
  ...TrackFlagFields,
222991
223504
  ...TrackRetrainFields
222992
223505
  });
223506
+ var KeyEventBatchQueryInput = zod.z.object({
223507
+ deviceIds: zod.z.array(zod.z.number()).min(1).max(200),
223508
+ since: zod.z.number(),
223509
+ until: zod.z.number(),
223510
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
223511
+ * across the set, which would let a busy camera starve a quiet one of its
223512
+ * rows and change what the merged feed contains. */
223513
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
223514
+ minImportance: zod.z.number().min(0).max(1).optional(),
223515
+ classFilter: zod.z.string().optional()
223516
+ });
223517
+ var KeyEventsForDeviceSchema = zod.z.object({
223518
+ deviceId: zod.z.number(),
223519
+ events: zod.z.array(KeyEventSchema).readonly()
223520
+ });
222993
223521
  var TrackedDetectionSchema = zod.z.object({
222994
223522
  trackId: zod.z.string(),
222995
223523
  className: zod.z.string(),
@@ -223154,6 +223682,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223154
223682
  /** Present when the pass ended by throwing. */
223155
223683
  error: zod.z.string().nullable()
223156
223684
  });
223685
+ var MediaReclaimStartResultSchema = zod.z.object({
223686
+ started: zod.z.boolean(),
223687
+ /** True when a pass was already running; the new request is ignored. */
223688
+ alreadyRunning: zod.z.boolean()
223689
+ });
223690
+ var MediaReclaimInputSchema = zod.z.object({
223691
+ mode: zod.z.enum(["report", "reclaim"]).default("report"),
223692
+ scopes: zod.z.array(zod.z.enum(["motion-still", "track-filmstrip"])).min(1).optional(),
223693
+ deviceIds: zod.z.array(zod.z.number().int()).min(1).optional(),
223694
+ restart: zod.z.boolean().optional(),
223695
+ pageSize: zod.z.number().int().min(50).max(5e3).optional(),
223696
+ maxRowsPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
223697
+ maxReclaimPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
223698
+ maxBytesPerRun: zod.z.number().int().min(1).optional(),
223699
+ budgetMinutes: zod.z.number().int().min(1).max(720).optional(),
223700
+ throttleBytesPerSec: zod.z.number().int().min(64 * 1024).optional(),
223701
+ graceMinutes: zod.z.number().int().min(1).max(10080).optional()
223702
+ });
223703
+ var MediaReclaimStatusSchema = zod.z.object({
223704
+ running: zod.z.boolean(),
223705
+ mode: zod.z.enum(["report", "reclaim"]).nullable(),
223706
+ totalExamined: zod.z.number(),
223707
+ totalEligible: zod.z.number(),
223708
+ totalReclaimed: zod.z.number(),
223709
+ totalBytesReclaimed: zod.z.number(),
223710
+ totalRefused: zod.z.number(),
223711
+ /** Device+scope windows finished in this pass. */
223712
+ devicesDone: zod.z.number(),
223713
+ complete: zod.z.boolean().nullable(),
223714
+ startedAtMs: zod.z.number().nullable(),
223715
+ finishedAtMs: zod.z.number().nullable(),
223716
+ error: zod.z.string().nullable()
223717
+ });
223157
223718
  var ReplayFrameInputSchema = zod.z.object({
223158
223719
  timestamp: zod.z.number(),
223159
223720
  frame: PipelineRunResultBridge
@@ -223284,6 +223845,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223284
223845
  * scored on-read (no write). Degrades to `[]` on error.
223285
223846
  */
223286
223847
  getKeyEvents: require_sleep.method(KeyEventQueryInput, zod.z.array(KeyEventSchema).readonly()),
223848
+ /**
223849
+ * The same ranking, for a SET of cameras, in one round trip.
223850
+ *
223851
+ * The Detection Intelligence events feed asks this of every selected
223852
+ * camera and re-asks on a 30s timer. Fanned out client-side that is N
223853
+ * round trips — browser → hub → post-analysis — for N independent,
223854
+ * already-indexed store queries. Batched, the queries are unchanged and
223855
+ * run concurrently INSIDE the owner; only the transport collapses.
223856
+ *
223857
+ * Deliberately per-device rather than pre-merged: `limit` stays per
223858
+ * camera (a total would let a busy camera starve a quiet one), and a
223859
+ * caller that renders one camera's lane needs to know which camera a row
223860
+ * came from. `getKeyEvents` stays for single-device callers.
223861
+ */
223862
+ getKeyEventsBatch: require_sleep.method(KeyEventBatchQueryInput, zod.z.array(KeyEventsForDeviceSchema).readonly()),
223287
223863
  /** Server-side bucketed event counts for the 24-hour timeline.
223288
223864
  * Returns one entry per non-empty bucket; empty buckets are omitted. */
223289
223865
  getEventDensity: require_sleep.method(zod.z.object({
@@ -223562,6 +224138,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223562
224138
  auth: "admin"
223563
224139
  }),
223564
224140
  /**
224141
+ * Reclaim the standing debug-media backlog (motion-visit stills + track
224142
+ * filmstrips). DETACHED: returns `{ started }` immediately. The pass is
224143
+ * tens of minutes; awaiting it is a timeout, not a verdict. Dry-run unless
224144
+ * `mode: 'reclaim'`. Poll `getMediaReclaimStatus`.
224145
+ */
224146
+ reclaimDebugMedia: require_sleep.method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
224147
+ kind: "mutation",
224148
+ auth: "admin"
224149
+ }),
224150
+ getMediaReclaimStatus: require_sleep.method(zod.z.object({}), MediaReclaimStatusSchema, {
224151
+ kind: "query",
224152
+ auth: "admin"
224153
+ }),
224154
+ /**
223565
224155
  * The CHEAP QUESTION, asked before any media moves: how big is the dataset
223566
224156
  * the marked (`markForTrain`) tracks would produce?
223567
224157
  *
@@ -233401,6 +233991,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233401
233991
  kind: "query",
233402
233992
  auth: "admin"
233403
233993
  }),
233994
+ /**
233995
+ * Does this location's LEDGER tell the truth about its disk? (D319)
233996
+ *
233997
+ * One `readdir` per claimed hour, diffed both ways: durable rows whose file
233998
+ * is not there, and files no durable row names. `apply` defaults to FALSE —
233999
+ * the report is the product, and the dry run is how an operator
234000
+ * sanity-checks the destructive run before authorising it.
234001
+ *
234002
+ * REFUSES a source that is still a write target: a listing of a live
234003
+ * location is a lower bound, which is not the strong evidence that lets
234004
+ * this pass forget without a budget. A live location is reconciled by the
234005
+ * mover, under D318's bound.
234006
+ */
234007
+ reconcileLedgerAgainstDisk: require_sleep.method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
234008
+ kind: "mutation",
234009
+ auth: "admin"
234010
+ }),
233404
234011
  /** Cancel a running or queued relocate job. A queued job never runs. */
233405
234012
  cancelRelocateJob: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
233406
234013
  kind: "mutation",
@@ -233715,6 +234322,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233715
234322
  monitors: zod.z.array(SceneMonitorSchema),
233716
234323
  lastFetchedAt: zod.z.number()
233717
234324
  });
234325
+ var SceneMonitorStatusForDeviceSchema = zod.z.object({
234326
+ deviceId: zod.z.number(),
234327
+ status: SceneMonitorStatusSchema.nullable()
234328
+ });
233718
234329
  var sceneMonitorCapability = {
233719
234330
  name: "scene-monitor",
233720
234331
  scope: "device",
@@ -233724,6 +234335,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233724
234335
  deviceTypes: [require_sleep.DeviceType.Camera],
233725
234336
  methods: {
233726
234337
  listScenes: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
234338
+ /**
234339
+ * The same answer, for a SET of cameras, in one round trip.
234340
+ *
234341
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
234342
+ * poll behind the push slice. Fanned out client-side that was one query
234343
+ * per camera — 29 round trips through the browser, the hub and the
234344
+ * post-analysis runner every 30 seconds to read an in-memory map the
234345
+ * owner had already merged. The work is unchanged (`statusFor` per
234346
+ * device, all in-process at the owner); what collapses is the transport.
234347
+ *
234348
+ * A camera that cannot answer still gets a row, with `status: null` —
234349
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
234350
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
234351
+ * tell which two are missing, or that any are.
234352
+ */
234353
+ listScenesBatch: require_sleep.method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(SceneMonitorStatusForDeviceSchema).readonly()),
233727
234354
  createScene: require_sleep.method(zod.z.object({
233728
234355
  deviceId: zod.z.number(),
233729
234356
  label: zod.z.string(),
@@ -235525,6 +236152,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
235525
236152
  * than as repeated tracks/events. */
235526
236153
  stationaryObjects: zod.z.array(StationaryObjectSchema).readonly().optional()
235527
236154
  });
236155
+ var CameraOccupancySnapshotForDeviceSchema = zod.z.object({
236156
+ deviceId: zod.z.number(),
236157
+ read: zod.z.enum(["read", "unreadable"]),
236158
+ snapshot: CameraOccupancySnapshotSchema.nullable()
236159
+ });
235528
236160
  var HistoryResolutionEnum = zod.z.enum([
235529
236161
  "minute",
235530
236162
  "5min",
@@ -235554,6 +236186,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
235554
236186
  * (no inference result emitted since boot or since binding was
235555
236187
  * activated). */
235556
236188
  getCurrentSnapshot: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), CameraOccupancySnapshotSchema.nullable()),
236189
+ /**
236190
+ * The same snapshot, for a SET of cameras, in one round trip.
236191
+ *
236192
+ * The Events page's Stationary section polls this every 15s for every
236193
+ * selected camera. Fanned out client-side that is one query per camera to
236194
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
236195
+ * costs everything. Batched, N transports become one and the per-device
236196
+ * work is unchanged.
236197
+ *
236198
+ * Every requested deviceId gets a row, tagged `read` — see
236199
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
236200
+ * not answer for is `'unreadable'`, never an empty reading.
236201
+ */
236202
+ getCurrentSnapshotBatch: require_sleep.method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(CameraOccupancySnapshotForDeviceSchema).readonly()),
235557
236203
  /** Time-series object count inside one zone. `className` optional —
235558
236204
  * omit to count every class in the zone. */
235559
236205
  getZoneHistory: require_sleep.method(zod.z.object({
@@ -242419,6 +243065,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242419
243065
  addonId: null,
242420
243066
  access: "view"
242421
243067
  },
243068
+ "pipelineAnalytics.getKeyEventsBatch": {
243069
+ capName: "pipeline-analytics",
243070
+ capScope: "device",
243071
+ addonId: null,
243072
+ access: "view"
243073
+ },
243074
+ "pipelineAnalytics.getMediaReclaimStatus": {
243075
+ capName: "pipeline-analytics",
243076
+ capScope: "device",
243077
+ addonId: null,
243078
+ access: "view"
243079
+ },
242422
243080
  "pipelineAnalytics.getMotionEvents": {
242423
243081
  capName: "pipeline-analytics",
242424
243082
  capScope: "device",
@@ -242593,6 +243251,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242593
243251
  addonId: null,
242594
243252
  access: "create"
242595
243253
  },
243254
+ "pipelineAnalytics.reclaimDebugMedia": {
243255
+ capName: "pipeline-analytics",
243256
+ capScope: "device",
243257
+ addonId: null,
243258
+ access: "create"
243259
+ },
242596
243260
  "pipelineAnalytics.reconcileFromDisk": {
242597
243261
  capName: "pipeline-analytics",
242598
243262
  capScope: "device",
@@ -243595,6 +244259,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243595
244259
  addonId: null,
243596
244260
  access: "view"
243597
244261
  },
244262
+ "recording.reconcileLedgerAgainstDisk": {
244263
+ capName: "recording",
244264
+ capScope: "system",
244265
+ addonId: null,
244266
+ access: "create"
244267
+ },
243598
244268
  "recording.refreshStorageLocationsForMigration": {
243599
244269
  capName: "recording",
243600
244270
  capScope: "system",
@@ -243721,6 +244391,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243721
244391
  addonId: null,
243722
244392
  access: "view"
243723
244393
  },
244394
+ "sceneMonitor.listScenesBatch": {
244395
+ capName: "scene-monitor",
244396
+ capScope: "device",
244397
+ addonId: null,
244398
+ access: "view"
244399
+ },
243724
244400
  "sceneMonitor.recheckNow": {
243725
244401
  capName: "scene-monitor",
243726
244402
  capScope: "device",
@@ -245077,6 +245753,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
245077
245753
  addonId: null,
245078
245754
  access: "view"
245079
245755
  },
245756
+ "zoneAnalytics.getCurrentSnapshotBatch": {
245757
+ capName: "zone-analytics",
245758
+ capScope: "device",
245759
+ addonId: null,
245760
+ access: "view"
245761
+ },
245080
245762
  "zoneAnalytics.getUnzonedHistory": {
245081
245763
  capName: "zone-analytics",
245082
245764
  capScope: "device",
@@ -246339,6 +247021,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246339
247021
  form: "single",
246340
247022
  optional: false
246341
247023
  }],
247024
+ "pipelineAnalytics.getKeyEventsBatch": [{
247025
+ name: "deviceIds",
247026
+ form: "array",
247027
+ optional: false
247028
+ }],
246342
247029
  "pipelineAnalytics.getMotionEvents": [{
246343
247030
  name: "deviceId",
246344
247031
  form: "single",
@@ -246444,6 +247131,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246444
247131
  form: "single",
246445
247132
  optional: true
246446
247133
  }],
247134
+ "pipelineAnalytics.reclaimDebugMedia": [{
247135
+ name: "deviceIds",
247136
+ form: "array",
247137
+ optional: true
247138
+ }],
246447
247139
  "pipelineAnalytics.reconcileFromDisk": [{
246448
247140
  name: "deviceId",
246449
247141
  form: "single",
@@ -246779,6 +247471,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246779
247471
  form: "single",
246780
247472
  optional: false
246781
247473
  }],
247474
+ "recording.reconcileLedgerAgainstDisk": [{
247475
+ name: "deviceId",
247476
+ form: "single",
247477
+ optional: true
247478
+ }],
246782
247479
  "recording.relocateFootage": [{
246783
247480
  name: "deviceId",
246784
247481
  form: "single",
@@ -246844,6 +247541,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246844
247541
  form: "single",
246845
247542
  optional: false
246846
247543
  }],
247544
+ "sceneMonitor.listScenesBatch": [{
247545
+ name: "deviceIds",
247546
+ form: "array",
247547
+ optional: false
247548
+ }],
246847
247549
  "sceneMonitor.recheckNow": [{
246848
247550
  name: "deviceId",
246849
247551
  form: "single",
@@ -247105,6 +247807,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247105
247807
  form: "single",
247106
247808
  optional: false
247107
247809
  }],
247810
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
247811
+ name: "deviceIds",
247812
+ form: "array",
247813
+ optional: false
247814
+ }],
247108
247815
  "zoneAnalytics.getUnzonedHistory": [{
247109
247816
  name: "deviceId",
247110
247817
  form: "single",
@@ -247257,6 +247964,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247257
247964
  "recording.readGopBytes",
247258
247965
  "recording.readSegmentBytes",
247259
247966
  "recording.readWindowBytes",
247967
+ "recording.reconcileLedgerAgainstDisk",
247260
247968
  "recording.relocateFootage",
247261
247969
  "recording.renderClip",
247262
247970
  "recording.renderGif",
@@ -248142,6 +248850,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248142
248850
  relocateFootage: (input) => dispatch("recording", "relocateFootage", "mutation", input),
248143
248851
  listRelocateJobs: (input) => dispatch("recording", "listRelocateJobs", "query", input),
248144
248852
  getRelocateResidue: (input) => dispatch("recording", "getRelocateResidue", "query", input),
248853
+ reconcileLedgerAgainstDisk: (input) => dispatch("recording", "reconcileLedgerAgainstDisk", "mutation", input),
248145
248854
  cancelRelocateJob: (input) => dispatch("recording", "cancelRelocateJob", "mutation", input),
248146
248855
  planStorageRebalance: (input) => dispatch("recording", "planStorageRebalance", "query", input),
248147
248856
  startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input)
@@ -251102,6 +251811,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251102
251811
  exports.CameraMetricsSchema = CameraMetricsSchema;
251103
251812
  exports.CameraMetricsWithDeviceIdSchema = CameraMetricsWithDeviceIdSchema;
251104
251813
  exports.CameraMotionStatusSchema = CameraMotionStatusSchema;
251814
+ exports.CameraOccupancySnapshotForDeviceSchema = CameraOccupancySnapshotForDeviceSchema;
251105
251815
  exports.CameraRecordingModeSchema = CameraRecordingModeSchema;
251106
251816
  exports.CameraRecordingStatusSchema = CameraRecordingStatusSchema;
251107
251817
  exports.CameraSourceStatusSchema = CameraSourceStatusSchema;
@@ -251352,6 +252062,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251352
252062
  exports.LabelTierSchema = LabelTierSchema;
251353
252063
  exports.LawnMowerActivitySchema = LawnMowerActivitySchema;
251354
252064
  exports.LawnMowerControlStatusSchema = LawnMowerControlStatusSchema;
252065
+ exports.LedgerWalkDeviceReportSchema = LedgerWalkDeviceReportSchema;
252066
+ exports.LedgerWalkInputSchema = LedgerWalkInputSchema;
252067
+ exports.LedgerWalkRefusalSchema = LedgerWalkRefusalSchema;
252068
+ exports.LedgerWalkReportSchema = LedgerWalkReportSchema;
252069
+ exports.LedgerWalkSkipCountsSchema = LedgerWalkSkipCountsSchema;
252070
+ exports.LedgerWalkSkipReasonSchema = LedgerWalkSkipReasonSchema;
251355
252071
  exports.LinkedDeviceSchema = LinkedDeviceSchema;
251356
252072
  exports.LinkedDevicesModeSchema = LinkedDevicesModeSchema;
251357
252073
  exports.ListGroupsPageSchema = ListGroupsPageSchema;
@@ -251773,6 +252489,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251773
252489
  exports.SceneConfirmSchema = SceneConfirmSchema;
251774
252490
  exports.SceneMonitorSchema = SceneMonitorSchema;
251775
252491
  exports.SceneMonitorStateSchema = SceneMonitorStateSchema;
252492
+ exports.SceneMonitorStatusForDeviceSchema = SceneMonitorStatusForDeviceSchema;
251776
252493
  exports.SceneMonitorStatusSchema = SceneMonitorStatusSchema;
251777
252494
  exports.SceneReferenceSchema = SceneReferenceSchema;
251778
252495
  exports.SceneUnavailableSchema = SceneUnavailableSchema;
@@ -410593,6 +411310,7 @@ var require_cap_providers = __commonJS({
410593
411310
  exports.buildSettingsStoreGateway = buildSettingsStoreGateway;
410594
411311
  exports.buildNetworkQualityProvider = buildNetworkQualityProvider;
410595
411312
  exports.buildToastProvider = buildToastProvider;
411313
+ exports.computeTopologyPass = computeTopologyPass;
410596
411314
  exports.computeTopology = computeTopology;
410597
411315
  exports.createNodeRootPackageLookup = createNodeRootPackageLookup;
410598
411316
  exports.buildNodesProvider = buildNodesProvider;
@@ -410741,9 +411459,19 @@ var require_cap_providers = __commonJS({
410741
411459
  }
410742
411460
  return ips;
410743
411461
  }
411462
+ async function computeTopologyPass(agentRegistry, addonRegistry, getNodeRootPackage) {
411463
+ const [nodes, liveNodes] = await computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage);
411464
+ return { nodes, liveNodes };
411465
+ }
410744
411466
  async function computeTopology(agentRegistry, addonRegistry, getNodeRootPackage) {
410745
- const nodes = await agentRegistry.listNodes();
410746
- const history = await agentRegistry.getClusterNodeHistory();
411467
+ const [nodes] = await computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage);
411468
+ return nodes;
411469
+ }
411470
+ async function computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage) {
411471
+ const [nodes, history] = await Promise.all([
411472
+ agentRegistry.listNodes(),
411473
+ agentRegistry.getClusterNodeHistory()
411474
+ ]);
410747
411475
  const historyById = new Map(history.map((h) => [h.id, h]));
410748
411476
  const liveIds = new Set(nodes.map((n) => n.info.id));
410749
411477
  const allAddons = addonRegistry?.listAddons() ?? [];
@@ -410898,7 +411626,7 @@ var require_cap_providers = __commonJS({
410898
411626
  categories: [],
410899
411627
  rootPackage: null
410900
411628
  }));
410901
- return [...liveNodes, ...offlineNodes];
411629
+ return [[...liveNodes, ...offlineNodes], nodes];
410902
411630
  }
410903
411631
  function createNodeRootPackageLookup(moleculer, serverUpdate) {
410904
411632
  return (nodeId, isHub) => {
@@ -420624,6 +421352,19 @@ var require_agent_registry_service = __commonJS({
420624
421352
  addonIds: [...entry.agentAddons ?? []]
420625
421353
  };
420626
421354
  }
421355
+ function toSubProcess(p) {
421356
+ return {
421357
+ pid: p.pid ?? 0,
421358
+ name: p.name ?? "",
421359
+ command: "moleculer-service",
421360
+ state: p.state ?? "running",
421361
+ cpuPercent: p.cpuPercent ?? 0,
421362
+ memoryRss: p.memoryRss ?? 0,
421363
+ uptimeSeconds: p.uptimeSeconds ?? 0,
421364
+ addonIds: p.addonIds ?? [],
421365
+ groupId: p.groupId ?? null
421366
+ };
421367
+ }
420627
421368
  var AgentRegistryService = class {
420628
421369
  eventBus;
420629
421370
  moleculer;
@@ -421150,107 +421891,113 @@ var require_agent_registry_service = __commonJS({
421150
421891
  return toNodeLiveness(this.moleculer.broker.registry.getNodeList({ onlyAvailable: false }));
421151
421892
  }
421152
421893
  async listNodes() {
421153
- let hubProcesses = [];
421894
+ const registry = this.moleculer.broker.registry;
421895
+ const nodes = registry?.getNodeList?.({ onlyAvailable: false }) ?? [];
421896
+ const remoteTargets = nodes.filter((node) => typeof node.id === "string" && node.id !== "hub" && !node.id.includes("/"));
421897
+ const [hubEntry, remoteEntries] = await Promise.all([
421898
+ this.listHubProcesses().then((procs) => this.buildHubEntry(procs)),
421899
+ Promise.all(remoteTargets.map((node) => node.available ? (
421900
+ // Online: one wave of `$agent.status` + `$process.list`.
421901
+ this.buildRemoteEntry(node.id)
421902
+ ) : (
421903
+ // Offline: skip the RPC fan-out entirely (the node is unreachable —
421904
+ // those calls would just time out) and surface a minimal,
421905
+ // honestly-degraded row instead.
421906
+ Promise.resolve(this.buildOfflineEntry(node))
421907
+ )))
421908
+ ]);
421909
+ return [hubEntry, ...remoteEntries.filter((entry) => entry !== null)];
421910
+ }
421911
+ /** The hub's own runner subprocesses (`$process.list`, hub-local). */
421912
+ async listHubProcesses() {
421154
421913
  try {
421155
421914
  const processes = await this.broker.call("$process.list");
421156
- hubProcesses = processes.map((p) => ({
421157
- pid: p.pid ?? 0,
421158
- name: p.name ?? "",
421159
- command: "moleculer-service",
421160
- state: p.state ?? "running",
421161
- cpuPercent: p.cpuPercent ?? 0,
421162
- memoryRss: p.memoryRss ?? 0,
421163
- uptimeSeconds: p.uptimeSeconds ?? 0,
421164
- addonIds: p.addonIds ?? [],
421165
- groupId: p.groupId ?? null
421166
- }));
421915
+ return processes.map(toSubProcess);
421167
421916
  } catch {
421917
+ return [];
421168
421918
  }
421169
- const hubEntry = await this.buildHubEntry(hubProcesses);
421170
- const remoteEntries = [];
421171
- const registry = this.moleculer.broker.registry;
421172
- const nodes = registry?.getNodeList?.({ onlyAvailable: false }) ?? [];
421173
- for (const node of nodes) {
421174
- const nodeId = node.id;
421175
- if (typeof nodeId !== "string" || nodeId === "hub" || nodeId.includes("/"))
421176
- continue;
421177
- if (!node.available) {
421178
- remoteEntries.push(this.buildOfflineEntry(node));
421179
- continue;
421180
- }
421181
- try {
421182
- const status = await this.broker.call("$agent.status", {}, {
421183
- nodeID: nodeId,
421184
- timeout: 5e3
421185
- });
421186
- let subProcesses = [];
421187
- try {
421188
- const processes = await this.broker.call("$process.list", {}, {
421189
- nodeID: nodeId,
421190
- timeout: 5e3
421191
- });
421192
- subProcesses = processes.map((p) => ({
421193
- pid: p.pid ?? 0,
421194
- name: p.name ?? "",
421195
- command: "moleculer-service",
421196
- state: p.state ?? "running",
421197
- cpuPercent: p.cpuPercent ?? 0,
421198
- memoryRss: p.memoryRss ?? 0,
421199
- uptimeSeconds: p.uptimeSeconds ?? 0,
421200
- addonIds: p.addonIds ?? [],
421201
- groupId: p.groupId ?? null
421202
- }));
421203
- } catch {
421204
- subProcesses = status.addons?.map((a) => ({
421205
- pid: 0,
421206
- name: a.id ?? "",
421207
- command: "moleculer-service",
421208
- state: a.status ?? "running",
421209
- cpuPercent: 0,
421210
- memoryRss: 0,
421211
- uptimeSeconds: 0
421212
- })) ?? [];
421213
- }
421214
- const agentAddons = status.addons?.map((a) => a.id) ?? [];
421215
- const hostname = typeof status.hostname === "string" ? status.hostname : null;
421216
- const agentName = typeof status.name === "string" ? status.name : nodeId;
421217
- remoteEntries.push({
421218
- info: {
421219
- id: nodeId,
421220
- name: agentName,
421221
- hostname: hostname ?? nodeId,
421222
- capabilities: [],
421223
- platform: status.platform ?? "unknown",
421224
- arch: status.arch ?? "unknown",
421225
- cpuCores: status.cpuCores ?? 0,
421226
- memoryMB: status.totalMemoryMB ?? 0,
421227
- cpuModel: status.cpuModel
421228
- },
421229
- localIps: Array.isArray(status.localIps) ? status.localIps : [],
421230
- status: {
421231
- activeCameras: 0,
421232
- cpuPercent: status.cpuPercent ?? 0,
421233
- memoryPercent: status.memoryPercent ?? 0,
421234
- fps: {},
421235
- errors: []
421236
- },
421237
- connectedSince: typeof status.uptime === "number" ? Date.now() - status.uptime * 1e3 : Date.now(),
421238
- isHub: false,
421239
- subProcesses,
421240
- agentAddons
421241
- });
421242
- } catch {
421243
- }
421244
- }
421245
- await this.snapshotOnlineNodes([hubEntry, ...remoteEntries]);
421246
- return [hubEntry, ...remoteEntries];
421247
421919
  }
421248
421920
  /**
421249
- * Best-effort upsert of every online entry's descriptor into the history
421250
- * store. Never throws the store swallows its own errors and this awaits
421251
- * `allSettled` so a storage hiccup can never fail `listNodes()`.
421921
+ * One online agent's row: `$agent.status` for the descriptor + live metrics,
421922
+ * `$process.list` for real sub-process stats. `null` when the node has no
421923
+ * `$agent` service (it is then simply absent from the list, as before).
421924
+ *
421925
+ * The two calls are ONE wave, not two: `$process.list` never reads anything
421926
+ * from `$agent.status` — only its FALLBACK does, and the fallback is applied
421927
+ * after both have settled.
421928
+ */
421929
+ async buildRemoteEntry(nodeId) {
421930
+ const [statusResult, processResult] = await Promise.allSettled([
421931
+ this.broker.call("$agent.status", {}, { nodeID: nodeId, timeout: 5e3 }),
421932
+ this.broker.call("$process.list", {}, { nodeID: nodeId, timeout: 5e3 })
421933
+ ]);
421934
+ if (statusResult.status === "rejected")
421935
+ return null;
421936
+ const status = statusResult.value;
421937
+ const subProcesses = processResult.status === "fulfilled" ? processResult.value.map(toSubProcess) : (
421938
+ // Fall back to the addon list from $agent.status (no stats)
421939
+ status.addons?.map((a) => ({
421940
+ pid: 0,
421941
+ name: a.id ?? "",
421942
+ command: "moleculer-service",
421943
+ state: a.status ?? "running",
421944
+ cpuPercent: 0,
421945
+ memoryRss: 0,
421946
+ uptimeSeconds: 0
421947
+ })) ?? []
421948
+ );
421949
+ const agentAddons = status.addons?.map((a) => a.id) ?? [];
421950
+ const hostname = typeof status.hostname === "string" ? status.hostname : null;
421951
+ const agentName = typeof status.name === "string" ? status.name : nodeId;
421952
+ return {
421953
+ info: {
421954
+ id: nodeId,
421955
+ name: agentName,
421956
+ hostname: hostname ?? nodeId,
421957
+ capabilities: [],
421958
+ platform: status.platform ?? "unknown",
421959
+ arch: status.arch ?? "unknown",
421960
+ cpuCores: status.cpuCores ?? 0,
421961
+ memoryMB: status.totalMemoryMB ?? 0,
421962
+ cpuModel: status.cpuModel
421963
+ },
421964
+ localIps: Array.isArray(status.localIps) ? status.localIps : [],
421965
+ status: {
421966
+ activeCameras: 0,
421967
+ cpuPercent: status.cpuPercent ?? 0,
421968
+ memoryPercent: status.memoryPercent ?? 0,
421969
+ fps: {},
421970
+ errors: []
421971
+ },
421972
+ connectedSince: typeof status.uptime === "number" ? Date.now() - status.uptime * 1e3 : Date.now(),
421973
+ isHub: false,
421974
+ subProcesses,
421975
+ agentAddons
421976
+ };
421977
+ }
421978
+ /**
421979
+ * A1 (R2 capture point): snapshot every ONLINE node's static descriptor +
421980
+ * last-known addon roster into the durable, routing-blind history store.
421981
+ * This is the ONLY place cpuModel/cores/memory/localIps are resolved from a
421982
+ * live `$agent.status`, so it is where an offline row's descriptor must be
421983
+ * captured while the node is still reachable. Offline entries are NOT
421984
+ * re-snapshotted — their `lastActive` is stamped on `$node.disconnected`
421985
+ * instead. This is a separate durable store, never a `knownAgents`-style
421986
+ * shadow of `HubNodeRegistry` (which stays the ephemeral live cap authority).
421987
+ *
421988
+ * **It belongs to the PERIODIC PASS, not to a read** (D317). It used to run
421989
+ * at the tail of `listNodes()`, which made every operator poll of
421990
+ * `nodes.topology` perform one durable read-back — and, past the five-minute
421991
+ * heartbeat, one durable write — per online node, in a phase that had to
421992
+ * finish before the history read could even start. Measured on the live hub
421993
+ * on 2026-08-31: one settings-store round trip cost 14.8–18.3 s and
421994
+ * `nodes.topology` cost exactly two of them, 30.5 s. `TopologyEmitterService`
421995
+ * already recomputes this picture every 30 s; the capture is its work.
421996
+ *
421997
+ * Never throws — the store swallows its own errors and this awaits
421998
+ * `allSettled` so a storage hiccup can never fail the pass.
421252
421999
  */
421253
- async snapshotOnlineNodes(entries) {
422000
+ async captureNodeHistory(entries) {
421254
422001
  if (!this.historyStore)
421255
422002
  return;
421256
422003
  const online = entries.filter((e) => e.isOnline !== false);
@@ -423900,7 +424647,7 @@ var require_topology_emitter_service = __commonJS({
423900
424647
  return;
423901
424648
  this.emitting = true;
423902
424649
  try {
423903
- const nodes = await (0, cap_providers_1.computeTopology)(this.agentRegistry, this.addonRegistry, this.getNodeRootPackage);
424650
+ const { nodes, liveNodes } = await (0, cap_providers_1.computeTopologyPass)(this.agentRegistry, this.addonRegistry, this.getNodeRootPackage);
423904
424651
  this.eventBus.emit({
423905
424652
  id: (0, node_crypto_1.randomUUID)(),
423906
424653
  timestamp: /* @__PURE__ */ new Date(),
@@ -423908,6 +424655,7 @@ var require_topology_emitter_service = __commonJS({
423908
424655
  category: types_1.EventCategory.ClusterTopologySnapshot,
423909
424656
  data: { nodes, timestamp: Date.now() }
423910
424657
  });
424658
+ await this.agentRegistry.captureNodeHistory(liveNodes);
423911
424659
  } catch {
423912
424660
  } finally {
423913
424661
  this.emitting = false;