@camstack/addon-decoder-nodeav 1.2.47 → 1.2.49

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.
package/dist/index.js CHANGED
@@ -8018,6 +8018,20 @@ var RelocateJobSchema = object({
8018
8018
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8019
8019
  */
8020
8020
  rowsReconciled: number().int().nonnegative().optional(),
8021
+ /**
8022
+ * Rows this run FORGOT because the file they name is not on disk.
8023
+ *
8024
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8025
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8026
+ * and the durable row is dropped through the same channel eviction uses. It
8027
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8028
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8029
+ * the same failure as one that quietly skips them (D295).
8030
+ *
8031
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8032
+ * ledger claimed 5.65 GB of footage that no longer existed.
8033
+ */
8034
+ rowsForgotten: number().int().nonnegative().optional(),
8021
8035
  startedAt: number(),
8022
8036
  finishedAt: number().nullable(),
8023
8037
  error: string().nullable()
@@ -8381,6 +8395,91 @@ var RelocateResidueSchema = object({
8381
8395
  segments: number().int().nonnegative(),
8382
8396
  bytes: number().int().nonnegative()
8383
8397
  }).nullable();
8398
+ /**
8399
+ * Ask one location whether its durable hour rows describe the disk — the walk
8400
+ * (D319).
8401
+ *
8402
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8403
+ * missing tool is the question, and the dry run is how they sanity-check the
8404
+ * destructive run before authorising it.
8405
+ */
8406
+ var LedgerWalkInputSchema = object({
8407
+ locationId: string().min(1),
8408
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8409
+ apply: boolean().optional(),
8410
+ /** Narrow to one camera. */
8411
+ deviceId: number().int().positive().optional(),
8412
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8413
+ profiles: array(string().min(1)).optional()
8414
+ });
8415
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8416
+ var LedgerWalkRefusalSchema = _enum([
8417
+ "location-unknown",
8418
+ "source-writable",
8419
+ "no-ledger",
8420
+ "archive-unreadable",
8421
+ "anchor-absent",
8422
+ "anchor-unreadable",
8423
+ "anchor-moved"
8424
+ ]);
8425
+ _enum([
8426
+ "live-tail",
8427
+ "listing-error",
8428
+ "path-mismatch",
8429
+ "durable-refused"
8430
+ ]);
8431
+ /** Every skip reason, always present, always a number — so a reason that never
8432
+ * fired reports as zero rather than absent and the report shape is constant
8433
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8434
+ var LedgerWalkSkipCountsSchema = object({
8435
+ "live-tail": number().int().nonnegative(),
8436
+ "listing-error": number().int().nonnegative(),
8437
+ "path-mismatch": number().int().nonnegative(),
8438
+ "durable-refused": number().int().nonnegative()
8439
+ });
8440
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8441
+ var LedgerWalkDeviceReportSchema = object({
8442
+ deviceId: number().int(),
8443
+ hoursWalked: number().int().nonnegative(),
8444
+ hoursMissing: number().int().nonnegative(),
8445
+ ghostSegments: number().int().nonnegative(),
8446
+ ghostBytes: number().int().nonnegative(),
8447
+ forgottenSegments: number().int().nonnegative(),
8448
+ orphanFiles: number().int().nonnegative()
8449
+ });
8450
+ /**
8451
+ * What one walk claimed, listed, found and (only when armed) forgot.
8452
+ *
8453
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8454
+ * walk that saw a fraction of the location is visible in its own report rather
8455
+ * than in the absence of one.
8456
+ */
8457
+ var LedgerWalkReportSchema = object({
8458
+ locationId: string(),
8459
+ applied: boolean(),
8460
+ refused: LedgerWalkRefusalSchema.nullable(),
8461
+ archiveSegments: number().int().nonnegative().nullable(),
8462
+ archiveBytes: number().int().nonnegative().nullable(),
8463
+ hoursClaimed: number().int().nonnegative(),
8464
+ hoursWalked: number().int().nonnegative(),
8465
+ hoursMissing: number().int().nonnegative(),
8466
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8467
+ listings: number().int().nonnegative(),
8468
+ segmentsClaimed: number().int().nonnegative(),
8469
+ ghostSegments: number().int().nonnegative(),
8470
+ ghostBytes: number().int().nonnegative(),
8471
+ ghostHoursWhole: number().int().nonnegative(),
8472
+ forgottenSegments: number().int().nonnegative(),
8473
+ forgottenBytes: number().int().nonnegative(),
8474
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8475
+ orphanFiles: number().int().nonnegative(),
8476
+ orphanSample: array(string()).readonly(),
8477
+ hoursSkipped: number().int().nonnegative(),
8478
+ skippedByReason: LedgerWalkSkipCountsSchema,
8479
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8480
+ bounded: boolean(),
8481
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8482
+ });
8384
8483
  /** How many rows a media pass would still act on against a given target — the
8385
8484
  * media lane's denominator AND its residue, from ONE derivation so the two can
8386
8485
  * never disagree. `null` = the count could not be taken. */
@@ -18784,13 +18883,15 @@ var ListGroupsPageSchema = object({
18784
18883
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18785
18884
  nextCursor: string().nullable()
18786
18885
  });
18886
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
18887
+ var KEY_EVENTS_MAX_LIMIT = 200;
18787
18888
  var KeyEventQueryInput = object({
18788
18889
  deviceId: number(),
18789
18890
  /** Window lower bound (track firstSeen ≥ since). */
18790
18891
  since: number(),
18791
18892
  /** Window upper bound (track firstSeen ≤ until). */
18792
18893
  until: number(),
18793
- limit: number().int().min(1).max(200).default(50),
18894
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18794
18895
  /** Drop tracks scoring below this importance. */
18795
18896
  minImportance: number().min(0).max(1).optional(),
18796
18897
  /** Restrict to a single class (e.g. 'person'). */
@@ -18812,6 +18913,32 @@ var KeyEventSchema = object({
18812
18913
  ...TrackFlagFields,
18813
18914
  ...TrackRetrainFields
18814
18915
  });
18916
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
18917
+ var KeyEventBatchQueryInput = object({
18918
+ deviceIds: array(number()).min(1).max(200),
18919
+ since: number(),
18920
+ until: number(),
18921
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
18922
+ * across the set, which would let a busy camera starve a quiet one of its
18923
+ * rows and change what the merged feed contains. */
18924
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18925
+ minImportance: number().min(0).max(1).optional(),
18926
+ classFilter: string().optional()
18927
+ });
18928
+ /**
18929
+ * One camera's key events in a batch answer.
18930
+ *
18931
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
18932
+ * error rather than throwing, so a camera whose store read failed and one with
18933
+ * no events in the window were ALREADY indistinguishable per camera — the
18934
+ * batch does not make that worse, and the row keeps the deviceId the single
18935
+ * method's output never carried (the caller used to stamp it from the fan-out
18936
+ * key, which only worked because there was one query per camera).
18937
+ */
18938
+ var KeyEventsForDeviceSchema = object({
18939
+ deviceId: number(),
18940
+ events: array(KeyEventSchema).readonly()
18941
+ });
18815
18942
  object({
18816
18943
  trackId: string(),
18817
18944
  className: string(),
@@ -18883,6 +19010,50 @@ var EventStoreFootprintSchema = object({
18883
19010
  totalBytes: number().int(),
18884
19011
  devices: array(EventStoreDeviceFootprintSchema).readonly()
18885
19012
  });
19013
+ /** Event-media footprint for one {@link MediaFileKind}. */
19014
+ var EventMediaKindFootprintSchema = object({
19015
+ kind: MediaFileKindEnum,
19016
+ /** Media rows of this kind. */
19017
+ rows: number().int(),
19018
+ /** Bytes on disk held by those rows. */
19019
+ bytes: number().int()
19020
+ });
19021
+ /**
19022
+ * The media footprint broken down by KIND — the axis a deletion decision
19023
+ * actually turns on.
19024
+ *
19025
+ * A byte total says how much there is; it cannot say what is safe to remove.
19026
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19027
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19028
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19029
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19030
+ * nothing else, so sizing a deletion means summing per kind.
19031
+ *
19032
+ * ## Why `unaccounted*` exists
19033
+ *
19034
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19035
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19036
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19037
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19038
+ * retired code path, or by a version that knew a kind this one does not) would
19039
+ * otherwise vanish from the total silently, and an operator would delete
19040
+ * against a denominator smaller than the disk.
19041
+ *
19042
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19043
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19044
+ */
19045
+ var EventMediaKindBreakdownSchema = object({
19046
+ /** Every media row in scope, from one unfiltered aggregate. */
19047
+ totalRows: number().int(),
19048
+ /** Every media byte in scope, from that same aggregate. */
19049
+ totalBytes: number().int(),
19050
+ /** Per-kind footprint, ordered by bytes descending. */
19051
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19052
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19053
+ unaccountedRows: number().int(),
19054
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19055
+ unaccountedBytes: number().int()
19056
+ });
18886
19057
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
18887
19058
  var EventPruneCountsSchema = object({
18888
19059
  motion: number().int(),
@@ -19037,7 +19208,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19037
19208
  until: number().optional(),
19038
19209
  kinds: array(string()).optional(),
19039
19210
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19040
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19211
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19041
19212
  deviceId: number(),
19042
19213
  since: number(),
19043
19214
  until: number(),
@@ -19086,6 +19257,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19086
19257
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19087
19258
  kind: "query",
19088
19259
  auth: "admin"
19260
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19261
+ kind: "query",
19262
+ auth: "admin"
19089
19263
  }), method(object({
19090
19264
  olderThanMs: number(),
19091
19265
  reason: OpsLogReasonSchema.optional()
@@ -21037,6 +21211,20 @@ method(object({
21037
21211
  error: string().optional()
21038
21212
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21039
21213
  providerId: string(),
21214
+ /**
21215
+ * The location this config is an UNSAVED edit of, when there is one.
21216
+ *
21217
+ * `listLocations` replaces every declared secret with the redaction
21218
+ * sentinel, so the edit modal's form state holds the sentinel for any
21219
+ * credential the operator did not retype — and posting that here
21220
+ * without a way to resolve it makes the provider try to authenticate
21221
+ * as `__camstack_redacted__` and report the operator's own working
21222
+ * password as wrong. Given this id, the orchestrator restores each
21223
+ * sentinel from the stored config (same rule as `upsertLocation`)
21224
+ * before dispatching. Omitted by the "Add location" wizard, where
21225
+ * every value was typed just now and nothing is stored yet.
21226
+ */
21227
+ locationId: string().optional(),
21040
21228
  config: record(string(), unknown())
21041
21229
  }), object({
21042
21230
  ok: boolean(),
@@ -26672,6 +26860,9 @@ method(object({
26672
26860
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26673
26861
  kind: "query",
26674
26862
  auth: "admin"
26863
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
26864
+ kind: "mutation",
26865
+ auth: "admin"
26675
26866
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26676
26867
  kind: "mutation",
26677
26868
  auth: "admin"
@@ -27063,7 +27254,26 @@ var SceneMonitorStatusSchema = object({
27063
27254
  monitors: array(SceneMonitorSchema),
27064
27255
  lastFetchedAt: number()
27065
27256
  });
27066
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27257
+ /**
27258
+ * One camera's row in a `listScenesBatch` answer.
27259
+ *
27260
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27261
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27262
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27263
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27264
+ * configured. Fanned out per camera the difference was visible — one query
27265
+ * errored while the others resolved — and a batch that returned only the rows
27266
+ * it managed would have destroyed it, silently, by making an unreachable camera
27267
+ * indistinguishable from one that answered `monitors: []`.
27268
+ *
27269
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27270
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27271
+ */
27272
+ var SceneMonitorStatusForDeviceSchema = object({
27273
+ deviceId: number(),
27274
+ status: SceneMonitorStatusSchema.nullable()
27275
+ });
27276
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27067
27277
  deviceId: number(),
27068
27278
  label: string(),
27069
27279
  roi: MaskRectShapeSchema,
@@ -28387,6 +28597,27 @@ var CameraOccupancySnapshotSchema = object({
28387
28597
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28388
28598
  });
28389
28599
  /**
28600
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28601
+ *
28602
+ * THREE outcomes, and the single-camera method could only express two of them
28603
+ * because `snapshot: null` was already spoken for:
28604
+ *
28605
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28606
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28607
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28608
+ * - `read: 'unreadable'` — the owner could not answer for this
28609
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28610
+ *
28611
+ * Collapsing the last two is the failure this field exists to prevent: a
28612
+ * hydration that threw would otherwise render as an empty Stationary section,
28613
+ * which is a definite claim about a camera nobody could read.
28614
+ */
28615
+ var CameraOccupancySnapshotForDeviceSchema = object({
28616
+ deviceId: number(),
28617
+ read: _enum(["read", "unreadable"]),
28618
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28619
+ });
28620
+ /**
28390
28621
  * Time-series resolution. The history methods return one bucket per
28391
28622
  * step over the requested range. Smaller resolutions cost more
28392
28623
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28410,7 +28641,7 @@ var HistoryPointSchema = object({
28410
28641
  /** Object count averaged over the bucket (rounded to nearest integer). */
28411
28642
  count: number().int().nonnegative()
28412
28643
  });
28413
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28644
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28414
28645
  deviceId: number(),
28415
28646
  zoneId: string(),
28416
28647
  className: string().optional()
@@ -31691,6 +31922,12 @@ Object.freeze({
31691
31922
  addonId: null,
31692
31923
  access: "view"
31693
31924
  },
31925
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
31926
+ capName: "pipeline-analytics",
31927
+ capScope: "device",
31928
+ addonId: null,
31929
+ access: "view"
31930
+ },
31694
31931
  "pipelineAnalytics.getEventStoreFootprint": {
31695
31932
  capName: "pipeline-analytics",
31696
31933
  capScope: "device",
@@ -31709,6 +31946,12 @@ Object.freeze({
31709
31946
  addonId: null,
31710
31947
  access: "view"
31711
31948
  },
31949
+ "pipelineAnalytics.getKeyEventsBatch": {
31950
+ capName: "pipeline-analytics",
31951
+ capScope: "device",
31952
+ addonId: null,
31953
+ access: "view"
31954
+ },
31712
31955
  "pipelineAnalytics.getMotionEvents": {
31713
31956
  capName: "pipeline-analytics",
31714
31957
  capScope: "device",
@@ -32885,6 +33128,12 @@ Object.freeze({
32885
33128
  addonId: null,
32886
33129
  access: "view"
32887
33130
  },
33131
+ "recording.reconcileLedgerAgainstDisk": {
33132
+ capName: "recording",
33133
+ capScope: "system",
33134
+ addonId: null,
33135
+ access: "create"
33136
+ },
32888
33137
  "recording.refreshStorageLocationsForMigration": {
32889
33138
  capName: "recording",
32890
33139
  capScope: "system",
@@ -33011,6 +33260,12 @@ Object.freeze({
33011
33260
  addonId: null,
33012
33261
  access: "view"
33013
33262
  },
33263
+ "sceneMonitor.listScenesBatch": {
33264
+ capName: "scene-monitor",
33265
+ capScope: "device",
33266
+ addonId: null,
33267
+ access: "view"
33268
+ },
33014
33269
  "sceneMonitor.recheckNow": {
33015
33270
  capName: "scene-monitor",
33016
33271
  capScope: "device",
@@ -34367,6 +34622,12 @@ Object.freeze({
34367
34622
  addonId: null,
34368
34623
  access: "view"
34369
34624
  },
34625
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34626
+ capName: "zone-analytics",
34627
+ capScope: "device",
34628
+ addonId: null,
34629
+ access: "view"
34630
+ },
34370
34631
  "zoneAnalytics.getUnzonedHistory": {
34371
34632
  capName: "zone-analytics",
34372
34633
  capScope: "device",
@@ -35356,6 +35617,11 @@ Object.freeze({
35356
35617
  form: "single",
35357
35618
  optional: false
35358
35619
  }],
35620
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
35621
+ name: "deviceId",
35622
+ form: "single",
35623
+ optional: true
35624
+ }],
35359
35625
  "pipelineAnalytics.getGroup": [{
35360
35626
  name: "deviceId",
35361
35627
  form: "single",
@@ -35366,6 +35632,11 @@ Object.freeze({
35366
35632
  form: "single",
35367
35633
  optional: false
35368
35634
  }],
35635
+ "pipelineAnalytics.getKeyEventsBatch": [{
35636
+ name: "deviceIds",
35637
+ form: "array",
35638
+ optional: false
35639
+ }],
35369
35640
  "pipelineAnalytics.getMotionEvents": [{
35370
35641
  name: "deviceId",
35371
35642
  form: "single",
@@ -35806,6 +36077,11 @@ Object.freeze({
35806
36077
  form: "single",
35807
36078
  optional: false
35808
36079
  }],
36080
+ "recording.reconcileLedgerAgainstDisk": [{
36081
+ name: "deviceId",
36082
+ form: "single",
36083
+ optional: true
36084
+ }],
35809
36085
  "recording.relocateFootage": [{
35810
36086
  name: "deviceId",
35811
36087
  form: "single",
@@ -35871,6 +36147,11 @@ Object.freeze({
35871
36147
  form: "single",
35872
36148
  optional: false
35873
36149
  }],
36150
+ "sceneMonitor.listScenesBatch": [{
36151
+ name: "deviceIds",
36152
+ form: "array",
36153
+ optional: false
36154
+ }],
35874
36155
  "sceneMonitor.recheckNow": [{
35875
36156
  name: "deviceId",
35876
36157
  form: "single",
@@ -36132,6 +36413,11 @@ Object.freeze({
36132
36413
  form: "single",
36133
36414
  optional: false
36134
36415
  }],
36416
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36417
+ name: "deviceIds",
36418
+ form: "array",
36419
+ optional: false
36420
+ }],
36135
36421
  "zoneAnalytics.getUnzonedHistory": [{
36136
36422
  name: "deviceId",
36137
36423
  form: "single",
package/dist/index.mjs CHANGED
@@ -8014,6 +8014,20 @@ var RelocateJobSchema = object({
8014
8014
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8015
8015
  */
8016
8016
  rowsReconciled: number().int().nonnegative().optional(),
8017
+ /**
8018
+ * Rows this run FORGOT because the file they name is not on disk.
8019
+ *
8020
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8021
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8022
+ * and the durable row is dropped through the same channel eviction uses. It
8023
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8024
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8025
+ * the same failure as one that quietly skips them (D295).
8026
+ *
8027
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8028
+ * ledger claimed 5.65 GB of footage that no longer existed.
8029
+ */
8030
+ rowsForgotten: number().int().nonnegative().optional(),
8017
8031
  startedAt: number(),
8018
8032
  finishedAt: number().nullable(),
8019
8033
  error: string().nullable()
@@ -8377,6 +8391,91 @@ var RelocateResidueSchema = object({
8377
8391
  segments: number().int().nonnegative(),
8378
8392
  bytes: number().int().nonnegative()
8379
8393
  }).nullable();
8394
+ /**
8395
+ * Ask one location whether its durable hour rows describe the disk — the walk
8396
+ * (D319).
8397
+ *
8398
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8399
+ * missing tool is the question, and the dry run is how they sanity-check the
8400
+ * destructive run before authorising it.
8401
+ */
8402
+ var LedgerWalkInputSchema = object({
8403
+ locationId: string().min(1),
8404
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8405
+ apply: boolean().optional(),
8406
+ /** Narrow to one camera. */
8407
+ deviceId: number().int().positive().optional(),
8408
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8409
+ profiles: array(string().min(1)).optional()
8410
+ });
8411
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8412
+ var LedgerWalkRefusalSchema = _enum([
8413
+ "location-unknown",
8414
+ "source-writable",
8415
+ "no-ledger",
8416
+ "archive-unreadable",
8417
+ "anchor-absent",
8418
+ "anchor-unreadable",
8419
+ "anchor-moved"
8420
+ ]);
8421
+ _enum([
8422
+ "live-tail",
8423
+ "listing-error",
8424
+ "path-mismatch",
8425
+ "durable-refused"
8426
+ ]);
8427
+ /** Every skip reason, always present, always a number — so a reason that never
8428
+ * fired reports as zero rather than absent and the report shape is constant
8429
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8430
+ var LedgerWalkSkipCountsSchema = object({
8431
+ "live-tail": number().int().nonnegative(),
8432
+ "listing-error": number().int().nonnegative(),
8433
+ "path-mismatch": number().int().nonnegative(),
8434
+ "durable-refused": number().int().nonnegative()
8435
+ });
8436
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8437
+ var LedgerWalkDeviceReportSchema = object({
8438
+ deviceId: number().int(),
8439
+ hoursWalked: number().int().nonnegative(),
8440
+ hoursMissing: number().int().nonnegative(),
8441
+ ghostSegments: number().int().nonnegative(),
8442
+ ghostBytes: number().int().nonnegative(),
8443
+ forgottenSegments: number().int().nonnegative(),
8444
+ orphanFiles: number().int().nonnegative()
8445
+ });
8446
+ /**
8447
+ * What one walk claimed, listed, found and (only when armed) forgot.
8448
+ *
8449
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8450
+ * walk that saw a fraction of the location is visible in its own report rather
8451
+ * than in the absence of one.
8452
+ */
8453
+ var LedgerWalkReportSchema = object({
8454
+ locationId: string(),
8455
+ applied: boolean(),
8456
+ refused: LedgerWalkRefusalSchema.nullable(),
8457
+ archiveSegments: number().int().nonnegative().nullable(),
8458
+ archiveBytes: number().int().nonnegative().nullable(),
8459
+ hoursClaimed: number().int().nonnegative(),
8460
+ hoursWalked: number().int().nonnegative(),
8461
+ hoursMissing: number().int().nonnegative(),
8462
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8463
+ listings: number().int().nonnegative(),
8464
+ segmentsClaimed: number().int().nonnegative(),
8465
+ ghostSegments: number().int().nonnegative(),
8466
+ ghostBytes: number().int().nonnegative(),
8467
+ ghostHoursWhole: number().int().nonnegative(),
8468
+ forgottenSegments: number().int().nonnegative(),
8469
+ forgottenBytes: number().int().nonnegative(),
8470
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8471
+ orphanFiles: number().int().nonnegative(),
8472
+ orphanSample: array(string()).readonly(),
8473
+ hoursSkipped: number().int().nonnegative(),
8474
+ skippedByReason: LedgerWalkSkipCountsSchema,
8475
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8476
+ bounded: boolean(),
8477
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8478
+ });
8380
8479
  /** How many rows a media pass would still act on against a given target — the
8381
8480
  * media lane's denominator AND its residue, from ONE derivation so the two can
8382
8481
  * never disagree. `null` = the count could not be taken. */
@@ -18780,13 +18879,15 @@ var ListGroupsPageSchema = object({
18780
18879
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18781
18880
  nextCursor: string().nullable()
18782
18881
  });
18882
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
18883
+ var KEY_EVENTS_MAX_LIMIT = 200;
18783
18884
  var KeyEventQueryInput = object({
18784
18885
  deviceId: number(),
18785
18886
  /** Window lower bound (track firstSeen ≥ since). */
18786
18887
  since: number(),
18787
18888
  /** Window upper bound (track firstSeen ≤ until). */
18788
18889
  until: number(),
18789
- limit: number().int().min(1).max(200).default(50),
18890
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18790
18891
  /** Drop tracks scoring below this importance. */
18791
18892
  minImportance: number().min(0).max(1).optional(),
18792
18893
  /** Restrict to a single class (e.g. 'person'). */
@@ -18808,6 +18909,32 @@ var KeyEventSchema = object({
18808
18909
  ...TrackFlagFields,
18809
18910
  ...TrackRetrainFields
18810
18911
  });
18912
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
18913
+ var KeyEventBatchQueryInput = object({
18914
+ deviceIds: array(number()).min(1).max(200),
18915
+ since: number(),
18916
+ until: number(),
18917
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
18918
+ * across the set, which would let a busy camera starve a quiet one of its
18919
+ * rows and change what the merged feed contains. */
18920
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18921
+ minImportance: number().min(0).max(1).optional(),
18922
+ classFilter: string().optional()
18923
+ });
18924
+ /**
18925
+ * One camera's key events in a batch answer.
18926
+ *
18927
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
18928
+ * error rather than throwing, so a camera whose store read failed and one with
18929
+ * no events in the window were ALREADY indistinguishable per camera — the
18930
+ * batch does not make that worse, and the row keeps the deviceId the single
18931
+ * method's output never carried (the caller used to stamp it from the fan-out
18932
+ * key, which only worked because there was one query per camera).
18933
+ */
18934
+ var KeyEventsForDeviceSchema = object({
18935
+ deviceId: number(),
18936
+ events: array(KeyEventSchema).readonly()
18937
+ });
18811
18938
  object({
18812
18939
  trackId: string(),
18813
18940
  className: string(),
@@ -18879,6 +19006,50 @@ var EventStoreFootprintSchema = object({
18879
19006
  totalBytes: number().int(),
18880
19007
  devices: array(EventStoreDeviceFootprintSchema).readonly()
18881
19008
  });
19009
+ /** Event-media footprint for one {@link MediaFileKind}. */
19010
+ var EventMediaKindFootprintSchema = object({
19011
+ kind: MediaFileKindEnum,
19012
+ /** Media rows of this kind. */
19013
+ rows: number().int(),
19014
+ /** Bytes on disk held by those rows. */
19015
+ bytes: number().int()
19016
+ });
19017
+ /**
19018
+ * The media footprint broken down by KIND — the axis a deletion decision
19019
+ * actually turns on.
19020
+ *
19021
+ * A byte total says how much there is; it cannot say what is safe to remove.
19022
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19023
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19024
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19025
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19026
+ * nothing else, so sizing a deletion means summing per kind.
19027
+ *
19028
+ * ## Why `unaccounted*` exists
19029
+ *
19030
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19031
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19032
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19033
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19034
+ * retired code path, or by a version that knew a kind this one does not) would
19035
+ * otherwise vanish from the total silently, and an operator would delete
19036
+ * against a denominator smaller than the disk.
19037
+ *
19038
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19039
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19040
+ */
19041
+ var EventMediaKindBreakdownSchema = object({
19042
+ /** Every media row in scope, from one unfiltered aggregate. */
19043
+ totalRows: number().int(),
19044
+ /** Every media byte in scope, from that same aggregate. */
19045
+ totalBytes: number().int(),
19046
+ /** Per-kind footprint, ordered by bytes descending. */
19047
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19048
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19049
+ unaccountedRows: number().int(),
19050
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19051
+ unaccountedBytes: number().int()
19052
+ });
18882
19053
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
18883
19054
  var EventPruneCountsSchema = object({
18884
19055
  motion: number().int(),
@@ -19033,7 +19204,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19033
19204
  until: number().optional(),
19034
19205
  kinds: array(string()).optional(),
19035
19206
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19036
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19207
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19037
19208
  deviceId: number(),
19038
19209
  since: number(),
19039
19210
  until: number(),
@@ -19082,6 +19253,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19082
19253
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19083
19254
  kind: "query",
19084
19255
  auth: "admin"
19256
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19257
+ kind: "query",
19258
+ auth: "admin"
19085
19259
  }), method(object({
19086
19260
  olderThanMs: number(),
19087
19261
  reason: OpsLogReasonSchema.optional()
@@ -21033,6 +21207,20 @@ method(object({
21033
21207
  error: string().optional()
21034
21208
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21035
21209
  providerId: string(),
21210
+ /**
21211
+ * The location this config is an UNSAVED edit of, when there is one.
21212
+ *
21213
+ * `listLocations` replaces every declared secret with the redaction
21214
+ * sentinel, so the edit modal's form state holds the sentinel for any
21215
+ * credential the operator did not retype — and posting that here
21216
+ * without a way to resolve it makes the provider try to authenticate
21217
+ * as `__camstack_redacted__` and report the operator's own working
21218
+ * password as wrong. Given this id, the orchestrator restores each
21219
+ * sentinel from the stored config (same rule as `upsertLocation`)
21220
+ * before dispatching. Omitted by the "Add location" wizard, where
21221
+ * every value was typed just now and nothing is stored yet.
21222
+ */
21223
+ locationId: string().optional(),
21036
21224
  config: record(string(), unknown())
21037
21225
  }), object({
21038
21226
  ok: boolean(),
@@ -26668,6 +26856,9 @@ method(object({
26668
26856
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26669
26857
  kind: "query",
26670
26858
  auth: "admin"
26859
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
26860
+ kind: "mutation",
26861
+ auth: "admin"
26671
26862
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26672
26863
  kind: "mutation",
26673
26864
  auth: "admin"
@@ -27059,7 +27250,26 @@ var SceneMonitorStatusSchema = object({
27059
27250
  monitors: array(SceneMonitorSchema),
27060
27251
  lastFetchedAt: number()
27061
27252
  });
27062
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27253
+ /**
27254
+ * One camera's row in a `listScenesBatch` answer.
27255
+ *
27256
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27257
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27258
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27259
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27260
+ * configured. Fanned out per camera the difference was visible — one query
27261
+ * errored while the others resolved — and a batch that returned only the rows
27262
+ * it managed would have destroyed it, silently, by making an unreachable camera
27263
+ * indistinguishable from one that answered `monitors: []`.
27264
+ *
27265
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27266
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27267
+ */
27268
+ var SceneMonitorStatusForDeviceSchema = object({
27269
+ deviceId: number(),
27270
+ status: SceneMonitorStatusSchema.nullable()
27271
+ });
27272
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27063
27273
  deviceId: number(),
27064
27274
  label: string(),
27065
27275
  roi: MaskRectShapeSchema,
@@ -28383,6 +28593,27 @@ var CameraOccupancySnapshotSchema = object({
28383
28593
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28384
28594
  });
28385
28595
  /**
28596
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28597
+ *
28598
+ * THREE outcomes, and the single-camera method could only express two of them
28599
+ * because `snapshot: null` was already spoken for:
28600
+ *
28601
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28602
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28603
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28604
+ * - `read: 'unreadable'` — the owner could not answer for this
28605
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28606
+ *
28607
+ * Collapsing the last two is the failure this field exists to prevent: a
28608
+ * hydration that threw would otherwise render as an empty Stationary section,
28609
+ * which is a definite claim about a camera nobody could read.
28610
+ */
28611
+ var CameraOccupancySnapshotForDeviceSchema = object({
28612
+ deviceId: number(),
28613
+ read: _enum(["read", "unreadable"]),
28614
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28615
+ });
28616
+ /**
28386
28617
  * Time-series resolution. The history methods return one bucket per
28387
28618
  * step over the requested range. Smaller resolutions cost more
28388
28619
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28406,7 +28637,7 @@ var HistoryPointSchema = object({
28406
28637
  /** Object count averaged over the bucket (rounded to nearest integer). */
28407
28638
  count: number().int().nonnegative()
28408
28639
  });
28409
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28640
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28410
28641
  deviceId: number(),
28411
28642
  zoneId: string(),
28412
28643
  className: string().optional()
@@ -31687,6 +31918,12 @@ Object.freeze({
31687
31918
  addonId: null,
31688
31919
  access: "view"
31689
31920
  },
31921
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
31922
+ capName: "pipeline-analytics",
31923
+ capScope: "device",
31924
+ addonId: null,
31925
+ access: "view"
31926
+ },
31690
31927
  "pipelineAnalytics.getEventStoreFootprint": {
31691
31928
  capName: "pipeline-analytics",
31692
31929
  capScope: "device",
@@ -31705,6 +31942,12 @@ Object.freeze({
31705
31942
  addonId: null,
31706
31943
  access: "view"
31707
31944
  },
31945
+ "pipelineAnalytics.getKeyEventsBatch": {
31946
+ capName: "pipeline-analytics",
31947
+ capScope: "device",
31948
+ addonId: null,
31949
+ access: "view"
31950
+ },
31708
31951
  "pipelineAnalytics.getMotionEvents": {
31709
31952
  capName: "pipeline-analytics",
31710
31953
  capScope: "device",
@@ -32881,6 +33124,12 @@ Object.freeze({
32881
33124
  addonId: null,
32882
33125
  access: "view"
32883
33126
  },
33127
+ "recording.reconcileLedgerAgainstDisk": {
33128
+ capName: "recording",
33129
+ capScope: "system",
33130
+ addonId: null,
33131
+ access: "create"
33132
+ },
32884
33133
  "recording.refreshStorageLocationsForMigration": {
32885
33134
  capName: "recording",
32886
33135
  capScope: "system",
@@ -33007,6 +33256,12 @@ Object.freeze({
33007
33256
  addonId: null,
33008
33257
  access: "view"
33009
33258
  },
33259
+ "sceneMonitor.listScenesBatch": {
33260
+ capName: "scene-monitor",
33261
+ capScope: "device",
33262
+ addonId: null,
33263
+ access: "view"
33264
+ },
33010
33265
  "sceneMonitor.recheckNow": {
33011
33266
  capName: "scene-monitor",
33012
33267
  capScope: "device",
@@ -34363,6 +34618,12 @@ Object.freeze({
34363
34618
  addonId: null,
34364
34619
  access: "view"
34365
34620
  },
34621
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34622
+ capName: "zone-analytics",
34623
+ capScope: "device",
34624
+ addonId: null,
34625
+ access: "view"
34626
+ },
34366
34627
  "zoneAnalytics.getUnzonedHistory": {
34367
34628
  capName: "zone-analytics",
34368
34629
  capScope: "device",
@@ -35352,6 +35613,11 @@ Object.freeze({
35352
35613
  form: "single",
35353
35614
  optional: false
35354
35615
  }],
35616
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
35617
+ name: "deviceId",
35618
+ form: "single",
35619
+ optional: true
35620
+ }],
35355
35621
  "pipelineAnalytics.getGroup": [{
35356
35622
  name: "deviceId",
35357
35623
  form: "single",
@@ -35362,6 +35628,11 @@ Object.freeze({
35362
35628
  form: "single",
35363
35629
  optional: false
35364
35630
  }],
35631
+ "pipelineAnalytics.getKeyEventsBatch": [{
35632
+ name: "deviceIds",
35633
+ form: "array",
35634
+ optional: false
35635
+ }],
35365
35636
  "pipelineAnalytics.getMotionEvents": [{
35366
35637
  name: "deviceId",
35367
35638
  form: "single",
@@ -35802,6 +36073,11 @@ Object.freeze({
35802
36073
  form: "single",
35803
36074
  optional: false
35804
36075
  }],
36076
+ "recording.reconcileLedgerAgainstDisk": [{
36077
+ name: "deviceId",
36078
+ form: "single",
36079
+ optional: true
36080
+ }],
35805
36081
  "recording.relocateFootage": [{
35806
36082
  name: "deviceId",
35807
36083
  form: "single",
@@ -35867,6 +36143,11 @@ Object.freeze({
35867
36143
  form: "single",
35868
36144
  optional: false
35869
36145
  }],
36146
+ "sceneMonitor.listScenesBatch": [{
36147
+ name: "deviceIds",
36148
+ form: "array",
36149
+ optional: false
36150
+ }],
35870
36151
  "sceneMonitor.recheckNow": [{
35871
36152
  name: "deviceId",
35872
36153
  form: "single",
@@ -36128,6 +36409,11 @@ Object.freeze({
36128
36409
  form: "single",
36129
36410
  optional: false
36130
36411
  }],
36412
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36413
+ name: "deviceIds",
36414
+ form: "array",
36415
+ optional: false
36416
+ }],
36131
36417
  "zoneAnalytics.getUnzonedHistory": [{
36132
36418
  name: "deviceId",
36133
36419
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-nodeav",
3
- "version": "1.2.47",
3
+ "version": "1.2.49",
4
4
  "description": "Standalone in-process node-av decoder addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",