camstack 1.2.68 → 1.2.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-CCIBlacf.js
23637
- var require_dist_CCIBlacf = __commonJS({
23638
- "../system/dist/dist-CCIBlacf.js"(exports) {
23636
+ // ../system/dist/dist-B29Skpzo.js
23637
+ var require_dist_B29Skpzo = __commonJS({
23638
+ "../system/dist/dist-B29Skpzo.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -26274,6 +26274,20 @@ var require_dist_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(),
@@ -37320,6 +37416,21 @@ var require_dist_CCIBlacf = __commonJS({
37320
37416
  * scored on-read (no write). Degrades to `[]` on error.
37321
37417
  */
37322
37418
  getKeyEvents: method(KeyEventQueryInput, zod.z.array(KeyEventSchema).readonly()),
37419
+ /**
37420
+ * The same ranking, for a SET of cameras, in one round trip.
37421
+ *
37422
+ * The Detection Intelligence events feed asks this of every selected
37423
+ * camera and re-asks on a 30s timer. Fanned out client-side that is N
37424
+ * round trips — browser → hub → post-analysis — for N independent,
37425
+ * already-indexed store queries. Batched, the queries are unchanged and
37426
+ * run concurrently INSIDE the owner; only the transport collapses.
37427
+ *
37428
+ * Deliberately per-device rather than pre-merged: `limit` stays per
37429
+ * camera (a total would let a busy camera starve a quiet one), and a
37430
+ * caller that renders one camera's lane needs to know which camera a row
37431
+ * came from. `getKeyEvents` stays for single-device callers.
37432
+ */
37433
+ getKeyEventsBatch: method(KeyEventBatchQueryInput, zod.z.array(KeyEventsForDeviceSchema).readonly()),
37323
37434
  /** Server-side bucketed event counts for the 24-hour timeline.
37324
37435
  * Returns one entry per non-empty bucket; empty buckets are omitted. */
37325
37436
  getEventDensity: method(zod.z.object({
@@ -47264,6 +47375,23 @@ var require_dist_CCIBlacf = __commonJS({
47264
47375
  kind: "query",
47265
47376
  auth: "admin"
47266
47377
  }),
47378
+ /**
47379
+ * Does this location's LEDGER tell the truth about its disk? (D319)
47380
+ *
47381
+ * One `readdir` per claimed hour, diffed both ways: durable rows whose file
47382
+ * is not there, and files no durable row names. `apply` defaults to FALSE —
47383
+ * the report is the product, and the dry run is how an operator
47384
+ * sanity-checks the destructive run before authorising it.
47385
+ *
47386
+ * REFUSES a source that is still a write target: a listing of a live
47387
+ * location is a lower bound, which is not the strong evidence that lets
47388
+ * this pass forget without a budget. A live location is reconciled by the
47389
+ * mover, under D318's bound.
47390
+ */
47391
+ reconcileLedgerAgainstDisk: method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
47392
+ kind: "mutation",
47393
+ auth: "admin"
47394
+ }),
47267
47395
  /** Cancel a running or queued relocate job. A queued job never runs. */
47268
47396
  cancelRelocateJob: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
47269
47397
  kind: "mutation",
@@ -47561,6 +47689,10 @@ var require_dist_CCIBlacf = __commonJS({
47561
47689
  monitors: zod.z.array(SceneMonitorSchema),
47562
47690
  lastFetchedAt: zod.z.number()
47563
47691
  });
47692
+ var SceneMonitorStatusForDeviceSchema = zod.z.object({
47693
+ deviceId: zod.z.number(),
47694
+ status: SceneMonitorStatusSchema.nullable()
47695
+ });
47564
47696
  var sceneMonitorCapability = {
47565
47697
  name: "scene-monitor",
47566
47698
  scope: "device",
@@ -47570,6 +47702,22 @@ var require_dist_CCIBlacf = __commonJS({
47570
47702
  deviceTypes: [DeviceType.Camera],
47571
47703
  methods: {
47572
47704
  listScenes: method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
47705
+ /**
47706
+ * The same answer, for a SET of cameras, in one round trip.
47707
+ *
47708
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
47709
+ * poll behind the push slice. Fanned out client-side that was one query
47710
+ * per camera — 29 round trips through the browser, the hub and the
47711
+ * post-analysis runner every 30 seconds to read an in-memory map the
47712
+ * owner had already merged. The work is unchanged (`statusFor` per
47713
+ * device, all in-process at the owner); what collapses is the transport.
47714
+ *
47715
+ * A camera that cannot answer still gets a row, with `status: null` —
47716
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
47717
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
47718
+ * tell which two are missing, or that any are.
47719
+ */
47720
+ listScenesBatch: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(SceneMonitorStatusForDeviceSchema).readonly()),
47573
47721
  createScene: method(zod.z.object({
47574
47722
  deviceId: zod.z.number(),
47575
47723
  label: zod.z.string(),
@@ -49370,6 +49518,11 @@ var require_dist_CCIBlacf = __commonJS({
49370
49518
  * than as repeated tracks/events. */
49371
49519
  stationaryObjects: zod.z.array(StationaryObjectSchema).readonly().optional()
49372
49520
  });
49521
+ var CameraOccupancySnapshotForDeviceSchema = zod.z.object({
49522
+ deviceId: zod.z.number(),
49523
+ read: zod.z.enum(["read", "unreadable"]),
49524
+ snapshot: CameraOccupancySnapshotSchema.nullable()
49525
+ });
49373
49526
  var HistoryResolutionEnum = zod.z.enum([
49374
49527
  "minute",
49375
49528
  "5min",
@@ -49399,6 +49552,20 @@ var require_dist_CCIBlacf = __commonJS({
49399
49552
  * (no inference result emitted since boot or since binding was
49400
49553
  * activated). */
49401
49554
  getCurrentSnapshot: method(zod.z.object({ deviceId: zod.z.number() }), CameraOccupancySnapshotSchema.nullable()),
49555
+ /**
49556
+ * The same snapshot, for a SET of cameras, in one round trip.
49557
+ *
49558
+ * The Events page's Stationary section polls this every 15s for every
49559
+ * selected camera. Fanned out client-side that is one query per camera to
49560
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
49561
+ * costs everything. Batched, N transports become one and the per-device
49562
+ * work is unchanged.
49563
+ *
49564
+ * Every requested deviceId gets a row, tagged `read` — see
49565
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
49566
+ * not answer for is `'unreadable'`, never an empty reading.
49567
+ */
49568
+ getCurrentSnapshotBatch: method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(CameraOccupancySnapshotForDeviceSchema).readonly()),
49402
49569
  /** Time-series object count inside one zone. `className` optional —
49403
49570
  * omit to count every class in the zone. */
49404
49571
  getZoneHistory: method(zod.z.object({
@@ -53346,6 +53513,12 @@ var require_dist_CCIBlacf = __commonJS({
53346
53513
  addonId: null,
53347
53514
  access: "view"
53348
53515
  },
53516
+ "pipelineAnalytics.getKeyEventsBatch": {
53517
+ capName: "pipeline-analytics",
53518
+ capScope: "device",
53519
+ addonId: null,
53520
+ access: "view"
53521
+ },
53349
53522
  "pipelineAnalytics.getMotionEvents": {
53350
53523
  capName: "pipeline-analytics",
53351
53524
  capScope: "device",
@@ -54522,6 +54695,12 @@ var require_dist_CCIBlacf = __commonJS({
54522
54695
  addonId: null,
54523
54696
  access: "view"
54524
54697
  },
54698
+ "recording.reconcileLedgerAgainstDisk": {
54699
+ capName: "recording",
54700
+ capScope: "system",
54701
+ addonId: null,
54702
+ access: "create"
54703
+ },
54525
54704
  "recording.refreshStorageLocationsForMigration": {
54526
54705
  capName: "recording",
54527
54706
  capScope: "system",
@@ -54648,6 +54827,12 @@ var require_dist_CCIBlacf = __commonJS({
54648
54827
  addonId: null,
54649
54828
  access: "view"
54650
54829
  },
54830
+ "sceneMonitor.listScenesBatch": {
54831
+ capName: "scene-monitor",
54832
+ capScope: "device",
54833
+ addonId: null,
54834
+ access: "view"
54835
+ },
54651
54836
  "sceneMonitor.recheckNow": {
54652
54837
  capName: "scene-monitor",
54653
54838
  capScope: "device",
@@ -56004,6 +56189,12 @@ var require_dist_CCIBlacf = __commonJS({
56004
56189
  addonId: null,
56005
56190
  access: "view"
56006
56191
  },
56192
+ "zoneAnalytics.getCurrentSnapshotBatch": {
56193
+ capName: "zone-analytics",
56194
+ capScope: "device",
56195
+ addonId: null,
56196
+ access: "view"
56197
+ },
56007
56198
  "zoneAnalytics.getUnzonedHistory": {
56008
56199
  capName: "zone-analytics",
56009
56200
  capScope: "device",
@@ -57008,6 +57199,11 @@ var require_dist_CCIBlacf = __commonJS({
57008
57199
  form: "single",
57009
57200
  optional: false
57010
57201
  }],
57202
+ "pipelineAnalytics.getKeyEventsBatch": [{
57203
+ name: "deviceIds",
57204
+ form: "array",
57205
+ optional: false
57206
+ }],
57011
57207
  "pipelineAnalytics.getMotionEvents": [{
57012
57208
  name: "deviceId",
57013
57209
  form: "single",
@@ -57448,6 +57644,11 @@ var require_dist_CCIBlacf = __commonJS({
57448
57644
  form: "single",
57449
57645
  optional: false
57450
57646
  }],
57647
+ "recording.reconcileLedgerAgainstDisk": [{
57648
+ name: "deviceId",
57649
+ form: "single",
57650
+ optional: true
57651
+ }],
57451
57652
  "recording.relocateFootage": [{
57452
57653
  name: "deviceId",
57453
57654
  form: "single",
@@ -57513,6 +57714,11 @@ var require_dist_CCIBlacf = __commonJS({
57513
57714
  form: "single",
57514
57715
  optional: false
57515
57716
  }],
57717
+ "sceneMonitor.listScenesBatch": [{
57718
+ name: "deviceIds",
57719
+ form: "array",
57720
+ optional: false
57721
+ }],
57516
57722
  "sceneMonitor.recheckNow": [{
57517
57723
  name: "deviceId",
57518
57724
  form: "single",
@@ -57774,6 +57980,11 @@ var require_dist_CCIBlacf = __commonJS({
57774
57980
  form: "single",
57775
57981
  optional: false
57776
57982
  }],
57983
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
57984
+ name: "deviceIds",
57985
+ form: "array",
57986
+ optional: false
57987
+ }],
57777
57988
  "zoneAnalytics.getUnzonedHistory": [{
57778
57989
  name: "deviceId",
57779
57990
  form: "single",
@@ -59207,7 +59418,7 @@ var require_alerts_addon = __commonJS({
59207
59418
  [Symbol.toStringTag]: { value: "Module" }
59208
59419
  });
59209
59420
  require_chunk_Cek0wNdY();
59210
- var require_dist10 = require_dist_CCIBlacf();
59421
+ var require_dist10 = require_dist_B29Skpzo();
59211
59422
  function selectExpired(alerts, cutoffMs) {
59212
59423
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59213
59424
  }
@@ -60026,7 +60237,7 @@ var require_console_logging = __commonJS({
60026
60237
  [Symbol.toStringTag]: { value: "Module" }
60027
60238
  });
60028
60239
  require_chunk_Cek0wNdY();
60029
- var require_dist10 = require_dist_CCIBlacf();
60240
+ var require_dist10 = require_dist_B29Skpzo();
60030
60241
  var require_formatter = require_formatter_DqAKDlvN();
60031
60242
  var LEVEL_RANK = {
60032
60243
  debug: 0,
@@ -60120,7 +60331,7 @@ var require_core_blocks_addon = __commonJS({
60120
60331
  "use strict";
60121
60332
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60122
60333
  var require_chunk = require_chunk_Cek0wNdY();
60123
- var require_dist10 = require_dist_CCIBlacf();
60334
+ var require_dist10 = require_dist_B29Skpzo();
60124
60335
  var node_crypto = __require("crypto");
60125
60336
  var node_fs_promises = __require("fs/promises");
60126
60337
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -61017,11 +61228,11 @@ var require_core_blocks = __commonJS({
61017
61228
  }
61018
61229
  });
61019
61230
 
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) {
61231
+ // ../system/dist/retired-settings-keys-CRL0qOnU.js
61232
+ var require_retired_settings_keys_CRL0qOnU = __commonJS({
61233
+ "../system/dist/retired-settings-keys-CRL0qOnU.js"(exports) {
61023
61234
  "use strict";
61024
- var require_dist10 = require_dist_CCIBlacf();
61235
+ var require_dist10 = require_dist_B29Skpzo();
61025
61236
  function settingsStoreIsAuthoritativeHere(env) {
61026
61237
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
61027
61238
  return raw === "" || raw === "hub";
@@ -63235,8 +63446,8 @@ var require_device_manager_addon = __commonJS({
63235
63446
  [Symbol.toStringTag]: { value: "Module" }
63236
63447
  });
63237
63448
  require_chunk_Cek0wNdY();
63238
- var require_dist10 = require_dist_CCIBlacf();
63239
- var require_retired_settings_keys = require_retired_settings_keys_BxV3e2Km();
63449
+ var require_dist10 = require_dist_B29Skpzo();
63450
+ var require_retired_settings_keys = require_retired_settings_keys_CRL0qOnU();
63240
63451
  var node_crypto = __require("crypto");
63241
63452
  var _camstack_types_node = require_node();
63242
63453
  var JOB_HISTORY = 20;
@@ -68049,7 +68260,7 @@ var require_hub_forwarder = __commonJS({
68049
68260
  [Symbol.toStringTag]: { value: "Module" }
68050
68261
  });
68051
68262
  require_chunk_Cek0wNdY();
68052
- var require_dist10 = require_dist_CCIBlacf();
68263
+ var require_dist10 = require_dist_B29Skpzo();
68053
68264
  var require_formatter = require_formatter_DqAKDlvN();
68054
68265
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
68055
68266
  var HubForwarderDestination = class {
@@ -68186,7 +68397,7 @@ var require_liveness_monitor_addon = __commonJS({
68186
68397
  "use strict";
68187
68398
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68188
68399
  require_chunk_Cek0wNdY();
68189
- var require_dist10 = require_dist_CCIBlacf();
68400
+ var require_dist10 = require_dist_B29Skpzo();
68190
68401
  var NO_DEVICES = "liveness:no-devices";
68191
68402
  var ALL_OFFLINE = "liveness:all-devices-offline";
68192
68403
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68376,7 +68587,7 @@ var require_local_auth_addon = __commonJS({
68376
68587
  [Symbol.toStringTag]: { value: "Module" }
68377
68588
  });
68378
68589
  var require_chunk = require_chunk_Cek0wNdY();
68379
- var require_dist10 = require_dist_CCIBlacf();
68590
+ var require_dist10 = require_dist_B29Skpzo();
68380
68591
  var node_crypto = __require("crypto");
68381
68592
  node_crypto = require_chunk.__toESM(node_crypto);
68382
68593
  var crypto$1 = __require("crypto");
@@ -76189,7 +76400,7 @@ var require_loki_logging = __commonJS({
76189
76400
  [Symbol.toStringTag]: { value: "Module" }
76190
76401
  });
76191
76402
  require_chunk_Cek0wNdY();
76192
- var require_dist10 = require_dist_CCIBlacf();
76403
+ var require_dist10 = require_dist_B29Skpzo();
76193
76404
  function sanitizeLabelName(raw) {
76194
76405
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76195
76406
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -76754,7 +76965,7 @@ var require_native_metrics_addon = __commonJS({
76754
76965
  [Symbol.toStringTag]: { value: "Module" }
76755
76966
  });
76756
76967
  var require_chunk = require_chunk_Cek0wNdY();
76757
- var require_dist10 = require_dist_CCIBlacf();
76968
+ var require_dist10 = require_dist_B29Skpzo();
76758
76969
  var node_fs_promises = __require("fs/promises");
76759
76970
  var node_child_process = __require("child_process");
76760
76971
  var node_util = __require("util");
@@ -79376,7 +79587,7 @@ var require_filesystem_storage_addon = __commonJS({
79376
79587
  [Symbol.toStringTag]: { value: "Module" }
79377
79588
  });
79378
79589
  var require_chunk = require_chunk_Cek0wNdY();
79379
- var require_dist10 = require_dist_CCIBlacf();
79590
+ var require_dist10 = require_dist_B29Skpzo();
79380
79591
  var node_crypto = __require("crypto");
79381
79592
  var node_fs_promises = __require("fs/promises");
79382
79593
  var node_path = __require("path");
@@ -80492,8 +80703,8 @@ var require_sqlite_settings_addon = __commonJS({
80492
80703
  [Symbol.toStringTag]: { value: "Module" }
80493
80704
  });
80494
80705
  var require_chunk = require_chunk_Cek0wNdY();
80495
- var require_dist10 = require_dist_CCIBlacf();
80496
- var require_retired_settings_keys = require_retired_settings_keys_BxV3e2Km();
80706
+ var require_dist10 = require_dist_B29Skpzo();
80707
+ var require_retired_settings_keys = require_retired_settings_keys_CRL0qOnU();
80497
80708
  var node_crypto = __require("crypto");
80498
80709
  var node_fs = __require("fs");
80499
80710
  var node_module = __require("module");
@@ -82872,7 +83083,7 @@ var require_storage_orchestrator_addon = __commonJS({
82872
83083
  [Symbol.toStringTag]: { value: "Module" }
82873
83084
  });
82874
83085
  var require_chunk = require_chunk_Cek0wNdY();
82875
- var require_dist10 = require_dist_CCIBlacf();
83086
+ var require_dist10 = require_dist_B29Skpzo();
82876
83087
  var node_crypto = __require("crypto");
82877
83088
  var node_fs_promises = __require("fs/promises");
82878
83089
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -85469,7 +85680,7 @@ var require_system_config_addon = __commonJS({
85469
85680
  [Symbol.toStringTag]: { value: "Module" }
85470
85681
  });
85471
85682
  require_chunk_Cek0wNdY();
85472
- var require_dist10 = require_dist_CCIBlacf();
85683
+ var require_dist10 = require_dist_B29Skpzo();
85473
85684
  var SECTION_TITLES = {
85474
85685
  server: "Server",
85475
85686
  auth: "Authentication"
@@ -103530,7 +103741,7 @@ var require_winston_logging = __commonJS({
103530
103741
  [Symbol.toStringTag]: { value: "Module" }
103531
103742
  });
103532
103743
  var require_chunk = require_chunk_Cek0wNdY();
103533
- var require_dist10 = require_dist_CCIBlacf();
103744
+ var require_dist10 = require_dist_B29Skpzo();
103534
103745
  var require_formatter = require_formatter_DqAKDlvN();
103535
103746
  var node_path = __require("path");
103536
103747
  node_path = require_chunk.__toESM(node_path);
@@ -105473,9 +105684,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105473
105684
  }
105474
105685
  });
105475
105686
 
105476
- // ../types/dist/sleep-D5821NGq.js
105477
- var require_sleep_D5821NGq = __commonJS({
105478
- "../types/dist/sleep-D5821NGq.js"(exports) {
105687
+ // ../types/dist/sleep-DIg3xuEw.js
105688
+ var require_sleep_DIg3xuEw = __commonJS({
105689
+ "../types/dist/sleep-DIg3xuEw.js"(exports) {
105479
105690
  "use strict";
105480
105691
  var require_event_category = require_event_category_BaEgqJNv();
105481
105692
  var zod = require_zod();
@@ -108158,6 +108369,7 @@ var require_sleep_D5821NGq = __commonJS({
108158
108369
  listEventKindsBatch: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listEventKindsBatch", "query", input),
108159
108370
  getSensorEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getSensorEvents", "query", input),
108160
108371
  getKeyEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEvents", "query", input),
108372
+ getKeyEventsBatch: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getKeyEventsBatch", "query", input),
108161
108373
  getEventDensity: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getEventDensity", "query", input),
108162
108374
  pruneEventsBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneEventsBefore", "mutation", input),
108163
108375
  pruneTracksBefore: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "pruneTracksBefore", "mutation", input),
@@ -108239,6 +108451,7 @@ var require_sleep_D5821NGq = __commonJS({
108239
108451
  reboot: { reboot: (input) => dispatch("reboot", "reboot", "reboot", "mutation", input) },
108240
108452
  sceneMonitor: {
108241
108453
  listScenes: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenes", "query", input),
108454
+ listScenesBatch: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenesBatch", "query", input),
108242
108455
  createScene: (input) => dispatch("scene-monitor", "sceneMonitor", "createScene", "mutation", input),
108243
108456
  updateScene: (input) => dispatch("scene-monitor", "sceneMonitor", "updateScene", "mutation", input),
108244
108457
  deleteScene: (input) => dispatch("scene-monitor", "sceneMonitor", "deleteScene", "mutation", input),
@@ -108323,6 +108536,7 @@ var require_sleep_D5821NGq = __commonJS({
108323
108536
  },
108324
108537
  zoneAnalytics: {
108325
108538
  getCurrentSnapshot: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCurrentSnapshot", "query", input),
108539
+ getCurrentSnapshotBatch: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCurrentSnapshotBatch", "query", input),
108326
108540
  getZoneHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getZoneHistory", "query", input),
108327
108541
  getCameraHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getCameraHistory", "query", input),
108328
108542
  getUnzonedHistory: (input) => dispatch("zone-analytics", "zoneAnalytics", "getUnzonedHistory", "query", input)
@@ -109143,7 +109357,7 @@ var require_addon = __commonJS({
109143
109357
  "use strict";
109144
109358
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
109145
109359
  var require_event_category = require_event_category_BaEgqJNv();
109146
- var require_sleep = require_sleep_D5821NGq();
109360
+ var require_sleep = require_sleep_DIg3xuEw();
109147
109361
  var require_err_msg = require_err_msg_COpsHMw2();
109148
109362
  var CAP_INPUT_DEFAULTS = Object.freeze({
109149
109363
  "addons": { "getLogs": { "limit": 100 } },
@@ -109253,6 +109467,7 @@ var require_addon = __commonJS({
109253
109467
  "pipeline-analytics": {
109254
109468
  "getAudioEvents": { "limit": 1e3 },
109255
109469
  "getKeyEvents": { "limit": 50 },
109470
+ "getKeyEventsBatch": { "limit": 50 },
109256
109471
  "getMotionEvents": { "limit": 1e3 },
109257
109472
  "getObjectEvents": { "limit": 1e3 },
109258
109473
  "getSensorEvents": { "limit": 1e3 },
@@ -116025,12 +116240,12 @@ var require_dist2 = __commonJS({
116025
116240
  }
116026
116241
  });
116027
116242
 
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) {
116243
+ // ../system/dist/manifest-system-deps-DBje540e.js
116244
+ var require_manifest_system_deps_DBje540e = __commonJS({
116245
+ "../system/dist/manifest-system-deps-DBje540e.js"(exports) {
116031
116246
  "use strict";
116032
116247
  var require_chunk = require_chunk_Cek0wNdY();
116033
- require_dist_CCIBlacf();
116248
+ require_dist_B29Skpzo();
116034
116249
  var node_crypto = __require("crypto");
116035
116250
  node_crypto = require_chunk.__toESM(node_crypto);
116036
116251
  var _camstack_types_node = require_node();
@@ -128059,7 +128274,7 @@ var require_dist3 = __commonJS({
128059
128274
  "use strict";
128060
128275
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
128061
128276
  var require_chunk = require_chunk_Cek0wNdY();
128062
- var require_dist10 = require_dist_CCIBlacf();
128277
+ var require_dist10 = require_dist_B29Skpzo();
128063
128278
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
128064
128279
  require_alerts();
128065
128280
  var require_formatter = require_formatter_DqAKDlvN();
@@ -128085,7 +128300,7 @@ var require_dist3 = __commonJS({
128085
128300
  var require_builtins_winston_logging_index = require_winston_logging();
128086
128301
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
128087
128302
  var require_tls$1 = require_tls_BxQlomxd();
128088
- var require_manifest_system_deps = require_manifest_system_deps_alrkBKVQ();
128303
+ var require_manifest_system_deps = require_manifest_system_deps_DBje540e();
128089
128304
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
128090
128305
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
128091
128306
  var zod = require_zod();
@@ -133973,19 +134188,37 @@ var require_dist3 = __commonJS({
133973
134188
  if (!isRecord$1(parsed) || !("value" in parsed)) return parsed;
133974
134189
  return parsed.value;
133975
134190
  }
134191
+ var PREFIX_PAGE_ROWS = 1e3;
134192
+ var PREFIX_PAGE_BUDGET = 512;
133976
134193
  async function loadPrefixed(door, collection, prefix) {
133977
134194
  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
134195
  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);
134196
+ let cursor = range?.lo ?? null;
134197
+ for (let page = 0; ; page++) {
134198
+ if (page >= PREFIX_PAGE_BUDGET) throw new Error(`settings door: reading "${prefix}" from "${collection}" exceeded ${PREFIX_PAGE_BUDGET} pages of ${PREFIX_PAGE_ROWS} rows. Refusing to return the part that was read \u2014 a partial scope read is indistinguishable from an empty one.`);
134199
+ const rows = await door.query({
134200
+ collection,
134201
+ filter: {
134202
+ ...range !== null && cursor !== null ? { whereBetween: { id: [cursor, range.hi] } } : {},
134203
+ orderBy: {
134204
+ field: "id",
134205
+ direction: "asc"
134206
+ },
134207
+ limit: PREFIX_PAGE_ROWS
134208
+ }
134209
+ });
134210
+ for (const row of rows) {
134211
+ if (range !== null && (row.id < range.lo || row.id >= range.hi)) continue;
134212
+ if (!row.id.startsWith(prefix)) continue;
134213
+ result[row.id.slice(prefix.length)] = unwrapValue(row.data);
134214
+ }
134215
+ if (rows.length < PREFIX_PAGE_ROWS) return result;
134216
+ const last = rows[rows.length - 1];
134217
+ if (last === void 0) return result;
134218
+ if (range === null) throw new Error(`settings door: prefix "${prefix}" on "${collection}" cannot be expressed as an id range, and the unfiltered read filled a page \u2014 the answer would be partial. Use an ASCII, non-empty prefix.`);
134219
+ if (last.id === cursor) throw new Error(`settings door: reading "${prefix}" from "${collection}" made no progress at id "${last.id}" \u2014 ids under a prefix must be unique.`);
134220
+ cursor = last.id;
133987
134221
  }
133988
- return result;
133989
134222
  }
133990
134223
  async function replacePrefixed(door, collection, prefix, values, wrap3) {
133991
134224
  const range = require_builtins_sqlite_storage_sqlite_settings_addon.prefixRange(prefix);
@@ -208909,7 +209142,7 @@ var require_dist4 = __commonJS({
208909
209142
  "use strict";
208910
209143
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
208911
209144
  var require_event_category = require_event_category_BaEgqJNv();
208912
- var require_sleep = require_sleep_D5821NGq();
209145
+ var require_sleep = require_sleep_DIg3xuEw();
208913
209146
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
208914
209147
  var require_enums2 = require_enums();
208915
209148
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -210256,6 +210489,20 @@ var require_dist4 = __commonJS({
210256
210489
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
210257
210490
  */
210258
210491
  rowsReconciled: zod.z.number().int().nonnegative().optional(),
210492
+ /**
210493
+ * Rows this run FORGOT because the file they name is not on disk.
210494
+ *
210495
+ * The mover derived the path from the row's own fields and `stat`ed it; an
210496
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
210497
+ * and the durable row is dropped through the same channel eviction uses. It
210498
+ * is reported for the same reason `rowsReconciled` is: this is a durable
210499
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
210500
+ * the same failure as one that quietly skips them (D295).
210501
+ *
210502
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
210503
+ * ledger claimed 5.65 GB of footage that no longer existed.
210504
+ */
210505
+ rowsForgotten: zod.z.number().int().nonnegative().optional(),
210259
210506
  startedAt: zod.z.number(),
210260
210507
  finishedAt: zod.z.number().nullable(),
210261
210508
  error: zod.z.string().nullable()
@@ -210463,6 +210710,71 @@ var require_dist4 = __commonJS({
210463
210710
  segments: zod.z.number().int().nonnegative(),
210464
210711
  bytes: zod.z.number().int().nonnegative()
210465
210712
  }).nullable();
210713
+ var LedgerWalkInputSchema = zod.z.object({
210714
+ locationId: zod.z.string().min(1),
210715
+ /** Forget the confirmed-absent rows, rather than only counting them. */
210716
+ apply: zod.z.boolean().optional(),
210717
+ /** Narrow to one camera. */
210718
+ deviceId: zod.z.number().int().positive().optional(),
210719
+ /** Narrow to these recording profiles; empty/absent = every profile. */
210720
+ profiles: zod.z.array(zod.z.string().min(1)).optional()
210721
+ });
210722
+ var LedgerWalkRefusalSchema = zod.z.enum([
210723
+ "location-unknown",
210724
+ "source-writable",
210725
+ "no-ledger",
210726
+ "archive-unreadable",
210727
+ "anchor-absent",
210728
+ "anchor-unreadable",
210729
+ "anchor-moved"
210730
+ ]);
210731
+ var LedgerWalkSkipReasonSchema = zod.z.enum([
210732
+ "live-tail",
210733
+ "listing-error",
210734
+ "path-mismatch",
210735
+ "durable-refused"
210736
+ ]);
210737
+ var LedgerWalkSkipCountsSchema = zod.z.object({
210738
+ "live-tail": zod.z.number().int().nonnegative(),
210739
+ "listing-error": zod.z.number().int().nonnegative(),
210740
+ "path-mismatch": zod.z.number().int().nonnegative(),
210741
+ "durable-refused": zod.z.number().int().nonnegative()
210742
+ });
210743
+ var LedgerWalkDeviceReportSchema = zod.z.object({
210744
+ deviceId: zod.z.number().int(),
210745
+ hoursWalked: zod.z.number().int().nonnegative(),
210746
+ hoursMissing: zod.z.number().int().nonnegative(),
210747
+ ghostSegments: zod.z.number().int().nonnegative(),
210748
+ ghostBytes: zod.z.number().int().nonnegative(),
210749
+ forgottenSegments: zod.z.number().int().nonnegative(),
210750
+ orphanFiles: zod.z.number().int().nonnegative()
210751
+ });
210752
+ var LedgerWalkReportSchema = zod.z.object({
210753
+ locationId: zod.z.string(),
210754
+ applied: zod.z.boolean(),
210755
+ refused: LedgerWalkRefusalSchema.nullable(),
210756
+ archiveSegments: zod.z.number().int().nonnegative().nullable(),
210757
+ archiveBytes: zod.z.number().int().nonnegative().nullable(),
210758
+ hoursClaimed: zod.z.number().int().nonnegative(),
210759
+ hoursWalked: zod.z.number().int().nonnegative(),
210760
+ hoursMissing: zod.z.number().int().nonnegative(),
210761
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
210762
+ listings: zod.z.number().int().nonnegative(),
210763
+ segmentsClaimed: zod.z.number().int().nonnegative(),
210764
+ ghostSegments: zod.z.number().int().nonnegative(),
210765
+ ghostBytes: zod.z.number().int().nonnegative(),
210766
+ ghostHoursWhole: zod.z.number().int().nonnegative(),
210767
+ forgottenSegments: zod.z.number().int().nonnegative(),
210768
+ forgottenBytes: zod.z.number().int().nonnegative(),
210769
+ /** Files under a claimed hour that no durable row names. Never deleted. */
210770
+ orphanFiles: zod.z.number().int().nonnegative(),
210771
+ orphanSample: zod.z.array(zod.z.string()).readonly(),
210772
+ hoursSkipped: zod.z.number().int().nonnegative(),
210773
+ skippedByReason: LedgerWalkSkipCountsSchema,
210774
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
210775
+ bounded: zod.z.boolean(),
210776
+ byDevice: zod.z.array(LedgerWalkDeviceReportSchema).readonly()
210777
+ });
210466
210778
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
210467
210779
  var RelocatableMediaCountInputSchema = zod.z.object({
210468
210780
  toLocationId: zod.z.string().min(1),
@@ -222962,13 +223274,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222962
223274
  groups: zod.z.array(AnalyticsGroupRecordSchema).readonly(),
222963
223275
  nextCursor: zod.z.string().nullable()
222964
223276
  });
223277
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
223278
+ var KEY_EVENTS_MAX_LIMIT = 200;
222965
223279
  var KeyEventQueryInput = zod.z.object({
222966
223280
  deviceId: zod.z.number(),
222967
223281
  /** Window lower bound (track firstSeen ≥ since). */
222968
223282
  since: zod.z.number(),
222969
223283
  /** Window upper bound (track firstSeen ≤ until). */
222970
223284
  until: zod.z.number(),
222971
- limit: zod.z.number().int().min(1).max(200).default(50),
223285
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
222972
223286
  /** Drop tracks scoring below this importance. */
222973
223287
  minImportance: zod.z.number().min(0).max(1).optional(),
222974
223288
  /** Restrict to a single class (e.g. 'person'). */
@@ -222990,6 +223304,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
222990
223304
  ...TrackFlagFields,
222991
223305
  ...TrackRetrainFields
222992
223306
  });
223307
+ var KeyEventBatchQueryInput = zod.z.object({
223308
+ deviceIds: zod.z.array(zod.z.number()).min(1).max(200),
223309
+ since: zod.z.number(),
223310
+ until: zod.z.number(),
223311
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
223312
+ * across the set, which would let a busy camera starve a quiet one of its
223313
+ * rows and change what the merged feed contains. */
223314
+ limit: zod.z.number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
223315
+ minImportance: zod.z.number().min(0).max(1).optional(),
223316
+ classFilter: zod.z.string().optional()
223317
+ });
223318
+ var KeyEventsForDeviceSchema = zod.z.object({
223319
+ deviceId: zod.z.number(),
223320
+ events: zod.z.array(KeyEventSchema).readonly()
223321
+ });
222993
223322
  var TrackedDetectionSchema = zod.z.object({
222994
223323
  trackId: zod.z.string(),
222995
223324
  className: zod.z.string(),
@@ -223284,6 +223613,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223284
223613
  * scored on-read (no write). Degrades to `[]` on error.
223285
223614
  */
223286
223615
  getKeyEvents: require_sleep.method(KeyEventQueryInput, zod.z.array(KeyEventSchema).readonly()),
223616
+ /**
223617
+ * The same ranking, for a SET of cameras, in one round trip.
223618
+ *
223619
+ * The Detection Intelligence events feed asks this of every selected
223620
+ * camera and re-asks on a 30s timer. Fanned out client-side that is N
223621
+ * round trips — browser → hub → post-analysis — for N independent,
223622
+ * already-indexed store queries. Batched, the queries are unchanged and
223623
+ * run concurrently INSIDE the owner; only the transport collapses.
223624
+ *
223625
+ * Deliberately per-device rather than pre-merged: `limit` stays per
223626
+ * camera (a total would let a busy camera starve a quiet one), and a
223627
+ * caller that renders one camera's lane needs to know which camera a row
223628
+ * came from. `getKeyEvents` stays for single-device callers.
223629
+ */
223630
+ getKeyEventsBatch: require_sleep.method(KeyEventBatchQueryInput, zod.z.array(KeyEventsForDeviceSchema).readonly()),
223287
223631
  /** Server-side bucketed event counts for the 24-hour timeline.
223288
223632
  * Returns one entry per non-empty bucket; empty buckets are omitted. */
223289
223633
  getEventDensity: require_sleep.method(zod.z.object({
@@ -233401,6 +233745,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233401
233745
  kind: "query",
233402
233746
  auth: "admin"
233403
233747
  }),
233748
+ /**
233749
+ * Does this location's LEDGER tell the truth about its disk? (D319)
233750
+ *
233751
+ * One `readdir` per claimed hour, diffed both ways: durable rows whose file
233752
+ * is not there, and files no durable row names. `apply` defaults to FALSE —
233753
+ * the report is the product, and the dry run is how an operator
233754
+ * sanity-checks the destructive run before authorising it.
233755
+ *
233756
+ * REFUSES a source that is still a write target: a listing of a live
233757
+ * location is a lower bound, which is not the strong evidence that lets
233758
+ * this pass forget without a budget. A live location is reconciled by the
233759
+ * mover, under D318's bound.
233760
+ */
233761
+ reconcileLedgerAgainstDisk: require_sleep.method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
233762
+ kind: "mutation",
233763
+ auth: "admin"
233764
+ }),
233404
233765
  /** Cancel a running or queued relocate job. A queued job never runs. */
233405
233766
  cancelRelocateJob: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
233406
233767
  kind: "mutation",
@@ -233715,6 +234076,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233715
234076
  monitors: zod.z.array(SceneMonitorSchema),
233716
234077
  lastFetchedAt: zod.z.number()
233717
234078
  });
234079
+ var SceneMonitorStatusForDeviceSchema = zod.z.object({
234080
+ deviceId: zod.z.number(),
234081
+ status: SceneMonitorStatusSchema.nullable()
234082
+ });
233718
234083
  var sceneMonitorCapability = {
233719
234084
  name: "scene-monitor",
233720
234085
  scope: "device",
@@ -233724,6 +234089,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233724
234089
  deviceTypes: [require_sleep.DeviceType.Camera],
233725
234090
  methods: {
233726
234091
  listScenes: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), SceneMonitorStatusSchema),
234092
+ /**
234093
+ * The same answer, for a SET of cameras, in one round trip.
234094
+ *
234095
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
234096
+ * poll behind the push slice. Fanned out client-side that was one query
234097
+ * per camera — 29 round trips through the browser, the hub and the
234098
+ * post-analysis runner every 30 seconds to read an in-memory map the
234099
+ * owner had already merged. The work is unchanged (`statusFor` per
234100
+ * device, all in-process at the owner); what collapses is the transport.
234101
+ *
234102
+ * A camera that cannot answer still gets a row, with `status: null` —
234103
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
234104
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
234105
+ * tell which two are missing, or that any are.
234106
+ */
234107
+ listScenesBatch: require_sleep.method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(SceneMonitorStatusForDeviceSchema).readonly()),
233727
234108
  createScene: require_sleep.method(zod.z.object({
233728
234109
  deviceId: zod.z.number(),
233729
234110
  label: zod.z.string(),
@@ -235525,6 +235906,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
235525
235906
  * than as repeated tracks/events. */
235526
235907
  stationaryObjects: zod.z.array(StationaryObjectSchema).readonly().optional()
235527
235908
  });
235909
+ var CameraOccupancySnapshotForDeviceSchema = zod.z.object({
235910
+ deviceId: zod.z.number(),
235911
+ read: zod.z.enum(["read", "unreadable"]),
235912
+ snapshot: CameraOccupancySnapshotSchema.nullable()
235913
+ });
235528
235914
  var HistoryResolutionEnum = zod.z.enum([
235529
235915
  "minute",
235530
235916
  "5min",
@@ -235554,6 +235940,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
235554
235940
  * (no inference result emitted since boot or since binding was
235555
235941
  * activated). */
235556
235942
  getCurrentSnapshot: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), CameraOccupancySnapshotSchema.nullable()),
235943
+ /**
235944
+ * The same snapshot, for a SET of cameras, in one round trip.
235945
+ *
235946
+ * The Events page's Stationary section polls this every 15s for every
235947
+ * selected camera. Fanned out client-side that is one query per camera to
235948
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
235949
+ * costs everything. Batched, N transports become one and the per-device
235950
+ * work is unchanged.
235951
+ *
235952
+ * Every requested deviceId gets a row, tagged `read` — see
235953
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
235954
+ * not answer for is `'unreadable'`, never an empty reading.
235955
+ */
235956
+ getCurrentSnapshotBatch: require_sleep.method(zod.z.object({ deviceIds: zod.z.array(zod.z.number()).min(1).max(200) }), zod.z.array(CameraOccupancySnapshotForDeviceSchema).readonly()),
235557
235957
  /** Time-series object count inside one zone. `className` optional —
235558
235958
  * omit to count every class in the zone. */
235559
235959
  getZoneHistory: require_sleep.method(zod.z.object({
@@ -242419,6 +242819,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242419
242819
  addonId: null,
242420
242820
  access: "view"
242421
242821
  },
242822
+ "pipelineAnalytics.getKeyEventsBatch": {
242823
+ capName: "pipeline-analytics",
242824
+ capScope: "device",
242825
+ addonId: null,
242826
+ access: "view"
242827
+ },
242422
242828
  "pipelineAnalytics.getMotionEvents": {
242423
242829
  capName: "pipeline-analytics",
242424
242830
  capScope: "device",
@@ -243595,6 +244001,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243595
244001
  addonId: null,
243596
244002
  access: "view"
243597
244003
  },
244004
+ "recording.reconcileLedgerAgainstDisk": {
244005
+ capName: "recording",
244006
+ capScope: "system",
244007
+ addonId: null,
244008
+ access: "create"
244009
+ },
243598
244010
  "recording.refreshStorageLocationsForMigration": {
243599
244011
  capName: "recording",
243600
244012
  capScope: "system",
@@ -243721,6 +244133,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243721
244133
  addonId: null,
243722
244134
  access: "view"
243723
244135
  },
244136
+ "sceneMonitor.listScenesBatch": {
244137
+ capName: "scene-monitor",
244138
+ capScope: "device",
244139
+ addonId: null,
244140
+ access: "view"
244141
+ },
243724
244142
  "sceneMonitor.recheckNow": {
243725
244143
  capName: "scene-monitor",
243726
244144
  capScope: "device",
@@ -245077,6 +245495,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
245077
245495
  addonId: null,
245078
245496
  access: "view"
245079
245497
  },
245498
+ "zoneAnalytics.getCurrentSnapshotBatch": {
245499
+ capName: "zone-analytics",
245500
+ capScope: "device",
245501
+ addonId: null,
245502
+ access: "view"
245503
+ },
245080
245504
  "zoneAnalytics.getUnzonedHistory": {
245081
245505
  capName: "zone-analytics",
245082
245506
  capScope: "device",
@@ -246339,6 +246763,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246339
246763
  form: "single",
246340
246764
  optional: false
246341
246765
  }],
246766
+ "pipelineAnalytics.getKeyEventsBatch": [{
246767
+ name: "deviceIds",
246768
+ form: "array",
246769
+ optional: false
246770
+ }],
246342
246771
  "pipelineAnalytics.getMotionEvents": [{
246343
246772
  name: "deviceId",
246344
246773
  form: "single",
@@ -246779,6 +247208,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246779
247208
  form: "single",
246780
247209
  optional: false
246781
247210
  }],
247211
+ "recording.reconcileLedgerAgainstDisk": [{
247212
+ name: "deviceId",
247213
+ form: "single",
247214
+ optional: true
247215
+ }],
246782
247216
  "recording.relocateFootage": [{
246783
247217
  name: "deviceId",
246784
247218
  form: "single",
@@ -246844,6 +247278,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246844
247278
  form: "single",
246845
247279
  optional: false
246846
247280
  }],
247281
+ "sceneMonitor.listScenesBatch": [{
247282
+ name: "deviceIds",
247283
+ form: "array",
247284
+ optional: false
247285
+ }],
246847
247286
  "sceneMonitor.recheckNow": [{
246848
247287
  name: "deviceId",
246849
247288
  form: "single",
@@ -247105,6 +247544,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247105
247544
  form: "single",
247106
247545
  optional: false
247107
247546
  }],
247547
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
247548
+ name: "deviceIds",
247549
+ form: "array",
247550
+ optional: false
247551
+ }],
247108
247552
  "zoneAnalytics.getUnzonedHistory": [{
247109
247553
  name: "deviceId",
247110
247554
  form: "single",
@@ -247257,6 +247701,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247257
247701
  "recording.readGopBytes",
247258
247702
  "recording.readSegmentBytes",
247259
247703
  "recording.readWindowBytes",
247704
+ "recording.reconcileLedgerAgainstDisk",
247260
247705
  "recording.relocateFootage",
247261
247706
  "recording.renderClip",
247262
247707
  "recording.renderGif",
@@ -248142,6 +248587,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248142
248587
  relocateFootage: (input) => dispatch("recording", "relocateFootage", "mutation", input),
248143
248588
  listRelocateJobs: (input) => dispatch("recording", "listRelocateJobs", "query", input),
248144
248589
  getRelocateResidue: (input) => dispatch("recording", "getRelocateResidue", "query", input),
248590
+ reconcileLedgerAgainstDisk: (input) => dispatch("recording", "reconcileLedgerAgainstDisk", "mutation", input),
248145
248591
  cancelRelocateJob: (input) => dispatch("recording", "cancelRelocateJob", "mutation", input),
248146
248592
  planStorageRebalance: (input) => dispatch("recording", "planStorageRebalance", "query", input),
248147
248593
  startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input)
@@ -251102,6 +251548,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251102
251548
  exports.CameraMetricsSchema = CameraMetricsSchema;
251103
251549
  exports.CameraMetricsWithDeviceIdSchema = CameraMetricsWithDeviceIdSchema;
251104
251550
  exports.CameraMotionStatusSchema = CameraMotionStatusSchema;
251551
+ exports.CameraOccupancySnapshotForDeviceSchema = CameraOccupancySnapshotForDeviceSchema;
251105
251552
  exports.CameraRecordingModeSchema = CameraRecordingModeSchema;
251106
251553
  exports.CameraRecordingStatusSchema = CameraRecordingStatusSchema;
251107
251554
  exports.CameraSourceStatusSchema = CameraSourceStatusSchema;
@@ -251352,6 +251799,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251352
251799
  exports.LabelTierSchema = LabelTierSchema;
251353
251800
  exports.LawnMowerActivitySchema = LawnMowerActivitySchema;
251354
251801
  exports.LawnMowerControlStatusSchema = LawnMowerControlStatusSchema;
251802
+ exports.LedgerWalkDeviceReportSchema = LedgerWalkDeviceReportSchema;
251803
+ exports.LedgerWalkInputSchema = LedgerWalkInputSchema;
251804
+ exports.LedgerWalkRefusalSchema = LedgerWalkRefusalSchema;
251805
+ exports.LedgerWalkReportSchema = LedgerWalkReportSchema;
251806
+ exports.LedgerWalkSkipCountsSchema = LedgerWalkSkipCountsSchema;
251807
+ exports.LedgerWalkSkipReasonSchema = LedgerWalkSkipReasonSchema;
251355
251808
  exports.LinkedDeviceSchema = LinkedDeviceSchema;
251356
251809
  exports.LinkedDevicesModeSchema = LinkedDevicesModeSchema;
251357
251810
  exports.ListGroupsPageSchema = ListGroupsPageSchema;
@@ -251773,6 +252226,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251773
252226
  exports.SceneConfirmSchema = SceneConfirmSchema;
251774
252227
  exports.SceneMonitorSchema = SceneMonitorSchema;
251775
252228
  exports.SceneMonitorStateSchema = SceneMonitorStateSchema;
252229
+ exports.SceneMonitorStatusForDeviceSchema = SceneMonitorStatusForDeviceSchema;
251776
252230
  exports.SceneMonitorStatusSchema = SceneMonitorStatusSchema;
251777
252231
  exports.SceneReferenceSchema = SceneReferenceSchema;
251778
252232
  exports.SceneUnavailableSchema = SceneUnavailableSchema;
@@ -410593,6 +411047,7 @@ var require_cap_providers = __commonJS({
410593
411047
  exports.buildSettingsStoreGateway = buildSettingsStoreGateway;
410594
411048
  exports.buildNetworkQualityProvider = buildNetworkQualityProvider;
410595
411049
  exports.buildToastProvider = buildToastProvider;
411050
+ exports.computeTopologyPass = computeTopologyPass;
410596
411051
  exports.computeTopology = computeTopology;
410597
411052
  exports.createNodeRootPackageLookup = createNodeRootPackageLookup;
410598
411053
  exports.buildNodesProvider = buildNodesProvider;
@@ -410741,9 +411196,19 @@ var require_cap_providers = __commonJS({
410741
411196
  }
410742
411197
  return ips;
410743
411198
  }
411199
+ async function computeTopologyPass(agentRegistry, addonRegistry, getNodeRootPackage) {
411200
+ const [nodes, liveNodes] = await computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage);
411201
+ return { nodes, liveNodes };
411202
+ }
410744
411203
  async function computeTopology(agentRegistry, addonRegistry, getNodeRootPackage) {
410745
- const nodes = await agentRegistry.listNodes();
410746
- const history = await agentRegistry.getClusterNodeHistory();
411204
+ const [nodes] = await computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage);
411205
+ return nodes;
411206
+ }
411207
+ async function computeTopologyInternal(agentRegistry, addonRegistry, getNodeRootPackage) {
411208
+ const [nodes, history] = await Promise.all([
411209
+ agentRegistry.listNodes(),
411210
+ agentRegistry.getClusterNodeHistory()
411211
+ ]);
410747
411212
  const historyById = new Map(history.map((h) => [h.id, h]));
410748
411213
  const liveIds = new Set(nodes.map((n) => n.info.id));
410749
411214
  const allAddons = addonRegistry?.listAddons() ?? [];
@@ -410898,7 +411363,7 @@ var require_cap_providers = __commonJS({
410898
411363
  categories: [],
410899
411364
  rootPackage: null
410900
411365
  }));
410901
- return [...liveNodes, ...offlineNodes];
411366
+ return [[...liveNodes, ...offlineNodes], nodes];
410902
411367
  }
410903
411368
  function createNodeRootPackageLookup(moleculer, serverUpdate) {
410904
411369
  return (nodeId, isHub) => {
@@ -420624,6 +421089,19 @@ var require_agent_registry_service = __commonJS({
420624
421089
  addonIds: [...entry.agentAddons ?? []]
420625
421090
  };
420626
421091
  }
421092
+ function toSubProcess(p) {
421093
+ return {
421094
+ pid: p.pid ?? 0,
421095
+ name: p.name ?? "",
421096
+ command: "moleculer-service",
421097
+ state: p.state ?? "running",
421098
+ cpuPercent: p.cpuPercent ?? 0,
421099
+ memoryRss: p.memoryRss ?? 0,
421100
+ uptimeSeconds: p.uptimeSeconds ?? 0,
421101
+ addonIds: p.addonIds ?? [],
421102
+ groupId: p.groupId ?? null
421103
+ };
421104
+ }
420627
421105
  var AgentRegistryService = class {
420628
421106
  eventBus;
420629
421107
  moleculer;
@@ -421150,107 +421628,113 @@ var require_agent_registry_service = __commonJS({
421150
421628
  return toNodeLiveness(this.moleculer.broker.registry.getNodeList({ onlyAvailable: false }));
421151
421629
  }
421152
421630
  async listNodes() {
421153
- let hubProcesses = [];
421631
+ const registry = this.moleculer.broker.registry;
421632
+ const nodes = registry?.getNodeList?.({ onlyAvailable: false }) ?? [];
421633
+ const remoteTargets = nodes.filter((node) => typeof node.id === "string" && node.id !== "hub" && !node.id.includes("/"));
421634
+ const [hubEntry, remoteEntries] = await Promise.all([
421635
+ this.listHubProcesses().then((procs) => this.buildHubEntry(procs)),
421636
+ Promise.all(remoteTargets.map((node) => node.available ? (
421637
+ // Online: one wave of `$agent.status` + `$process.list`.
421638
+ this.buildRemoteEntry(node.id)
421639
+ ) : (
421640
+ // Offline: skip the RPC fan-out entirely (the node is unreachable —
421641
+ // those calls would just time out) and surface a minimal,
421642
+ // honestly-degraded row instead.
421643
+ Promise.resolve(this.buildOfflineEntry(node))
421644
+ )))
421645
+ ]);
421646
+ return [hubEntry, ...remoteEntries.filter((entry) => entry !== null)];
421647
+ }
421648
+ /** The hub's own runner subprocesses (`$process.list`, hub-local). */
421649
+ async listHubProcesses() {
421154
421650
  try {
421155
421651
  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
- }));
421652
+ return processes.map(toSubProcess);
421167
421653
  } catch {
421654
+ return [];
421168
421655
  }
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
421656
  }
421248
421657
  /**
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()`.
421658
+ * One online agent's row: `$agent.status` for the descriptor + live metrics,
421659
+ * `$process.list` for real sub-process stats. `null` when the node has no
421660
+ * `$agent` service (it is then simply absent from the list, as before).
421661
+ *
421662
+ * The two calls are ONE wave, not two: `$process.list` never reads anything
421663
+ * from `$agent.status` — only its FALLBACK does, and the fallback is applied
421664
+ * after both have settled.
421665
+ */
421666
+ async buildRemoteEntry(nodeId) {
421667
+ const [statusResult, processResult] = await Promise.allSettled([
421668
+ this.broker.call("$agent.status", {}, { nodeID: nodeId, timeout: 5e3 }),
421669
+ this.broker.call("$process.list", {}, { nodeID: nodeId, timeout: 5e3 })
421670
+ ]);
421671
+ if (statusResult.status === "rejected")
421672
+ return null;
421673
+ const status = statusResult.value;
421674
+ const subProcesses = processResult.status === "fulfilled" ? processResult.value.map(toSubProcess) : (
421675
+ // Fall back to the addon list from $agent.status (no stats)
421676
+ status.addons?.map((a) => ({
421677
+ pid: 0,
421678
+ name: a.id ?? "",
421679
+ command: "moleculer-service",
421680
+ state: a.status ?? "running",
421681
+ cpuPercent: 0,
421682
+ memoryRss: 0,
421683
+ uptimeSeconds: 0
421684
+ })) ?? []
421685
+ );
421686
+ const agentAddons = status.addons?.map((a) => a.id) ?? [];
421687
+ const hostname = typeof status.hostname === "string" ? status.hostname : null;
421688
+ const agentName = typeof status.name === "string" ? status.name : nodeId;
421689
+ return {
421690
+ info: {
421691
+ id: nodeId,
421692
+ name: agentName,
421693
+ hostname: hostname ?? nodeId,
421694
+ capabilities: [],
421695
+ platform: status.platform ?? "unknown",
421696
+ arch: status.arch ?? "unknown",
421697
+ cpuCores: status.cpuCores ?? 0,
421698
+ memoryMB: status.totalMemoryMB ?? 0,
421699
+ cpuModel: status.cpuModel
421700
+ },
421701
+ localIps: Array.isArray(status.localIps) ? status.localIps : [],
421702
+ status: {
421703
+ activeCameras: 0,
421704
+ cpuPercent: status.cpuPercent ?? 0,
421705
+ memoryPercent: status.memoryPercent ?? 0,
421706
+ fps: {},
421707
+ errors: []
421708
+ },
421709
+ connectedSince: typeof status.uptime === "number" ? Date.now() - status.uptime * 1e3 : Date.now(),
421710
+ isHub: false,
421711
+ subProcesses,
421712
+ agentAddons
421713
+ };
421714
+ }
421715
+ /**
421716
+ * A1 (R2 capture point): snapshot every ONLINE node's static descriptor +
421717
+ * last-known addon roster into the durable, routing-blind history store.
421718
+ * This is the ONLY place cpuModel/cores/memory/localIps are resolved from a
421719
+ * live `$agent.status`, so it is where an offline row's descriptor must be
421720
+ * captured while the node is still reachable. Offline entries are NOT
421721
+ * re-snapshotted — their `lastActive` is stamped on `$node.disconnected`
421722
+ * instead. This is a separate durable store, never a `knownAgents`-style
421723
+ * shadow of `HubNodeRegistry` (which stays the ephemeral live cap authority).
421724
+ *
421725
+ * **It belongs to the PERIODIC PASS, not to a read** (D317). It used to run
421726
+ * at the tail of `listNodes()`, which made every operator poll of
421727
+ * `nodes.topology` perform one durable read-back — and, past the five-minute
421728
+ * heartbeat, one durable write — per online node, in a phase that had to
421729
+ * finish before the history read could even start. Measured on the live hub
421730
+ * on 2026-08-31: one settings-store round trip cost 14.8–18.3 s and
421731
+ * `nodes.topology` cost exactly two of them, 30.5 s. `TopologyEmitterService`
421732
+ * already recomputes this picture every 30 s; the capture is its work.
421733
+ *
421734
+ * Never throws — the store swallows its own errors and this awaits
421735
+ * `allSettled` so a storage hiccup can never fail the pass.
421252
421736
  */
421253
- async snapshotOnlineNodes(entries) {
421737
+ async captureNodeHistory(entries) {
421254
421738
  if (!this.historyStore)
421255
421739
  return;
421256
421740
  const online = entries.filter((e) => e.isOnline !== false);
@@ -423900,7 +424384,7 @@ var require_topology_emitter_service = __commonJS({
423900
424384
  return;
423901
424385
  this.emitting = true;
423902
424386
  try {
423903
- const nodes = await (0, cap_providers_1.computeTopology)(this.agentRegistry, this.addonRegistry, this.getNodeRootPackage);
424387
+ const { nodes, liveNodes } = await (0, cap_providers_1.computeTopologyPass)(this.agentRegistry, this.addonRegistry, this.getNodeRootPackage);
423904
424388
  this.eventBus.emit({
423905
424389
  id: (0, node_crypto_1.randomUUID)(),
423906
424390
  timestamp: /* @__PURE__ */ new Date(),
@@ -423908,6 +424392,7 @@ var require_topology_emitter_service = __commonJS({
423908
424392
  category: types_1.EventCategory.ClusterTopologySnapshot,
423909
424393
  data: { nodes, timestamp: Date.now() }
423910
424394
  });
424395
+ await this.agentRegistry.captureNodeHistory(liveNodes);
423911
424396
  } catch {
423912
424397
  } finally {
423913
424398
  this.emitting = false;