@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.js
CHANGED
|
@@ -8894,6 +8894,20 @@ var RelocateJobSchema = object({
|
|
|
8894
8894
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8895
8895
|
*/
|
|
8896
8896
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8897
|
+
/**
|
|
8898
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8899
|
+
*
|
|
8900
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8901
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8902
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8903
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8904
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8905
|
+
* the same failure as one that quietly skips them (D295).
|
|
8906
|
+
*
|
|
8907
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8908
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8909
|
+
*/
|
|
8910
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8897
8911
|
startedAt: number(),
|
|
8898
8912
|
finishedAt: number().nullable(),
|
|
8899
8913
|
error: string().nullable()
|
|
@@ -8958,6 +8972,13 @@ var MediaRelocateModeSchema = _enum([
|
|
|
8958
8972
|
]);
|
|
8959
8973
|
var RelocateMediaInputSchema = object({
|
|
8960
8974
|
toLocationId: string(),
|
|
8975
|
+
/**
|
|
8976
|
+
* Restrict the pass to rows currently on this location. Omitted / `'*'` =
|
|
8977
|
+
* every row that is not already on `toLocationId` (the historical
|
|
8978
|
+
* behaviour). A named source is what a from→to migration needs: without it
|
|
8979
|
+
* "move events off disk 2" also emptied disk 1.
|
|
8980
|
+
*/
|
|
8981
|
+
fromLocationId: string().optional(),
|
|
8961
8982
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
8962
8983
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
8963
8984
|
mode: MediaRelocateModeSchema.optional()
|
|
@@ -9025,6 +9046,19 @@ var StorageMigrationDestinationsSchema = object({
|
|
|
9025
9046
|
galleryMedia: string().min(1).optional()
|
|
9026
9047
|
}).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
|
|
9027
9048
|
/**
|
|
9049
|
+
* Optional named source per class. Omitted = the class's current default
|
|
9050
|
+
* (the historical behaviour). A named source that is NOT the default is a
|
|
9051
|
+
* drain of that disk: bytes move, the default stays, and the source is
|
|
9052
|
+
* disabled when the move finishes.
|
|
9053
|
+
*/
|
|
9054
|
+
var StorageMigrationSourcesSchema = object({
|
|
9055
|
+
recordings: string().min(1).optional(),
|
|
9056
|
+
recordingsLow: string().min(1).optional(),
|
|
9057
|
+
eventMedia: string().min(1).optional(),
|
|
9058
|
+
backups: string().min(1).optional(),
|
|
9059
|
+
galleryMedia: string().min(1).optional()
|
|
9060
|
+
}).optional();
|
|
9061
|
+
/**
|
|
9028
9062
|
* How a migration sequences the cutover against the byte move.
|
|
9029
9063
|
*
|
|
9030
9064
|
* - `blocking` — the historical order: pause, move every byte, repoint,
|
|
@@ -9046,6 +9080,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
|
|
|
9046
9080
|
/** Shared input for planning and starting an orchestrated storage migration. */
|
|
9047
9081
|
var StorageMigrationInputSchema = object({
|
|
9048
9082
|
destinations: StorageMigrationDestinationsSchema,
|
|
9083
|
+
/** Omitted = each class's current default. */
|
|
9084
|
+
sources: StorageMigrationSourcesSchema,
|
|
9049
9085
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
9050
9086
|
/** Omitted = `blocking`, which stays the default. */
|
|
9051
9087
|
mode: StorageMigrationModeSchema.optional()
|
|
@@ -9125,6 +9161,13 @@ var StorageMigrationMoveSchema = object({
|
|
|
9125
9161
|
storageClass: StorageMigrationClassSchema,
|
|
9126
9162
|
fromLocationId: string(),
|
|
9127
9163
|
toLocationId: string(),
|
|
9164
|
+
/**
|
|
9165
|
+
* True when `from` was NOT the class default at plan time. The move still
|
|
9166
|
+
* copies bytes, but the default is left alone and the source is disabled
|
|
9167
|
+
* once the copy verifies. Absent on jobs planned before this field existed
|
|
9168
|
+
* — those jobs always repointed, which is `false`.
|
|
9169
|
+
*/
|
|
9170
|
+
freezeSource: boolean().optional(),
|
|
9128
9171
|
moverJobId: string().nullable(),
|
|
9129
9172
|
state: RelocateJobStateSchema.nullable(),
|
|
9130
9173
|
error: string().nullable(),
|
|
@@ -9138,6 +9181,7 @@ var StorageMigrationJobSchema = object({
|
|
|
9138
9181
|
* can tell a seconds-long cutover from a thirty-hour one. */
|
|
9139
9182
|
mode: StorageMigrationModeSchema,
|
|
9140
9183
|
destinations: StorageMigrationDestinationsSchema,
|
|
9184
|
+
sources: StorageMigrationSourcesSchema,
|
|
9141
9185
|
throttleMbps: number(),
|
|
9142
9186
|
moves: array(StorageMigrationMoveSchema),
|
|
9143
9187
|
pauseLeaseId: string().nullable(),
|
|
@@ -9163,6 +9207,7 @@ var StorageMigrationFindingSchema = object({
|
|
|
9163
9207
|
});
|
|
9164
9208
|
var StorageMigrationPlanSchema = object({
|
|
9165
9209
|
destinations: StorageMigrationDestinationsSchema,
|
|
9210
|
+
sources: StorageMigrationSourcesSchema,
|
|
9166
9211
|
/** The mode this plan was built for. A plan is only valid for its mode: the
|
|
9167
9212
|
* `eventMedia` seal gate and the single-cardinality refusal both depend on
|
|
9168
9213
|
* it. */
|
|
@@ -9170,7 +9215,8 @@ var StorageMigrationPlanSchema = object({
|
|
|
9170
9215
|
moves: array(object({
|
|
9171
9216
|
storageClass: StorageMigrationClassSchema,
|
|
9172
9217
|
fromLocationId: string(),
|
|
9173
|
-
toLocationId: string()
|
|
9218
|
+
toLocationId: string(),
|
|
9219
|
+
freezeSource: boolean().optional()
|
|
9174
9220
|
})),
|
|
9175
9221
|
findings: array(StorageMigrationFindingSchema)
|
|
9176
9222
|
});
|
|
@@ -9257,16 +9303,142 @@ var RelocateResidueSchema = object({
|
|
|
9257
9303
|
segments: number().int().nonnegative(),
|
|
9258
9304
|
bytes: number().int().nonnegative()
|
|
9259
9305
|
}).nullable();
|
|
9306
|
+
/**
|
|
9307
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
9308
|
+
* (D319).
|
|
9309
|
+
*
|
|
9310
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
9311
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
9312
|
+
* destructive run before authorising it.
|
|
9313
|
+
*/
|
|
9314
|
+
var LedgerWalkInputSchema = object({
|
|
9315
|
+
locationId: string().min(1),
|
|
9316
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
9317
|
+
apply: boolean().optional(),
|
|
9318
|
+
/** Narrow to one camera. */
|
|
9319
|
+
deviceId: number().int().positive().optional(),
|
|
9320
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
9321
|
+
profiles: array(string().min(1)).optional()
|
|
9322
|
+
});
|
|
9323
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
9324
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
9325
|
+
"location-unknown",
|
|
9326
|
+
"source-writable",
|
|
9327
|
+
"no-ledger",
|
|
9328
|
+
"archive-unreadable",
|
|
9329
|
+
"anchor-absent",
|
|
9330
|
+
"anchor-unreadable",
|
|
9331
|
+
"anchor-moved"
|
|
9332
|
+
]);
|
|
9333
|
+
_enum([
|
|
9334
|
+
"live-tail",
|
|
9335
|
+
"listing-error",
|
|
9336
|
+
"path-mismatch",
|
|
9337
|
+
"durable-refused"
|
|
9338
|
+
]);
|
|
9339
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
9340
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
9341
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
9342
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
9343
|
+
"live-tail": number().int().nonnegative(),
|
|
9344
|
+
"listing-error": number().int().nonnegative(),
|
|
9345
|
+
"path-mismatch": number().int().nonnegative(),
|
|
9346
|
+
"durable-refused": number().int().nonnegative()
|
|
9347
|
+
});
|
|
9348
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
9349
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
9350
|
+
deviceId: number().int(),
|
|
9351
|
+
hoursWalked: number().int().nonnegative(),
|
|
9352
|
+
hoursMissing: number().int().nonnegative(),
|
|
9353
|
+
ghostSegments: number().int().nonnegative(),
|
|
9354
|
+
ghostBytes: number().int().nonnegative(),
|
|
9355
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9356
|
+
orphanFiles: number().int().nonnegative()
|
|
9357
|
+
});
|
|
9358
|
+
/**
|
|
9359
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
9360
|
+
*
|
|
9361
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
9362
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
9363
|
+
* than in the absence of one.
|
|
9364
|
+
*/
|
|
9365
|
+
var LedgerWalkReportSchema = object({
|
|
9366
|
+
locationId: string(),
|
|
9367
|
+
applied: boolean(),
|
|
9368
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
9369
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
9370
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
9371
|
+
hoursClaimed: number().int().nonnegative(),
|
|
9372
|
+
hoursWalked: number().int().nonnegative(),
|
|
9373
|
+
hoursMissing: number().int().nonnegative(),
|
|
9374
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
9375
|
+
listings: number().int().nonnegative(),
|
|
9376
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
9377
|
+
ghostSegments: number().int().nonnegative(),
|
|
9378
|
+
ghostBytes: number().int().nonnegative(),
|
|
9379
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
9380
|
+
forgottenSegments: number().int().nonnegative(),
|
|
9381
|
+
forgottenBytes: number().int().nonnegative(),
|
|
9382
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
9383
|
+
orphanFiles: number().int().nonnegative(),
|
|
9384
|
+
orphanSample: array(string()).readonly(),
|
|
9385
|
+
hoursSkipped: number().int().nonnegative(),
|
|
9386
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
9387
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
9388
|
+
bounded: boolean(),
|
|
9389
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
9390
|
+
});
|
|
9260
9391
|
/** How many rows a media pass would still act on against a given target — the
|
|
9261
9392
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
9262
9393
|
* never disagree. `null` = the count could not be taken. */
|
|
9263
9394
|
var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
|
|
9264
9395
|
var RelocatableMediaCountInputSchema = object({
|
|
9265
9396
|
toLocationId: string().min(1),
|
|
9397
|
+
/** Restrict the count to one source; omitted / `'*'` = every non-target row. */
|
|
9398
|
+
fromLocationId: string().optional(),
|
|
9266
9399
|
/** Omitted = `move`. */
|
|
9267
9400
|
mode: MediaRelocateModeSchema.optional()
|
|
9268
9401
|
});
|
|
9269
9402
|
/**
|
|
9403
|
+
* Operator cleanup of leftover analytics rows, optional debug media, and
|
|
9404
|
+
* ghost ledger entries on frozen footage locations.
|
|
9405
|
+
*
|
|
9406
|
+
* A pass is tens of minutes on a standing backlog. The hub method RETURNS
|
|
9407
|
+
* `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
|
|
9408
|
+
* is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
|
|
9409
|
+
* with no operator-visible status.
|
|
9410
|
+
*/
|
|
9411
|
+
var StorageCleanupPhaseSchema = _enum([
|
|
9412
|
+
"orphans",
|
|
9413
|
+
"debug-media",
|
|
9414
|
+
"ghost-ledger",
|
|
9415
|
+
"done",
|
|
9416
|
+
"failed",
|
|
9417
|
+
"cancelled"
|
|
9418
|
+
]);
|
|
9419
|
+
var StorageCleanupInputSchema = object({
|
|
9420
|
+
/** Also walk motion stills / track filmstrips. Off by default. */
|
|
9421
|
+
includeDebugMedia: boolean().optional() });
|
|
9422
|
+
var StorageCleanupJobSchema = object({
|
|
9423
|
+
jobId: string(),
|
|
9424
|
+
phase: StorageCleanupPhaseSchema,
|
|
9425
|
+
includeDebugMedia: boolean(),
|
|
9426
|
+
orphansReclaimed: number().int().nonnegative(),
|
|
9427
|
+
orphanBytesReclaimed: number().int().nonnegative(),
|
|
9428
|
+
debugMediaReclaimed: number().int().nonnegative(),
|
|
9429
|
+
debugMediaBytesReclaimed: number().int().nonnegative(),
|
|
9430
|
+
ghostsForgotten: number().int().nonnegative(),
|
|
9431
|
+
ghostBytesForgotten: number().int().nonnegative(),
|
|
9432
|
+
/** Short operator-facing line: current collection, pass, or location. */
|
|
9433
|
+
detail: string().nullable(),
|
|
9434
|
+
cancelRequested: boolean(),
|
|
9435
|
+
startedAt: number(),
|
|
9436
|
+
updatedAt: number(),
|
|
9437
|
+
finishedAt: number().nullable(),
|
|
9438
|
+
error: string().nullable()
|
|
9439
|
+
});
|
|
9440
|
+
var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
|
|
9441
|
+
/**
|
|
9270
9442
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
9271
9443
|
* storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
|
|
9272
9444
|
* so the persisted record schema and the consumer-facing cap can both consume it
|
|
@@ -9294,11 +9466,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
|
9294
9466
|
* The default location for a type uses `id === <type>:default` by
|
|
9295
9467
|
* convention (the bare type ref like `'backups'` resolves to it).
|
|
9296
9468
|
*
|
|
9297
|
-
* `isSystem
|
|
9298
|
-
*
|
|
9299
|
-
*
|
|
9300
|
-
*
|
|
9301
|
-
* deleting it is rejected at the cap level.
|
|
9469
|
+
* `isSystem` is a legacy persisted flag. Seed still creates the initial
|
|
9470
|
+
* `<type>:default` locations; the flag is no longer a lock, a badge, or a
|
|
9471
|
+
* prune selector. New writes leave it false. Deletion is gated on uniqueness
|
|
9472
|
+
* / last-enabled, not on this bit.
|
|
9302
9473
|
*/
|
|
9303
9474
|
var StorageLocationSchema = object({
|
|
9304
9475
|
id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
|
|
@@ -19833,13 +20004,15 @@ var ListGroupsPageSchema = object({
|
|
|
19833
20004
|
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19834
20005
|
nextCursor: string().nullable()
|
|
19835
20006
|
});
|
|
20007
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
20008
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19836
20009
|
var KeyEventQueryInput = object({
|
|
19837
20010
|
deviceId: number(),
|
|
19838
20011
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19839
20012
|
since: number(),
|
|
19840
20013
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19841
20014
|
until: number(),
|
|
19842
|
-
limit: number().int().min(1).max(
|
|
20015
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19843
20016
|
/** Drop tracks scoring below this importance. */
|
|
19844
20017
|
minImportance: number().min(0).max(1).optional(),
|
|
19845
20018
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19861,6 +20034,32 @@ var KeyEventSchema = object({
|
|
|
19861
20034
|
...TrackFlagFields,
|
|
19862
20035
|
...TrackRetrainFields
|
|
19863
20036
|
});
|
|
20037
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
20038
|
+
var KeyEventBatchQueryInput = object({
|
|
20039
|
+
deviceIds: array(number()).min(1).max(200),
|
|
20040
|
+
since: number(),
|
|
20041
|
+
until: number(),
|
|
20042
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
20043
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
20044
|
+
* rows and change what the merged feed contains. */
|
|
20045
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
20046
|
+
minImportance: number().min(0).max(1).optional(),
|
|
20047
|
+
classFilter: string().optional()
|
|
20048
|
+
});
|
|
20049
|
+
/**
|
|
20050
|
+
* One camera's key events in a batch answer.
|
|
20051
|
+
*
|
|
20052
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
20053
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
20054
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
20055
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
20056
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
20057
|
+
* key, which only worked because there was one query per camera).
|
|
20058
|
+
*/
|
|
20059
|
+
var KeyEventsForDeviceSchema = object({
|
|
20060
|
+
deviceId: number(),
|
|
20061
|
+
events: array(KeyEventSchema).readonly()
|
|
20062
|
+
});
|
|
19864
20063
|
object({
|
|
19865
20064
|
trackId: string(),
|
|
19866
20065
|
className: string(),
|
|
@@ -20080,6 +20279,47 @@ var RebuildStatusSchema = object({
|
|
|
20080
20279
|
/** Present when the pass ended by throwing. */
|
|
20081
20280
|
error: string().nullable()
|
|
20082
20281
|
});
|
|
20282
|
+
/**
|
|
20283
|
+
* Acknowledgement that a debug-media reclaim STARTED.
|
|
20284
|
+
*
|
|
20285
|
+
* The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
|
|
20286
|
+
* it runs detached and this returns immediately. Awaiting it is how the
|
|
20287
|
+
* `addons.custom` door hit the 60 s UDS deadline while the walk carried on
|
|
20288
|
+
* with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
|
|
20289
|
+
*/
|
|
20290
|
+
var MediaReclaimStartResultSchema = object({
|
|
20291
|
+
started: boolean(),
|
|
20292
|
+
/** True when a pass was already running; the new request is ignored. */
|
|
20293
|
+
alreadyRunning: boolean()
|
|
20294
|
+
});
|
|
20295
|
+
var MediaReclaimInputSchema = object({
|
|
20296
|
+
mode: _enum(["report", "reclaim"]).default("report"),
|
|
20297
|
+
scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
|
|
20298
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
20299
|
+
restart: boolean().optional(),
|
|
20300
|
+
pageSize: number().int().min(50).max(5e3).optional(),
|
|
20301
|
+
maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
|
|
20302
|
+
maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
|
|
20303
|
+
maxBytesPerRun: number().int().min(1).optional(),
|
|
20304
|
+
budgetMinutes: number().int().min(1).max(720).optional(),
|
|
20305
|
+
throttleBytesPerSec: number().int().min(64 * 1024).optional(),
|
|
20306
|
+
graceMinutes: number().int().min(1).max(10080).optional()
|
|
20307
|
+
});
|
|
20308
|
+
var MediaReclaimStatusSchema = object({
|
|
20309
|
+
running: boolean(),
|
|
20310
|
+
mode: _enum(["report", "reclaim"]).nullable(),
|
|
20311
|
+
totalExamined: number(),
|
|
20312
|
+
totalEligible: number(),
|
|
20313
|
+
totalReclaimed: number(),
|
|
20314
|
+
totalBytesReclaimed: number(),
|
|
20315
|
+
totalRefused: number(),
|
|
20316
|
+
/** Device+scope windows finished in this pass. */
|
|
20317
|
+
devicesDone: number(),
|
|
20318
|
+
complete: boolean().nullable(),
|
|
20319
|
+
startedAtMs: number().nullable(),
|
|
20320
|
+
finishedAtMs: number().nullable(),
|
|
20321
|
+
error: string().nullable()
|
|
20322
|
+
});
|
|
20083
20323
|
var ReplayFrameInputSchema = object({
|
|
20084
20324
|
timestamp: number(),
|
|
20085
20325
|
frame: PipelineRunResultBridge
|
|
@@ -20130,7 +20370,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20130
20370
|
until: number().optional(),
|
|
20131
20371
|
kinds: array(string()).optional(),
|
|
20132
20372
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
20133
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
20373
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
20134
20374
|
deviceId: number(),
|
|
20135
20375
|
since: number(),
|
|
20136
20376
|
until: number(),
|
|
@@ -20221,6 +20461,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
20221
20461
|
}), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
|
|
20222
20462
|
kind: "query",
|
|
20223
20463
|
auth: "admin"
|
|
20464
|
+
}), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
|
|
20465
|
+
kind: "mutation",
|
|
20466
|
+
auth: "admin"
|
|
20467
|
+
}), method(object({}), MediaReclaimStatusSchema, {
|
|
20468
|
+
kind: "query",
|
|
20469
|
+
auth: "admin"
|
|
20224
20470
|
}), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
|
|
20225
20471
|
kind: "query",
|
|
20226
20472
|
auth: "admin"
|
|
@@ -22672,7 +22918,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
|
|
|
22672
22918
|
}), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
|
|
22673
22919
|
kind: "mutation",
|
|
22674
22920
|
auth: "admin"
|
|
22675
|
-
})
|
|
22921
|
+
}), method(StorageCleanupInputSchema, object({ jobId: string() }), {
|
|
22922
|
+
kind: "mutation",
|
|
22923
|
+
auth: "admin"
|
|
22924
|
+
}), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
22925
|
+
kind: "mutation",
|
|
22926
|
+
auth: "admin"
|
|
22927
|
+
}), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
|
|
22676
22928
|
var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
22677
22929
|
providerId: string().min(1),
|
|
22678
22930
|
displayName: string().min(1),
|
|
@@ -28050,6 +28302,33 @@ var RecordingRebalanceInputSchema = object({
|
|
|
28050
28302
|
minMoveGb: number().min(0).optional()
|
|
28051
28303
|
});
|
|
28052
28304
|
/**
|
|
28305
|
+
* Operator-facing placement of one camera onto a recordings location.
|
|
28306
|
+
*
|
|
28307
|
+
* `pinLocationId` is the operator instruction: a location id, or `null` for
|
|
28308
|
+
* Auto (the planner may move this camera). `locationId` is where high/mid
|
|
28309
|
+
* currently write — the plan, which may disagree with the pin when Auto.
|
|
28310
|
+
*/
|
|
28311
|
+
var RecordingDevicePlacementSchema = object({
|
|
28312
|
+
deviceId: number().int(),
|
|
28313
|
+
profile: string(),
|
|
28314
|
+
locationId: string()
|
|
28315
|
+
});
|
|
28316
|
+
var RecordingDevicePinSchema = object({
|
|
28317
|
+
deviceId: number().int(),
|
|
28318
|
+
/** Recordings-class location this camera is pinned to. */
|
|
28319
|
+
locationId: string()
|
|
28320
|
+
});
|
|
28321
|
+
var RecordingPlacementViewSchema = object({
|
|
28322
|
+
assignments: array(RecordingDevicePlacementSchema),
|
|
28323
|
+
pins: array(RecordingDevicePinSchema),
|
|
28324
|
+
defaultLocations: record(string(), string())
|
|
28325
|
+
});
|
|
28326
|
+
var RecordingSetDevicePlacementInputSchema = object({
|
|
28327
|
+
deviceId: number().int(),
|
|
28328
|
+
/** `null` = Auto. Otherwise a `recordings:*` location id. */
|
|
28329
|
+
locationId: string().nullable()
|
|
28330
|
+
});
|
|
28331
|
+
/**
|
|
28053
28332
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
28054
28333
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
28055
28334
|
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
@@ -28263,6 +28542,9 @@ method(object({
|
|
|
28263
28542
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28264
28543
|
kind: "query",
|
|
28265
28544
|
auth: "admin"
|
|
28545
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
28546
|
+
kind: "mutation",
|
|
28547
|
+
auth: "admin"
|
|
28266
28548
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28267
28549
|
kind: "mutation",
|
|
28268
28550
|
auth: "admin"
|
|
@@ -28272,6 +28554,12 @@ method(object({
|
|
|
28272
28554
|
}), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
|
|
28273
28555
|
kind: "mutation",
|
|
28274
28556
|
auth: "admin"
|
|
28557
|
+
}), method(object({}), RecordingPlacementViewSchema, {
|
|
28558
|
+
kind: "query",
|
|
28559
|
+
auth: "admin"
|
|
28560
|
+
}), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
|
|
28561
|
+
kind: "mutation",
|
|
28562
|
+
auth: "admin"
|
|
28275
28563
|
});
|
|
28276
28564
|
/**
|
|
28277
28565
|
* `recording-export` cap — render a footage time range into a single downloadable
|
|
@@ -28654,7 +28942,26 @@ var SceneMonitorStatusSchema = object({
|
|
|
28654
28942
|
monitors: array(SceneMonitorSchema),
|
|
28655
28943
|
lastFetchedAt: number()
|
|
28656
28944
|
});
|
|
28657
|
-
|
|
28945
|
+
/**
|
|
28946
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
28947
|
+
*
|
|
28948
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
28949
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
28950
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
28951
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
28952
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
28953
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
28954
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
28955
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
28956
|
+
*
|
|
28957
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
28958
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
28959
|
+
*/
|
|
28960
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
28961
|
+
deviceId: number(),
|
|
28962
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
28963
|
+
});
|
|
28964
|
+
DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
|
|
28658
28965
|
deviceId: number(),
|
|
28659
28966
|
label: string(),
|
|
28660
28967
|
roi: MaskRectShapeSchema,
|
|
@@ -29978,6 +30285,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
29978
30285
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
29979
30286
|
});
|
|
29980
30287
|
/**
|
|
30288
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
30289
|
+
*
|
|
30290
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
30291
|
+
* because `snapshot: null` was already spoken for:
|
|
30292
|
+
*
|
|
30293
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
30294
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
30295
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
30296
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
30297
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
30298
|
+
*
|
|
30299
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
30300
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
30301
|
+
* which is a definite claim about a camera nobody could read.
|
|
30302
|
+
*/
|
|
30303
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
30304
|
+
deviceId: number(),
|
|
30305
|
+
read: _enum(["read", "unreadable"]),
|
|
30306
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
30307
|
+
});
|
|
30308
|
+
/**
|
|
29981
30309
|
* Time-series resolution. The history methods return one bucket per
|
|
29982
30310
|
* step over the requested range. Smaller resolutions cost more
|
|
29983
30311
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -30001,7 +30329,7 @@ var HistoryPointSchema = object({
|
|
|
30001
30329
|
/** Object count averaged over the bucket (rounded to nearest integer). */
|
|
30002
30330
|
count: number().int().nonnegative()
|
|
30003
30331
|
});
|
|
30004
|
-
DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
|
|
30332
|
+
DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
|
|
30005
30333
|
deviceId: number(),
|
|
30006
30334
|
zoneId: string(),
|
|
30007
30335
|
className: string().optional()
|
|
@@ -33390,6 +33718,18 @@ Object.freeze({
|
|
|
33390
33718
|
addonId: null,
|
|
33391
33719
|
access: "view"
|
|
33392
33720
|
},
|
|
33721
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
33722
|
+
capName: "pipeline-analytics",
|
|
33723
|
+
capScope: "device",
|
|
33724
|
+
addonId: null,
|
|
33725
|
+
access: "view"
|
|
33726
|
+
},
|
|
33727
|
+
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
33728
|
+
capName: "pipeline-analytics",
|
|
33729
|
+
capScope: "device",
|
|
33730
|
+
addonId: null,
|
|
33731
|
+
access: "view"
|
|
33732
|
+
},
|
|
33393
33733
|
"pipelineAnalytics.getMotionEvents": {
|
|
33394
33734
|
capName: "pipeline-analytics",
|
|
33395
33735
|
capScope: "device",
|
|
@@ -33564,6 +33904,12 @@ Object.freeze({
|
|
|
33564
33904
|
addonId: null,
|
|
33565
33905
|
access: "create"
|
|
33566
33906
|
},
|
|
33907
|
+
"pipelineAnalytics.reclaimDebugMedia": {
|
|
33908
|
+
capName: "pipeline-analytics",
|
|
33909
|
+
capScope: "device",
|
|
33910
|
+
addonId: null,
|
|
33911
|
+
access: "create"
|
|
33912
|
+
},
|
|
33567
33913
|
"pipelineAnalytics.reconcileFromDisk": {
|
|
33568
33914
|
capName: "pipeline-analytics",
|
|
33569
33915
|
capScope: "device",
|
|
@@ -34488,6 +34834,12 @@ Object.freeze({
|
|
|
34488
34834
|
addonId: null,
|
|
34489
34835
|
access: "view"
|
|
34490
34836
|
},
|
|
34837
|
+
"recording.getPlacement": {
|
|
34838
|
+
capName: "recording",
|
|
34839
|
+
capScope: "system",
|
|
34840
|
+
addonId: null,
|
|
34841
|
+
access: "view"
|
|
34842
|
+
},
|
|
34491
34843
|
"recording.getPlaybackManifest": {
|
|
34492
34844
|
capName: "recording",
|
|
34493
34845
|
capScope: "system",
|
|
@@ -34566,6 +34918,12 @@ Object.freeze({
|
|
|
34566
34918
|
addonId: null,
|
|
34567
34919
|
access: "view"
|
|
34568
34920
|
},
|
|
34921
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
34922
|
+
capName: "recording",
|
|
34923
|
+
capScope: "system",
|
|
34924
|
+
addonId: null,
|
|
34925
|
+
access: "create"
|
|
34926
|
+
},
|
|
34569
34927
|
"recording.refreshStorageLocationsForMigration": {
|
|
34570
34928
|
capName: "recording",
|
|
34571
34929
|
capScope: "system",
|
|
@@ -34608,6 +34966,12 @@ Object.freeze({
|
|
|
34608
34966
|
addonId: null,
|
|
34609
34967
|
access: "create"
|
|
34610
34968
|
},
|
|
34969
|
+
"recording.setDevicePlacement": {
|
|
34970
|
+
capName: "recording",
|
|
34971
|
+
capScope: "system",
|
|
34972
|
+
addonId: null,
|
|
34973
|
+
access: "create"
|
|
34974
|
+
},
|
|
34611
34975
|
"recording.startStorageMigrationMove": {
|
|
34612
34976
|
capName: "recording",
|
|
34613
34977
|
capScope: "system",
|
|
@@ -34692,6 +35056,12 @@ Object.freeze({
|
|
|
34692
35056
|
addonId: null,
|
|
34693
35057
|
access: "view"
|
|
34694
35058
|
},
|
|
35059
|
+
"sceneMonitor.listScenesBatch": {
|
|
35060
|
+
capName: "scene-monitor",
|
|
35061
|
+
capScope: "device",
|
|
35062
|
+
addonId: null,
|
|
35063
|
+
access: "view"
|
|
35064
|
+
},
|
|
34695
35065
|
"sceneMonitor.recheckNow": {
|
|
34696
35066
|
capName: "scene-monitor",
|
|
34697
35067
|
capScope: "device",
|
|
@@ -35046,12 +35416,36 @@ Object.freeze({
|
|
|
35046
35416
|
addonId: null,
|
|
35047
35417
|
access: "create"
|
|
35048
35418
|
},
|
|
35419
|
+
"storageMigration.cleanupCancel": {
|
|
35420
|
+
capName: "storage-migration",
|
|
35421
|
+
capScope: "system",
|
|
35422
|
+
addonId: null,
|
|
35423
|
+
access: "create"
|
|
35424
|
+
},
|
|
35425
|
+
"storageMigration.cleanupStart": {
|
|
35426
|
+
capName: "storage-migration",
|
|
35427
|
+
capScope: "system",
|
|
35428
|
+
addonId: null,
|
|
35429
|
+
access: "create"
|
|
35430
|
+
},
|
|
35431
|
+
"storageMigration.cleanupStatus": {
|
|
35432
|
+
capName: "storage-migration",
|
|
35433
|
+
capScope: "system",
|
|
35434
|
+
addonId: null,
|
|
35435
|
+
access: "view"
|
|
35436
|
+
},
|
|
35049
35437
|
"storageMigration.drain": {
|
|
35050
35438
|
capName: "storage-migration",
|
|
35051
35439
|
capScope: "system",
|
|
35052
35440
|
addonId: null,
|
|
35053
35441
|
access: "create"
|
|
35054
35442
|
},
|
|
35443
|
+
"storageMigration.history": {
|
|
35444
|
+
capName: "storage-migration",
|
|
35445
|
+
capScope: "system",
|
|
35446
|
+
addonId: null,
|
|
35447
|
+
access: "view"
|
|
35448
|
+
},
|
|
35055
35449
|
"storageMigration.movers": {
|
|
35056
35450
|
capName: "storage-migration",
|
|
35057
35451
|
capScope: "system",
|
|
@@ -36048,6 +36442,12 @@ Object.freeze({
|
|
|
36048
36442
|
addonId: null,
|
|
36049
36443
|
access: "view"
|
|
36050
36444
|
},
|
|
36445
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
36446
|
+
capName: "zone-analytics",
|
|
36447
|
+
capScope: "device",
|
|
36448
|
+
addonId: null,
|
|
36449
|
+
access: "view"
|
|
36450
|
+
},
|
|
36051
36451
|
"zoneAnalytics.getUnzonedHistory": {
|
|
36052
36452
|
capName: "zone-analytics",
|
|
36053
36453
|
capScope: "device",
|
|
@@ -37052,6 +37452,11 @@ Object.freeze({
|
|
|
37052
37452
|
form: "single",
|
|
37053
37453
|
optional: false
|
|
37054
37454
|
}],
|
|
37455
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
37456
|
+
name: "deviceIds",
|
|
37457
|
+
form: "array",
|
|
37458
|
+
optional: false
|
|
37459
|
+
}],
|
|
37055
37460
|
"pipelineAnalytics.getMotionEvents": [{
|
|
37056
37461
|
name: "deviceId",
|
|
37057
37462
|
form: "single",
|
|
@@ -37157,6 +37562,11 @@ Object.freeze({
|
|
|
37157
37562
|
form: "single",
|
|
37158
37563
|
optional: true
|
|
37159
37564
|
}],
|
|
37565
|
+
"pipelineAnalytics.reclaimDebugMedia": [{
|
|
37566
|
+
name: "deviceIds",
|
|
37567
|
+
form: "array",
|
|
37568
|
+
optional: true
|
|
37569
|
+
}],
|
|
37160
37570
|
"pipelineAnalytics.reconcileFromDisk": [{
|
|
37161
37571
|
name: "deviceId",
|
|
37162
37572
|
form: "single",
|
|
@@ -37492,6 +37902,11 @@ Object.freeze({
|
|
|
37492
37902
|
form: "single",
|
|
37493
37903
|
optional: false
|
|
37494
37904
|
}],
|
|
37905
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
37906
|
+
name: "deviceId",
|
|
37907
|
+
form: "single",
|
|
37908
|
+
optional: true
|
|
37909
|
+
}],
|
|
37495
37910
|
"recording.relocateFootage": [{
|
|
37496
37911
|
name: "deviceId",
|
|
37497
37912
|
form: "single",
|
|
@@ -37517,6 +37932,11 @@ Object.freeze({
|
|
|
37517
37932
|
form: "single",
|
|
37518
37933
|
optional: false
|
|
37519
37934
|
}],
|
|
37935
|
+
"recording.setDevicePlacement": [{
|
|
37936
|
+
name: "deviceId",
|
|
37937
|
+
form: "single",
|
|
37938
|
+
optional: false
|
|
37939
|
+
}],
|
|
37520
37940
|
"recording.startStorageMigrationMove": [{
|
|
37521
37941
|
name: "deviceId",
|
|
37522
37942
|
form: "single",
|
|
@@ -37557,6 +37977,11 @@ Object.freeze({
|
|
|
37557
37977
|
form: "single",
|
|
37558
37978
|
optional: false
|
|
37559
37979
|
}],
|
|
37980
|
+
"sceneMonitor.listScenesBatch": [{
|
|
37981
|
+
name: "deviceIds",
|
|
37982
|
+
form: "array",
|
|
37983
|
+
optional: false
|
|
37984
|
+
}],
|
|
37560
37985
|
"sceneMonitor.recheckNow": [{
|
|
37561
37986
|
name: "deviceId",
|
|
37562
37987
|
form: "single",
|
|
@@ -37818,6 +38243,11 @@ Object.freeze({
|
|
|
37818
38243
|
form: "single",
|
|
37819
38244
|
optional: false
|
|
37820
38245
|
}],
|
|
38246
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
38247
|
+
name: "deviceIds",
|
|
38248
|
+
form: "array",
|
|
38249
|
+
optional: false
|
|
38250
|
+
}],
|
|
37821
38251
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
37822
38252
|
name: "deviceId",
|
|
37823
38253
|
form: "single",
|
|
@@ -40927,6 +41357,124 @@ function composeCameraStatus(input) {
|
|
|
40927
41357
|
};
|
|
40928
41358
|
}
|
|
40929
41359
|
//#endregion
|
|
41360
|
+
//#region src/camera-status/fleet-read-pass.ts
|
|
41361
|
+
/**
|
|
41362
|
+
* Keep a rejection from becoming an UNHANDLED one.
|
|
41363
|
+
*
|
|
41364
|
+
* A shared promise is created by the first camera to ask and consumed by the
|
|
41365
|
+
* others later — `listBindableCapsForDeviceType` is reached in stage 2 of
|
|
41366
|
+
* `readSwitchAuthorities`, after an await, so two cameras genuinely attach
|
|
41367
|
+
* their handlers in different ticks. Without this the first rejection would be
|
|
41368
|
+
* unhandled for the window in between.
|
|
41369
|
+
*
|
|
41370
|
+
* The handler is a no-op ON PURPOSE: it never becomes the answer. Every real
|
|
41371
|
+
* consumer attaches its own `.catch` and reports the failure per camera.
|
|
41372
|
+
*/
|
|
41373
|
+
function keepAlive(p) {
|
|
41374
|
+
p.catch(() => void 0);
|
|
41375
|
+
return p;
|
|
41376
|
+
}
|
|
41377
|
+
var FleetReadPass = class {
|
|
41378
|
+
deps;
|
|
41379
|
+
roster = null;
|
|
41380
|
+
bindings = null;
|
|
41381
|
+
mutes = null;
|
|
41382
|
+
bindableByType = /* @__PURE__ */ new Map();
|
|
41383
|
+
slotsByNode = /* @__PURE__ */ new Map();
|
|
41384
|
+
rtspByNode = /* @__PURE__ */ new Map();
|
|
41385
|
+
provisioningByNode = /* @__PURE__ */ new Map();
|
|
41386
|
+
selectedEngineByNode = /* @__PURE__ */ new Map();
|
|
41387
|
+
constructor(deps) {
|
|
41388
|
+
this.deps = deps;
|
|
41389
|
+
}
|
|
41390
|
+
/** The cameras this pass covers — the set both batch reads are issued for. */
|
|
41391
|
+
get deviceIds() {
|
|
41392
|
+
return this.deps.deviceIds;
|
|
41393
|
+
}
|
|
41394
|
+
/**
|
|
41395
|
+
* The device rows for the pass's set.
|
|
41396
|
+
*
|
|
41397
|
+
* **The shape follows the SET, not the caller.** A pass of one asks
|
|
41398
|
+
* `getDevice` — the exact question, and the one the write path
|
|
41399
|
+
* (`CameraSwitchService.setCameraSwitch`) has always asked. A pass of many
|
|
41400
|
+
* asks `listAll({deviceIds})`, which exists precisely because "these N" was
|
|
41401
|
+
* otherwise either N round trips or the whole fleet. Answering a set of one
|
|
41402
|
+
* with the fleet-shaped method would be no cheaper and would move the write
|
|
41403
|
+
* path onto a read it does not need.
|
|
41404
|
+
*
|
|
41405
|
+
* `projection: 'slim'` because the only fields read off it are `type` and
|
|
41406
|
+
* `disabled`: the full projection reads each device's settings row, which is
|
|
41407
|
+
* the per-device round trip this whole change exists to remove.
|
|
41408
|
+
*/
|
|
41409
|
+
deviceRoster(api) {
|
|
41410
|
+
const single = this.deps.deviceIds.length === 1 ? this.deps.deviceIds[0] : void 0;
|
|
41411
|
+
const source = single === void 0 ? "deviceManager.listAll" : "deviceManager.getDevice";
|
|
41412
|
+
if (this.roster === null) this.roster = keepAlive(single === void 0 ? api.deviceManager.listAll.query({
|
|
41413
|
+
deviceIds: [...this.deps.deviceIds],
|
|
41414
|
+
projection: "slim"
|
|
41415
|
+
}) : api.deviceManager.getDevice.query({ deviceId: single }).then((row) => row === null ? [] : [row]));
|
|
41416
|
+
return {
|
|
41417
|
+
source,
|
|
41418
|
+
result: this.roster
|
|
41419
|
+
};
|
|
41420
|
+
}
|
|
41421
|
+
/** The binding rows for the pass's set — `getBindings` for one, the batch for many. */
|
|
41422
|
+
bindingRows(api) {
|
|
41423
|
+
const single = this.deps.deviceIds.length === 1 ? this.deps.deviceIds[0] : void 0;
|
|
41424
|
+
const source = single === void 0 ? "deviceManager.getBindingsBatch" : "deviceManager.getBindings";
|
|
41425
|
+
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]));
|
|
41426
|
+
return {
|
|
41427
|
+
source,
|
|
41428
|
+
result: this.bindings
|
|
41429
|
+
};
|
|
41430
|
+
}
|
|
41431
|
+
/** The fleet's muted-camera list. One answer for every camera in the pass. */
|
|
41432
|
+
mutedDevices(api) {
|
|
41433
|
+
if (this.mutes === null) this.mutes = keepAlive(api.notificationRules.listDeviceMutes.query({}));
|
|
41434
|
+
return this.mutes;
|
|
41435
|
+
}
|
|
41436
|
+
/** The bindable caps for one device TYPE. Every camera shares one answer. */
|
|
41437
|
+
bindableCapsFor(api, deviceType) {
|
|
41438
|
+
const existing = this.bindableByType.get(deviceType);
|
|
41439
|
+
if (existing !== void 0) return existing;
|
|
41440
|
+
const created = keepAlive(api.deviceManager.listBindableCapsForDeviceType.query({ deviceType }));
|
|
41441
|
+
this.bindableByType.set(deviceType, created);
|
|
41442
|
+
return created;
|
|
41443
|
+
}
|
|
41444
|
+
/** One source node's whole profile-slot table. */
|
|
41445
|
+
profileSlots(api, sourceNodeId) {
|
|
41446
|
+
const existing = this.slotsByNode.get(sourceNodeId);
|
|
41447
|
+
if (existing !== void 0) return existing;
|
|
41448
|
+
const created = keepAlive(api.streamBroker.listAllProfileSlots.query(void 0, nodePin(sourceNodeId)));
|
|
41449
|
+
this.slotsByNode.set(sourceNodeId, created);
|
|
41450
|
+
return created;
|
|
41451
|
+
}
|
|
41452
|
+
/** One source node's whole RTSP-restream table. */
|
|
41453
|
+
rtspEntries(api, sourceNodeId) {
|
|
41454
|
+
const existing = this.rtspByNode.get(sourceNodeId);
|
|
41455
|
+
if (existing !== void 0) return existing;
|
|
41456
|
+
const created = keepAlive(api.streamBroker.getAllRtspEntries.query({}, nodePin(sourceNodeId)));
|
|
41457
|
+
this.rtspByNode.set(sourceNodeId, created);
|
|
41458
|
+
return created;
|
|
41459
|
+
}
|
|
41460
|
+
/** One detection node's runtime-provisioning snapshot. */
|
|
41461
|
+
engineProvisioning(api, detectionNodeId) {
|
|
41462
|
+
const existing = this.provisioningByNode.get(detectionNodeId);
|
|
41463
|
+
if (existing !== void 0) return existing;
|
|
41464
|
+
const created = keepAlive(api.pipelineExecutor.getEngineProvisioning.query({ nodeId: detectionNodeId }));
|
|
41465
|
+
this.provisioningByNode.set(detectionNodeId, created);
|
|
41466
|
+
return created;
|
|
41467
|
+
}
|
|
41468
|
+
/** The executor's bootstrap engine, as the detection stage reads it. */
|
|
41469
|
+
selectedEngine(api, detectionNodeId) {
|
|
41470
|
+
const existing = this.selectedEngineByNode.get(detectionNodeId);
|
|
41471
|
+
if (existing !== void 0) return existing;
|
|
41472
|
+
const created = keepAlive(api.pipelineExecutor.getSelectedEngine.query({ nodeId: detectionNodeId }));
|
|
41473
|
+
this.selectedEngineByNode.set(detectionNodeId, created);
|
|
41474
|
+
return created;
|
|
41475
|
+
}
|
|
41476
|
+
};
|
|
41477
|
+
//#endregion
|
|
40930
41478
|
//#region src/camera-status-service.ts
|
|
40931
41479
|
/** WebRTC consumer kinds counted toward `BrokerResult.webrtcSessions`. */
|
|
40932
41480
|
var WEBRTC_KINDS = new Set([
|
|
@@ -41115,7 +41663,7 @@ var CameraStatusService = class {
|
|
|
41115
41663
|
* broker's actual decode-session node into `liveDecoder` (T6) — the first
|
|
41116
41664
|
* slot that reports one wins.
|
|
41117
41665
|
*/
|
|
41118
|
-
buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, liveDecoder, sink) {
|
|
41666
|
+
buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, sink) {
|
|
41119
41667
|
if (!api || !allSlotsFetch) return Promise.resolve(null);
|
|
41120
41668
|
return this.boundedStage(allSlotsFetch.then(async (slots) => {
|
|
41121
41669
|
const deviceSlots = slots.filter((s) => s.deviceId === deviceId);
|
|
@@ -41125,12 +41673,13 @@ var CameraStatusService = class {
|
|
|
41125
41673
|
rtspRestream: false
|
|
41126
41674
|
};
|
|
41127
41675
|
const [statsAndClients, rtspEntry] = await Promise.all([Promise.all(deviceSlots.map(async (slot) => {
|
|
41676
|
+
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)]);
|
|
41128
41677
|
return {
|
|
41129
41678
|
slot,
|
|
41130
|
-
stats
|
|
41131
|
-
clients
|
|
41679
|
+
stats,
|
|
41680
|
+
clients
|
|
41132
41681
|
};
|
|
41133
|
-
})),
|
|
41682
|
+
})), pass.rtspEntries(api, sourceNodeId).catch(() => null)]);
|
|
41134
41683
|
const profileDetails = statsAndClients.map(({ slot, stats, clients }) => ({
|
|
41135
41684
|
profile: slot.profile,
|
|
41136
41685
|
status: slot.status,
|
|
@@ -41191,13 +41740,16 @@ var CameraStatusService = class {
|
|
|
41191
41740
|
};
|
|
41192
41741
|
}
|
|
41193
41742
|
/** Detection stage (pipeline-executor + runner metrics). */
|
|
41194
|
-
buildDetectionStage(api, detectionNodeId, deviceId, sink) {
|
|
41743
|
+
buildDetectionStage(api, detectionNodeId, deviceId, pass, sink) {
|
|
41195
41744
|
if (!api || !detectionNodeId) return Promise.resolve(null);
|
|
41196
|
-
return this.boundedStage(Promise.all([
|
|
41197
|
-
|
|
41745
|
+
return this.boundedStage(Promise.all([
|
|
41746
|
+
pass.engineProvisioning(api, detectionNodeId).catch(() => null),
|
|
41747
|
+
pass.selectedEngine(api, detectionNodeId).catch(() => null),
|
|
41748
|
+
api.pipelineRunner.getCameraMetrics.query({
|
|
41198
41749
|
deviceId,
|
|
41199
41750
|
nodeId: detectionNodeId
|
|
41200
|
-
}).catch(() => null)
|
|
41751
|
+
}).catch(() => null)
|
|
41752
|
+
]).then(async ([provisioning, engine, metrics]) => {
|
|
41201
41753
|
const phase = (() => {
|
|
41202
41754
|
const p = metrics?.phase;
|
|
41203
41755
|
if (p === "active") return "active";
|
|
@@ -41309,9 +41861,9 @@ var CameraStatusService = class {
|
|
|
41309
41861
|
* badge on a working camera); what matters is that the emptiness travels with
|
|
41310
41862
|
* the reason it is empty.
|
|
41311
41863
|
*/
|
|
41312
|
-
buildSwitchStage(deviceId, sink) {
|
|
41864
|
+
buildSwitchStage(deviceId, pass, sink) {
|
|
41313
41865
|
const startedAt = Date.now();
|
|
41314
|
-
return this.boundedStage(this.deps.switchAuthoritiesFor(deviceId), STAGE_TIMEOUT_MS, "switches", deviceId, sink).then((reads) => {
|
|
41866
|
+
return this.boundedStage(this.deps.switchAuthoritiesFor(deviceId, pass), STAGE_TIMEOUT_MS, "switches", deviceId, sink).then((reads) => {
|
|
41315
41867
|
if (reads === null) return [];
|
|
41316
41868
|
const { switchedOff, unreadable } = composeSwitchedOff(reads);
|
|
41317
41869
|
if (unreadable.length > 0) this.recordDegraded(sink, deviceId, "switches", "partial", Date.now() - startedAt, { unreadableAuthorities: unreadable });
|
|
@@ -41329,18 +41881,28 @@ var CameraStatusService = class {
|
|
|
41329
41881
|
* `null` of a camera that legitimately has no such stage.
|
|
41330
41882
|
*/
|
|
41331
41883
|
async getCameraStatus(deviceId) {
|
|
41884
|
+
return this.composeOne(deviceId, new FleetReadPass({ deviceIds: [deviceId] }));
|
|
41885
|
+
}
|
|
41886
|
+
/**
|
|
41887
|
+
* One camera's status, composed WITHIN a pass.
|
|
41888
|
+
*
|
|
41889
|
+
* Everything the camera alone can answer is fetched here; everything the pass
|
|
41890
|
+
* already knows (a node's slot table, the fleet's mute list, a device type's
|
|
41891
|
+
* bindable caps) is asked of `pass`, which issues it once for the whole call.
|
|
41892
|
+
*/
|
|
41893
|
+
async composeOne(deviceId, pass) {
|
|
41332
41894
|
const api = this.deps.api();
|
|
41333
41895
|
const degradations = { entries: [] };
|
|
41334
41896
|
const { detectionNodeId, sourceNodeId, pinned, detectionReason, audioNodeId, audioPinned } = this.buildAssignmentContext(deviceId);
|
|
41335
41897
|
const liveDecoder = { nodeId: null };
|
|
41336
|
-
const allSlotsFetch = api ?
|
|
41898
|
+
const allSlotsFetch = api ? pass.profileSlots(api, sourceNodeId) : null;
|
|
41337
41899
|
const sourceFetch = this.buildSourceStage(api, allSlotsFetch, deviceId, degradations);
|
|
41338
|
-
const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, liveDecoder, degradations);
|
|
41900
|
+
const brokerFetch = this.buildBrokerStage(api, allSlotsFetch, deviceId, sourceNodeId, pass, liveDecoder, degradations);
|
|
41339
41901
|
const decoderFetch = this.buildDecoderStage(detectionNodeId);
|
|
41340
41902
|
const motionResult = this.buildMotionStage(deviceId);
|
|
41341
|
-
const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, degradations);
|
|
41903
|
+
const detectionFetch = this.buildDetectionStage(api, detectionNodeId, deviceId, pass, degradations);
|
|
41342
41904
|
const recordingFetch = this.buildRecordingStage(api, deviceId, degradations);
|
|
41343
|
-
const switchesFetch = this.buildSwitchStage(deviceId, degradations);
|
|
41905
|
+
const switchesFetch = this.buildSwitchStage(deviceId, pass, degradations);
|
|
41344
41906
|
const [sourceResult, brokerResult, decoderResult, detectionResult, recordingResult, switchedOff] = await Promise.all([
|
|
41345
41907
|
sourceFetch,
|
|
41346
41908
|
brokerFetch,
|
|
@@ -41383,15 +41945,30 @@ var CameraStatusService = class {
|
|
|
41383
41945
|
* `deviceIds` defaults to all cameras currently tracked by the
|
|
41384
41946
|
* orchestrator's assignment map when omitted.
|
|
41385
41947
|
*
|
|
41386
|
-
*
|
|
41387
|
-
*
|
|
41388
|
-
*
|
|
41389
|
-
*
|
|
41390
|
-
*
|
|
41948
|
+
* The whole call runs inside ONE {@link FleetReadPass}, and that is what
|
|
41949
|
+
* makes a five-second poll affordable. Composing a camera reaches seven
|
|
41950
|
+
* stages, but six of the questions those stages ask are about a NODE, a
|
|
41951
|
+
* device TYPE or the whole FLEET — the slot table, the restream table, the
|
|
41952
|
+
* mute list, the engine pair, the bindable caps — and asking them per camera
|
|
41953
|
+
* meant N identical round trips for N identical answers. Two more (the device
|
|
41954
|
+
* row, the binding table) are per-camera questions the device manager already
|
|
41955
|
+
* answers for a set. The pass asks each exactly once and shares the promise;
|
|
41956
|
+
* it holds nothing between calls, so the next poll asks again from scratch.
|
|
41957
|
+
*
|
|
41958
|
+
* What is left is genuinely per camera and stays per camera: the broker slot
|
|
41959
|
+
* stats, the runner metrics, the recorder, the broker mute and the camera's
|
|
41960
|
+
* own privacy provider. Collapsing any of those would be a narrower answer,
|
|
41961
|
+
* not a cheaper one.
|
|
41962
|
+
*
|
|
41963
|
+
* `Promise.all` over per-device composition (no concurrency cap). The fan-out
|
|
41964
|
+
* is now ~8 round trips per camera rather than ~16, and the six fleet reads
|
|
41965
|
+
* no longer multiply — which is the part that made the count grow with the
|
|
41966
|
+
* fleet twice over.
|
|
41391
41967
|
*/
|
|
41392
41968
|
async getCameraStatuses(deviceIds) {
|
|
41393
41969
|
const ids = deviceIds !== void 0 && deviceIds.length > 0 ? deviceIds : this.deps.listAssignedDeviceIds();
|
|
41394
|
-
|
|
41970
|
+
const pass = new FleetReadPass({ deviceIds: ids });
|
|
41971
|
+
return Promise.all(ids.map((deviceId) => this.composeOne(deviceId, pass)));
|
|
41395
41972
|
}
|
|
41396
41973
|
};
|
|
41397
41974
|
//#endregion
|
|
@@ -41640,10 +42217,15 @@ async function probeCamera(api, deviceId, deps) {
|
|
|
41640
42217
|
* @param deviceId The camera.
|
|
41641
42218
|
* @param deps Logger + source-owner resolver.
|
|
41642
42219
|
*/
|
|
41643
|
-
async function readSwitchAuthorities(api, deviceId, deps) {
|
|
42220
|
+
async function readSwitchAuthorities(api, deviceId, deps, sharedPass) {
|
|
41644
42221
|
if (!api) return unknownReads(deviceId);
|
|
41645
|
-
const
|
|
41646
|
-
|
|
42222
|
+
const pass = sharedPass ?? new FleetReadPass({ deviceIds: [deviceId] });
|
|
42223
|
+
const roster = pass.deviceRoster(api);
|
|
42224
|
+
const devicePromise = bounded(deps, deviceId, roster.source, roster.result.then((rows) => {
|
|
42225
|
+
const row = rows.find((d) => d.id === deviceId);
|
|
42226
|
+
return isDeviceShape(row) ? row : null;
|
|
42227
|
+
}).catch((err) => {
|
|
42228
|
+
warnUnreachable(deps, deviceId, roster.source, err);
|
|
41647
42229
|
return null;
|
|
41648
42230
|
}), null);
|
|
41649
42231
|
const unknownBindings = {
|
|
@@ -41651,11 +42233,14 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41651
42233
|
providerAddonIdByCap: /* @__PURE__ */ new Map(),
|
|
41652
42234
|
allCapNames: null
|
|
41653
42235
|
};
|
|
41654
|
-
const
|
|
42236
|
+
const bindingRead = pass.bindingRows(api);
|
|
42237
|
+
const bindingsPromise = bounded(deps, deviceId, bindingRead.source, bindingRead.result.then((rows) => {
|
|
42238
|
+
const row = rows.find((r) => r.deviceId === deviceId);
|
|
42239
|
+
if (row === void 0) return unknownBindings;
|
|
41655
42240
|
const active = [];
|
|
41656
42241
|
const all = [];
|
|
41657
42242
|
const providers = /* @__PURE__ */ new Map();
|
|
41658
|
-
for (const e of
|
|
42243
|
+
for (const e of row.entries) {
|
|
41659
42244
|
all.push(e.capName);
|
|
41660
42245
|
if (e.kind !== "wrapped") continue;
|
|
41661
42246
|
active.push(e.capName);
|
|
@@ -41667,14 +42252,14 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41667
42252
|
allCapNames: all
|
|
41668
42253
|
};
|
|
41669
42254
|
}).catch((err) => {
|
|
41670
|
-
warnUnreachable(deps, deviceId,
|
|
42255
|
+
warnUnreachable(deps, deviceId, bindingRead.source, err);
|
|
41671
42256
|
return unknownBindings;
|
|
41672
42257
|
}), unknownBindings);
|
|
41673
42258
|
const recordingPromise = bounded(deps, deviceId, "recording.getDeviceConfig", api.recording.getDeviceConfig.query({ deviceId }).then((c) => isRecordingConfig(c) ? c : null).catch((err) => {
|
|
41674
42259
|
warnUnreachable(deps, deviceId, "recording.getDeviceConfig", err);
|
|
41675
42260
|
return null;
|
|
41676
42261
|
}), null);
|
|
41677
|
-
const mutesPromise = bounded(deps, deviceId, "notificationRules.listDeviceMutes",
|
|
42262
|
+
const mutesPromise = bounded(deps, deviceId, "notificationRules.listDeviceMutes", pass.mutedDevices(api).then((r) => r.mutedDeviceIds).catch((err) => {
|
|
41678
42263
|
warnUnreachable(deps, deviceId, "notificationRules.listDeviceMutes", err);
|
|
41679
42264
|
return null;
|
|
41680
42265
|
}), null);
|
|
@@ -41689,7 +42274,7 @@ async function readSwitchAuthorities(api, deviceId, deps) {
|
|
|
41689
42274
|
mutesPromise,
|
|
41690
42275
|
brokerAudioPromise
|
|
41691
42276
|
]);
|
|
41692
|
-
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : bounded(deps, deviceId, "deviceManager.listBindableCapsForDeviceType",
|
|
42277
|
+
const [bindable, privacy] = await Promise.all([device === null ? Promise.resolve(null) : bounded(deps, deviceId, "deviceManager.listBindableCapsForDeviceType", pass.bindableCapsFor(api, device.type).catch((err) => {
|
|
41693
42278
|
warnUnreachable(deps, deviceId, "listBindableCapsForDeviceType", err);
|
|
41694
42279
|
return null;
|
|
41695
42280
|
}), null), readPrivacyPlanes(api, deviceId, deps, bindings.allCapNames)]);
|
|
@@ -50396,11 +50981,11 @@ async function buildOrchestratorControllers(deps) {
|
|
|
50396
50981
|
assignSource: (deviceId) => topology.assignSource(deviceId),
|
|
50397
50982
|
listAssignedDeviceIds: () => [...new Set([...ledger.listAssignedDeviceIds(), ...detectionWiring.activeDeviceIds()])],
|
|
50398
50983
|
isSessionCamera: (deviceId) => deps.isSessionCamera(deviceId),
|
|
50399
|
-
switchAuthoritiesFor: async (deviceId) => (await readSwitchAuthorities(deps.ctx().api ?? null, deviceId, {
|
|
50984
|
+
switchAuthoritiesFor: async (deviceId, pass) => (await readSwitchAuthorities(deps.ctx().api ?? null, deviceId, {
|
|
50400
50985
|
logger: deps.ctx().logger,
|
|
50401
50986
|
assignSource: (id) => topology.assignSource(id),
|
|
50402
50987
|
warnSampler: switchWarnSampler
|
|
50403
|
-
})).derivation
|
|
50988
|
+
}, pass)).derivation
|
|
50404
50989
|
});
|
|
50405
50990
|
const reconcile = new ReconcileController({
|
|
50406
50991
|
api: () => deps.ctx().api ?? null,
|