@camstack/addon-pipeline-orchestrator 1.2.128 → 1.2.130
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/_stub.js +2 -2
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-D6mD53Q1.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_orchestrator_widgets-DHT4wgFE.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DooFAyw5.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C-vHvRY0.mjs +26 -0
- package/dist/{hostInit-CAeXD4PM.mjs → hostInit-C957H1iP.mjs} +3 -3
- package/dist/index.js +626 -41
- package/dist/index.mjs +626 -41
- package/dist/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-ChOONdP8.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_pipeline_orchestrator_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CKZAmz0G.mjs +0 -26
package/dist/index.mjs
CHANGED
|
@@ -8866,6 +8866,20 @@ var RelocateJobSchema = object({
|
|
|
8866
8866
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8867
8867
|
*/
|
|
8868
8868
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8869
|
+
/**
|
|
8870
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8871
|
+
*
|
|
8872
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8873
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8874
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8875
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8876
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8877
|
+
* the same failure as one that quietly skips them (D295).
|
|
8878
|
+
*
|
|
8879
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8880
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8881
|
+
*/
|
|
8882
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8869
8883
|
startedAt: number(),
|
|
8870
8884
|
finishedAt: number().nullable(),
|
|
8871
8885
|
error: string().nullable()
|
|
@@ -8930,6 +8944,13 @@ var MediaRelocateModeSchema = _enum([
|
|
|
8930
8944
|
]);
|
|
8931
8945
|
var RelocateMediaInputSchema = object({
|
|
8932
8946
|
toLocationId: string(),
|
|
8947
|
+
/**
|
|
8948
|
+
* Restrict the pass to rows currently on this location. Omitted / `'*'` =
|
|
8949
|
+
* every row that is not already on `toLocationId` (the historical
|
|
8950
|
+
* behaviour). A named source is what a from→to migration needs: without it
|
|
8951
|
+
* "move events off disk 2" also emptied disk 1.
|
|
8952
|
+
*/
|
|
8953
|
+
fromLocationId: string().optional(),
|
|
8933
8954
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
8934
8955
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
8935
8956
|
mode: MediaRelocateModeSchema.optional()
|
|
@@ -8997,6 +9018,19 @@ var StorageMigrationDestinationsSchema = object({
|
|
|
8997
9018
|
galleryMedia: string().min(1).optional()
|
|
8998
9019
|
}).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
|
|
8999
9020
|
/**
|
|
9021
|
+
* Optional named source per class. Omitted = the class's current default
|
|
9022
|
+
* (the historical behaviour). A named source that is NOT the default is a
|
|
9023
|
+
* drain of that disk: bytes move, the default stays, and the source is
|
|
9024
|
+
* disabled when the move finishes.
|
|
9025
|
+
*/
|
|
9026
|
+
var StorageMigrationSourcesSchema = object({
|
|
9027
|
+
recordings: string().min(1).optional(),
|
|
9028
|
+
recordingsLow: string().min(1).optional(),
|
|
9029
|
+
eventMedia: string().min(1).optional(),
|
|
9030
|
+
backups: string().min(1).optional(),
|
|
9031
|
+
galleryMedia: string().min(1).optional()
|
|
9032
|
+
}).optional();
|
|
9033
|
+
/**
|
|
9000
9034
|
* How a migration sequences the cutover against the byte move.
|
|
9001
9035
|
*
|
|
9002
9036
|
* - `blocking` — the historical order: pause, move every byte, repoint,
|
|
@@ -9018,6 +9052,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
|
|
|
9018
9052
|
/** Shared input for planning and starting an orchestrated storage migration. */
|
|
9019
9053
|
var StorageMigrationInputSchema = object({
|
|
9020
9054
|
destinations: StorageMigrationDestinationsSchema,
|
|
9055
|
+
/** Omitted = each class's current default. */
|
|
9056
|
+
sources: StorageMigrationSourcesSchema,
|
|
9021
9057
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
9022
9058
|
/** Omitted = `blocking`, which stays the default. */
|
|
9023
9059
|
mode: StorageMigrationModeSchema.optional()
|
|
@@ -9097,6 +9133,13 @@ var StorageMigrationMoveSchema = object({
|
|
|
9097
9133
|
storageClass: StorageMigrationClassSchema,
|
|
9098
9134
|
fromLocationId: string(),
|
|
9099
9135
|
toLocationId: string(),
|
|
9136
|
+
/**
|
|
9137
|
+
* True when `from` was NOT the class default at plan time. The move still
|
|
9138
|
+
* copies bytes, but the default is left alone and the source is disabled
|
|
9139
|
+
* once the copy verifies. Absent on jobs planned before this field existed
|
|
9140
|
+
* — those jobs always repointed, which is `false`.
|
|
9141
|
+
*/
|
|
9142
|
+
freezeSource: boolean().optional(),
|
|
9100
9143
|
moverJobId: string().nullable(),
|
|
9101
9144
|
state: RelocateJobStateSchema.nullable(),
|
|
9102
9145
|
error: string().nullable(),
|
|
@@ -9110,6 +9153,7 @@ var StorageMigrationJobSchema = object({
|
|
|
9110
9153
|
* can tell a seconds-long cutover from a thirty-hour one. */
|
|
9111
9154
|
mode: StorageMigrationModeSchema,
|
|
9112
9155
|
destinations: StorageMigrationDestinationsSchema,
|
|
9156
|
+
sources: StorageMigrationSourcesSchema,
|
|
9113
9157
|
throttleMbps: number(),
|
|
9114
9158
|
moves: array(StorageMigrationMoveSchema),
|
|
9115
9159
|
pauseLeaseId: string().nullable(),
|
|
@@ -9135,6 +9179,7 @@ var StorageMigrationFindingSchema = object({
|
|
|
9135
9179
|
});
|
|
9136
9180
|
var StorageMigrationPlanSchema = object({
|
|
9137
9181
|
destinations: StorageMigrationDestinationsSchema,
|
|
9182
|
+
sources: StorageMigrationSourcesSchema,
|
|
9138
9183
|
/** The mode this plan was built for. A plan is only valid for its mode: the
|
|
9139
9184
|
* `eventMedia` seal gate and the single-cardinality refusal both depend on
|
|
9140
9185
|
* it. */
|
|
@@ -9142,7 +9187,8 @@ var StorageMigrationPlanSchema = object({
|
|
|
9142
9187
|
moves: array(object({
|
|
9143
9188
|
storageClass: StorageMigrationClassSchema,
|
|
9144
9189
|
fromLocationId: string(),
|
|
9145
|
-
toLocationId: string()
|
|
9190
|
+
toLocationId: string(),
|
|
9191
|
+
freezeSource: boolean().optional()
|
|
9146
9192
|
})),
|
|
9147
9193
|
findings: array(StorageMigrationFindingSchema)
|
|
9148
9194
|
});
|
|
@@ -9229,16 +9275,142 @@ var RelocateResidueSchema = object({
|
|
|
9229
9275
|
segments: number().int().nonnegative(),
|
|
9230
9276
|
bytes: number().int().nonnegative()
|
|
9231
9277
|
}).nullable();
|
|
9278
|
+
/**
|
|
9279
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
9280
|
+
* (D319).
|
|
9281
|
+
*
|
|
9282
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
9283
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
9284
|
+
* destructive run before authorising it.
|
|
9285
|
+
*/
|
|
9286
|
+
var LedgerWalkInputSchema = object({
|
|
9287
|
+
locationId: string().min(1),
|
|
9288
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
9289
|
+
apply: boolean().optional(),
|
|
9290
|
+
/** Narrow to one camera. */
|
|
9291
|
+
deviceId: number().int().positive().optional(),
|
|
9292
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
9293
|
+
profiles: array(string().min(1)).optional()
|
|
9294
|
+
});
|
|
9295
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
9296
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
9297
|
+
"location-unknown",
|
|
9298
|
+
"source-writable",
|
|
9299
|
+
"no-ledger",
|
|
9300
|
+
"archive-unreadable",
|
|
9301
|
+
"anchor-absent",
|
|
9302
|
+
"anchor-unreadable",
|
|
9303
|
+
"anchor-moved"
|
|
9304
|
+
]);
|
|
9305
|
+
_enum([
|
|
9306
|
+
"live-tail",
|
|
9307
|
+
"listing-error",
|
|
9308
|
+
"path-mismatch",
|
|
9309
|
+
"durable-refused"
|
|
9310
|
+
]);
|
|
9311
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
9312
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
9313
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
9314
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
9315
|
+
"live-tail": number().int().nonnegative(),
|
|
9316
|
+
"listing-error": number().int().nonnegative(),
|
|
9317
|
+
"path-mismatch": number().int().nonnegative(),
|
|
9318
|
+
"durable-refused": number().int().nonnegative()
|
|
9319
|
+
});
|
|
9320
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
9321
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
9322
|
+
deviceId: number().int(),
|
|
9323
|
+
hoursWalked: number().int().nonnegative(),
|
|
9324
|
+
hoursMissing: number().int().nonnegative(),
|
|
9325
|
+
ghostSegments: number().int().nonnegative(),
|
|
9326
|
+
ghostBytes: number().int().nonnegative(),
|
|
9327
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9328
|
+
orphanFiles: number().int().nonnegative()
|
|
9329
|
+
});
|
|
9330
|
+
/**
|
|
9331
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
9332
|
+
*
|
|
9333
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
9334
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
9335
|
+
* than in the absence of one.
|
|
9336
|
+
*/
|
|
9337
|
+
var LedgerWalkReportSchema = object({
|
|
9338
|
+
locationId: string(),
|
|
9339
|
+
applied: boolean(),
|
|
9340
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
9341
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
9342
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
9343
|
+
hoursClaimed: number().int().nonnegative(),
|
|
9344
|
+
hoursWalked: number().int().nonnegative(),
|
|
9345
|
+
hoursMissing: number().int().nonnegative(),
|
|
9346
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
9347
|
+
listings: number().int().nonnegative(),
|
|
9348
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
9349
|
+
ghostSegments: number().int().nonnegative(),
|
|
9350
|
+
ghostBytes: number().int().nonnegative(),
|
|
9351
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
9352
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9353
|
+
forgottenBytes: number().int().nonnegative(),
|
|
9354
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
9355
|
+
orphanFiles: number().int().nonnegative(),
|
|
9356
|
+
orphanSample: array(string()).readonly(),
|
|
9357
|
+
hoursSkipped: number().int().nonnegative(),
|
|
9358
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
9359
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
9360
|
+
bounded: boolean(),
|
|
9361
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
9362
|
+
});
|
|
9232
9363
|
/** How many rows a media pass would still act on against a given target — the
|
|
9233
9364
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
9234
9365
|
* never disagree. `null` = the count could not be taken. */
|
|
9235
9366
|
var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
|
|
9236
9367
|
var RelocatableMediaCountInputSchema = object({
|
|
9237
9368
|
toLocationId: string().min(1),
|
|
9369
|
+
/** Restrict the count to one source; omitted / `'*'` = every non-target row. */
|
|
9370
|
+
fromLocationId: string().optional(),
|
|
9238
9371
|
/** Omitted = `move`. */
|
|
9239
9372
|
mode: MediaRelocateModeSchema.optional()
|
|
9240
9373
|
});
|
|
9241
9374
|
/**
|
|
9375
|
+
* Operator cleanup of leftover analytics rows, optional debug media, and
|
|
9376
|
+
* ghost ledger entries on frozen footage locations.
|
|
9377
|
+
*
|
|
9378
|
+
* A pass is tens of minutes on a standing backlog. The hub method RETURNS
|
|
9379
|
+
* `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
|
|
9380
|
+
* is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
|
|
9381
|
+
* with no operator-visible status.
|
|
9382
|
+
*/
|
|
9383
|
+
var StorageCleanupPhaseSchema = _enum([
|
|
9384
|
+
"orphans",
|
|
9385
|
+
"debug-media",
|
|
9386
|
+
"ghost-ledger",
|
|
9387
|
+
"done",
|
|
9388
|
+
"failed",
|
|
9389
|
+
"cancelled"
|
|
9390
|
+
]);
|
|
9391
|
+
var StorageCleanupInputSchema = object({
|
|
9392
|
+
/** Also walk motion stills / track filmstrips. Off by default. */
|
|
9393
|
+
includeDebugMedia: boolean().optional() });
|
|
9394
|
+
var StorageCleanupJobSchema = object({
|
|
9395
|
+
jobId: string(),
|
|
9396
|
+
phase: StorageCleanupPhaseSchema,
|
|
9397
|
+
includeDebugMedia: boolean(),
|
|
9398
|
+
orphansReclaimed: number().int().nonnegative(),
|
|
9399
|
+
orphanBytesReclaimed: number().int().nonnegative(),
|
|
9400
|
+
debugMediaReclaimed: number().int().nonnegative(),
|
|
9401
|
+
debugMediaBytesReclaimed: number().int().nonnegative(),
|
|
9402
|
+
ghostsForgotten: number().int().nonnegative(),
|
|
9403
|
+
ghostBytesForgotten: number().int().nonnegative(),
|
|
9404
|
+
/** Short operator-facing line: current collection, pass, or location. */
|
|
9405
|
+
detail: string().nullable(),
|
|
9406
|
+
cancelRequested: boolean(),
|
|
9407
|
+
startedAt: number(),
|
|
9408
|
+
updatedAt: number(),
|
|
9409
|
+
finishedAt: number().nullable(),
|
|
9410
|
+
error: string().nullable()
|
|
9411
|
+
});
|
|
9412
|
+
var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
|
|
9413
|
+
/**
|
|
9242
9414
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
9243
9415
|
* storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
|
|
9244
9416
|
* so the persisted record schema and the consumer-facing cap can both consume it
|
|
@@ -9266,11 +9438,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
|
9266
9438
|
* The default location for a type uses `id === <type>:default` by
|
|
9267
9439
|
* convention (the bare type ref like `'backups'` resolves to it).
|
|
9268
9440
|
*
|
|
9269
|
-
* `isSystem
|
|
9270
|
-
*
|
|
9271
|
-
*
|
|
9272
|
-
*
|
|
9273
|
-
* deleting it is rejected at the cap level.
|
|
9441
|
+
* `isSystem` is a legacy persisted flag. Seed still creates the initial
|
|
9442
|
+
* `<type>:default` locations; the flag is no longer a lock, a badge, or a
|
|
9443
|
+
* prune selector. New writes leave it false. Deletion is gated on uniqueness
|
|
9444
|
+
* / last-enabled, not on this bit.
|
|
9274
9445
|
*/
|
|
9275
9446
|
var StorageLocationSchema = object({
|
|
9276
9447
|
id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
|
|
@@ -19805,13 +19976,15 @@ var ListGroupsPageSchema = object({
|
|
|
19805
19976
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19806
19977
|
nextCursor: string().nullable()
|
|
19807
19978
|
});
|
|
19979
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
19980
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19808
19981
|
var KeyEventQueryInput = object({
|
|
19809
19982
|
deviceId: number(),
|
|
19810
19983
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19811
19984
|
since: number(),
|
|
19812
19985
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19813
19986
|
until: number(),
|
|
19814
|
-
limit: number().int().min(1).max(
|
|
19987
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19815
19988
|
/** Drop tracks scoring below this importance. */
|
|
19816
19989
|
minImportance: number().min(0).max(1).optional(),
|
|
19817
19990
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19833,6 +20006,32 @@ var KeyEventSchema = object({
|
|
|
19833
20006
|
...TrackFlagFields,
|
|
19834
20007
|
...TrackRetrainFields
|
|
19835
20008
|
});
|
|
20009
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
20010
|
+
var KeyEventBatchQueryInput = object({
|
|
20011
|
+
deviceIds: array(number()).min(1).max(200),
|
|
20012
|
+
since: number(),
|
|
20013
|
+
until: number(),
|
|
20014
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
20015
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
20016
|
+
* rows and change what the merged feed contains. */
|
|
20017
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
20018
|
+
minImportance: number().min(0).max(1).optional(),
|
|
20019
|
+
classFilter: string().optional()
|
|
20020
|
+
});
|
|
20021
|
+
/**
|
|
20022
|
+
* One camera's key events in a batch answer.
|
|
20023
|
+
*
|
|
20024
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
20025
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
20026
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
20027
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
20028
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
20029
|
+
* key, which only worked because there was one query per camera).
|
|
20030
|
+
*/
|
|
20031
|
+
var KeyEventsForDeviceSchema = object({
|
|
20032
|
+
deviceId: number(),
|
|
20033
|
+
events: array(KeyEventSchema).readonly()
|
|
20034
|
+
});
|
|
19836
20035
|
object({
|
|
19837
20036
|
trackId: string(),
|
|
19838
20037
|
className: string(),
|
|
@@ -20052,6 +20251,47 @@ var RebuildStatusSchema = object({
|
|
|
20052
20251
|
/** Present when the pass ended by throwing. */
|
|
20053
20252
|
error: string().nullable()
|
|
20054
20253
|
});
|
|
20254
|
+
/**
|
|
20255
|
+
* Acknowledgement that a debug-media reclaim STARTED.
|
|
20256
|
+
*
|
|
20257
|
+
* The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
|
|
20258
|
+
* it runs detached and this returns immediately. Awaiting it is how the
|
|
20259
|
+
* `addons.custom` door hit the 60 s UDS deadline while the walk carried on
|
|
20260
|
+
* with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
|
|
20261
|
+
*/
|
|
20262
|
+
var MediaReclaimStartResultSchema = object({
|
|
20263
|
+
started: boolean(),
|
|
20264
|
+
/** True when a pass was already running; the new request is ignored. */
|
|
20265
|
+
alreadyRunning: boolean()
|
|
20266
|
+
});
|
|
20267
|
+
var MediaReclaimInputSchema = object({
|
|
20268
|
+
mode: _enum(["report", "reclaim"]).default("report"),
|
|
20269
|
+
scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
|
|
20270
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
20271
|
+
restart: boolean().optional(),
|
|
20272
|
+
pageSize: number().int().min(50).max(5e3).optional(),
|
|
20273
|
+
maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
|
|
20274
|
+
maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
|
|
20275
|
+
maxBytesPerRun: number().int().min(1).optional(),
|
|
20276
|
+
budgetMinutes: number().int().min(1).max(720).optional(),
|
|
20277
|
+
throttleBytesPerSec: number().int().min(64 * 1024).optional(),
|
|
20278
|
+
graceMinutes: number().int().min(1).max(10080).optional()
|
|
20279
|
+
});
|
|
20280
|
+
var MediaReclaimStatusSchema = object({
|
|
20281
|
+
running: boolean(),
|
|
20282
|
+
mode: _enum(["report", "reclaim"]).nullable(),
|
|
20283
|
+
totalExamined: number(),
|
|
20284
|
+
totalEligible: number(),
|
|
20285
|
+
totalReclaimed: number(),
|
|
20286
|
+
totalBytesReclaimed: number(),
|
|
20287
|
+
totalRefused: number(),
|
|
20288
|
+
/** Device+scope windows finished in this pass. */
|
|
20289
|
+
devicesDone: number(),
|
|
20290
|
+
complete: boolean().nullable(),
|
|
20291
|
+
startedAtMs: number().nullable(),
|
|
20292
|
+
finishedAtMs: number().nullable(),
|
|
20293
|
+
error: string().nullable()
|
|
20294
|
+
});
|
|
20055
20295
|
var ReplayFrameInputSchema = object({
|
|
20056
20296
|
timestamp: number(),
|
|
20057
20297
|
frame: PipelineRunResultBridge
|
|
@@ -20102,7 +20342,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20102
20342
|
until: number().optional(),
|
|
20103
20343
|
kinds: array(string()).optional(),
|
|
20104
20344
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
20105
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
20345
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
20106
20346
|
deviceId: number(),
|
|
20107
20347
|
since: number(),
|
|
20108
20348
|
until: number(),
|
|
@@ -20193,6 +20433,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20193
20433
|
}), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
|
|
20194
20434
|
kind: "query",
|
|
20195
20435
|
auth: "admin"
|
|
20436
|
+
}), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
|
|
20437
|
+
kind: "mutation",
|
|
20438
|
+
auth: "admin"
|
|
20439
|
+
}), method(object({}), MediaReclaimStatusSchema, {
|
|
20440
|
+
kind: "query",
|
|
20441
|
+
auth: "admin"
|
|
20196
20442
|
}), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
|
|
20197
20443
|
kind: "query",
|
|
20198
20444
|
auth: "admin"
|
|
@@ -22644,7 +22890,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
|
|
|
22644
22890
|
}), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
|
|
22645
22891
|
kind: "mutation",
|
|
22646
22892
|
auth: "admin"
|
|
22647
|
-
})
|
|
22893
|
+
}), method(StorageCleanupInputSchema, object({ jobId: string() }), {
|
|
22894
|
+
kind: "mutation",
|
|
22895
|
+
auth: "admin"
|
|
22896
|
+
}), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
22897
|
+
kind: "mutation",
|
|
22898
|
+
auth: "admin"
|
|
22899
|
+
}), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
|
|
22648
22900
|
var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
22649
22901
|
providerId: string().min(1),
|
|
22650
22902
|
displayName: string().min(1),
|
|
@@ -28022,6 +28274,33 @@ var RecordingRebalanceInputSchema = object({
|
|
|
28022
28274
|
minMoveGb: number().min(0).optional()
|
|
28023
28275
|
});
|
|
28024
28276
|
/**
|
|
28277
|
+
* Operator-facing placement of one camera onto a recordings location.
|
|
28278
|
+
*
|
|
28279
|
+
* `pinLocationId` is the operator instruction: a location id, or `null` for
|
|
28280
|
+
* Auto (the planner may move this camera). `locationId` is where high/mid
|
|
28281
|
+
* currently write — the plan, which may disagree with the pin when Auto.
|
|
28282
|
+
*/
|
|
28283
|
+
var RecordingDevicePlacementSchema = object({
|
|
28284
|
+
deviceId: number().int(),
|
|
28285
|
+
profile: string(),
|
|
28286
|
+
locationId: string()
|
|
28287
|
+
});
|
|
28288
|
+
var RecordingDevicePinSchema = object({
|
|
28289
|
+
deviceId: number().int(),
|
|
28290
|
+
/** Recordings-class location this camera is pinned to. */
|
|
28291
|
+
locationId: string()
|
|
28292
|
+
});
|
|
28293
|
+
var RecordingPlacementViewSchema = object({
|
|
28294
|
+
assignments: array(RecordingDevicePlacementSchema),
|
|
28295
|
+
pins: array(RecordingDevicePinSchema),
|
|
28296
|
+
defaultLocations: record(string(), string())
|
|
28297
|
+
});
|
|
28298
|
+
var RecordingSetDevicePlacementInputSchema = object({
|
|
28299
|
+
deviceId: number().int(),
|
|
28300
|
+
/** `null` = Auto. Otherwise a `recordings:*` location id. */
|
|
28301
|
+
locationId: string().nullable()
|
|
28302
|
+
});
|
|
28303
|
+
/**
|
|
28025
28304
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
28026
28305
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
28027
28306
|
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
@@ -28235,6 +28514,9 @@ method(object({
|
|
|
28235
28514
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28236
28515
|
kind: "query",
|
|
28237
28516
|
auth: "admin"
|
|
28517
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
28518
|
+
kind: "mutation",
|
|
28519
|
+
auth: "admin"
|
|
28238
28520
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28239
28521
|
kind: "mutation",
|
|
28240
28522
|
auth: "admin"
|
|
@@ -28244,6 +28526,12 @@ method(object({
|
|
|
28244
28526
|
}), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
|
|
28245
28527
|
kind: "mutation",
|
|
28246
28528
|
auth: "admin"
|
|
28529
|
+
}), method(object({}), RecordingPlacementViewSchema, {
|
|
28530
|
+
kind: "query",
|
|
28531
|
+
auth: "admin"
|
|
28532
|
+
}), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
|
|
28533
|
+
kind: "mutation",
|
|
28534
|
+
auth: "admin"
|
|
28247
28535
|
});
|
|
28248
28536
|
/**
|
|
28249
28537
|
* `recording-export` cap — render a footage time range into a single downloadable
|
|
@@ -28626,7 +28914,26 @@ var SceneMonitorStatusSchema = object({
|
|
|
28626
28914
|
monitors: array(SceneMonitorSchema),
|
|
28627
28915
|
lastFetchedAt: number()
|
|
28628
28916
|
});
|
|
28629
|
-
|
|
28917
|
+
/**
|
|
28918
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
28919
|
+
*
|
|
28920
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
28921
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
28922
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
28923
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
28924
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
28925
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
28926
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
28927
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
28928
|
+
*
|
|
28929
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
28930
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
28931
|
+
*/
|
|
28932
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
28933
|
+
deviceId: number(),
|
|
28934
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
28935
|
+
});
|
|
28936
|
+
DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
|
|
28630
28937
|
deviceId: number(),
|
|
28631
28938
|
label: string(),
|
|
28632
28939
|
roi: MaskRectShapeSchema,
|
|
@@ -29950,6 +30257,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
29950
30257
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
29951
30258
|
});
|
|
29952
30259
|
/**
|
|
30260
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
30261
|
+
*
|
|
30262
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
30263
|
+
* because `snapshot: null` was already spoken for:
|
|
30264
|
+
*
|
|
30265
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
30266
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
30267
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
30268
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
30269
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
30270
|
+
*
|
|
30271
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
30272
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
30273
|
+
* which is a definite claim about a camera nobody could read.
|
|
30274
|
+
*/
|
|
30275
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
30276
|
+
deviceId: number(),
|
|
30277
|
+
read: _enum(["read", "unreadable"]),
|
|
30278
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
30279
|
+
});
|
|
30280
|
+
/**
|
|
29953
30281
|
* Time-series resolution. The history methods return one bucket per
|
|
29954
30282
|
* step over the requested range. Smaller resolutions cost more
|
|
29955
30283
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -29973,7 +30301,7 @@ var HistoryPointSchema = object({
|
|
|
29973
30301
|
/** Object count averaged over the bucket (rounded to nearest integer). */
|
|
29974
30302
|
count: number().int().nonnegative()
|
|
29975
30303
|
});
|
|
29976
|
-
DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
|
|
30304
|
+
DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
|
|
29977
30305
|
deviceId: number(),
|
|
29978
30306
|
zoneId: string(),
|
|
29979
30307
|
className: string().optional()
|
|
@@ -33362,6 +33690,18 @@ Object.freeze({
|
|
|
33362
33690
|
addonId: null,
|
|
33363
33691
|
access: "view"
|
|
33364
33692
|
},
|
|
33693
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
33694
|
+
capName: "pipeline-analytics",
|
|
33695
|
+
capScope: "device",
|
|
33696
|
+
addonId: null,
|
|
33697
|
+
access: "view"
|
|
33698
|
+
},
|
|
33699
|
+
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
33700
|
+
capName: "pipeline-analytics",
|
|
33701
|
+
capScope: "device",
|
|
33702
|
+
addonId: null,
|
|
33703
|
+
access: "view"
|
|
33704
|
+
},
|
|
33365
33705
|
"pipelineAnalytics.getMotionEvents": {
|
|
33366
33706
|
capName: "pipeline-analytics",
|
|
33367
33707
|
capScope: "device",
|
|
@@ -33536,6 +33876,12 @@ Object.freeze({
|
|
|
33536
33876
|
addonId: null,
|
|
33537
33877
|
access: "create"
|
|
33538
33878
|
},
|
|
33879
|
+
"pipelineAnalytics.reclaimDebugMedia": {
|
|
33880
|
+
capName: "pipeline-analytics",
|
|
33881
|
+
capScope: "device",
|
|
33882
|
+
addonId: null,
|
|
33883
|
+
access: "create"
|
|
33884
|
+
},
|
|
33539
33885
|
"pipelineAnalytics.reconcileFromDisk": {
|
|
33540
33886
|
capName: "pipeline-analytics",
|
|
33541
33887
|
capScope: "device",
|
|
@@ -34460,6 +34806,12 @@ Object.freeze({
|
|
|
34460
34806
|
addonId: null,
|
|
34461
34807
|
access: "view"
|
|
34462
34808
|
},
|
|
34809
|
+
"recording.getPlacement": {
|
|
34810
|
+
capName: "recording",
|
|
34811
|
+
capScope: "system",
|
|
34812
|
+
addonId: null,
|
|
34813
|
+
access: "view"
|
|
34814
|
+
},
|
|
34463
34815
|
"recording.getPlaybackManifest": {
|
|
34464
34816
|
capName: "recording",
|
|
34465
34817
|
capScope: "system",
|
|
@@ -34538,6 +34890,12 @@ Object.freeze({
|
|
|
34538
34890
|
addonId: null,
|
|
34539
34891
|
access: "view"
|
|
34540
34892
|
},
|
|
34893
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
34894
|
+
capName: "recording",
|
|
34895
|
+
capScope: "system",
|
|
34896
|
+
addonId: null,
|
|
34897
|
+
access: "create"
|
|
34898
|
+
},
|
|
34541
34899
|
"recording.refreshStorageLocationsForMigration": {
|
|
34542
34900
|
capName: "recording",
|
|
34543
34901
|
capScope: "system",
|
|
@@ -34580,6 +34938,12 @@ Object.freeze({
|
|
|
34580
34938
|
addonId: null,
|
|
34581
34939
|
access: "create"
|
|
34582
34940
|
},
|
|
34941
|
+
"recording.setDevicePlacement": {
|
|
34942
|
+
capName: "recording",
|
|
34943
|
+
capScope: "system",
|
|
34944
|
+
addonId: null,
|
|
34945
|
+
access: "create"
|
|
34946
|
+
},
|
|
34583
34947
|
"recording.startStorageMigrationMove": {
|
|
34584
34948
|
capName: "recording",
|
|
34585
34949
|
capScope: "system",
|
|
@@ -34664,6 +35028,12 @@ Object.freeze({
|
|
|
34664
35028
|
addonId: null,
|
|
34665
35029
|
access: "view"
|
|
34666
35030
|
},
|
|
35031
|
+
"sceneMonitor.listScenesBatch": {
|
|
35032
|
+
capName: "scene-monitor",
|
|
35033
|
+
capScope: "device",
|
|
35034
|
+
addonId: null,
|
|
35035
|
+
access: "view"
|
|
35036
|
+
},
|
|
34667
35037
|
"sceneMonitor.recheckNow": {
|
|
34668
35038
|
capName: "scene-monitor",
|
|
34669
35039
|
capScope: "device",
|
|
@@ -35018,12 +35388,36 @@ Object.freeze({
|
|
|
35018
35388
|
addonId: null,
|
|
35019
35389
|
access: "create"
|
|
35020
35390
|
},
|
|
35391
|
+
"storageMigration.cleanupCancel": {
|
|
35392
|
+
capName: "storage-migration",
|
|
35393
|
+
capScope: "system",
|
|
35394
|
+
addonId: null,
|
|
35395
|
+
access: "create"
|
|
35396
|
+
},
|
|
35397
|
+
"storageMigration.cleanupStart": {
|
|
35398
|
+
capName: "storage-migration",
|
|
35399
|
+
capScope: "system",
|
|
35400
|
+
addonId: null,
|
|
35401
|
+
access: "create"
|
|
35402
|
+
},
|
|
35403
|
+
"storageMigration.cleanupStatus": {
|
|
35404
|
+
capName: "storage-migration",
|
|
35405
|
+
capScope: "system",
|
|
35406
|
+
addonId: null,
|
|
35407
|
+
access: "view"
|
|
35408
|
+
},
|
|
35021
35409
|
"storageMigration.drain": {
|
|
35022
35410
|
capName: "storage-migration",
|
|
35023
35411
|
capScope: "system",
|
|
35024
35412
|
addonId: null,
|
|
35025
35413
|
access: "create"
|
|
35026
35414
|
},
|
|
35415
|
+
"storageMigration.history": {
|
|
35416
|
+
capName: "storage-migration",
|
|
35417
|
+
capScope: "system",
|
|
35418
|
+
addonId: null,
|
|
35419
|
+
access: "view"
|
|
35420
|
+
},
|
|
35027
35421
|
"storageMigration.movers": {
|
|
35028
35422
|
capName: "storage-migration",
|
|
35029
35423
|
capScope: "system",
|
|
@@ -36020,6 +36414,12 @@ Object.freeze({
|
|
|
36020
36414
|
addonId: null,
|
|
36021
36415
|
access: "view"
|
|
36022
36416
|
},
|
|
36417
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
36418
|
+
capName: "zone-analytics",
|
|
36419
|
+
capScope: "device",
|
|
36420
|
+
addonId: null,
|
|
36421
|
+
access: "view"
|
|
36422
|
+
},
|
|
36023
36423
|
"zoneAnalytics.getUnzonedHistory": {
|
|
36024
36424
|
capName: "zone-analytics",
|
|
36025
36425
|
capScope: "device",
|
|
@@ -37024,6 +37424,11 @@ Object.freeze({
|
|
|
37024
37424
|
form: "single",
|
|
37025
37425
|
optional: false
|
|
37026
37426
|
}],
|
|
37427
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
37428
|
+
name: "deviceIds",
|
|
37429
|
+
form: "array",
|
|
37430
|
+
optional: false
|
|
37431
|
+
}],
|
|
37027
37432
|
"pipelineAnalytics.getMotionEvents": [{
|
|
37028
37433
|
name: "deviceId",
|
|
37029
37434
|
form: "single",
|
|
@@ -37129,6 +37534,11 @@ Object.freeze({
|
|
|
37129
37534
|
form: "single",
|
|
37130
37535
|
optional: true
|
|
37131
37536
|
}],
|
|
37537
|
+
"pipelineAnalytics.reclaimDebugMedia": [{
|
|
37538
|
+
name: "deviceIds",
|
|
37539
|
+
form: "array",
|
|
37540
|
+
optional: true
|
|
37541
|
+
}],
|
|
37132
37542
|
"pipelineAnalytics.reconcileFromDisk": [{
|
|
37133
37543
|
name: "deviceId",
|
|
37134
37544
|
form: "single",
|
|
@@ -37464,6 +37874,11 @@ Object.freeze({
|
|
|
37464
37874
|
form: "single",
|
|
37465
37875
|
optional: false
|
|
37466
37876
|
}],
|
|
37877
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
37878
|
+
name: "deviceId",
|
|
37879
|
+
form: "single",
|
|
37880
|
+
optional: true
|
|
37881
|
+
}],
|
|
37467
37882
|
"recording.relocateFootage": [{
|
|
37468
37883
|
name: "deviceId",
|
|
37469
37884
|
form: "single",
|
|
@@ -37489,6 +37904,11 @@ Object.freeze({
|
|
|
37489
37904
|
form: "single",
|
|
37490
37905
|
optional: false
|
|
37491
37906
|
}],
|
|
37907
|
+
"recording.setDevicePlacement": [{
|
|
37908
|
+
name: "deviceId",
|
|
37909
|
+
form: "single",
|
|
37910
|
+
optional: false
|
|
37911
|
+
}],
|
|
37492
37912
|
"recording.startStorageMigrationMove": [{
|
|
37493
37913
|
name: "deviceId",
|
|
37494
37914
|
form: "single",
|
|
@@ -37529,6 +37949,11 @@ Object.freeze({
|
|
|
37529
37949
|
form: "single",
|
|
37530
37950
|
optional: false
|
|
37531
37951
|
}],
|
|
37952
|
+
"sceneMonitor.listScenesBatch": [{
|
|
37953
|
+
name: "deviceIds",
|
|
37954
|
+
form: "array",
|
|
37955
|
+
optional: false
|
|
37956
|
+
}],
|
|
37532
37957
|
"sceneMonitor.recheckNow": [{
|
|
37533
37958
|
name: "deviceId",
|
|
37534
37959
|
form: "single",
|
|
@@ -37790,6 +38215,11 @@ Object.freeze({
|
|
|
37790
38215
|
form: "single",
|
|
37791
38216
|
optional: false
|
|
37792
38217
|
}],
|
|
38218
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
38219
|
+
name: "deviceIds",
|
|
38220
|
+
form: "array",
|
|
38221
|
+
optional: false
|
|
38222
|
+
}],
|
|
37793
38223
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
37794
38224
|
name: "deviceId",
|
|
37795
38225
|
form: "single",
|
|
@@ -40899,6 +41329,124 @@ function composeCameraStatus(input) {
|
|
|
40899
41329
|
};
|
|
40900
41330
|
}
|
|
40901
41331
|
//#endregion
|
|
41332
|
+
//#region src/camera-status/fleet-read-pass.ts
|
|
41333
|
+
/**
|
|
41334
|
+
* Keep a rejection from becoming an UNHANDLED one.
|
|
41335
|
+
*
|
|
41336
|
+
* A shared promise is created by the first camera to ask and consumed by the
|
|
41337
|
+
* others later — `listBindableCapsForDeviceType` is reached in stage 2 of
|
|
41338
|
+
* `readSwitchAuthorities`, after an await, so two cameras genuinely attach
|
|
41339
|
+
* their handlers in different ticks. Without this the first rejection would be
|
|
41340
|
+
* unhandled for the window in between.
|
|
41341
|
+
*
|
|
41342
|
+
* The handler is a no-op ON PURPOSE: it never becomes the answer. Every real
|
|
41343
|
+
* consumer attaches its own `.catch` and reports the failure per camera.
|
|
41344
|
+
*/
|
|
41345
|
+
function keepAlive(p) {
|
|
41346
|
+
p.catch(() => void 0);
|
|
41347
|
+
return p;
|
|
41348
|
+
}
|
|
41349
|
+
var FleetReadPass = class {
|
|
41350
|
+
deps;
|
|
41351
|
+
roster = null;
|
|
41352
|
+
bindings = null;
|
|
41353
|
+
mutes = null;
|
|
41354
|
+
bindableByType = /* @__PURE__ */ new Map();
|
|
41355
|
+
slotsByNode = /* @__PURE__ */ new Map();
|
|
41356
|
+
rtspByNode = /* @__PURE__ */ new Map();
|
|
41357
|
+
provisioningByNode = /* @__PURE__ */ new Map();
|
|
41358
|
+
selectedEngineByNode = /* @__PURE__ */ new Map();
|
|
41359
|
+
constructor(deps) {
|
|
41360
|
+
this.deps = deps;
|
|
41361
|
+
}
|
|
41362
|
+
/** The cameras this pass covers — the set both batch reads are issued for. */
|
|
41363
|
+
get deviceIds() {
|
|
41364
|
+
return this.deps.deviceIds;
|
|
41365
|
+
}
|
|
41366
|
+
/**
|
|
41367
|
+
* The device rows for the pass's set.
|
|
41368
|
+
*
|
|
41369
|
+
* **The shape follows the SET, not the caller.** A pass of one asks
|
|
41370
|
+
* `getDevice` — the exact question, and the one the write path
|
|
41371
|
+
* (`CameraSwitchService.setCameraSwitch`) has always asked. A pass of many
|
|
41372
|
+
* asks `listAll({deviceIds})`, which exists precisely because "these N" was
|
|
41373
|
+
* otherwise either N round trips or the whole fleet. Answering a set of one
|
|
41374
|
+
* with the fleet-shaped method would be no cheaper and would move the write
|
|
41375
|
+
* path onto a read it does not need.
|
|
41376
|
+
*
|
|
41377
|
+
* `projection: 'slim'` because the only fields read off it are `type` and
|
|
41378
|
+
* `disabled`: the full projection reads each device's settings row, which is
|
|
41379
|
+
* the per-device round trip this whole change exists to remove.
|
|
41380
|
+
*/
|
|
41381
|
+
deviceRoster(api) {
|
|
41382
|
+
const single = this.deps.deviceIds.length === 1 ? this.deps.deviceIds[0] : void 0;
|
|
41383
|
+
const source = single === void 0 ? "deviceManager.listAll" : "deviceManager.getDevice";
|
|
41384
|
+
if (this.roster === null) this.roster = keepAlive(single === void 0 ? api.deviceManager.listAll.query({
|
|
41385
|
+
deviceIds: [...this.deps.deviceIds],
|
|
41386
|
+
projection: "slim"
|
|
41387
|
+
}) : api.deviceManager.getDevice.query({ deviceId: single }).then((row) => row === null ? [] : [row]));
|
|
41388
|
+
return {
|
|
41389
|
+
source,
|
|
41390
|
+
result: this.roster
|
|
41391
|
+
};
|
|
41392
|
+
}
|
|
41393
|
+
/** The binding rows for the pass's set — `getBindings` for one, the batch for many. */
|
|
41394
|
+
bindingRows(api) {
|
|
41395
|
+
const single = this.deps.deviceIds.length === 1 ? this.deps.deviceIds[0] : void 0;
|
|
41396
|
+
const source = single === void 0 ? "deviceManager.getBindingsBatch" : "deviceManager.getBindings";
|
|
41397
|
+
if (this.bindings === null) this.bindings = keepAlive(single === void 0 ? api.deviceManager.getBindingsBatch.query({ deviceIds: [...this.deps.deviceIds] }) : api.deviceManager.getBindings.query({ deviceId: single }).then((row) => [row]));
|
|
41398
|
+
return {
|
|
41399
|
+
source,
|
|
41400
|
+
result: this.bindings
|
|
41401
|
+
};
|
|
41402
|
+
}
|
|
41403
|
+
/** The fleet's muted-camera list. One answer for every camera in the pass. */
|
|
41404
|
+
mutedDevices(api) {
|
|
41405
|
+
if (this.mutes === null) this.mutes = keepAlive(api.notificationRules.listDeviceMutes.query({}));
|
|
41406
|
+
return this.mutes;
|
|
41407
|
+
}
|
|
41408
|
+
/** The bindable caps for one device TYPE. Every camera shares one answer. */
|
|
41409
|
+
bindableCapsFor(api, deviceType) {
|
|
41410
|
+
const existing = this.bindableByType.get(deviceType);
|
|
41411
|
+
if (existing !== void 0) return existing;
|
|
41412
|
+
const created = keepAlive(api.deviceManager.listBindableCapsForDeviceType.query({ deviceType }));
|
|
41413
|
+
this.bindableByType.set(deviceType, created);
|
|
41414
|
+
return created;
|
|
41415
|
+
}
|
|
41416
|
+
/** One source node's whole profile-slot table. */
|
|
41417
|
+
profileSlots(api, sourceNodeId) {
|
|
41418
|
+
const existing = this.slotsByNode.get(sourceNodeId);
|
|
41419
|
+
if (existing !== void 0) return existing;
|
|
41420
|
+
const created = keepAlive(api.streamBroker.listAllProfileSlots.query(void 0, nodePin(sourceNodeId)));
|
|
41421
|
+
this.slotsByNode.set(sourceNodeId, created);
|
|
41422
|
+
return created;
|
|
41423
|
+
}
|
|
41424
|
+
/** One source node's whole RTSP-restream table. */
|
|
41425
|
+
rtspEntries(api, sourceNodeId) {
|
|
41426
|
+
const existing = this.rtspByNode.get(sourceNodeId);
|
|
41427
|
+
if (existing !== void 0) return existing;
|
|
41428
|
+
const created = keepAlive(api.streamBroker.getAllRtspEntries.query({}, nodePin(sourceNodeId)));
|
|
41429
|
+
this.rtspByNode.set(sourceNodeId, created);
|
|
41430
|
+
return created;
|
|
41431
|
+
}
|
|
41432
|
+
/** One detection node's runtime-provisioning snapshot. */
|
|
41433
|
+
engineProvisioning(api, detectionNodeId) {
|
|
41434
|
+
const existing = this.provisioningByNode.get(detectionNodeId);
|
|
41435
|
+
if (existing !== void 0) return existing;
|
|
41436
|
+
const created = keepAlive(api.pipelineExecutor.getEngineProvisioning.query({ nodeId: detectionNodeId }));
|
|
41437
|
+
this.provisioningByNode.set(detectionNodeId, created);
|
|
41438
|
+
return created;
|
|
41439
|
+
}
|
|
41440
|
+
/** The executor's bootstrap engine, as the detection stage reads it. */
|
|
41441
|
+
selectedEngine(api, detectionNodeId) {
|
|
41442
|
+
const existing = this.selectedEngineByNode.get(detectionNodeId);
|
|
41443
|
+
if (existing !== void 0) return existing;
|
|
41444
|
+
const created = keepAlive(api.pipelineExecutor.getSelectedEngine.query({ nodeId: detectionNodeId }));
|
|
41445
|
+
this.selectedEngineByNode.set(detectionNodeId, created);
|
|
41446
|
+
return created;
|
|
41447
|
+
}
|
|
41448
|
+
};
|
|
41449
|
+
//#endregion
|
|
40902
41450
|
//#region src/camera-status-service.ts
|
|
40903
41451
|
/** WebRTC consumer kinds counted toward `BrokerResult.webrtcSessions`. */
|
|
40904
41452
|
var WEBRTC_KINDS = new Set([
|
|
@@ -41087,7 +41635,7 @@ var CameraStatusService = class {
|
|
|
41087
41635
|
* broker's actual decode-session node into `liveDecoder` (T6) — the first
|
|
41088
41636
|
* slot that reports one wins.
|
|
41089
41637
|
*/
|
|
41090
|
-
buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, liveDecoder, sink) {
|
|
41638
|
+
buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, sink) {
|
|
41091
41639
|
if (!api || !allSlotsFetch) return Promise.resolve(null);
|
|
41092
41640
|
return this.boundedStage(allSlotsFetch.then(async (slots) => {
|
|
41093
41641
|
const deviceSlots = slots.filter((s) => s.deviceId === deviceId);
|
|
@@ -41097,12 +41645,13 @@ var CameraStatusService = class {
|
|
|
41097
41645
|
rtspRestream: false
|
|
41098
41646
|
};
|
|
41099
41647
|
const [statsAndClients, rtspEntry] = await Promise.all([Promise.all(deviceSlots.map(async (slot) => {
|
|
41648
|
+
const [stats, clients] = await Promise.all([api.streamBroker.getBrokerStats.query({ brokerId: slot.brokerId }, nodePin(sourceNodeId)).catch(() => null), api.streamBroker.listClients.query({ brokerId: slot.brokerId }, nodePin(sourceNodeId)).catch(() => null)]);
|
|
41100
41649
|
return {
|
|
41101
41650
|
slot,
|
|
41102
|
-
stats
|
|
41103
|
-
clients
|
|
41651
|
+
stats,
|
|
41652
|
+
clients
|
|
41104
41653
|
};
|
|
41105
|
-
})),
|
|
41654
|
+
})), pass.rtspEntries(api, sourceNodeId).catch(() => null)]);
|
|
41106
41655
|
const profileDetails = statsAndClients.map(({ slot, stats, clients }) => ({
|
|
41107
41656
|
profile: slot.profile,
|
|
41108
41657
|
status: slot.status,
|
|
@@ -41163,13 +41712,16 @@ var CameraStatusService = class {
|
|
|
41163
41712
|
};
|
|
41164
41713
|
}
|
|
41165
41714
|
/** Detection stage (pipeline-executor + runner metrics). */
|
|
41166
|
-
buildDetectionStage(api, detectionNodeId, deviceId, sink) {
|
|
41715
|
+
buildDetectionStage(api, detectionNodeId, deviceId, pass, sink) {
|
|
41167
41716
|
if (!api || !detectionNodeId) return Promise.resolve(null);
|
|
41168
|
-
return this.boundedStage(Promise.all([
|
|
41169
|
-
|
|
41717
|
+
return this.boundedStage(Promise.all([
|
|
41718
|
+
pass.engineProvisioning(api, detectionNodeId).catch(() => null),
|
|
41719
|
+
pass.selectedEngine(api, detectionNodeId).catch(() => null),
|
|
41720
|
+
api.pipelineRunner.getCameraMetrics.query({
|
|
41170
41721
|
deviceId,
|
|
41171
41722
|
nodeId: detectionNodeId
|
|
41172
|
-
}).catch(() => null)
|
|
41723
|
+
}).catch(() => null)
|
|
41724
|
+
]).then(async ([provisioning, engine, metrics]) => {
|
|
41173
41725
|
const phase = (() => {
|
|
41174
41726
|
const p = metrics?.phase;
|
|
41175
41727
|
if (p === "active") return "active";
|
|
@@ -41281,9 +41833,9 @@ var CameraStatusService = class {
|
|
|
41281
41833
|
* badge on a working camera); what matters is that the emptiness travels with
|
|
41282
41834
|
* the reason it is empty.
|
|
41283
41835
|
*/
|
|
41284
|
-
buildSwitchStage(deviceId, sink) {
|
|
41836
|
+
buildSwitchStage(deviceId, pass, sink) {
|
|
41285
41837
|
const startedAt = Date.now();
|
|
41286
|
-
return this.boundedStage(this.deps.switchAuthoritiesFor(deviceId), STAGE_TIMEOUT_MS, "switches", deviceId, sink).then((reads) => {
|
|
41838
|
+
return this.boundedStage(this.deps.switchAuthoritiesFor(deviceId, pass), STAGE_TIMEOUT_MS, "switches", deviceId, sink).then((reads) => {
|
|
41287
41839
|
if (reads === null) return [];
|
|
41288
41840
|
const { switchedOff, unreadable } = composeSwitchedOff(reads);
|
|
41289
41841
|
if (unreadable.length > 0) this.recordDegraded(sink, deviceId, "switches", "partial", Date.now() - startedAt, { unreadableAuthorities: unreadable });
|
|
@@ -41301,18 +41853,28 @@ var CameraStatusService = class {
|
|
|
41301
41853
|
* `null` of a camera that legitimately has no such stage.
|
|
41302
41854
|
*/
|
|
41303
41855
|
async getCameraStatus(deviceId) {
|
|
41856
|
+
return this.composeOne(deviceId, new FleetReadPass({ deviceIds: [deviceId] }));
|
|
41857
|
+
}
|
|
41858
|
+
/**
|
|
41859
|
+
* One camera's status, composed WITHIN a pass.
|
|
41860
|
+
*
|
|
41861
|
+
* Everything the camera alone can answer is fetched here; everything the pass
|
|
41862
|
+
* already knows (a node's slot table, the fleet's mute list, a device type's
|
|
41863
|
+
* bindable caps) is asked of `pass`, which issues it once for the whole call.
|
|
41864
|
+
*/
|
|
41865
|
+
async composeOne(deviceId, pass) {
|
|
41304
41866
|
const api = this.deps.api();
|
|
41305
41867
|
const degradations = { entries: [] };
|
|
41306
41868
|
const { detectionNodeId, sourceNodeId, pinned, detectionReason, audioNodeId, audioPinned } = this.buildAssignmentContext(deviceId);
|
|
41307
41869
|
const liveDecoder = { nodeId: null };
|
|
41308
|
-
const allSlotsFetch = api ?
|
|
41870
|
+
const allSlotsFetch = api ? pass.profileSlots(api, sourceNodeId) : null;
|
|
41309
41871
|
const sourceFetch = this.buildSourceStage(api, allSlotsFetch, deviceId, degradations);
|
|
41310
|
-
const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, liveDecoder, degradations);
|
|
41872
|
+
const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, degradations);
|
|
41311
41873
|
const decoderFetch = this.buildDecoderStage(detectionNodeId);
|
|
41312
41874
|
const motionResult = this.buildMotionStage(deviceId);
|
|
41313
|
-
const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, degradations);
|
|
41875
|
+
const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, pass, degradations);
|
|
41314
41876
|
const recordingFetch = this.buildRecordingStage(api, deviceId, degradations);
|
|
41315
|
-
const switchesFetch = this.buildSwitchStage(deviceId, degradations);
|
|
41877
|
+
const switchesFetch = this.buildSwitchStage(deviceId, pass, degradations);
|
|
41316
41878
|
const [sourceResult, brokerResult, decoderResult, detectionResult, recordingResult, switchedOff] = await Promise.all([
|
|
41317
41879
|
sourceFetch,
|
|
41318
41880
|
brokerFetch,
|
|
@@ -41355,15 +41917,30 @@ var CameraStatusService = class {
|
|
|
41355
41917
|
* `deviceIds` defaults to all cameras currently tracked by the
|
|
41356
41918
|
* orchestrator's assignment map when omitted.
|
|
41357
41919
|
*
|
|
41358
|
-
*
|
|
41359
|
-
*
|
|
41360
|
-
*
|
|
41361
|
-
*
|
|
41362
|
-
*
|
|
41920
|
+
* The whole call runs inside ONE {@link FleetReadPass}, and that is what
|
|
41921
|
+
* makes a five-second poll affordable. Composing a camera reaches seven
|
|
41922
|
+
* stages, but six of the questions those stages ask are about a NODE, a
|
|
41923
|
+
* device TYPE or the whole FLEET — the slot table, the restream table, the
|
|
41924
|
+
* mute list, the engine pair, the bindable caps — and asking them per camera
|
|
41925
|
+
* meant N identical round trips for N identical answers. Two more (the device
|
|
41926
|
+
* row, the binding table) are per-camera questions the device manager already
|
|
41927
|
+
* answers for a set. The pass asks each exactly once and shares the promise;
|
|
41928
|
+
* it holds nothing between calls, so the next poll asks again from scratch.
|
|
41929
|
+
*
|
|
41930
|
+
* What is left is genuinely per camera and stays per camera: the broker slot
|
|
41931
|
+
* stats, the runner metrics, the recorder, the broker mute and the camera's
|
|
41932
|
+
* own privacy provider. Collapsing any of those would be a narrower answer,
|
|
41933
|
+
* not a cheaper one.
|
|
41934
|
+
*
|
|
41935
|
+
* `Promise.all` over per-device composition (no concurrency cap). The fan-out
|
|
41936
|
+
* is now ~8 round trips per camera rather than ~16, and the six fleet reads
|
|
41937
|
+
* no longer multiply — which is the part that made the count grow with the
|
|
41938
|
+
* fleet twice over.
|
|
41363
41939
|
*/
|
|
41364
41940
|
async getCameraStatuses(deviceIds) {
|
|
41365
41941
|
const ids = deviceIds !== void 0 && deviceIds.length > 0 ? deviceIds : this.deps.listAssignedDeviceIds();
|
|
41366
|
-
|
|
41942
|
+
const pass = new FleetReadPass({ deviceIds: ids });
|
|
41943
|
+
return Promise.all(ids.map((deviceId) => this.composeOne(deviceId, pass)));
|
|
41367
41944
|
}
|
|
41368
41945
|
};
|
|
41369
41946
|
//#endregion
|
|
@@ -41612,10 +42189,15 @@ async function probeCamera(api, deviceId, deps) {
|
|
|
41612
42189
|
* @param deviceId The camera.
|
|
41613
42190
|
* @param deps Logger + source-owner resolver.
|
|
41614
42191
|
*/
|
|
41615
|
-
async function readSwitchAuthorities(api, deviceId, deps) {
|
|
42192
|
+
async function readSwitchAuthorities(api, deviceId, deps, sharedPass) {
|
|
41616
42193
|
if (!api) return unknownReads(deviceId);
|
|
41617
|
-
const
|
|
41618
|
-
|
|
42194
|
+
const pass = sharedPass ?? new FleetReadPass({ deviceIds: [deviceId] });
|
|
42195
|
+
const roster = pass.deviceRoster(api);
|
|
42196
|
+
const devicePromise = bounded(deps, deviceId, roster.source, roster.result.then((rows) => {
|
|
42197
|
+
const row = rows.find((d) => d.id === deviceId);
|
|
42198
|
+
return isDeviceShape(row) ? row : null;
|
|
42199
|
+
}).catch((err) => {
|
|
42200
|
+
warnUnreachable(deps, deviceId, roster.source, err);
|
|
41619
42201
|
return null;
|
|
41620
42202
|
}), null);
|
|
41621
42203
|
const unknownBindings = {
|
|
@@ -41623,11 +42205,14 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41623
42205
|
providerAddonIdByCap: /* @__PURE__ */ new Map(),
|
|
41624
42206
|
allCapNames: null
|
|
41625
42207
|
};
|
|
41626
|
-
const
|
|
42208
|
+
const bindingRead = pass.bindingRows(api);
|
|
42209
|
+
const bindingsPromise = bounded(deps, deviceId, bindingRead.source, bindingRead.result.then((rows) => {
|
|
42210
|
+
const row = rows.find((r) => r.deviceId === deviceId);
|
|
42211
|
+
if (row === void 0) return unknownBindings;
|
|
41627
42212
|
const active = [];
|
|
41628
42213
|
const all = [];
|
|
41629
42214
|
const providers = /* @__PURE__ */ new Map();
|
|
41630
|
-
for (const e of
|
|
42215
|
+
for (const e of row.entries) {
|
|
41631
42216
|
all.push(e.capName);
|
|
41632
42217
|
if (e.kind !== "wrapped") continue;
|
|
41633
42218
|
active.push(e.capName);
|
|
@@ -41639,14 +42224,14 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41639
42224
|
allCapNames: all
|
|
41640
42225
|
};
|
|
41641
42226
|
}).catch((err) => {
|
|
41642
|
-
warnUnreachable(deps, deviceId,
|
|
42227
|
+
warnUnreachable(deps, deviceId, bindingRead.source, err);
|
|
41643
42228
|
return unknownBindings;
|
|
41644
42229
|
}), unknownBindings);
|
|
41645
42230
|
const recordingPromise = bounded(deps, deviceId, "recording.getDeviceConfig", api.recording.getDeviceConfig.query({ deviceId }).then((c) => isRecordingConfig(c) ? c : null).catch((err) => {
|
|
41646
42231
|
warnUnreachable(deps, deviceId, "recording.getDeviceConfig", err);
|
|
41647
42232
|
return null;
|
|
41648
42233
|
}), null);
|
|
41649
|
-
const mutesPromise = bounded(deps, deviceId, "notificationRules.listDeviceMutes",
|
|
42234
|
+
const mutesPromise = bounded(deps, deviceId, "notificationRules.listDeviceMutes", pass.mutedDevices(api).then((r) => r.mutedDeviceIds).catch((err) => {
|
|
41650
42235
|
warnUnreachable(deps, deviceId, "notificationRules.listDeviceMutes", err);
|
|
41651
42236
|
return null;
|
|
41652
42237
|
}), null);
|
|
@@ -41661,7 +42246,7 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41661
42246
|
mutesPromise,
|
|
41662
42247
|
brokerAudioPromise
|
|
41663
42248
|
]);
|
|
41664
|
-
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : bounded(deps, deviceId, "deviceManager.listBindableCapsForDeviceType",
|
|
42249
|
+
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : bounded(deps, deviceId, "deviceManager.listBindableCapsForDeviceType", pass.bindableCapsFor(api, device.type).catch((err) => {
|
|
41665
42250
|
warnUnreachable(deps, deviceId, "listBindableCapsForDeviceType", err);
|
|
41666
42251
|
return null;
|
|
41667
42252
|
}), null), readPrivacyPlanes(api, deviceId, deps, bindings.allCapNames)]);
|
|
@@ -50368,11 +50953,11 @@ async function buildOrchestratorControllers(deps) {
|
|
|
50368
50953
|
assignSource: (deviceId) => topology.assignSource(deviceId),
|
|
50369
50954
|
listAssignedDeviceIds: () => [...new Set([...ledger.listAssignedDeviceIds(), ...detectionWiring.activeDeviceIds()])],
|
|
50370
50955
|
isSessionCamera: (deviceId) => deps.isSessionCamera(deviceId),
|
|
50371
|
-
switchAuthoritiesFor: async (deviceId) => (await readSwitchAuthorities(deps.ctx().api ?? null, deviceId, {
|
|
50956
|
+
switchAuthoritiesFor: async (deviceId, pass) => (await readSwitchAuthorities(deps.ctx().api ?? null, deviceId, {
|
|
50372
50957
|
logger: deps.ctx().logger,
|
|
50373
50958
|
assignSource: (id) => topology.assignSource(id),
|
|
50374
50959
|
warnSampler: switchWarnSampler
|
|
50375
|
-
})).derivation
|
|
50960
|
+
}, pass)).derivation
|
|
50376
50961
|
});
|
|
50377
50962
|
const reconcile = new ReconcileController({
|
|
50378
50963
|
api: () => deps.ctx().api ?? null,
|