@camstack/addon-provider-rademacher 0.2.46 → 0.2.48
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
|
@@ -9003,6 +9003,20 @@ var RelocateJobSchema = object({
|
|
|
9003
9003
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
9004
9004
|
*/
|
|
9005
9005
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
9006
|
+
/**
|
|
9007
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
9008
|
+
*
|
|
9009
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
9010
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
9011
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
9012
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
9013
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
9014
|
+
* the same failure as one that quietly skips them (D295).
|
|
9015
|
+
*
|
|
9016
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
9017
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
9018
|
+
*/
|
|
9019
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
9006
9020
|
startedAt: number(),
|
|
9007
9021
|
finishedAt: number().nullable(),
|
|
9008
9022
|
error: string().nullable()
|
|
@@ -9366,6 +9380,91 @@ var RelocateResidueSchema = object({
|
|
|
9366
9380
|
segments: number().int().nonnegative(),
|
|
9367
9381
|
bytes: number().int().nonnegative()
|
|
9368
9382
|
}).nullable();
|
|
9383
|
+
/**
|
|
9384
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
9385
|
+
* (D319).
|
|
9386
|
+
*
|
|
9387
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
9388
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
9389
|
+
* destructive run before authorising it.
|
|
9390
|
+
*/
|
|
9391
|
+
var LedgerWalkInputSchema = object({
|
|
9392
|
+
locationId: string().min(1),
|
|
9393
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
9394
|
+
apply: boolean().optional(),
|
|
9395
|
+
/** Narrow to one camera. */
|
|
9396
|
+
deviceId: number().int().positive().optional(),
|
|
9397
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
9398
|
+
profiles: array(string().min(1)).optional()
|
|
9399
|
+
});
|
|
9400
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
9401
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
9402
|
+
"location-unknown",
|
|
9403
|
+
"source-writable",
|
|
9404
|
+
"no-ledger",
|
|
9405
|
+
"archive-unreadable",
|
|
9406
|
+
"anchor-absent",
|
|
9407
|
+
"anchor-unreadable",
|
|
9408
|
+
"anchor-moved"
|
|
9409
|
+
]);
|
|
9410
|
+
_enum([
|
|
9411
|
+
"live-tail",
|
|
9412
|
+
"listing-error",
|
|
9413
|
+
"path-mismatch",
|
|
9414
|
+
"durable-refused"
|
|
9415
|
+
]);
|
|
9416
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
9417
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
9418
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
9419
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
9420
|
+
"live-tail": number().int().nonnegative(),
|
|
9421
|
+
"listing-error": number().int().nonnegative(),
|
|
9422
|
+
"path-mismatch": number().int().nonnegative(),
|
|
9423
|
+
"durable-refused": number().int().nonnegative()
|
|
9424
|
+
});
|
|
9425
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
9426
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
9427
|
+
deviceId: number().int(),
|
|
9428
|
+
hoursWalked: number().int().nonnegative(),
|
|
9429
|
+
hoursMissing: number().int().nonnegative(),
|
|
9430
|
+
ghostSegments: number().int().nonnegative(),
|
|
9431
|
+
ghostBytes: number().int().nonnegative(),
|
|
9432
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9433
|
+
orphanFiles: number().int().nonnegative()
|
|
9434
|
+
});
|
|
9435
|
+
/**
|
|
9436
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
9437
|
+
*
|
|
9438
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
9439
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
9440
|
+
* than in the absence of one.
|
|
9441
|
+
*/
|
|
9442
|
+
var LedgerWalkReportSchema = object({
|
|
9443
|
+
locationId: string(),
|
|
9444
|
+
applied: boolean(),
|
|
9445
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
9446
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
9447
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
9448
|
+
hoursClaimed: number().int().nonnegative(),
|
|
9449
|
+
hoursWalked: number().int().nonnegative(),
|
|
9450
|
+
hoursMissing: number().int().nonnegative(),
|
|
9451
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
9452
|
+
listings: number().int().nonnegative(),
|
|
9453
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
9454
|
+
ghostSegments: number().int().nonnegative(),
|
|
9455
|
+
ghostBytes: number().int().nonnegative(),
|
|
9456
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
9457
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9458
|
+
forgottenBytes: number().int().nonnegative(),
|
|
9459
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
9460
|
+
orphanFiles: number().int().nonnegative(),
|
|
9461
|
+
orphanSample: array(string()).readonly(),
|
|
9462
|
+
hoursSkipped: number().int().nonnegative(),
|
|
9463
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
9464
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
9465
|
+
bounded: boolean(),
|
|
9466
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
9467
|
+
});
|
|
9369
9468
|
/** How many rows a media pass would still act on against a given target — the
|
|
9370
9469
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
9371
9470
|
* never disagree. `null` = the count could not be taken. */
|
|
@@ -19981,13 +20080,15 @@ var ListGroupsPageSchema = object({
|
|
|
19981
20080
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19982
20081
|
nextCursor: string().nullable()
|
|
19983
20082
|
});
|
|
20083
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
20084
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19984
20085
|
var KeyEventQueryInput = object({
|
|
19985
20086
|
deviceId: number(),
|
|
19986
20087
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19987
20088
|
since: number(),
|
|
19988
20089
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19989
20090
|
until: number(),
|
|
19990
|
-
limit: number().int().min(1).max(
|
|
20091
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19991
20092
|
/** Drop tracks scoring below this importance. */
|
|
19992
20093
|
minImportance: number().min(0).max(1).optional(),
|
|
19993
20094
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -20009,6 +20110,32 @@ var KeyEventSchema = object({
|
|
|
20009
20110
|
...TrackFlagFields,
|
|
20010
20111
|
...TrackRetrainFields
|
|
20011
20112
|
});
|
|
20113
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
20114
|
+
var KeyEventBatchQueryInput = object({
|
|
20115
|
+
deviceIds: array(number()).min(1).max(200),
|
|
20116
|
+
since: number(),
|
|
20117
|
+
until: number(),
|
|
20118
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
20119
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
20120
|
+
* rows and change what the merged feed contains. */
|
|
20121
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
20122
|
+
minImportance: number().min(0).max(1).optional(),
|
|
20123
|
+
classFilter: string().optional()
|
|
20124
|
+
});
|
|
20125
|
+
/**
|
|
20126
|
+
* One camera's key events in a batch answer.
|
|
20127
|
+
*
|
|
20128
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
20129
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
20130
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
20131
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
20132
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
20133
|
+
* key, which only worked because there was one query per camera).
|
|
20134
|
+
*/
|
|
20135
|
+
var KeyEventsForDeviceSchema = object({
|
|
20136
|
+
deviceId: number(),
|
|
20137
|
+
events: array(KeyEventSchema).readonly()
|
|
20138
|
+
});
|
|
20012
20139
|
object({
|
|
20013
20140
|
trackId: string(),
|
|
20014
20141
|
className: string(),
|
|
@@ -20080,6 +20207,50 @@ var EventStoreFootprintSchema = object({
|
|
|
20080
20207
|
totalBytes: number().int(),
|
|
20081
20208
|
devices: array(EventStoreDeviceFootprintSchema).readonly()
|
|
20082
20209
|
});
|
|
20210
|
+
/** Event-media footprint for one {@link MediaFileKind}. */
|
|
20211
|
+
var EventMediaKindFootprintSchema = object({
|
|
20212
|
+
kind: MediaFileKindEnum,
|
|
20213
|
+
/** Media rows of this kind. */
|
|
20214
|
+
rows: number().int(),
|
|
20215
|
+
/** Bytes on disk held by those rows. */
|
|
20216
|
+
bytes: number().int()
|
|
20217
|
+
});
|
|
20218
|
+
/**
|
|
20219
|
+
* The media footprint broken down by KIND — the axis a deletion decision
|
|
20220
|
+
* actually turns on.
|
|
20221
|
+
*
|
|
20222
|
+
* A byte total says how much there is; it cannot say what is safe to remove.
|
|
20223
|
+
* The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
|
|
20224
|
+
* motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
|
|
20225
|
+
* `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
|
|
20226
|
+
* buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
|
|
20227
|
+
* nothing else, so sizing a deletion means summing per kind.
|
|
20228
|
+
*
|
|
20229
|
+
* ## Why `unaccounted*` exists
|
|
20230
|
+
*
|
|
20231
|
+
* `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
|
|
20232
|
+
* writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
|
|
20233
|
+
* from a SEPARATE unfiltered aggregate over the same rows, never from adding
|
|
20234
|
+
* `kinds` up. A row whose stored `kind` is not in the enum (written by a
|
|
20235
|
+
* retired code path, or by a version that knew a kind this one does not) would
|
|
20236
|
+
* otherwise vanish from the total silently, and an operator would delete
|
|
20237
|
+
* against a denominator smaller than the disk.
|
|
20238
|
+
*
|
|
20239
|
+
* `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
|
|
20240
|
+
* zero; a non-zero value is a real finding and must be shown, not rounded away.
|
|
20241
|
+
*/
|
|
20242
|
+
var EventMediaKindBreakdownSchema = object({
|
|
20243
|
+
/** Every media row in scope, from one unfiltered aggregate. */
|
|
20244
|
+
totalRows: number().int(),
|
|
20245
|
+
/** Every media byte in scope, from that same aggregate. */
|
|
20246
|
+
totalBytes: number().int(),
|
|
20247
|
+
/** Per-kind footprint, ordered by bytes descending. */
|
|
20248
|
+
kinds: array(EventMediaKindFootprintSchema).readonly(),
|
|
20249
|
+
/** `totalRows` minus the summed `kinds` rows — see the schema note. */
|
|
20250
|
+
unaccountedRows: number().int(),
|
|
20251
|
+
/** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
|
|
20252
|
+
unaccountedBytes: number().int()
|
|
20253
|
+
});
|
|
20083
20254
|
/** Per-kind counts returned by the event-prune / device-delete mutations. */
|
|
20084
20255
|
var EventPruneCountsSchema = object({
|
|
20085
20256
|
motion: number().int(),
|
|
@@ -20234,7 +20405,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20234
20405
|
until: number().optional(),
|
|
20235
20406
|
kinds: array(string()).optional(),
|
|
20236
20407
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
20237
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
20408
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
20238
20409
|
deviceId: number(),
|
|
20239
20410
|
since: number(),
|
|
20240
20411
|
until: number(),
|
|
@@ -20283,6 +20454,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20283
20454
|
}), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
|
|
20284
20455
|
kind: "query",
|
|
20285
20456
|
auth: "admin"
|
|
20457
|
+
}), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
|
|
20458
|
+
kind: "query",
|
|
20459
|
+
auth: "admin"
|
|
20286
20460
|
}), method(object({
|
|
20287
20461
|
olderThanMs: number(),
|
|
20288
20462
|
reason: OpsLogReasonSchema.optional()
|
|
@@ -22234,6 +22408,20 @@ method(object({
|
|
|
22234
22408
|
error: string().optional()
|
|
22235
22409
|
}), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
|
|
22236
22410
|
providerId: string(),
|
|
22411
|
+
/**
|
|
22412
|
+
* The location this config is an UNSAVED edit of, when there is one.
|
|
22413
|
+
*
|
|
22414
|
+
* `listLocations` replaces every declared secret with the redaction
|
|
22415
|
+
* sentinel, so the edit modal's form state holds the sentinel for any
|
|
22416
|
+
* credential the operator did not retype — and posting that here
|
|
22417
|
+
* without a way to resolve it makes the provider try to authenticate
|
|
22418
|
+
* as `__camstack_redacted__` and report the operator's own working
|
|
22419
|
+
* password as wrong. Given this id, the orchestrator restores each
|
|
22420
|
+
* sentinel from the stored config (same rule as `upsertLocation`)
|
|
22421
|
+
* before dispatching. Omitted by the "Add location" wizard, where
|
|
22422
|
+
* every value was typed just now and nothing is stored yet.
|
|
22423
|
+
*/
|
|
22424
|
+
locationId: string().optional(),
|
|
22237
22425
|
config: record(string(), unknown())
|
|
22238
22426
|
}), object({
|
|
22239
22427
|
ok: boolean(),
|
|
@@ -29584,6 +29772,9 @@ method(object({
|
|
|
29584
29772
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
29585
29773
|
kind: "query",
|
|
29586
29774
|
auth: "admin"
|
|
29775
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
29776
|
+
kind: "mutation",
|
|
29777
|
+
auth: "admin"
|
|
29587
29778
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
29588
29779
|
kind: "mutation",
|
|
29589
29780
|
auth: "admin"
|
|
@@ -29975,6 +30166,25 @@ var SceneMonitorStatusSchema = object({
|
|
|
29975
30166
|
monitors: array(SceneMonitorSchema),
|
|
29976
30167
|
lastFetchedAt: number()
|
|
29977
30168
|
});
|
|
30169
|
+
/**
|
|
30170
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
30171
|
+
*
|
|
30172
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
30173
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
30174
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
30175
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
30176
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
30177
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
30178
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
30179
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
30180
|
+
*
|
|
30181
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
30182
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
30183
|
+
*/
|
|
30184
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
30185
|
+
deviceId: number(),
|
|
30186
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
30187
|
+
});
|
|
29978
30188
|
var sceneMonitorCapability = {
|
|
29979
30189
|
name: "scene-monitor",
|
|
29980
30190
|
scope: "device",
|
|
@@ -29984,6 +30194,22 @@ var sceneMonitorCapability = {
|
|
|
29984
30194
|
deviceTypes: [DeviceType.Camera],
|
|
29985
30195
|
methods: {
|
|
29986
30196
|
listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
|
|
30197
|
+
/**
|
|
30198
|
+
* The same answer, for a SET of cameras, in one round trip.
|
|
30199
|
+
*
|
|
30200
|
+
* `/scenes` renders every camera and re-reads them on a 30s safety-net
|
|
30201
|
+
* poll behind the push slice. Fanned out client-side that was one query
|
|
30202
|
+
* per camera — 29 round trips through the browser, the hub and the
|
|
30203
|
+
* post-analysis runner every 30 seconds to read an in-memory map the
|
|
30204
|
+
* owner had already merged. The work is unchanged (`statusFor` per
|
|
30205
|
+
* device, all in-process at the owner); what collapses is the transport.
|
|
30206
|
+
*
|
|
30207
|
+
* A camera that cannot answer still gets a row, with `status: null` —
|
|
30208
|
+
* see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
|
|
30209
|
+
* ids: a caller that asked for twenty-nine and got twenty-seven cannot
|
|
30210
|
+
* tell which two are missing, or that any are.
|
|
30211
|
+
*/
|
|
30212
|
+
listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
|
|
29987
30213
|
createScene: method(object({
|
|
29988
30214
|
deviceId: number(),
|
|
29989
30215
|
label: string(),
|
|
@@ -31819,6 +32045,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
31819
32045
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
31820
32046
|
});
|
|
31821
32047
|
/**
|
|
32048
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
32049
|
+
*
|
|
32050
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
32051
|
+
* because `snapshot: null` was already spoken for:
|
|
32052
|
+
*
|
|
32053
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
32054
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
32055
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
32056
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
32057
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
32058
|
+
*
|
|
32059
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
32060
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
32061
|
+
* which is a definite claim about a camera nobody could read.
|
|
32062
|
+
*/
|
|
32063
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
32064
|
+
deviceId: number(),
|
|
32065
|
+
read: _enum(["read", "unreadable"]),
|
|
32066
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
32067
|
+
});
|
|
32068
|
+
/**
|
|
31822
32069
|
* Time-series resolution. The history methods return one bucket per
|
|
31823
32070
|
* step over the requested range. Smaller resolutions cost more
|
|
31824
32071
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -31875,6 +32122,20 @@ var zoneAnalyticsCapability = {
|
|
|
31875
32122
|
* (no inference result emitted since boot or since binding was
|
|
31876
32123
|
* activated). */
|
|
31877
32124
|
getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
|
|
32125
|
+
/**
|
|
32126
|
+
* The same snapshot, for a SET of cameras, in one round trip.
|
|
32127
|
+
*
|
|
32128
|
+
* The Events page's Stationary section polls this every 15s for every
|
|
32129
|
+
* selected camera. Fanned out client-side that is one query per camera to
|
|
32130
|
+
* read a `Map.get` at the owner — the answer costs nothing, the round trip
|
|
32131
|
+
* costs everything. Batched, N transports become one and the per-device
|
|
32132
|
+
* work is unchanged.
|
|
32133
|
+
*
|
|
32134
|
+
* Every requested deviceId gets a row, tagged `read` — see
|
|
32135
|
+
* {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
|
|
32136
|
+
* not answer for is `'unreadable'`, never an empty reading.
|
|
32137
|
+
*/
|
|
32138
|
+
getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
|
|
31878
32139
|
/** Time-series object count inside one zone. `className` optional —
|
|
31879
32140
|
* omit to count every class in the zone. */
|
|
31880
32141
|
getZoneHistory: method(object({
|
|
@@ -36272,6 +36533,12 @@ Object.freeze({
|
|
|
36272
36533
|
addonId: null,
|
|
36273
36534
|
access: "view"
|
|
36274
36535
|
},
|
|
36536
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": {
|
|
36537
|
+
capName: "pipeline-analytics",
|
|
36538
|
+
capScope: "device",
|
|
36539
|
+
addonId: null,
|
|
36540
|
+
access: "view"
|
|
36541
|
+
},
|
|
36275
36542
|
"pipelineAnalytics.getEventStoreFootprint": {
|
|
36276
36543
|
capName: "pipeline-analytics",
|
|
36277
36544
|
capScope: "device",
|
|
@@ -36290,6 +36557,12 @@ Object.freeze({
|
|
|
36290
36557
|
addonId: null,
|
|
36291
36558
|
access: "view"
|
|
36292
36559
|
},
|
|
36560
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
36561
|
+
capName: "pipeline-analytics",
|
|
36562
|
+
capScope: "device",
|
|
36563
|
+
addonId: null,
|
|
36564
|
+
access: "view"
|
|
36565
|
+
},
|
|
36293
36566
|
"pipelineAnalytics.getMotionEvents": {
|
|
36294
36567
|
capName: "pipeline-analytics",
|
|
36295
36568
|
capScope: "device",
|
|
@@ -37466,6 +37739,12 @@ Object.freeze({
|
|
|
37466
37739
|
addonId: null,
|
|
37467
37740
|
access: "view"
|
|
37468
37741
|
},
|
|
37742
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
37743
|
+
capName: "recording",
|
|
37744
|
+
capScope: "system",
|
|
37745
|
+
addonId: null,
|
|
37746
|
+
access: "create"
|
|
37747
|
+
},
|
|
37469
37748
|
"recording.refreshStorageLocationsForMigration": {
|
|
37470
37749
|
capName: "recording",
|
|
37471
37750
|
capScope: "system",
|
|
@@ -37592,6 +37871,12 @@ Object.freeze({
|
|
|
37592
37871
|
addonId: null,
|
|
37593
37872
|
access: "view"
|
|
37594
37873
|
},
|
|
37874
|
+
"sceneMonitor.listScenesBatch": {
|
|
37875
|
+
capName: "scene-monitor",
|
|
37876
|
+
capScope: "device",
|
|
37877
|
+
addonId: null,
|
|
37878
|
+
access: "view"
|
|
37879
|
+
},
|
|
37595
37880
|
"sceneMonitor.recheckNow": {
|
|
37596
37881
|
capName: "scene-monitor",
|
|
37597
37882
|
capScope: "device",
|
|
@@ -38948,6 +39233,12 @@ Object.freeze({
|
|
|
38948
39233
|
addonId: null,
|
|
38949
39234
|
access: "view"
|
|
38950
39235
|
},
|
|
39236
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
39237
|
+
capName: "zone-analytics",
|
|
39238
|
+
capScope: "device",
|
|
39239
|
+
addonId: null,
|
|
39240
|
+
access: "view"
|
|
39241
|
+
},
|
|
38951
39242
|
"zoneAnalytics.getUnzonedHistory": {
|
|
38952
39243
|
capName: "zone-analytics",
|
|
38953
39244
|
capScope: "device",
|
|
@@ -39937,6 +40228,11 @@ Object.freeze({
|
|
|
39937
40228
|
form: "single",
|
|
39938
40229
|
optional: false
|
|
39939
40230
|
}],
|
|
40231
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": [{
|
|
40232
|
+
name: "deviceId",
|
|
40233
|
+
form: "single",
|
|
40234
|
+
optional: true
|
|
40235
|
+
}],
|
|
39940
40236
|
"pipelineAnalytics.getGroup": [{
|
|
39941
40237
|
name: "deviceId",
|
|
39942
40238
|
form: "single",
|
|
@@ -39947,6 +40243,11 @@ Object.freeze({
|
|
|
39947
40243
|
form: "single",
|
|
39948
40244
|
optional: false
|
|
39949
40245
|
}],
|
|
40246
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
40247
|
+
name: "deviceIds",
|
|
40248
|
+
form: "array",
|
|
40249
|
+
optional: false
|
|
40250
|
+
}],
|
|
39950
40251
|
"pipelineAnalytics.getMotionEvents": [{
|
|
39951
40252
|
name: "deviceId",
|
|
39952
40253
|
form: "single",
|
|
@@ -40387,6 +40688,11 @@ Object.freeze({
|
|
|
40387
40688
|
form: "single",
|
|
40388
40689
|
optional: false
|
|
40389
40690
|
}],
|
|
40691
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
40692
|
+
name: "deviceId",
|
|
40693
|
+
form: "single",
|
|
40694
|
+
optional: true
|
|
40695
|
+
}],
|
|
40390
40696
|
"recording.relocateFootage": [{
|
|
40391
40697
|
name: "deviceId",
|
|
40392
40698
|
form: "single",
|
|
@@ -40452,6 +40758,11 @@ Object.freeze({
|
|
|
40452
40758
|
form: "single",
|
|
40453
40759
|
optional: false
|
|
40454
40760
|
}],
|
|
40761
|
+
"sceneMonitor.listScenesBatch": [{
|
|
40762
|
+
name: "deviceIds",
|
|
40763
|
+
form: "array",
|
|
40764
|
+
optional: false
|
|
40765
|
+
}],
|
|
40455
40766
|
"sceneMonitor.recheckNow": [{
|
|
40456
40767
|
name: "deviceId",
|
|
40457
40768
|
form: "single",
|
|
@@ -40713,6 +41024,11 @@ Object.freeze({
|
|
|
40713
41024
|
form: "single",
|
|
40714
41025
|
optional: false
|
|
40715
41026
|
}],
|
|
41027
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
41028
|
+
name: "deviceIds",
|
|
41029
|
+
form: "array",
|
|
41030
|
+
optional: false
|
|
41031
|
+
}],
|
|
40716
41032
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
40717
41033
|
name: "deviceId",
|
|
40718
41034
|
form: "single",
|
package/dist/addon.mjs
CHANGED
|
@@ -9002,6 +9002,20 @@ var RelocateJobSchema = object({
|
|
|
9002
9002
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
9003
9003
|
*/
|
|
9004
9004
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
9005
|
+
/**
|
|
9006
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
9007
|
+
*
|
|
9008
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
9009
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
9010
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
9011
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
9012
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
9013
|
+
* the same failure as one that quietly skips them (D295).
|
|
9014
|
+
*
|
|
9015
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
9016
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
9017
|
+
*/
|
|
9018
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
9005
9019
|
startedAt: number(),
|
|
9006
9020
|
finishedAt: number().nullable(),
|
|
9007
9021
|
error: string().nullable()
|
|
@@ -9365,6 +9379,91 @@ var RelocateResidueSchema = object({
|
|
|
9365
9379
|
segments: number().int().nonnegative(),
|
|
9366
9380
|
bytes: number().int().nonnegative()
|
|
9367
9381
|
}).nullable();
|
|
9382
|
+
/**
|
|
9383
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
9384
|
+
* (D319).
|
|
9385
|
+
*
|
|
9386
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
9387
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
9388
|
+
* destructive run before authorising it.
|
|
9389
|
+
*/
|
|
9390
|
+
var LedgerWalkInputSchema = object({
|
|
9391
|
+
locationId: string().min(1),
|
|
9392
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
9393
|
+
apply: boolean().optional(),
|
|
9394
|
+
/** Narrow to one camera. */
|
|
9395
|
+
deviceId: number().int().positive().optional(),
|
|
9396
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
9397
|
+
profiles: array(string().min(1)).optional()
|
|
9398
|
+
});
|
|
9399
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
9400
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
9401
|
+
"location-unknown",
|
|
9402
|
+
"source-writable",
|
|
9403
|
+
"no-ledger",
|
|
9404
|
+
"archive-unreadable",
|
|
9405
|
+
"anchor-absent",
|
|
9406
|
+
"anchor-unreadable",
|
|
9407
|
+
"anchor-moved"
|
|
9408
|
+
]);
|
|
9409
|
+
_enum([
|
|
9410
|
+
"live-tail",
|
|
9411
|
+
"listing-error",
|
|
9412
|
+
"path-mismatch",
|
|
9413
|
+
"durable-refused"
|
|
9414
|
+
]);
|
|
9415
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
9416
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
9417
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
9418
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
9419
|
+
"live-tail": number().int().nonnegative(),
|
|
9420
|
+
"listing-error": number().int().nonnegative(),
|
|
9421
|
+
"path-mismatch": number().int().nonnegative(),
|
|
9422
|
+
"durable-refused": number().int().nonnegative()
|
|
9423
|
+
});
|
|
9424
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
9425
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
9426
|
+
deviceId: number().int(),
|
|
9427
|
+
hoursWalked: number().int().nonnegative(),
|
|
9428
|
+
hoursMissing: number().int().nonnegative(),
|
|
9429
|
+
ghostSegments: number().int().nonnegative(),
|
|
9430
|
+
ghostBytes: number().int().nonnegative(),
|
|
9431
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9432
|
+
orphanFiles: number().int().nonnegative()
|
|
9433
|
+
});
|
|
9434
|
+
/**
|
|
9435
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
9436
|
+
*
|
|
9437
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
9438
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
9439
|
+
* than in the absence of one.
|
|
9440
|
+
*/
|
|
9441
|
+
var LedgerWalkReportSchema = object({
|
|
9442
|
+
locationId: string(),
|
|
9443
|
+
applied: boolean(),
|
|
9444
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
9445
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
9446
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
9447
|
+
hoursClaimed: number().int().nonnegative(),
|
|
9448
|
+
hoursWalked: number().int().nonnegative(),
|
|
9449
|
+
hoursMissing: number().int().nonnegative(),
|
|
9450
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
9451
|
+
listings: number().int().nonnegative(),
|
|
9452
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
9453
|
+
ghostSegments: number().int().nonnegative(),
|
|
9454
|
+
ghostBytes: number().int().nonnegative(),
|
|
9455
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
9456
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9457
|
+
forgottenBytes: number().int().nonnegative(),
|
|
9458
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
9459
|
+
orphanFiles: number().int().nonnegative(),
|
|
9460
|
+
orphanSample: array(string()).readonly(),
|
|
9461
|
+
hoursSkipped: number().int().nonnegative(),
|
|
9462
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
9463
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
9464
|
+
bounded: boolean(),
|
|
9465
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
9466
|
+
});
|
|
9368
9467
|
/** How many rows a media pass would still act on against a given target — the
|
|
9369
9468
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
9370
9469
|
* never disagree. `null` = the count could not be taken. */
|
|
@@ -19980,13 +20079,15 @@ var ListGroupsPageSchema = object({
|
|
|
19980
20079
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19981
20080
|
nextCursor: string().nullable()
|
|
19982
20081
|
});
|
|
20082
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
20083
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19983
20084
|
var KeyEventQueryInput = object({
|
|
19984
20085
|
deviceId: number(),
|
|
19985
20086
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19986
20087
|
since: number(),
|
|
19987
20088
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19988
20089
|
until: number(),
|
|
19989
|
-
limit: number().int().min(1).max(
|
|
20090
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19990
20091
|
/** Drop tracks scoring below this importance. */
|
|
19991
20092
|
minImportance: number().min(0).max(1).optional(),
|
|
19992
20093
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -20008,6 +20109,32 @@ var KeyEventSchema = object({
|
|
|
20008
20109
|
...TrackFlagFields,
|
|
20009
20110
|
...TrackRetrainFields
|
|
20010
20111
|
});
|
|
20112
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
20113
|
+
var KeyEventBatchQueryInput = object({
|
|
20114
|
+
deviceIds: array(number()).min(1).max(200),
|
|
20115
|
+
since: number(),
|
|
20116
|
+
until: number(),
|
|
20117
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
20118
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
20119
|
+
* rows and change what the merged feed contains. */
|
|
20120
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
20121
|
+
minImportance: number().min(0).max(1).optional(),
|
|
20122
|
+
classFilter: string().optional()
|
|
20123
|
+
});
|
|
20124
|
+
/**
|
|
20125
|
+
* One camera's key events in a batch answer.
|
|
20126
|
+
*
|
|
20127
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
20128
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
20129
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
20130
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
20131
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
20132
|
+
* key, which only worked because there was one query per camera).
|
|
20133
|
+
*/
|
|
20134
|
+
var KeyEventsForDeviceSchema = object({
|
|
20135
|
+
deviceId: number(),
|
|
20136
|
+
events: array(KeyEventSchema).readonly()
|
|
20137
|
+
});
|
|
20011
20138
|
object({
|
|
20012
20139
|
trackId: string(),
|
|
20013
20140
|
className: string(),
|
|
@@ -20079,6 +20206,50 @@ var EventStoreFootprintSchema = object({
|
|
|
20079
20206
|
totalBytes: number().int(),
|
|
20080
20207
|
devices: array(EventStoreDeviceFootprintSchema).readonly()
|
|
20081
20208
|
});
|
|
20209
|
+
/** Event-media footprint for one {@link MediaFileKind}. */
|
|
20210
|
+
var EventMediaKindFootprintSchema = object({
|
|
20211
|
+
kind: MediaFileKindEnum,
|
|
20212
|
+
/** Media rows of this kind. */
|
|
20213
|
+
rows: number().int(),
|
|
20214
|
+
/** Bytes on disk held by those rows. */
|
|
20215
|
+
bytes: number().int()
|
|
20216
|
+
});
|
|
20217
|
+
/**
|
|
20218
|
+
* The media footprint broken down by KIND — the axis a deletion decision
|
|
20219
|
+
* actually turns on.
|
|
20220
|
+
*
|
|
20221
|
+
* A byte total says how much there is; it cannot say what is safe to remove.
|
|
20222
|
+
* The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
|
|
20223
|
+
* motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
|
|
20224
|
+
* `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
|
|
20225
|
+
* buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
|
|
20226
|
+
* nothing else, so sizing a deletion means summing per kind.
|
|
20227
|
+
*
|
|
20228
|
+
* ## Why `unaccounted*` exists
|
|
20229
|
+
*
|
|
20230
|
+
* `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
|
|
20231
|
+
* writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
|
|
20232
|
+
* from a SEPARATE unfiltered aggregate over the same rows, never from adding
|
|
20233
|
+
* `kinds` up. A row whose stored `kind` is not in the enum (written by a
|
|
20234
|
+
* retired code path, or by a version that knew a kind this one does not) would
|
|
20235
|
+
* otherwise vanish from the total silently, and an operator would delete
|
|
20236
|
+
* against a denominator smaller than the disk.
|
|
20237
|
+
*
|
|
20238
|
+
* `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
|
|
20239
|
+
* zero; a non-zero value is a real finding and must be shown, not rounded away.
|
|
20240
|
+
*/
|
|
20241
|
+
var EventMediaKindBreakdownSchema = object({
|
|
20242
|
+
/** Every media row in scope, from one unfiltered aggregate. */
|
|
20243
|
+
totalRows: number().int(),
|
|
20244
|
+
/** Every media byte in scope, from that same aggregate. */
|
|
20245
|
+
totalBytes: number().int(),
|
|
20246
|
+
/** Per-kind footprint, ordered by bytes descending. */
|
|
20247
|
+
kinds: array(EventMediaKindFootprintSchema).readonly(),
|
|
20248
|
+
/** `totalRows` minus the summed `kinds` rows — see the schema note. */
|
|
20249
|
+
unaccountedRows: number().int(),
|
|
20250
|
+
/** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
|
|
20251
|
+
unaccountedBytes: number().int()
|
|
20252
|
+
});
|
|
20082
20253
|
/** Per-kind counts returned by the event-prune / device-delete mutations. */
|
|
20083
20254
|
var EventPruneCountsSchema = object({
|
|
20084
20255
|
motion: number().int(),
|
|
@@ -20233,7 +20404,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20233
20404
|
until: number().optional(),
|
|
20234
20405
|
kinds: array(string()).optional(),
|
|
20235
20406
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
20236
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
20407
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
20237
20408
|
deviceId: number(),
|
|
20238
20409
|
since: number(),
|
|
20239
20410
|
until: number(),
|
|
@@ -20282,6 +20453,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20282
20453
|
}), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
|
|
20283
20454
|
kind: "query",
|
|
20284
20455
|
auth: "admin"
|
|
20456
|
+
}), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
|
|
20457
|
+
kind: "query",
|
|
20458
|
+
auth: "admin"
|
|
20285
20459
|
}), method(object({
|
|
20286
20460
|
olderThanMs: number(),
|
|
20287
20461
|
reason: OpsLogReasonSchema.optional()
|
|
@@ -22233,6 +22407,20 @@ method(object({
|
|
|
22233
22407
|
error: string().optional()
|
|
22234
22408
|
}), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
|
|
22235
22409
|
providerId: string(),
|
|
22410
|
+
/**
|
|
22411
|
+
* The location this config is an UNSAVED edit of, when there is one.
|
|
22412
|
+
*
|
|
22413
|
+
* `listLocations` replaces every declared secret with the redaction
|
|
22414
|
+
* sentinel, so the edit modal's form state holds the sentinel for any
|
|
22415
|
+
* credential the operator did not retype — and posting that here
|
|
22416
|
+
* without a way to resolve it makes the provider try to authenticate
|
|
22417
|
+
* as `__camstack_redacted__` and report the operator's own working
|
|
22418
|
+
* password as wrong. Given this id, the orchestrator restores each
|
|
22419
|
+
* sentinel from the stored config (same rule as `upsertLocation`)
|
|
22420
|
+
* before dispatching. Omitted by the "Add location" wizard, where
|
|
22421
|
+
* every value was typed just now and nothing is stored yet.
|
|
22422
|
+
*/
|
|
22423
|
+
locationId: string().optional(),
|
|
22236
22424
|
config: record(string(), unknown())
|
|
22237
22425
|
}), object({
|
|
22238
22426
|
ok: boolean(),
|
|
@@ -29583,6 +29771,9 @@ method(object({
|
|
|
29583
29771
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
29584
29772
|
kind: "query",
|
|
29585
29773
|
auth: "admin"
|
|
29774
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
29775
|
+
kind: "mutation",
|
|
29776
|
+
auth: "admin"
|
|
29586
29777
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
29587
29778
|
kind: "mutation",
|
|
29588
29779
|
auth: "admin"
|
|
@@ -29974,6 +30165,25 @@ var SceneMonitorStatusSchema = object({
|
|
|
29974
30165
|
monitors: array(SceneMonitorSchema),
|
|
29975
30166
|
lastFetchedAt: number()
|
|
29976
30167
|
});
|
|
30168
|
+
/**
|
|
30169
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
30170
|
+
*
|
|
30171
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
30172
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
30173
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
30174
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
30175
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
30176
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
30177
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
30178
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
30179
|
+
*
|
|
30180
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
30181
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
30182
|
+
*/
|
|
30183
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
30184
|
+
deviceId: number(),
|
|
30185
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
30186
|
+
});
|
|
29977
30187
|
var sceneMonitorCapability = {
|
|
29978
30188
|
name: "scene-monitor",
|
|
29979
30189
|
scope: "device",
|
|
@@ -29983,6 +30193,22 @@ var sceneMonitorCapability = {
|
|
|
29983
30193
|
deviceTypes: [DeviceType.Camera],
|
|
29984
30194
|
methods: {
|
|
29985
30195
|
listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
|
|
30196
|
+
/**
|
|
30197
|
+
* The same answer, for a SET of cameras, in one round trip.
|
|
30198
|
+
*
|
|
30199
|
+
* `/scenes` renders every camera and re-reads them on a 30s safety-net
|
|
30200
|
+
* poll behind the push slice. Fanned out client-side that was one query
|
|
30201
|
+
* per camera — 29 round trips through the browser, the hub and the
|
|
30202
|
+
* post-analysis runner every 30 seconds to read an in-memory map the
|
|
30203
|
+
* owner had already merged. The work is unchanged (`statusFor` per
|
|
30204
|
+
* device, all in-process at the owner); what collapses is the transport.
|
|
30205
|
+
*
|
|
30206
|
+
* A camera that cannot answer still gets a row, with `status: null` —
|
|
30207
|
+
* see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
|
|
30208
|
+
* ids: a caller that asked for twenty-nine and got twenty-seven cannot
|
|
30209
|
+
* tell which two are missing, or that any are.
|
|
30210
|
+
*/
|
|
30211
|
+
listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
|
|
29986
30212
|
createScene: method(object({
|
|
29987
30213
|
deviceId: number(),
|
|
29988
30214
|
label: string(),
|
|
@@ -31818,6 +32044,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
31818
32044
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
31819
32045
|
});
|
|
31820
32046
|
/**
|
|
32047
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
32048
|
+
*
|
|
32049
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
32050
|
+
* because `snapshot: null` was already spoken for:
|
|
32051
|
+
*
|
|
32052
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
32053
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
32054
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
32055
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
32056
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
32057
|
+
*
|
|
32058
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
32059
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
32060
|
+
* which is a definite claim about a camera nobody could read.
|
|
32061
|
+
*/
|
|
32062
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
32063
|
+
deviceId: number(),
|
|
32064
|
+
read: _enum(["read", "unreadable"]),
|
|
32065
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
32066
|
+
});
|
|
32067
|
+
/**
|
|
31821
32068
|
* Time-series resolution. The history methods return one bucket per
|
|
31822
32069
|
* step over the requested range. Smaller resolutions cost more
|
|
31823
32070
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -31874,6 +32121,20 @@ var zoneAnalyticsCapability = {
|
|
|
31874
32121
|
* (no inference result emitted since boot or since binding was
|
|
31875
32122
|
* activated). */
|
|
31876
32123
|
getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
|
|
32124
|
+
/**
|
|
32125
|
+
* The same snapshot, for a SET of cameras, in one round trip.
|
|
32126
|
+
*
|
|
32127
|
+
* The Events page's Stationary section polls this every 15s for every
|
|
32128
|
+
* selected camera. Fanned out client-side that is one query per camera to
|
|
32129
|
+
* read a `Map.get` at the owner — the answer costs nothing, the round trip
|
|
32130
|
+
* costs everything. Batched, N transports become one and the per-device
|
|
32131
|
+
* work is unchanged.
|
|
32132
|
+
*
|
|
32133
|
+
* Every requested deviceId gets a row, tagged `read` — see
|
|
32134
|
+
* {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
|
|
32135
|
+
* not answer for is `'unreadable'`, never an empty reading.
|
|
32136
|
+
*/
|
|
32137
|
+
getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
|
|
31877
32138
|
/** Time-series object count inside one zone. `className` optional —
|
|
31878
32139
|
* omit to count every class in the zone. */
|
|
31879
32140
|
getZoneHistory: method(object({
|
|
@@ -36271,6 +36532,12 @@ Object.freeze({
|
|
|
36271
36532
|
addonId: null,
|
|
36272
36533
|
access: "view"
|
|
36273
36534
|
},
|
|
36535
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": {
|
|
36536
|
+
capName: "pipeline-analytics",
|
|
36537
|
+
capScope: "device",
|
|
36538
|
+
addonId: null,
|
|
36539
|
+
access: "view"
|
|
36540
|
+
},
|
|
36274
36541
|
"pipelineAnalytics.getEventStoreFootprint": {
|
|
36275
36542
|
capName: "pipeline-analytics",
|
|
36276
36543
|
capScope: "device",
|
|
@@ -36289,6 +36556,12 @@ Object.freeze({
|
|
|
36289
36556
|
addonId: null,
|
|
36290
36557
|
access: "view"
|
|
36291
36558
|
},
|
|
36559
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
36560
|
+
capName: "pipeline-analytics",
|
|
36561
|
+
capScope: "device",
|
|
36562
|
+
addonId: null,
|
|
36563
|
+
access: "view"
|
|
36564
|
+
},
|
|
36292
36565
|
"pipelineAnalytics.getMotionEvents": {
|
|
36293
36566
|
capName: "pipeline-analytics",
|
|
36294
36567
|
capScope: "device",
|
|
@@ -37465,6 +37738,12 @@ Object.freeze({
|
|
|
37465
37738
|
addonId: null,
|
|
37466
37739
|
access: "view"
|
|
37467
37740
|
},
|
|
37741
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
37742
|
+
capName: "recording",
|
|
37743
|
+
capScope: "system",
|
|
37744
|
+
addonId: null,
|
|
37745
|
+
access: "create"
|
|
37746
|
+
},
|
|
37468
37747
|
"recording.refreshStorageLocationsForMigration": {
|
|
37469
37748
|
capName: "recording",
|
|
37470
37749
|
capScope: "system",
|
|
@@ -37591,6 +37870,12 @@ Object.freeze({
|
|
|
37591
37870
|
addonId: null,
|
|
37592
37871
|
access: "view"
|
|
37593
37872
|
},
|
|
37873
|
+
"sceneMonitor.listScenesBatch": {
|
|
37874
|
+
capName: "scene-monitor",
|
|
37875
|
+
capScope: "device",
|
|
37876
|
+
addonId: null,
|
|
37877
|
+
access: "view"
|
|
37878
|
+
},
|
|
37594
37879
|
"sceneMonitor.recheckNow": {
|
|
37595
37880
|
capName: "scene-monitor",
|
|
37596
37881
|
capScope: "device",
|
|
@@ -38947,6 +39232,12 @@ Object.freeze({
|
|
|
38947
39232
|
addonId: null,
|
|
38948
39233
|
access: "view"
|
|
38949
39234
|
},
|
|
39235
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
39236
|
+
capName: "zone-analytics",
|
|
39237
|
+
capScope: "device",
|
|
39238
|
+
addonId: null,
|
|
39239
|
+
access: "view"
|
|
39240
|
+
},
|
|
38950
39241
|
"zoneAnalytics.getUnzonedHistory": {
|
|
38951
39242
|
capName: "zone-analytics",
|
|
38952
39243
|
capScope: "device",
|
|
@@ -39936,6 +40227,11 @@ Object.freeze({
|
|
|
39936
40227
|
form: "single",
|
|
39937
40228
|
optional: false
|
|
39938
40229
|
}],
|
|
40230
|
+
"pipelineAnalytics.getEventMediaFootprintByKind": [{
|
|
40231
|
+
name: "deviceId",
|
|
40232
|
+
form: "single",
|
|
40233
|
+
optional: true
|
|
40234
|
+
}],
|
|
39939
40235
|
"pipelineAnalytics.getGroup": [{
|
|
39940
40236
|
name: "deviceId",
|
|
39941
40237
|
form: "single",
|
|
@@ -39946,6 +40242,11 @@ Object.freeze({
|
|
|
39946
40242
|
form: "single",
|
|
39947
40243
|
optional: false
|
|
39948
40244
|
}],
|
|
40245
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
40246
|
+
name: "deviceIds",
|
|
40247
|
+
form: "array",
|
|
40248
|
+
optional: false
|
|
40249
|
+
}],
|
|
39949
40250
|
"pipelineAnalytics.getMotionEvents": [{
|
|
39950
40251
|
name: "deviceId",
|
|
39951
40252
|
form: "single",
|
|
@@ -40386,6 +40687,11 @@ Object.freeze({
|
|
|
40386
40687
|
form: "single",
|
|
40387
40688
|
optional: false
|
|
40388
40689
|
}],
|
|
40690
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
40691
|
+
name: "deviceId",
|
|
40692
|
+
form: "single",
|
|
40693
|
+
optional: true
|
|
40694
|
+
}],
|
|
40389
40695
|
"recording.relocateFootage": [{
|
|
40390
40696
|
name: "deviceId",
|
|
40391
40697
|
form: "single",
|
|
@@ -40451,6 +40757,11 @@ Object.freeze({
|
|
|
40451
40757
|
form: "single",
|
|
40452
40758
|
optional: false
|
|
40453
40759
|
}],
|
|
40760
|
+
"sceneMonitor.listScenesBatch": [{
|
|
40761
|
+
name: "deviceIds",
|
|
40762
|
+
form: "array",
|
|
40763
|
+
optional: false
|
|
40764
|
+
}],
|
|
40454
40765
|
"sceneMonitor.recheckNow": [{
|
|
40455
40766
|
name: "deviceId",
|
|
40456
40767
|
form: "single",
|
|
@@ -40712,6 +41023,11 @@ Object.freeze({
|
|
|
40712
41023
|
form: "single",
|
|
40713
41024
|
optional: false
|
|
40714
41025
|
}],
|
|
41026
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
41027
|
+
name: "deviceIds",
|
|
41028
|
+
form: "array",
|
|
41029
|
+
optional: false
|
|
41030
|
+
}],
|
|
40715
41031
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
40716
41032
|
name: "deviceId",
|
|
40717
41033
|
form: "single",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-rademacher",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.48",
|
|
4
4
|
"description": "Rademacher HomePilot device-provider addon for CamStack — wraps the @apocaliss92/noderademacher local-hub client (roller shutters over the cover cap)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|