@camstack/addon-provider-rtsp 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/addon.js +318 -2
- package/dist/addon.mjs +318 -2
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -8074,6 +8074,20 @@ var RelocateJobSchema = object({
|
|
|
8074
8074
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8075
8075
|
*/
|
|
8076
8076
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8077
|
+
/**
|
|
8078
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8079
|
+
*
|
|
8080
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8081
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8082
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8083
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8084
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8085
|
+
* the same failure as one that quietly skips them (D295).
|
|
8086
|
+
*
|
|
8087
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8088
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8089
|
+
*/
|
|
8090
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8077
8091
|
startedAt: number(),
|
|
8078
8092
|
finishedAt: number().nullable(),
|
|
8079
8093
|
error: string().nullable()
|
|
@@ -8437,6 +8451,91 @@ var RelocateResidueSchema = object({
|
|
|
8437
8451
|
segments: number().int().nonnegative(),
|
|
8438
8452
|
bytes: number().int().nonnegative()
|
|
8439
8453
|
}).nullable();
|
|
8454
|
+
/**
|
|
8455
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
8456
|
+
* (D319).
|
|
8457
|
+
*
|
|
8458
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
8459
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
8460
|
+
* destructive run before authorising it.
|
|
8461
|
+
*/
|
|
8462
|
+
var LedgerWalkInputSchema = object({
|
|
8463
|
+
locationId: string().min(1),
|
|
8464
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
8465
|
+
apply: boolean().optional(),
|
|
8466
|
+
/** Narrow to one camera. */
|
|
8467
|
+
deviceId: number().int().positive().optional(),
|
|
8468
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
8469
|
+
profiles: array(string().min(1)).optional()
|
|
8470
|
+
});
|
|
8471
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
8472
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
8473
|
+
"location-unknown",
|
|
8474
|
+
"source-writable",
|
|
8475
|
+
"no-ledger",
|
|
8476
|
+
"archive-unreadable",
|
|
8477
|
+
"anchor-absent",
|
|
8478
|
+
"anchor-unreadable",
|
|
8479
|
+
"anchor-moved"
|
|
8480
|
+
]);
|
|
8481
|
+
_enum([
|
|
8482
|
+
"live-tail",
|
|
8483
|
+
"listing-error",
|
|
8484
|
+
"path-mismatch",
|
|
8485
|
+
"durable-refused"
|
|
8486
|
+
]);
|
|
8487
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
8488
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
8489
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
8490
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
8491
|
+
"live-tail": number().int().nonnegative(),
|
|
8492
|
+
"listing-error": number().int().nonnegative(),
|
|
8493
|
+
"path-mismatch": number().int().nonnegative(),
|
|
8494
|
+
"durable-refused": number().int().nonnegative()
|
|
8495
|
+
});
|
|
8496
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
8497
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
8498
|
+
deviceId: number().int(),
|
|
8499
|
+
hoursWalked: number().int().nonnegative(),
|
|
8500
|
+
hoursMissing: number().int().nonnegative(),
|
|
8501
|
+
ghostSegments: number().int().nonnegative(),
|
|
8502
|
+
ghostBytes: number().int().nonnegative(),
|
|
8503
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8504
|
+
orphanFiles: number().int().nonnegative()
|
|
8505
|
+
});
|
|
8506
|
+
/**
|
|
8507
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
8508
|
+
*
|
|
8509
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
8510
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
8511
|
+
* than in the absence of one.
|
|
8512
|
+
*/
|
|
8513
|
+
var LedgerWalkReportSchema = object({
|
|
8514
|
+
locationId: string(),
|
|
8515
|
+
applied: boolean(),
|
|
8516
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
8517
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
8518
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
8519
|
+
hoursClaimed: number().int().nonnegative(),
|
|
8520
|
+
hoursWalked: number().int().nonnegative(),
|
|
8521
|
+
hoursMissing: number().int().nonnegative(),
|
|
8522
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
8523
|
+
listings: number().int().nonnegative(),
|
|
8524
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
8525
|
+
ghostSegments: number().int().nonnegative(),
|
|
8526
|
+
ghostBytes: number().int().nonnegative(),
|
|
8527
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
8528
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8529
|
+
forgottenBytes: number().int().nonnegative(),
|
|
8530
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
8531
|
+
orphanFiles: number().int().nonnegative(),
|
|
8532
|
+
orphanSample: array(string()).readonly(),
|
|
8533
|
+
hoursSkipped: number().int().nonnegative(),
|
|
8534
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
8535
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
8536
|
+
bounded: boolean(),
|
|
8537
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
8538
|
+
});
|
|
8440
8539
|
/** How many rows a media pass would still act on against a given target — the
|
|
8441
8540
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
8442
8541
|
* never disagree. `null` = the count could not be taken. */
|
|
@@ -19052,13 +19151,15 @@ var ListGroupsPageSchema = object({
|
|
|
19052
19151
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19053
19152
|
nextCursor: string().nullable()
|
|
19054
19153
|
});
|
|
19154
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
19155
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19055
19156
|
var KeyEventQueryInput = object({
|
|
19056
19157
|
deviceId: number(),
|
|
19057
19158
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19058
19159
|
since: number(),
|
|
19059
19160
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19060
19161
|
until: number(),
|
|
19061
|
-
limit: number().int().min(1).max(
|
|
19162
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19062
19163
|
/** Drop tracks scoring below this importance. */
|
|
19063
19164
|
minImportance: number().min(0).max(1).optional(),
|
|
19064
19165
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19080,6 +19181,32 @@ var KeyEventSchema = object({
|
|
|
19080
19181
|
...TrackFlagFields,
|
|
19081
19182
|
...TrackRetrainFields
|
|
19082
19183
|
});
|
|
19184
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
19185
|
+
var KeyEventBatchQueryInput = object({
|
|
19186
|
+
deviceIds: array(number()).min(1).max(200),
|
|
19187
|
+
since: number(),
|
|
19188
|
+
until: number(),
|
|
19189
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
19190
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
19191
|
+
* rows and change what the merged feed contains. */
|
|
19192
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19193
|
+
minImportance: number().min(0).max(1).optional(),
|
|
19194
|
+
classFilter: string().optional()
|
|
19195
|
+
});
|
|
19196
|
+
/**
|
|
19197
|
+
* One camera's key events in a batch answer.
|
|
19198
|
+
*
|
|
19199
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
19200
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
19201
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
19202
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
19203
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
19204
|
+
* key, which only worked because there was one query per camera).
|
|
19205
|
+
*/
|
|
19206
|
+
var KeyEventsForDeviceSchema = object({
|
|
19207
|
+
deviceId: number(),
|
|
19208
|
+
events: array(KeyEventSchema).readonly()
|
|
19209
|
+
});
|
|
19083
19210
|
object({
|
|
19084
19211
|
trackId: string(),
|
|
19085
19212
|
className: string(),
|
|
@@ -19151,6 +19278,50 @@ var EventStoreFootprintSchema = object({
|
|
|
19151
19278
|
totalBytes: number().int(),
|
|
19152
19279
|
devices: array(EventStoreDeviceFootprintSchema).readonly()
|
|
19153
19280
|
});
|
|
19281
|
+
/** Event-media footprint for one {@link MediaFileKind}. */
|
|
19282
|
+
var EventMediaKindFootprintSchema = object({
|
|
19283
|
+
kind: MediaFileKindEnum,
|
|
19284
|
+
/** Media rows of this kind. */
|
|
19285
|
+
rows: number().int(),
|
|
19286
|
+
/** Bytes on disk held by those rows. */
|
|
19287
|
+
bytes: number().int()
|
|
19288
|
+
});
|
|
19289
|
+
/**
|
|
19290
|
+
* The media footprint broken down by KIND — the axis a deletion decision
|
|
19291
|
+
* actually turns on.
|
|
19292
|
+
*
|
|
19293
|
+
* A byte total says how much there is; it cannot say what is safe to remove.
|
|
19294
|
+
* The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
|
|
19295
|
+
* motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
|
|
19296
|
+
* `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
|
|
19297
|
+
* buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
|
|
19298
|
+
* nothing else, so sizing a deletion means summing per kind.
|
|
19299
|
+
*
|
|
19300
|
+
* ## Why `unaccounted*` exists
|
|
19301
|
+
*
|
|
19302
|
+
* `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
|
|
19303
|
+
* writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
|
|
19304
|
+
* from a SEPARATE unfiltered aggregate over the same rows, never from adding
|
|
19305
|
+
* `kinds` up. A row whose stored `kind` is not in the enum (written by a
|
|
19306
|
+
* retired code path, or by a version that knew a kind this one does not) would
|
|
19307
|
+
* otherwise vanish from the total silently, and an operator would delete
|
|
19308
|
+
* against a denominator smaller than the disk.
|
|
19309
|
+
*
|
|
19310
|
+
* `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
|
|
19311
|
+
* zero; a non-zero value is a real finding and must be shown, not rounded away.
|
|
19312
|
+
*/
|
|
19313
|
+
var EventMediaKindBreakdownSchema = object({
|
|
19314
|
+
/** Every media row in scope, from one unfiltered aggregate. */
|
|
19315
|
+
totalRows: number().int(),
|
|
19316
|
+
/** Every media byte in scope, from that same aggregate. */
|
|
19317
|
+
totalBytes: number().int(),
|
|
19318
|
+
/** Per-kind footprint, ordered by bytes descending. */
|
|
19319
|
+
kinds: array(EventMediaKindFootprintSchema).readonly(),
|
|
19320
|
+
/** `totalRows` minus the summed `kinds` rows — see the schema note. */
|
|
19321
|
+
unaccountedRows: number().int(),
|
|
19322
|
+
/** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
|
|
19323
|
+
unaccountedBytes: number().int()
|
|
19324
|
+
});
|
|
19154
19325
|
/** Per-kind counts returned by the event-prune / device-delete mutations. */
|
|
19155
19326
|
var EventPruneCountsSchema = object({
|
|
19156
19327
|
motion: number().int(),
|
|
@@ -19305,7 +19476,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19305
19476
|
until: number().optional(),
|
|
19306
19477
|
kinds: array(string()).optional(),
|
|
19307
19478
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
19308
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
19479
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
19309
19480
|
deviceId: number(),
|
|
19310
19481
|
since: number(),
|
|
19311
19482
|
until: number(),
|
|
@@ -19354,6 +19525,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19354
19525
|
}), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
|
|
19355
19526
|
kind: "query",
|
|
19356
19527
|
auth: "admin"
|
|
19528
|
+
}), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
|
|
19529
|
+
kind: "query",
|
|
19530
|
+
auth: "admin"
|
|
19357
19531
|
}), method(object({
|
|
19358
19532
|
olderThanMs: number(),
|
|
19359
19533
|
reason: OpsLogReasonSchema.optional()
|
|
@@ -21409,6 +21583,20 @@ method(object({
|
|
|
21409
21583
|
error: string().optional()
|
|
21410
21584
|
}), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
|
|
21411
21585
|
providerId: string(),
|
|
21586
|
+
/**
|
|
21587
|
+
* The location this config is an UNSAVED edit of, when there is one.
|
|
21588
|
+
*
|
|
21589
|
+
* `listLocations` replaces every declared secret with the redaction
|
|
21590
|
+
* sentinel, so the edit modal's form state holds the sentinel for any
|
|
21591
|
+
* credential the operator did not retype — and posting that here
|
|
21592
|
+
* without a way to resolve it makes the provider try to authenticate
|
|
21593
|
+
* as `__camstack_redacted__` and report the operator's own working
|
|
21594
|
+
* password as wrong. Given this id, the orchestrator restores each
|
|
21595
|
+
* sentinel from the stored config (same rule as `upsertLocation`)
|
|
21596
|
+
* before dispatching. Omitted by the "Add location" wizard, where
|
|
21597
|
+
* every value was typed just now and nothing is stored yet.
|
|
21598
|
+
*/
|
|
21599
|
+
locationId: string().optional(),
|
|
21412
21600
|
config: record(string(), unknown())
|
|
21413
21601
|
}), object({
|
|
21414
21602
|
ok: boolean(),
|
|
@@ -28767,6 +28955,9 @@ method(object({
|
|
|
28767
28955
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28768
28956
|
kind: "query",
|
|
28769
28957
|
auth: "admin"
|
|
28958
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
28959
|
+
kind: "mutation",
|
|
28960
|
+
auth: "admin"
|
|
28770
28961
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28771
28962
|
kind: "mutation",
|
|
28772
28963
|
auth: "admin"
|
|
@@ -29158,6 +29349,25 @@ var SceneMonitorStatusSchema = object({
|
|
|
29158
29349
|
monitors: array(SceneMonitorSchema),
|
|
29159
29350
|
lastFetchedAt: number()
|
|
29160
29351
|
});
|
|
29352
|
+
/**
|
|
29353
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
29354
|
+
*
|
|
29355
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
29356
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
29357
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
29358
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
29359
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
29360
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
29361
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
29362
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
29363
|
+
*
|
|
29364
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
29365
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
29366
|
+
*/
|
|
29367
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
29368
|
+
deviceId: number(),
|
|
29369
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
29370
|
+
});
|
|
29161
29371
|
var sceneMonitorCapability = {
|
|
29162
29372
|
name: "scene-monitor",
|
|
29163
29373
|
scope: "device",
|
|
@@ -29167,6 +29377,22 @@ var sceneMonitorCapability = {
|
|
|
29167
29377
|
deviceTypes: [DeviceType.Camera],
|
|
29168
29378
|
methods: {
|
|
29169
29379
|
listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
|
|
29380
|
+
/**
|
|
29381
|
+
* The same answer, for a SET of cameras, in one round trip.
|
|
29382
|
+
*
|
|
29383
|
+
* `/scenes` renders every camera and re-reads them on a 30s safety-net
|
|
29384
|
+
* poll behind the push slice. Fanned out client-side that was one query
|
|
29385
|
+
* per camera — 29 round trips through the browser, the hub and the
|
|
29386
|
+
* post-analysis runner every 30 seconds to read an in-memory map the
|
|
29387
|
+
* owner had already merged. The work is unchanged (`statusFor` per
|
|
29388
|
+
* device, all in-process at the owner); what collapses is the transport.
|
|
29389
|
+
*
|
|
29390
|
+
* A camera that cannot answer still gets a row, with `status: null` —
|
|
29391
|
+
* see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
|
|
29392
|
+
* ids: a caller that asked for twenty-nine and got twenty-seven cannot
|
|
29393
|
+
* tell which two are missing, or that any are.
|
|
29394
|
+
*/
|
|
29395
|
+
listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
|
|
29170
29396
|
createScene: method(object({
|
|
29171
29397
|
deviceId: number(),
|
|
29172
29398
|
label: string(),
|
|
@@ -31002,6 +31228,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
31002
31228
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
31003
31229
|
});
|
|
31004
31230
|
/**
|
|
31231
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
31232
|
+
*
|
|
31233
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
31234
|
+
* because `snapshot: null` was already spoken for:
|
|
31235
|
+
*
|
|
31236
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
31237
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
31238
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
31239
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
31240
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
31241
|
+
*
|
|
31242
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
31243
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
31244
|
+
* which is a definite claim about a camera nobody could read.
|
|
31245
|
+
*/
|
|
31246
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
31247
|
+
deviceId: number(),
|
|
31248
|
+
read: _enum(["read", "unreadable"]),
|
|
31249
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
31250
|
+
});
|
|
31251
|
+
/**
|
|
31005
31252
|
* Time-series resolution. The history methods return one bucket per
|
|
31006
31253
|
* step over the requested range. Smaller resolutions cost more
|
|
31007
31254
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -31058,6 +31305,20 @@ var zoneAnalyticsCapability = {
|
|
|
31058
31305
|
* (no inference result emitted since boot or since binding was
|
|
31059
31306
|
* activated). */
|
|
31060
31307
|
getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
|
|
31308
|
+
/**
|
|
31309
|
+
* The same snapshot, for a SET of cameras, in one round trip.
|
|
31310
|
+
*
|
|
31311
|
+
* The Events page's Stationary section polls this every 15s for every
|
|
31312
|
+
* selected camera. Fanned out client-side that is one query per camera to
|
|
31313
|
+
* read a `Map.get` at the owner — the answer costs nothing, the round trip
|
|
31314
|
+
* costs everything. Batched, N transports become one and the per-device
|
|
31315
|
+
* work is unchanged.
|
|
31316
|
+
*
|
|
31317
|
+
* Every requested deviceId gets a row, tagged `read` — see
|
|
31318
|
+
* {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
|
|
31319
|
+
* not answer for is `'unreadable'`, never an empty reading.
|
|
31320
|
+
*/
|
|
31321
|
+
getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
|
|
31061
31322
|
/** Time-series object count inside one zone. `className` optional —
|
|
31062
31323
|
* omit to count every class in the zone. */
|
|
31063
31324
|
getZoneHistory: method(object({
|
|
@@ -35521,6 +35782,12 @@ Object.freeze({
|
|
|
35521
35782
|
addonId: null,
|
|
35522
35783
|
access: "view"
|
|
35523
35784
|
},
|
|
35785
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": {
|
|
35786
|
+
capName: "pipeline-analytics",
|
|
35787
|
+
capScope: "device",
|
|
35788
|
+
addonId: null,
|
|
35789
|
+
access: "view"
|
|
35790
|
+
},
|
|
35524
35791
|
"pipelineAnalytics.getEventStoreFootprint": {
|
|
35525
35792
|
capName: "pipeline-analytics",
|
|
35526
35793
|
capScope: "device",
|
|
@@ -35539,6 +35806,12 @@ Object.freeze({
|
|
|
35539
35806
|
addonId: null,
|
|
35540
35807
|
access: "view"
|
|
35541
35808
|
},
|
|
35809
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
35810
|
+
capName: "pipeline-analytics",
|
|
35811
|
+
capScope: "device",
|
|
35812
|
+
addonId: null,
|
|
35813
|
+
access: "view"
|
|
35814
|
+
},
|
|
35542
35815
|
"pipelineAnalytics.getMotionEvents": {
|
|
35543
35816
|
capName: "pipeline-analytics",
|
|
35544
35817
|
capScope: "device",
|
|
@@ -36715,6 +36988,12 @@ Object.freeze({
|
|
|
36715
36988
|
addonId: null,
|
|
36716
36989
|
access: "view"
|
|
36717
36990
|
},
|
|
36991
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
36992
|
+
capName: "recording",
|
|
36993
|
+
capScope: "system",
|
|
36994
|
+
addonId: null,
|
|
36995
|
+
access: "create"
|
|
36996
|
+
},
|
|
36718
36997
|
"recording.refreshStorageLocationsForMigration": {
|
|
36719
36998
|
capName: "recording",
|
|
36720
36999
|
capScope: "system",
|
|
@@ -36841,6 +37120,12 @@ Object.freeze({
|
|
|
36841
37120
|
addonId: null,
|
|
36842
37121
|
access: "view"
|
|
36843
37122
|
},
|
|
37123
|
+
"sceneMonitor.listScenesBatch": {
|
|
37124
|
+
capName: "scene-monitor",
|
|
37125
|
+
capScope: "device",
|
|
37126
|
+
addonId: null,
|
|
37127
|
+
access: "view"
|
|
37128
|
+
},
|
|
36844
37129
|
"sceneMonitor.recheckNow": {
|
|
36845
37130
|
capName: "scene-monitor",
|
|
36846
37131
|
capScope: "device",
|
|
@@ -38197,6 +38482,12 @@ Object.freeze({
|
|
|
38197
38482
|
addonId: null,
|
|
38198
38483
|
access: "view"
|
|
38199
38484
|
},
|
|
38485
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
38486
|
+
capName: "zone-analytics",
|
|
38487
|
+
capScope: "device",
|
|
38488
|
+
addonId: null,
|
|
38489
|
+
access: "view"
|
|
38490
|
+
},
|
|
38200
38491
|
"zoneAnalytics.getUnzonedHistory": {
|
|
38201
38492
|
capName: "zone-analytics",
|
|
38202
38493
|
capScope: "device",
|
|
@@ -39186,6 +39477,11 @@ Object.freeze({
|
|
|
39186
39477
|
form: "single",
|
|
39187
39478
|
optional: false
|
|
39188
39479
|
}],
|
|
39480
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": [{
|
|
39481
|
+
name: "deviceId",
|
|
39482
|
+
form: "single",
|
|
39483
|
+
optional: true
|
|
39484
|
+
}],
|
|
39189
39485
|
"pipelineAnalytics.getGroup": [{
|
|
39190
39486
|
name: "deviceId",
|
|
39191
39487
|
form: "single",
|
|
@@ -39196,6 +39492,11 @@ Object.freeze({
|
|
|
39196
39492
|
form: "single",
|
|
39197
39493
|
optional: false
|
|
39198
39494
|
}],
|
|
39495
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
39496
|
+
name: "deviceIds",
|
|
39497
|
+
form: "array",
|
|
39498
|
+
optional: false
|
|
39499
|
+
}],
|
|
39199
39500
|
"pipelineAnalytics.getMotionEvents": [{
|
|
39200
39501
|
name: "deviceId",
|
|
39201
39502
|
form: "single",
|
|
@@ -39636,6 +39937,11 @@ Object.freeze({
|
|
|
39636
39937
|
form: "single",
|
|
39637
39938
|
optional: false
|
|
39638
39939
|
}],
|
|
39940
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
39941
|
+
name: "deviceId",
|
|
39942
|
+
form: "single",
|
|
39943
|
+
optional: true
|
|
39944
|
+
}],
|
|
39639
39945
|
"recording.relocateFootage": [{
|
|
39640
39946
|
name: "deviceId",
|
|
39641
39947
|
form: "single",
|
|
@@ -39701,6 +40007,11 @@ Object.freeze({
|
|
|
39701
40007
|
form: "single",
|
|
39702
40008
|
optional: false
|
|
39703
40009
|
}],
|
|
40010
|
+
"sceneMonitor.listScenesBatch": [{
|
|
40011
|
+
name: "deviceIds",
|
|
40012
|
+
form: "array",
|
|
40013
|
+
optional: false
|
|
40014
|
+
}],
|
|
39704
40015
|
"sceneMonitor.recheckNow": [{
|
|
39705
40016
|
name: "deviceId",
|
|
39706
40017
|
form: "single",
|
|
@@ -39962,6 +40273,11 @@ Object.freeze({
|
|
|
39962
40273
|
form: "single",
|
|
39963
40274
|
optional: false
|
|
39964
40275
|
}],
|
|
40276
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
40277
|
+
name: "deviceIds",
|
|
40278
|
+
form: "array",
|
|
40279
|
+
optional: false
|
|
40280
|
+
}],
|
|
39965
40281
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
39966
40282
|
name: "deviceId",
|
|
39967
40283
|
form: "single",
|
package/dist/addon.mjs
CHANGED
|
@@ -8050,6 +8050,20 @@ var RelocateJobSchema = object({
|
|
|
8050
8050
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8051
8051
|
*/
|
|
8052
8052
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8053
|
+
/**
|
|
8054
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8055
|
+
*
|
|
8056
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8057
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8058
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8059
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8060
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8061
|
+
* the same failure as one that quietly skips them (D295).
|
|
8062
|
+
*
|
|
8063
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8064
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8065
|
+
*/
|
|
8066
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8053
8067
|
startedAt: number(),
|
|
8054
8068
|
finishedAt: number().nullable(),
|
|
8055
8069
|
error: string().nullable()
|
|
@@ -8413,6 +8427,91 @@ var RelocateResidueSchema = object({
|
|
|
8413
8427
|
segments: number().int().nonnegative(),
|
|
8414
8428
|
bytes: number().int().nonnegative()
|
|
8415
8429
|
}).nullable();
|
|
8430
|
+
/**
|
|
8431
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
8432
|
+
* (D319).
|
|
8433
|
+
*
|
|
8434
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
8435
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
8436
|
+
* destructive run before authorising it.
|
|
8437
|
+
*/
|
|
8438
|
+
var LedgerWalkInputSchema = object({
|
|
8439
|
+
locationId: string().min(1),
|
|
8440
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
8441
|
+
apply: boolean().optional(),
|
|
8442
|
+
/** Narrow to one camera. */
|
|
8443
|
+
deviceId: number().int().positive().optional(),
|
|
8444
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
8445
|
+
profiles: array(string().min(1)).optional()
|
|
8446
|
+
});
|
|
8447
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
8448
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
8449
|
+
"location-unknown",
|
|
8450
|
+
"source-writable",
|
|
8451
|
+
"no-ledger",
|
|
8452
|
+
"archive-unreadable",
|
|
8453
|
+
"anchor-absent",
|
|
8454
|
+
"anchor-unreadable",
|
|
8455
|
+
"anchor-moved"
|
|
8456
|
+
]);
|
|
8457
|
+
_enum([
|
|
8458
|
+
"live-tail",
|
|
8459
|
+
"listing-error",
|
|
8460
|
+
"path-mismatch",
|
|
8461
|
+
"durable-refused"
|
|
8462
|
+
]);
|
|
8463
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
8464
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
8465
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
8466
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
8467
|
+
"live-tail": number().int().nonnegative(),
|
|
8468
|
+
"listing-error": number().int().nonnegative(),
|
|
8469
|
+
"path-mismatch": number().int().nonnegative(),
|
|
8470
|
+
"durable-refused": number().int().nonnegative()
|
|
8471
|
+
});
|
|
8472
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
8473
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
8474
|
+
deviceId: number().int(),
|
|
8475
|
+
hoursWalked: number().int().nonnegative(),
|
|
8476
|
+
hoursMissing: number().int().nonnegative(),
|
|
8477
|
+
ghostSegments: number().int().nonnegative(),
|
|
8478
|
+
ghostBytes: number().int().nonnegative(),
|
|
8479
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8480
|
+
orphanFiles: number().int().nonnegative()
|
|
8481
|
+
});
|
|
8482
|
+
/**
|
|
8483
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
8484
|
+
*
|
|
8485
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
8486
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
8487
|
+
* than in the absence of one.
|
|
8488
|
+
*/
|
|
8489
|
+
var LedgerWalkReportSchema = object({
|
|
8490
|
+
locationId: string(),
|
|
8491
|
+
applied: boolean(),
|
|
8492
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
8493
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
8494
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
8495
|
+
hoursClaimed: number().int().nonnegative(),
|
|
8496
|
+
hoursWalked: number().int().nonnegative(),
|
|
8497
|
+
hoursMissing: number().int().nonnegative(),
|
|
8498
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
8499
|
+
listings: number().int().nonnegative(),
|
|
8500
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
8501
|
+
ghostSegments: number().int().nonnegative(),
|
|
8502
|
+
ghostBytes: number().int().nonnegative(),
|
|
8503
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
8504
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8505
|
+
forgottenBytes: number().int().nonnegative(),
|
|
8506
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
8507
|
+
orphanFiles: number().int().nonnegative(),
|
|
8508
|
+
orphanSample: array(string()).readonly(),
|
|
8509
|
+
hoursSkipped: number().int().nonnegative(),
|
|
8510
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
8511
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
8512
|
+
bounded: boolean(),
|
|
8513
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
8514
|
+
});
|
|
8416
8515
|
/** How many rows a media pass would still act on against a given target — the
|
|
8417
8516
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
8418
8517
|
* never disagree. `null` = the count could not be taken. */
|
|
@@ -19028,13 +19127,15 @@ var ListGroupsPageSchema = object({
|
|
|
19028
19127
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19029
19128
|
nextCursor: string().nullable()
|
|
19030
19129
|
});
|
|
19130
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
19131
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19031
19132
|
var KeyEventQueryInput = object({
|
|
19032
19133
|
deviceId: number(),
|
|
19033
19134
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19034
19135
|
since: number(),
|
|
19035
19136
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19036
19137
|
until: number(),
|
|
19037
|
-
limit: number().int().min(1).max(
|
|
19138
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19038
19139
|
/** Drop tracks scoring below this importance. */
|
|
19039
19140
|
minImportance: number().min(0).max(1).optional(),
|
|
19040
19141
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19056,6 +19157,32 @@ var KeyEventSchema = object({
|
|
|
19056
19157
|
...TrackFlagFields,
|
|
19057
19158
|
...TrackRetrainFields
|
|
19058
19159
|
});
|
|
19160
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
19161
|
+
var KeyEventBatchQueryInput = object({
|
|
19162
|
+
deviceIds: array(number()).min(1).max(200),
|
|
19163
|
+
since: number(),
|
|
19164
|
+
until: number(),
|
|
19165
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
19166
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
19167
|
+
* rows and change what the merged feed contains. */
|
|
19168
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19169
|
+
minImportance: number().min(0).max(1).optional(),
|
|
19170
|
+
classFilter: string().optional()
|
|
19171
|
+
});
|
|
19172
|
+
/**
|
|
19173
|
+
* One camera's key events in a batch answer.
|
|
19174
|
+
*
|
|
19175
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
19176
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
19177
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
19178
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
19179
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
19180
|
+
* key, which only worked because there was one query per camera).
|
|
19181
|
+
*/
|
|
19182
|
+
var KeyEventsForDeviceSchema = object({
|
|
19183
|
+
deviceId: number(),
|
|
19184
|
+
events: array(KeyEventSchema).readonly()
|
|
19185
|
+
});
|
|
19059
19186
|
object({
|
|
19060
19187
|
trackId: string(),
|
|
19061
19188
|
className: string(),
|
|
@@ -19127,6 +19254,50 @@ var EventStoreFootprintSchema = object({
|
|
|
19127
19254
|
totalBytes: number().int(),
|
|
19128
19255
|
devices: array(EventStoreDeviceFootprintSchema).readonly()
|
|
19129
19256
|
});
|
|
19257
|
+
/** Event-media footprint for one {@link MediaFileKind}. */
|
|
19258
|
+
var EventMediaKindFootprintSchema = object({
|
|
19259
|
+
kind: MediaFileKindEnum,
|
|
19260
|
+
/** Media rows of this kind. */
|
|
19261
|
+
rows: number().int(),
|
|
19262
|
+
/** Bytes on disk held by those rows. */
|
|
19263
|
+
bytes: number().int()
|
|
19264
|
+
});
|
|
19265
|
+
/**
|
|
19266
|
+
* The media footprint broken down by KIND — the axis a deletion decision
|
|
19267
|
+
* actually turns on.
|
|
19268
|
+
*
|
|
19269
|
+
* A byte total says how much there is; it cannot say what is safe to remove.
|
|
19270
|
+
* The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
|
|
19271
|
+
* motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
|
|
19272
|
+
* `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
|
|
19273
|
+
* buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
|
|
19274
|
+
* nothing else, so sizing a deletion means summing per kind.
|
|
19275
|
+
*
|
|
19276
|
+
* ## Why `unaccounted*` exists
|
|
19277
|
+
*
|
|
19278
|
+
* `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
|
|
19279
|
+
* writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
|
|
19280
|
+
* from a SEPARATE unfiltered aggregate over the same rows, never from adding
|
|
19281
|
+
* `kinds` up. A row whose stored `kind` is not in the enum (written by a
|
|
19282
|
+
* retired code path, or by a version that knew a kind this one does not) would
|
|
19283
|
+
* otherwise vanish from the total silently, and an operator would delete
|
|
19284
|
+
* against a denominator smaller than the disk.
|
|
19285
|
+
*
|
|
19286
|
+
* `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
|
|
19287
|
+
* zero; a non-zero value is a real finding and must be shown, not rounded away.
|
|
19288
|
+
*/
|
|
19289
|
+
var EventMediaKindBreakdownSchema = object({
|
|
19290
|
+
/** Every media row in scope, from one unfiltered aggregate. */
|
|
19291
|
+
totalRows: number().int(),
|
|
19292
|
+
/** Every media byte in scope, from that same aggregate. */
|
|
19293
|
+
totalBytes: number().int(),
|
|
19294
|
+
/** Per-kind footprint, ordered by bytes descending. */
|
|
19295
|
+
kinds: array(EventMediaKindFootprintSchema).readonly(),
|
|
19296
|
+
/** `totalRows` minus the summed `kinds` rows — see the schema note. */
|
|
19297
|
+
unaccountedRows: number().int(),
|
|
19298
|
+
/** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
|
|
19299
|
+
unaccountedBytes: number().int()
|
|
19300
|
+
});
|
|
19130
19301
|
/** Per-kind counts returned by the event-prune / device-delete mutations. */
|
|
19131
19302
|
var EventPruneCountsSchema = object({
|
|
19132
19303
|
motion: number().int(),
|
|
@@ -19281,7 +19452,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19281
19452
|
until: number().optional(),
|
|
19282
19453
|
kinds: array(string()).optional(),
|
|
19283
19454
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
19284
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
19455
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
19285
19456
|
deviceId: number(),
|
|
19286
19457
|
since: number(),
|
|
19287
19458
|
until: number(),
|
|
@@ -19330,6 +19501,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19330
19501
|
}), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
|
|
19331
19502
|
kind: "query",
|
|
19332
19503
|
auth: "admin"
|
|
19504
|
+
}), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
|
|
19505
|
+
kind: "query",
|
|
19506
|
+
auth: "admin"
|
|
19333
19507
|
}), method(object({
|
|
19334
19508
|
olderThanMs: number(),
|
|
19335
19509
|
reason: OpsLogReasonSchema.optional()
|
|
@@ -21385,6 +21559,20 @@ method(object({
|
|
|
21385
21559
|
error: string().optional()
|
|
21386
21560
|
}), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
|
|
21387
21561
|
providerId: string(),
|
|
21562
|
+
/**
|
|
21563
|
+
* The location this config is an UNSAVED edit of, when there is one.
|
|
21564
|
+
*
|
|
21565
|
+
* `listLocations` replaces every declared secret with the redaction
|
|
21566
|
+
* sentinel, so the edit modal's form state holds the sentinel for any
|
|
21567
|
+
* credential the operator did not retype — and posting that here
|
|
21568
|
+
* without a way to resolve it makes the provider try to authenticate
|
|
21569
|
+
* as `__camstack_redacted__` and report the operator's own working
|
|
21570
|
+
* password as wrong. Given this id, the orchestrator restores each
|
|
21571
|
+
* sentinel from the stored config (same rule as `upsertLocation`)
|
|
21572
|
+
* before dispatching. Omitted by the "Add location" wizard, where
|
|
21573
|
+
* every value was typed just now and nothing is stored yet.
|
|
21574
|
+
*/
|
|
21575
|
+
locationId: string().optional(),
|
|
21388
21576
|
config: record(string(), unknown())
|
|
21389
21577
|
}), object({
|
|
21390
21578
|
ok: boolean(),
|
|
@@ -28743,6 +28931,9 @@ method(object({
|
|
|
28743
28931
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28744
28932
|
kind: "query",
|
|
28745
28933
|
auth: "admin"
|
|
28934
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
28935
|
+
kind: "mutation",
|
|
28936
|
+
auth: "admin"
|
|
28746
28937
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28747
28938
|
kind: "mutation",
|
|
28748
28939
|
auth: "admin"
|
|
@@ -29134,6 +29325,25 @@ var SceneMonitorStatusSchema = object({
|
|
|
29134
29325
|
monitors: array(SceneMonitorSchema),
|
|
29135
29326
|
lastFetchedAt: number()
|
|
29136
29327
|
});
|
|
29328
|
+
/**
|
|
29329
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
29330
|
+
*
|
|
29331
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
29332
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
29333
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
29334
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
29335
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
29336
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
29337
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
29338
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
29339
|
+
*
|
|
29340
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
29341
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
29342
|
+
*/
|
|
29343
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
29344
|
+
deviceId: number(),
|
|
29345
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
29346
|
+
});
|
|
29137
29347
|
var sceneMonitorCapability = {
|
|
29138
29348
|
name: "scene-monitor",
|
|
29139
29349
|
scope: "device",
|
|
@@ -29143,6 +29353,22 @@ var sceneMonitorCapability = {
|
|
|
29143
29353
|
deviceTypes: [DeviceType.Camera],
|
|
29144
29354
|
methods: {
|
|
29145
29355
|
listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
|
|
29356
|
+
/**
|
|
29357
|
+
* The same answer, for a SET of cameras, in one round trip.
|
|
29358
|
+
*
|
|
29359
|
+
* `/scenes` renders every camera and re-reads them on a 30s safety-net
|
|
29360
|
+
* poll behind the push slice. Fanned out client-side that was one query
|
|
29361
|
+
* per camera — 29 round trips through the browser, the hub and the
|
|
29362
|
+
* post-analysis runner every 30 seconds to read an in-memory map the
|
|
29363
|
+
* owner had already merged. The work is unchanged (`statusFor` per
|
|
29364
|
+
* device, all in-process at the owner); what collapses is the transport.
|
|
29365
|
+
*
|
|
29366
|
+
* A camera that cannot answer still gets a row, with `status: null` —
|
|
29367
|
+
* see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
|
|
29368
|
+
* ids: a caller that asked for twenty-nine and got twenty-seven cannot
|
|
29369
|
+
* tell which two are missing, or that any are.
|
|
29370
|
+
*/
|
|
29371
|
+
listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
|
|
29146
29372
|
createScene: method(object({
|
|
29147
29373
|
deviceId: number(),
|
|
29148
29374
|
label: string(),
|
|
@@ -30978,6 +31204,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
30978
31204
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
30979
31205
|
});
|
|
30980
31206
|
/**
|
|
31207
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
31208
|
+
*
|
|
31209
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
31210
|
+
* because `snapshot: null` was already spoken for:
|
|
31211
|
+
*
|
|
31212
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
31213
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
31214
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
31215
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
31216
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
31217
|
+
*
|
|
31218
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
31219
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
31220
|
+
* which is a definite claim about a camera nobody could read.
|
|
31221
|
+
*/
|
|
31222
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
31223
|
+
deviceId: number(),
|
|
31224
|
+
read: _enum(["read", "unreadable"]),
|
|
31225
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
31226
|
+
});
|
|
31227
|
+
/**
|
|
30981
31228
|
* Time-series resolution. The history methods return one bucket per
|
|
30982
31229
|
* step over the requested range. Smaller resolutions cost more
|
|
30983
31230
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -31034,6 +31281,20 @@ var zoneAnalyticsCapability = {
|
|
|
31034
31281
|
* (no inference result emitted since boot or since binding was
|
|
31035
31282
|
* activated). */
|
|
31036
31283
|
getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
|
|
31284
|
+
/**
|
|
31285
|
+
* The same snapshot, for a SET of cameras, in one round trip.
|
|
31286
|
+
*
|
|
31287
|
+
* The Events page's Stationary section polls this every 15s for every
|
|
31288
|
+
* selected camera. Fanned out client-side that is one query per camera to
|
|
31289
|
+
* read a `Map.get` at the owner — the answer costs nothing, the round trip
|
|
31290
|
+
* costs everything. Batched, N transports become one and the per-device
|
|
31291
|
+
* work is unchanged.
|
|
31292
|
+
*
|
|
31293
|
+
* Every requested deviceId gets a row, tagged `read` — see
|
|
31294
|
+
* {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
|
|
31295
|
+
* not answer for is `'unreadable'`, never an empty reading.
|
|
31296
|
+
*/
|
|
31297
|
+
getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
|
|
31037
31298
|
/** Time-series object count inside one zone. `className` optional —
|
|
31038
31299
|
* omit to count every class in the zone. */
|
|
31039
31300
|
getZoneHistory: method(object({
|
|
@@ -35497,6 +35758,12 @@ Object.freeze({
|
|
|
35497
35758
|
addonId: null,
|
|
35498
35759
|
access: "view"
|
|
35499
35760
|
},
|
|
35761
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": {
|
|
35762
|
+
capName: "pipeline-analytics",
|
|
35763
|
+
capScope: "device",
|
|
35764
|
+
addonId: null,
|
|
35765
|
+
access: "view"
|
|
35766
|
+
},
|
|
35500
35767
|
"pipelineAnalytics.getEventStoreFootprint": {
|
|
35501
35768
|
capName: "pipeline-analytics",
|
|
35502
35769
|
capScope: "device",
|
|
@@ -35515,6 +35782,12 @@ Object.freeze({
|
|
|
35515
35782
|
addonId: null,
|
|
35516
35783
|
access: "view"
|
|
35517
35784
|
},
|
|
35785
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
35786
|
+
capName: "pipeline-analytics",
|
|
35787
|
+
capScope: "device",
|
|
35788
|
+
addonId: null,
|
|
35789
|
+
access: "view"
|
|
35790
|
+
},
|
|
35518
35791
|
"pipelineAnalytics.getMotionEvents": {
|
|
35519
35792
|
capName: "pipeline-analytics",
|
|
35520
35793
|
capScope: "device",
|
|
@@ -36691,6 +36964,12 @@ Object.freeze({
|
|
|
36691
36964
|
addonId: null,
|
|
36692
36965
|
access: "view"
|
|
36693
36966
|
},
|
|
36967
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
36968
|
+
capName: "recording",
|
|
36969
|
+
capScope: "system",
|
|
36970
|
+
addonId: null,
|
|
36971
|
+
access: "create"
|
|
36972
|
+
},
|
|
36694
36973
|
"recording.refreshStorageLocationsForMigration": {
|
|
36695
36974
|
capName: "recording",
|
|
36696
36975
|
capScope: "system",
|
|
@@ -36817,6 +37096,12 @@ Object.freeze({
|
|
|
36817
37096
|
addonId: null,
|
|
36818
37097
|
access: "view"
|
|
36819
37098
|
},
|
|
37099
|
+
"sceneMonitor.listScenesBatch": {
|
|
37100
|
+
capName: "scene-monitor",
|
|
37101
|
+
capScope: "device",
|
|
37102
|
+
addonId: null,
|
|
37103
|
+
access: "view"
|
|
37104
|
+
},
|
|
36820
37105
|
"sceneMonitor.recheckNow": {
|
|
36821
37106
|
capName: "scene-monitor",
|
|
36822
37107
|
capScope: "device",
|
|
@@ -38173,6 +38458,12 @@ Object.freeze({
|
|
|
38173
38458
|
addonId: null,
|
|
38174
38459
|
access: "view"
|
|
38175
38460
|
},
|
|
38461
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
38462
|
+
capName: "zone-analytics",
|
|
38463
|
+
capScope: "device",
|
|
38464
|
+
addonId: null,
|
|
38465
|
+
access: "view"
|
|
38466
|
+
},
|
|
38176
38467
|
"zoneAnalytics.getUnzonedHistory": {
|
|
38177
38468
|
capName: "zone-analytics",
|
|
38178
38469
|
capScope: "device",
|
|
@@ -39162,6 +39453,11 @@ Object.freeze({
|
|
|
39162
39453
|
form: "single",
|
|
39163
39454
|
optional: false
|
|
39164
39455
|
}],
|
|
39456
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": [{
|
|
39457
|
+
name: "deviceId",
|
|
39458
|
+
form: "single",
|
|
39459
|
+
optional: true
|
|
39460
|
+
}],
|
|
39165
39461
|
"pipelineAnalytics.getGroup": [{
|
|
39166
39462
|
name: "deviceId",
|
|
39167
39463
|
form: "single",
|
|
@@ -39172,6 +39468,11 @@ Object.freeze({
|
|
|
39172
39468
|
form: "single",
|
|
39173
39469
|
optional: false
|
|
39174
39470
|
}],
|
|
39471
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
39472
|
+
name: "deviceIds",
|
|
39473
|
+
form: "array",
|
|
39474
|
+
optional: false
|
|
39475
|
+
}],
|
|
39175
39476
|
"pipelineAnalytics.getMotionEvents": [{
|
|
39176
39477
|
name: "deviceId",
|
|
39177
39478
|
form: "single",
|
|
@@ -39612,6 +39913,11 @@ Object.freeze({
|
|
|
39612
39913
|
form: "single",
|
|
39613
39914
|
optional: false
|
|
39614
39915
|
}],
|
|
39916
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
39917
|
+
name: "deviceId",
|
|
39918
|
+
form: "single",
|
|
39919
|
+
optional: true
|
|
39920
|
+
}],
|
|
39615
39921
|
"recording.relocateFootage": [{
|
|
39616
39922
|
name: "deviceId",
|
|
39617
39923
|
form: "single",
|
|
@@ -39677,6 +39983,11 @@ Object.freeze({
|
|
|
39677
39983
|
form: "single",
|
|
39678
39984
|
optional: false
|
|
39679
39985
|
}],
|
|
39986
|
+
"sceneMonitor.listScenesBatch": [{
|
|
39987
|
+
name: "deviceIds",
|
|
39988
|
+
form: "array",
|
|
39989
|
+
optional: false
|
|
39990
|
+
}],
|
|
39680
39991
|
"sceneMonitor.recheckNow": [{
|
|
39681
39992
|
name: "deviceId",
|
|
39682
39993
|
form: "single",
|
|
@@ -39938,6 +40249,11 @@ Object.freeze({
|
|
|
39938
40249
|
form: "single",
|
|
39939
40250
|
optional: false
|
|
39940
40251
|
}],
|
|
40252
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
40253
|
+
name: "deviceIds",
|
|
40254
|
+
form: "array",
|
|
40255
|
+
optional: false
|
|
40256
|
+
}],
|
|
39941
40257
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
39942
40258
|
name: "deviceId",
|
|
39943
40259
|
form: "single",
|