@camstack/addon-provider-wyze 0.2.47 → 0.2.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +333 -12
  2. package/dist/addon.mjs +333 -12
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -8196,13 +8196,49 @@ var StorageMigrationParticipantSchema = _enum([
8196
8196
  "recorder",
8197
8197
  "analytics"
8198
8198
  ]);
8199
+ /**
8200
+ * The mover's own numbers, folded onto the coordinator's durable move record.
8201
+ *
8202
+ * The long half of a non-blocking migration is `draining`, and it is measured
8203
+ * in hours: 136 885 files at ~4 MB/s is about five of them. Before this shape
8204
+ * existed the only place those numbers appeared was a Loki line, so an operator
8205
+ * watching the Admin UI saw `phase: draining` and nothing else for a whole
8206
+ * afternoon.
8207
+ *
8208
+ * It is POLLED, never pushed. Events are telemetry and may be dropped
8209
+ * (D8/D11), and a dropped progress event is indistinguishable from a stalled
8210
+ * mover — which is the exact failure this is meant to end. The coordinator's
8211
+ * `waitForMoves` already fetches the whole {@link RelocateJob} on every tick to
8212
+ * read `state`; folding the counters costs no extra read and makes the durable
8213
+ * record say afterwards how far a move actually got.
8214
+ *
8215
+ * `filesTotal` is `null` for "no honest denominator" and is never zero-filled:
8216
+ * a windowed footage job (`sinceMs`) and a node with no ledger both genuinely
8217
+ * cannot say M, and a 0 there would render as "100 % done".
8218
+ */
8219
+ var StorageMigrationMoveProgressSchema = object({
8220
+ filesMoved: number().int().nonnegative(),
8221
+ /** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
8222
+ filesTotal: number().int().nonnegative().nullable(),
8223
+ bytesMoved: number().int().nonnegative(),
8224
+ /** The MOVER's start, not the migration's: a drain restarted after an addon
8225
+ * crash gets a new mover, and a rate computed from the migration's start
8226
+ * would silently average in the time nothing was running. */
8227
+ startedAt: number(),
8228
+ /** When the coordinator last read these numbers. Paired with `startedAt` it
8229
+ * is the only honest rate: both clocks are the hub's, so a UI never has to
8230
+ * subtract its own. */
8231
+ observedAt: number()
8232
+ });
8199
8233
  var StorageMigrationMoveSchema = object({
8200
8234
  storageClass: StorageMigrationClassSchema,
8201
8235
  fromLocationId: string(),
8202
8236
  toLocationId: string(),
8203
8237
  moverJobId: string().nullable(),
8204
8238
  state: RelocateJobStateSchema.nullable(),
8205
- error: string().nullable()
8239
+ error: string().nullable(),
8240
+ /** Last observed mover counters; `null` until the mover has been polled once. */
8241
+ progress: StorageMigrationMoveProgressSchema.nullable()
8206
8242
  });
8207
8243
  var StorageMigrationJobSchema = object({
8208
8244
  jobId: string(),
@@ -8248,6 +8284,98 @@ var StorageMigrationPlanSchema = object({
8248
8284
  findings: array(StorageMigrationFindingSchema)
8249
8285
  });
8250
8286
  /**
8287
+ * A mover as it exists RIGHT NOW, whether or not a migration job owns it.
8288
+ *
8289
+ * The coordinator's job record is the state of record for a migration, and its
8290
+ * moves carry {@link StorageMigrationMoveProgress}. But the movers are usable
8291
+ * standalone — `recording.relocateFootage` and `pipelineAnalytics.relocateMedia`
8292
+ * are both operator-callable, and on 2026-08-29 a five-hour drain was armed that
8293
+ * way because no supported UI path existed. A mover armed like that has no job
8294
+ * to fold progress into, so it has to be readable on its own or it is invisible.
8295
+ *
8296
+ * `migrationJobId` is what tells the two apart: `null` means nothing here
8297
+ * orchestrated it.
8298
+ */
8299
+ var StorageMigrationMoverSchema = object({
8300
+ lane: _enum(["footage", "media"]),
8301
+ job: RelocateJobSchema,
8302
+ /** The coordinator job that armed this mover, or `null` for a mover armed
8303
+ * directly against the owning addon. */
8304
+ migrationJobId: string().nullable(),
8305
+ /** When the hub read these counters. Stamped here so a rate is `bytesMoved`
8306
+ * over (`observedAt` − `job.startedAt`) with BOTH ends on the hub's clock —
8307
+ * a browser subtracting its own `Date.now()` from a server `startedAt` is a
8308
+ * rate made of two different clocks. */
8309
+ observedAt: number()
8310
+ });
8311
+ /**
8312
+ * What a SOURCE still holds for one storage class — the number that makes a
8313
+ * "drain remaining" action honest rather than hopeful.
8314
+ *
8315
+ * It comes from the archive (`SegmentHourLedger.census` for footage, the media
8316
+ * engine's own selection count for media), never from the resident index: a
8317
+ * drain sized off `RecordingIndex` is what reported `done` over 80.3 GB it had
8318
+ * never been told about (D295).
8319
+ *
8320
+ * `items`/`bytes` are `null` for "the archive could not be asked", which is
8321
+ * deliberately NOT zero: a drain is still offered for an unknown residue,
8322
+ * because refusing on an unanswerable read would hide exactly the case an
8323
+ * operator needs to act on.
8324
+ */
8325
+ var StorageMigrationResidueSchema = object({
8326
+ storageClass: StorageMigrationClassSchema,
8327
+ /** The location still holding the data. `'*'` for the media lane, whose rows
8328
+ * move from wherever they are rather than from one named source. */
8329
+ fromLocationId: string(),
8330
+ /** Where a drain would move it — the class's CURRENT default. */
8331
+ toLocationId: string(),
8332
+ /** Segments (footage lane) or rows (media lane) still on the source. */
8333
+ items: number().int().nonnegative().nullable(),
8334
+ /** Bytes on the source; `null` when the lane counts rows rather than bytes. */
8335
+ bytes: number().int().nonnegative().nullable()
8336
+ });
8337
+ /**
8338
+ * Run the DRAIN half and nothing else.
8339
+ *
8340
+ * A migration that reached `done` has already repointed, so `start` correctly
8341
+ * refuses its destination ("already the default") — there is nothing left to
8342
+ * repoint. But the drain can fail, be cancelled, be interrupted by a restart,
8343
+ * or finish against a work list that was a tenth of the archive (D295), and
8344
+ * before this there was no supported way to run only that half: the only way
8345
+ * through was calling `recording.relocateFootage` by hand over admin tRPC.
8346
+ *
8347
+ * `drain` NEVER calls `setDefaultLocations`. That is what keeps `start`'s
8348
+ * refusal meaningful: the two verbs are disjoint, so nothing here can silently
8349
+ * re-repoint a class that is already migrated.
8350
+ */
8351
+ var StorageMigrationDrainInputSchema = object({
8352
+ /** The classes to drain. Each must appear in `storageMigration.residue`, so
8353
+ * a class whose source is already empty is refused rather than started. */
8354
+ classes: array(StorageMigrationClassSchema).min(1),
8355
+ throttleMbps: number().min(1).max(1e3).optional()
8356
+ });
8357
+ /** What a footage source still holds, asked of the durable hour ledger. */
8358
+ var RelocateResidueInputSchema = object({
8359
+ fromLocationId: string().min(1),
8360
+ /** Narrow to one logical class; omit for every profile on the location. */
8361
+ footageClass: RelocateFootageClassSchema.optional()
8362
+ });
8363
+ /** `null` = the archive could not answer (no ledger on this node, or the
8364
+ * aggregate failed). Never conflated with an empty source. */
8365
+ var RelocateResidueSchema = object({
8366
+ segments: number().int().nonnegative(),
8367
+ bytes: number().int().nonnegative()
8368
+ }).nullable();
8369
+ /** How many rows a media pass would still act on against a given target — the
8370
+ * media lane's denominator AND its residue, from ONE derivation so the two can
8371
+ * never disagree. `null` = the count could not be taken. */
8372
+ var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8373
+ var RelocatableMediaCountInputSchema = object({
8374
+ toLocationId: string().min(1),
8375
+ /** Omitted = `move`. */
8376
+ mode: MediaRelocateModeSchema.optional()
8377
+ });
8378
+ /**
8251
8379
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8252
8380
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8253
8381
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8351,6 +8479,32 @@ var StorageLocationRefSchema = union([StorageLocationTypeSchema, string().regex(
8351
8479
  * two addons declaring the same `id` must agree on `cardinality` (validated
8352
8480
  * at kernel aggregation time, not here).
8353
8481
  */
8482
+ /**
8483
+ * `StorageAccess` — how the service that DECLARED a storage-location kind
8484
+ * actually reaches the bytes. It is the constraint that decides which
8485
+ * `storage-provider`s may back a location of that kind.
8486
+ *
8487
+ * - `'local-path'` — the service asks `storage.resolve` for a path string and
8488
+ * then does its own `node:fs` I/O on it (the recorder's segment writer, the
8489
+ * post-analysis media roots). Only a provider that serves a genuine local
8490
+ * filesystem (`getProviderInfo().nodeLocal === true`) can satisfy that: a
8491
+ * remote provider's `resolve` returns a path on the REMOTE host, and
8492
+ * `fs.readdir` of it on this node either fails or — far worse — succeeds
8493
+ * against a same-named local directory that is something else entirely.
8494
+ *
8495
+ * - `'cap-mediated'` — every byte travels through the `storage` cap
8496
+ * (`read`/`write`, or `beginUpload`/`writeChunk`/`finalizeUpload`). The
8497
+ * service never sees a path, so any provider can back it. `backups` is the
8498
+ * one kind that qualifies today.
8499
+ *
8500
+ * Before this existed, `recordings` was unreachable by SFTP/S3/WebDAV only as
8501
+ * an EMERGENT property of how the recorder happened to be written. Nothing
8502
+ * refused the configuration; the first write simply went somewhere wrong, and
8503
+ * a recording write that goes wrong surfaces as a silent black window rather
8504
+ * than an error (the read path does not `stat`). This turns that accident into
8505
+ * a declared, enforced, testable refusal.
8506
+ */
8507
+ var StorageAccessSchema = _enum(["local-path", "cap-mediated"]);
8354
8508
  var StorageLocationDeclarationSchema = object({
8355
8509
  /**
8356
8510
  * Global location identifier, e.g. `recordings` or `recordingsLow`.
@@ -8370,6 +8524,19 @@ var StorageLocationDeclarationSchema = object({
8370
8524
  */
8371
8525
  cardinality: _enum(["single", "multi"]),
8372
8526
  /**
8527
+ * HOW the declaring service reaches the bytes — and therefore WHICH
8528
+ * providers may back a location of this kind. See {@link StorageAccessSchema}
8529
+ * and {@link STORAGE_ACCESS_FALLBACK}.
8530
+ *
8531
+ * Absent means `'local-path'`. That default is FAIL-CLOSED on purpose: it
8532
+ * can only over-restrict (refuse a remote provider for a kind that might
8533
+ * have coped) and never under-restrict. Declaring `'cap-mediated'` is the
8534
+ * permissive direction and is therefore never inferred — a repo guard
8535
+ * (`scripts/check-storage-access-declarations.ts`) refuses to let it be
8536
+ * reached by omission.
8537
+ */
8538
+ access: StorageAccessSchema.optional(),
8539
+ /**
8373
8540
  * When set, the default instance for this location inherits its resolved
8374
8541
  * root from the named location's default instance. Useful for derivative
8375
8542
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
@@ -18254,8 +18421,10 @@ var TrackSchema = object({
18254
18421
  lastSeen: number(),
18255
18422
  /** Frame-rate position history (subject to maxPositionHistory cap). */
18256
18423
  positions: array(TrackPositionSchema).readonly(),
18257
- /** Periodic snapshots at snapshotIntervalMs cadence (subject to
18258
- * saveThumbnails policy). */
18424
+ /** Periodic snapshots at snapshotIntervalMs cadence — DEBUG media, produced
18425
+ * only while `MediaSettings.debugMediaEnabled` is on for the camera (D299;
18426
+ * the retired `saveThumbnails` used to gate this and the rolling
18427
+ * `lastFrame` together). Empty is the healthy default, not a capture gap. */
18259
18428
  snapshots: array(TrackSnapshotSchema).readonly(),
18260
18429
  /** Deduplicated zones the track has entered at least once. Zone IDS. */
18261
18430
  zonesVisited: array(string()).readonly(),
@@ -19115,7 +19284,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19115
19284
  }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
19116
19285
  kind: "mutation",
19117
19286
  auth: "admin"
19118
- }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(object({}), array(RelocateJobSchema).readonly(), {
19287
+ }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
19288
+ kind: "query",
19289
+ auth: "admin"
19290
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
19119
19291
  kind: "query",
19120
19292
  auth: "admin"
19121
19293
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
@@ -21178,6 +21350,9 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
21178
21350
  }), method(object({ jobId: string().optional() }), StorageMigrationJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
21179
21351
  kind: "mutation",
21180
21352
  auth: "admin"
21353
+ }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
21354
+ kind: "mutation",
21355
+ auth: "admin"
21181
21356
  });
21182
21357
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
21183
21358
  providerId: string().min(1),
@@ -21576,12 +21751,38 @@ response: record(string(), unknown()) }), object({
21576
21751
  *
21577
21752
  * ## Why this is a capability and not a helper
21578
21753
  *
21579
- * Six stores in `addon-post-analysis` already hold vectors — object CLIP, face,
21580
- * plate, vehicle, identity, and the event store's derivatives — and every one of
21581
- * them keeps its vectors in a `JSON` settings-store column and ranks them by
21582
- * brute-force cosine in JS. Measured on the live hub that costs ~11.7 KB per row
21583
- * (512 floats as TEXT, `JSON.parse`d on every search) and made semantic search
21584
- * load 5,000 rows before ranking anything.
21754
+ * This capability was introduced with the claim that SIX stores in
21755
+ * `addon-post-analysis` held vectors in a `JSON` settings-store column — object
21756
+ * CLIP, face, plate, vehicle, identity, and the event store's derivatives. That
21757
+ * claim was never true, and leaving it here made five stores look like pending
21758
+ * work when three of them have no vector at all. Counted column by column on
21759
+ * 2026-08-30, exactly THREE ever held one:
21760
+ *
21761
+ * - `object-clip` — 512-dim CLIP image embedding, migrated 2026-08-06.
21762
+ * - `faces.embedding` — 512-dim ArcFace face embedding, migrated 2026-08-30.
21763
+ * - `identity-samples.embedding` — the same ArcFace vector for an ENROLLED
21764
+ * face, migrated 2026-08-30 into its OWN index (see below).
21765
+ *
21766
+ * `plates` and `vehicle-samples` store a plate STRING and a score; `vehicles`
21767
+ * and `identities` store a name; the event store stores no derivative vector.
21768
+ * They are not migration candidates and never were.
21769
+ *
21770
+ * Measured on the live hub the JSON encoding cost ~11.7 KB per row (512 floats
21771
+ * as TEXT, `JSON.parse`d on every search) and made semantic search load 5,000
21772
+ * rows before ranking anything.
21773
+ *
21774
+ * ## One index per COMPARISON, never per encoder
21775
+ *
21776
+ * `faces` and `identity-samples` hold the same 512 ArcFace dims from the same
21777
+ * model, and they still get two indexes. An index is a set of things that are
21778
+ * ranked against each other and that live and die together, and these two are
21779
+ * neither: a `faces` row is TRACK-OWNED and cascades away with its track under
21780
+ * a per-camera capacity cap, an `identity-samples` row is retention-EXEMPT
21781
+ * forever and is the gallery every recognition ranks against. One index would
21782
+ * mean every gallery load and every reconcile carried a filter whose failure
21783
+ * mode is either ranking a candidate against itself or reclaiming an enrolled
21784
+ * person's only sample. The dimension they share is not a reason to share an
21785
+ * index; the question they answer is, and it differs.
21585
21786
  *
21586
21787
  * The fix is not a faster loop, it is a different backend — and the backend
21587
21788
  * should be replaceable without touching six callers. So: a singleton
@@ -21686,7 +21887,20 @@ var VectorQueryResultSchema = object({
21686
21887
  */
21687
21888
  scanned: number(),
21688
21889
  /** True when the backend could not consider every row that passed the filter. */
21689
- truncated: boolean()
21890
+ truncated: boolean(),
21891
+ /**
21892
+ * The `topK` the backend actually ran with.
21893
+ *
21894
+ * Every backend has a ceiling — sqlite-vec's is 4,096 — and a caller asking
21895
+ * past it used to learn nothing but a boolean, from a WARN in the provider's
21896
+ * own log rather than in its answer. That is how an audit asking for 20,000
21897
+ * consumed 4,096 and reported `examined: 4096` as if it had walked the index,
21898
+ * for weeks. `truncated` says THAT the answer was short; this says BY HOW
21899
+ * MUCH, in the return value, where the caller cannot fail to see it.
21900
+ *
21901
+ * Equals the requested `topK` whenever nothing was lowered.
21902
+ */
21903
+ effectiveTopK: number().int().positive()
21690
21904
  });
21691
21905
  var VectorDeleteInputSchema = object({
21692
21906
  index: string(),
@@ -21715,6 +21929,68 @@ var VectorGetResultSchema = object({ items: array(object({
21715
21929
  id: string(),
21716
21930
  metadata: VectorMetadataSchema
21717
21931
  })) });
21932
+ /**
21933
+ * Ids to read back WITH their vectors.
21934
+ *
21935
+ * The sibling of {@link VectorGetResultSchema}, and deliberately a separate
21936
+ * method rather than a flag on it: `getByIds` promises no vectors and its one
21937
+ * caller depends on that promise. This one promises the opposite.
21938
+ *
21939
+ * It exists because a store cannot put its vectors here otherwise. An ArcFace
21940
+ * gallery is ranked IN PROCESS, per detection, against every enrolled sample —
21941
+ * a per-face cross-process KNN would be a network round trip inside the
21942
+ * recognition loop. So the gallery is loaded once and held in RAM, and loading
21943
+ * it requires the index to hand the floats back. Without this method the only
21944
+ * way to keep a readable vector is a JSON column, which is the thing this
21945
+ * capability exists to delete.
21946
+ *
21947
+ * BOUNDED BY THE CALLER: ids are named, never "everything". Enumerating an
21948
+ * index is {@link VectorScanInputSchema}'s job, and it returns no vectors.
21949
+ */
21950
+ var VectorFetchInputSchema = object({
21951
+ index: string(),
21952
+ ids: array(string())
21953
+ });
21954
+ var VectorFetchResultSchema = object({ items: array(object({
21955
+ id: string(),
21956
+ /** base64 Float32LE — the same wire form `upsert` accepts. */
21957
+ vector: string(),
21958
+ metadata: VectorMetadataSchema
21959
+ })) });
21960
+ /**
21961
+ * ENUMERATE an index: one page of rows in a stable order, no ranking.
21962
+ *
21963
+ * A reconcile does not want the nearest rows, it wants ALL of them, and asking
21964
+ * a KNN for "all" is the wrong question twice over. It hits the backend's `k`
21965
+ * ceiling — 4,096 on sqlite-vec against a 22,128-row index — and it needs a
21966
+ * probe vector it does not have, so the audit passed a ZERO vector whose cosine
21967
+ * distance to every row is degenerate. `examined: 4096` then read as "we
21968
+ * looked" for as long as anyone cared to read it.
21969
+ *
21970
+ * This is the primitive that question actually needs: a bounded page, ordered
21971
+ * by the backend's own row order, costing no distance computation at all.
21972
+ * Vectors are NOT returned — an enumeration that shipped 2 KB per row would be
21973
+ * the full-table read this capability was built to stop.
21974
+ */
21975
+ var VectorScanInputSchema = object({
21976
+ index: string(),
21977
+ /** Opaque resume point. `0` starts at the top; pass back `nextCursor`. */
21978
+ cursor: number().int().nonnegative().default(0),
21979
+ limit: number().int().positive()
21980
+ });
21981
+ var VectorScanResultSchema = object({
21982
+ items: array(object({
21983
+ id: string(),
21984
+ metadata: VectorMetadataSchema
21985
+ })),
21986
+ /**
21987
+ * Where the next page starts, or `null` when the walk reached the end.
21988
+ *
21989
+ * `null` is the ONLY end-of-index signal. A caller must not infer the end
21990
+ * from a short page: a backend is free to return fewer rows than asked.
21991
+ */
21992
+ nextCursor: number().int().nonnegative().nullable()
21993
+ });
21718
21994
  var VectorStatsInputSchema = object({ index: string() });
21719
21995
  var VectorStatsResultSchema = object({
21720
21996
  /** Provider id, so an operator can tell brute force from an ANN index. */
@@ -21733,7 +22009,7 @@ method(VectorDeclareIndexInputSchema, _void(), {
21733
22009
  }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, {
21734
22010
  kind: "mutation",
21735
22011
  auth: "admin"
21736
- }), method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }), method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }), method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
22012
+ }), method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }), method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }), method(VectorFetchInputSchema, VectorFetchResultSchema, { auth: "admin" }), method(VectorScanInputSchema, VectorScanResultSchema, { auth: "admin" }), method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
21737
22013
  kind: "mutation",
21738
22014
  auth: "admin"
21739
22015
  }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, {
@@ -28360,6 +28636,9 @@ method(object({
28360
28636
  }), method(object({}), array(RelocateJobSchema).readonly(), {
28361
28637
  kind: "query",
28362
28638
  auth: "admin"
28639
+ }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28640
+ kind: "query",
28641
+ auth: "admin"
28363
28642
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28364
28643
  kind: "mutation",
28365
28644
  auth: "admin"
@@ -34994,6 +35273,12 @@ Object.freeze({
34994
35273
  addonId: null,
34995
35274
  access: "create"
34996
35275
  },
35276
+ "pipelineAnalytics.countRelocatableMedia": {
35277
+ capName: "pipeline-analytics",
35278
+ capScope: "device",
35279
+ addonId: null,
35280
+ access: "view"
35281
+ },
34997
35282
  "pipelineAnalytics.countUnstampedEventMedia": {
34998
35283
  capName: "pipeline-analytics",
34999
35284
  capScope: "device",
@@ -36158,6 +36443,12 @@ Object.freeze({
36158
36443
  addonId: null,
36159
36444
  access: "view"
36160
36445
  },
36446
+ "recording.getRelocateResidue": {
36447
+ capName: "recording",
36448
+ capScope: "system",
36449
+ addonId: null,
36450
+ access: "view"
36451
+ },
36161
36452
  "recording.getStorageMigrationMoveStatus": {
36162
36453
  capName: "recording",
36163
36454
  capScope: "system",
@@ -36704,12 +36995,30 @@ Object.freeze({
36704
36995
  addonId: null,
36705
36996
  access: "create"
36706
36997
  },
36998
+ "storageMigration.drain": {
36999
+ capName: "storage-migration",
37000
+ capScope: "system",
37001
+ addonId: null,
37002
+ access: "create"
37003
+ },
37004
+ "storageMigration.movers": {
37005
+ capName: "storage-migration",
37006
+ capScope: "system",
37007
+ addonId: null,
37008
+ access: "view"
37009
+ },
36707
37010
  "storageMigration.plan": {
36708
37011
  capName: "storage-migration",
36709
37012
  capScope: "system",
36710
37013
  addonId: null,
36711
37014
  access: "view"
36712
37015
  },
37016
+ "storageMigration.residue": {
37017
+ capName: "storage-migration",
37018
+ capScope: "system",
37019
+ addonId: null,
37020
+ access: "view"
37021
+ },
36713
37022
  "storageMigration.start": {
36714
37023
  capName: "storage-migration",
36715
37024
  capScope: "system",
@@ -37544,6 +37853,12 @@ Object.freeze({
37544
37853
  addonId: null,
37545
37854
  access: "delete"
37546
37855
  },
37856
+ "vectorStore.fetchByIds": {
37857
+ capName: "vector-store",
37858
+ capScope: "system",
37859
+ addonId: null,
37860
+ access: "view"
37861
+ },
37547
37862
  "vectorStore.getByIds": {
37548
37863
  capName: "vector-store",
37549
37864
  capScope: "system",
@@ -37556,6 +37871,12 @@ Object.freeze({
37556
37871
  addonId: null,
37557
37872
  access: "view"
37558
37873
  },
37874
+ "vectorStore.scan": {
37875
+ capName: "vector-store",
37876
+ capScope: "system",
37877
+ addonId: null,
37878
+ access: "view"
37879
+ },
37559
37880
  "vectorStore.stats": {
37560
37881
  capName: "vector-store",
37561
37882
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -8175,13 +8175,49 @@ var StorageMigrationParticipantSchema = _enum([
8175
8175
  "recorder",
8176
8176
  "analytics"
8177
8177
  ]);
8178
+ /**
8179
+ * The mover's own numbers, folded onto the coordinator's durable move record.
8180
+ *
8181
+ * The long half of a non-blocking migration is `draining`, and it is measured
8182
+ * in hours: 136 885 files at ~4 MB/s is about five of them. Before this shape
8183
+ * existed the only place those numbers appeared was a Loki line, so an operator
8184
+ * watching the Admin UI saw `phase: draining` and nothing else for a whole
8185
+ * afternoon.
8186
+ *
8187
+ * It is POLLED, never pushed. Events are telemetry and may be dropped
8188
+ * (D8/D11), and a dropped progress event is indistinguishable from a stalled
8189
+ * mover — which is the exact failure this is meant to end. The coordinator's
8190
+ * `waitForMoves` already fetches the whole {@link RelocateJob} on every tick to
8191
+ * read `state`; folding the counters costs no extra read and makes the durable
8192
+ * record say afterwards how far a move actually got.
8193
+ *
8194
+ * `filesTotal` is `null` for "no honest denominator" and is never zero-filled:
8195
+ * a windowed footage job (`sinceMs`) and a node with no ledger both genuinely
8196
+ * cannot say M, and a 0 there would render as "100 % done".
8197
+ */
8198
+ var StorageMigrationMoveProgressSchema = object({
8199
+ filesMoved: number().int().nonnegative(),
8200
+ /** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
8201
+ filesTotal: number().int().nonnegative().nullable(),
8202
+ bytesMoved: number().int().nonnegative(),
8203
+ /** The MOVER's start, not the migration's: a drain restarted after an addon
8204
+ * crash gets a new mover, and a rate computed from the migration's start
8205
+ * would silently average in the time nothing was running. */
8206
+ startedAt: number(),
8207
+ /** When the coordinator last read these numbers. Paired with `startedAt` it
8208
+ * is the only honest rate: both clocks are the hub's, so a UI never has to
8209
+ * subtract its own. */
8210
+ observedAt: number()
8211
+ });
8178
8212
  var StorageMigrationMoveSchema = object({
8179
8213
  storageClass: StorageMigrationClassSchema,
8180
8214
  fromLocationId: string(),
8181
8215
  toLocationId: string(),
8182
8216
  moverJobId: string().nullable(),
8183
8217
  state: RelocateJobStateSchema.nullable(),
8184
- error: string().nullable()
8218
+ error: string().nullable(),
8219
+ /** Last observed mover counters; `null` until the mover has been polled once. */
8220
+ progress: StorageMigrationMoveProgressSchema.nullable()
8185
8221
  });
8186
8222
  var StorageMigrationJobSchema = object({
8187
8223
  jobId: string(),
@@ -8227,6 +8263,98 @@ var StorageMigrationPlanSchema = object({
8227
8263
  findings: array(StorageMigrationFindingSchema)
8228
8264
  });
8229
8265
  /**
8266
+ * A mover as it exists RIGHT NOW, whether or not a migration job owns it.
8267
+ *
8268
+ * The coordinator's job record is the state of record for a migration, and its
8269
+ * moves carry {@link StorageMigrationMoveProgress}. But the movers are usable
8270
+ * standalone — `recording.relocateFootage` and `pipelineAnalytics.relocateMedia`
8271
+ * are both operator-callable, and on 2026-08-29 a five-hour drain was armed that
8272
+ * way because no supported UI path existed. A mover armed like that has no job
8273
+ * to fold progress into, so it has to be readable on its own or it is invisible.
8274
+ *
8275
+ * `migrationJobId` is what tells the two apart: `null` means nothing here
8276
+ * orchestrated it.
8277
+ */
8278
+ var StorageMigrationMoverSchema = object({
8279
+ lane: _enum(["footage", "media"]),
8280
+ job: RelocateJobSchema,
8281
+ /** The coordinator job that armed this mover, or `null` for a mover armed
8282
+ * directly against the owning addon. */
8283
+ migrationJobId: string().nullable(),
8284
+ /** When the hub read these counters. Stamped here so a rate is `bytesMoved`
8285
+ * over (`observedAt` − `job.startedAt`) with BOTH ends on the hub's clock —
8286
+ * a browser subtracting its own `Date.now()` from a server `startedAt` is a
8287
+ * rate made of two different clocks. */
8288
+ observedAt: number()
8289
+ });
8290
+ /**
8291
+ * What a SOURCE still holds for one storage class — the number that makes a
8292
+ * "drain remaining" action honest rather than hopeful.
8293
+ *
8294
+ * It comes from the archive (`SegmentHourLedger.census` for footage, the media
8295
+ * engine's own selection count for media), never from the resident index: a
8296
+ * drain sized off `RecordingIndex` is what reported `done` over 80.3 GB it had
8297
+ * never been told about (D295).
8298
+ *
8299
+ * `items`/`bytes` are `null` for "the archive could not be asked", which is
8300
+ * deliberately NOT zero: a drain is still offered for an unknown residue,
8301
+ * because refusing on an unanswerable read would hide exactly the case an
8302
+ * operator needs to act on.
8303
+ */
8304
+ var StorageMigrationResidueSchema = object({
8305
+ storageClass: StorageMigrationClassSchema,
8306
+ /** The location still holding the data. `'*'` for the media lane, whose rows
8307
+ * move from wherever they are rather than from one named source. */
8308
+ fromLocationId: string(),
8309
+ /** Where a drain would move it — the class's CURRENT default. */
8310
+ toLocationId: string(),
8311
+ /** Segments (footage lane) or rows (media lane) still on the source. */
8312
+ items: number().int().nonnegative().nullable(),
8313
+ /** Bytes on the source; `null` when the lane counts rows rather than bytes. */
8314
+ bytes: number().int().nonnegative().nullable()
8315
+ });
8316
+ /**
8317
+ * Run the DRAIN half and nothing else.
8318
+ *
8319
+ * A migration that reached `done` has already repointed, so `start` correctly
8320
+ * refuses its destination ("already the default") — there is nothing left to
8321
+ * repoint. But the drain can fail, be cancelled, be interrupted by a restart,
8322
+ * or finish against a work list that was a tenth of the archive (D295), and
8323
+ * before this there was no supported way to run only that half: the only way
8324
+ * through was calling `recording.relocateFootage` by hand over admin tRPC.
8325
+ *
8326
+ * `drain` NEVER calls `setDefaultLocations`. That is what keeps `start`'s
8327
+ * refusal meaningful: the two verbs are disjoint, so nothing here can silently
8328
+ * re-repoint a class that is already migrated.
8329
+ */
8330
+ var StorageMigrationDrainInputSchema = object({
8331
+ /** The classes to drain. Each must appear in `storageMigration.residue`, so
8332
+ * a class whose source is already empty is refused rather than started. */
8333
+ classes: array(StorageMigrationClassSchema).min(1),
8334
+ throttleMbps: number().min(1).max(1e3).optional()
8335
+ });
8336
+ /** What a footage source still holds, asked of the durable hour ledger. */
8337
+ var RelocateResidueInputSchema = object({
8338
+ fromLocationId: string().min(1),
8339
+ /** Narrow to one logical class; omit for every profile on the location. */
8340
+ footageClass: RelocateFootageClassSchema.optional()
8341
+ });
8342
+ /** `null` = the archive could not answer (no ledger on this node, or the
8343
+ * aggregate failed). Never conflated with an empty source. */
8344
+ var RelocateResidueSchema = object({
8345
+ segments: number().int().nonnegative(),
8346
+ bytes: number().int().nonnegative()
8347
+ }).nullable();
8348
+ /** How many rows a media pass would still act on against a given target — the
8349
+ * media lane's denominator AND its residue, from ONE derivation so the two can
8350
+ * never disagree. `null` = the count could not be taken. */
8351
+ var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8352
+ var RelocatableMediaCountInputSchema = object({
8353
+ toLocationId: string().min(1),
8354
+ /** Omitted = `move`. */
8355
+ mode: MediaRelocateModeSchema.optional()
8356
+ });
8357
+ /**
8230
8358
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8231
8359
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8232
8360
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8330,6 +8458,32 @@ var StorageLocationRefSchema = union([StorageLocationTypeSchema, string().regex(
8330
8458
  * two addons declaring the same `id` must agree on `cardinality` (validated
8331
8459
  * at kernel aggregation time, not here).
8332
8460
  */
8461
+ /**
8462
+ * `StorageAccess` — how the service that DECLARED a storage-location kind
8463
+ * actually reaches the bytes. It is the constraint that decides which
8464
+ * `storage-provider`s may back a location of that kind.
8465
+ *
8466
+ * - `'local-path'` — the service asks `storage.resolve` for a path string and
8467
+ * then does its own `node:fs` I/O on it (the recorder's segment writer, the
8468
+ * post-analysis media roots). Only a provider that serves a genuine local
8469
+ * filesystem (`getProviderInfo().nodeLocal === true`) can satisfy that: a
8470
+ * remote provider's `resolve` returns a path on the REMOTE host, and
8471
+ * `fs.readdir` of it on this node either fails or — far worse — succeeds
8472
+ * against a same-named local directory that is something else entirely.
8473
+ *
8474
+ * - `'cap-mediated'` — every byte travels through the `storage` cap
8475
+ * (`read`/`write`, or `beginUpload`/`writeChunk`/`finalizeUpload`). The
8476
+ * service never sees a path, so any provider can back it. `backups` is the
8477
+ * one kind that qualifies today.
8478
+ *
8479
+ * Before this existed, `recordings` was unreachable by SFTP/S3/WebDAV only as
8480
+ * an EMERGENT property of how the recorder happened to be written. Nothing
8481
+ * refused the configuration; the first write simply went somewhere wrong, and
8482
+ * a recording write that goes wrong surfaces as a silent black window rather
8483
+ * than an error (the read path does not `stat`). This turns that accident into
8484
+ * a declared, enforced, testable refusal.
8485
+ */
8486
+ var StorageAccessSchema = _enum(["local-path", "cap-mediated"]);
8333
8487
  var StorageLocationDeclarationSchema = object({
8334
8488
  /**
8335
8489
  * Global location identifier, e.g. `recordings` or `recordingsLow`.
@@ -8349,6 +8503,19 @@ var StorageLocationDeclarationSchema = object({
8349
8503
  */
8350
8504
  cardinality: _enum(["single", "multi"]),
8351
8505
  /**
8506
+ * HOW the declaring service reaches the bytes — and therefore WHICH
8507
+ * providers may back a location of this kind. See {@link StorageAccessSchema}
8508
+ * and {@link STORAGE_ACCESS_FALLBACK}.
8509
+ *
8510
+ * Absent means `'local-path'`. That default is FAIL-CLOSED on purpose: it
8511
+ * can only over-restrict (refuse a remote provider for a kind that might
8512
+ * have coped) and never under-restrict. Declaring `'cap-mediated'` is the
8513
+ * permissive direction and is therefore never inferred — a repo guard
8514
+ * (`scripts/check-storage-access-declarations.ts`) refuses to let it be
8515
+ * reached by omission.
8516
+ */
8517
+ access: StorageAccessSchema.optional(),
8518
+ /**
8352
8519
  * When set, the default instance for this location inherits its resolved
8353
8520
  * root from the named location's default instance. Useful for derivative
8354
8521
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
@@ -18233,8 +18400,10 @@ var TrackSchema = object({
18233
18400
  lastSeen: number(),
18234
18401
  /** Frame-rate position history (subject to maxPositionHistory cap). */
18235
18402
  positions: array(TrackPositionSchema).readonly(),
18236
- /** Periodic snapshots at snapshotIntervalMs cadence (subject to
18237
- * saveThumbnails policy). */
18403
+ /** Periodic snapshots at snapshotIntervalMs cadence — DEBUG media, produced
18404
+ * only while `MediaSettings.debugMediaEnabled` is on for the camera (D299;
18405
+ * the retired `saveThumbnails` used to gate this and the rolling
18406
+ * `lastFrame` together). Empty is the healthy default, not a capture gap. */
18238
18407
  snapshots: array(TrackSnapshotSchema).readonly(),
18239
18408
  /** Deduplicated zones the track has entered at least once. Zone IDS. */
18240
18409
  zonesVisited: array(string()).readonly(),
@@ -19094,7 +19263,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19094
19263
  }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
19095
19264
  kind: "mutation",
19096
19265
  auth: "admin"
19097
- }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(object({}), array(RelocateJobSchema).readonly(), {
19266
+ }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
19267
+ kind: "query",
19268
+ auth: "admin"
19269
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
19098
19270
  kind: "query",
19099
19271
  auth: "admin"
19100
19272
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
@@ -21157,6 +21329,9 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
21157
21329
  }), method(object({ jobId: string().optional() }), StorageMigrationJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
21158
21330
  kind: "mutation",
21159
21331
  auth: "admin"
21332
+ }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
21333
+ kind: "mutation",
21334
+ auth: "admin"
21160
21335
  });
21161
21336
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
21162
21337
  providerId: string().min(1),
@@ -21555,12 +21730,38 @@ response: record(string(), unknown()) }), object({
21555
21730
  *
21556
21731
  * ## Why this is a capability and not a helper
21557
21732
  *
21558
- * Six stores in `addon-post-analysis` already hold vectors — object CLIP, face,
21559
- * plate, vehicle, identity, and the event store's derivatives — and every one of
21560
- * them keeps its vectors in a `JSON` settings-store column and ranks them by
21561
- * brute-force cosine in JS. Measured on the live hub that costs ~11.7 KB per row
21562
- * (512 floats as TEXT, `JSON.parse`d on every search) and made semantic search
21563
- * load 5,000 rows before ranking anything.
21733
+ * This capability was introduced with the claim that SIX stores in
21734
+ * `addon-post-analysis` held vectors in a `JSON` settings-store column — object
21735
+ * CLIP, face, plate, vehicle, identity, and the event store's derivatives. That
21736
+ * claim was never true, and leaving it here made five stores look like pending
21737
+ * work when three of them have no vector at all. Counted column by column on
21738
+ * 2026-08-30, exactly THREE ever held one:
21739
+ *
21740
+ * - `object-clip` — 512-dim CLIP image embedding, migrated 2026-08-06.
21741
+ * - `faces.embedding` — 512-dim ArcFace face embedding, migrated 2026-08-30.
21742
+ * - `identity-samples.embedding` — the same ArcFace vector for an ENROLLED
21743
+ * face, migrated 2026-08-30 into its OWN index (see below).
21744
+ *
21745
+ * `plates` and `vehicle-samples` store a plate STRING and a score; `vehicles`
21746
+ * and `identities` store a name; the event store stores no derivative vector.
21747
+ * They are not migration candidates and never were.
21748
+ *
21749
+ * Measured on the live hub the JSON encoding cost ~11.7 KB per row (512 floats
21750
+ * as TEXT, `JSON.parse`d on every search) and made semantic search load 5,000
21751
+ * rows before ranking anything.
21752
+ *
21753
+ * ## One index per COMPARISON, never per encoder
21754
+ *
21755
+ * `faces` and `identity-samples` hold the same 512 ArcFace dims from the same
21756
+ * model, and they still get two indexes. An index is a set of things that are
21757
+ * ranked against each other and that live and die together, and these two are
21758
+ * neither: a `faces` row is TRACK-OWNED and cascades away with its track under
21759
+ * a per-camera capacity cap, an `identity-samples` row is retention-EXEMPT
21760
+ * forever and is the gallery every recognition ranks against. One index would
21761
+ * mean every gallery load and every reconcile carried a filter whose failure
21762
+ * mode is either ranking a candidate against itself or reclaiming an enrolled
21763
+ * person's only sample. The dimension they share is not a reason to share an
21764
+ * index; the question they answer is, and it differs.
21564
21765
  *
21565
21766
  * The fix is not a faster loop, it is a different backend — and the backend
21566
21767
  * should be replaceable without touching six callers. So: a singleton
@@ -21665,7 +21866,20 @@ var VectorQueryResultSchema = object({
21665
21866
  */
21666
21867
  scanned: number(),
21667
21868
  /** True when the backend could not consider every row that passed the filter. */
21668
- truncated: boolean()
21869
+ truncated: boolean(),
21870
+ /**
21871
+ * The `topK` the backend actually ran with.
21872
+ *
21873
+ * Every backend has a ceiling — sqlite-vec's is 4,096 — and a caller asking
21874
+ * past it used to learn nothing but a boolean, from a WARN in the provider's
21875
+ * own log rather than in its answer. That is how an audit asking for 20,000
21876
+ * consumed 4,096 and reported `examined: 4096` as if it had walked the index,
21877
+ * for weeks. `truncated` says THAT the answer was short; this says BY HOW
21878
+ * MUCH, in the return value, where the caller cannot fail to see it.
21879
+ *
21880
+ * Equals the requested `topK` whenever nothing was lowered.
21881
+ */
21882
+ effectiveTopK: number().int().positive()
21669
21883
  });
21670
21884
  var VectorDeleteInputSchema = object({
21671
21885
  index: string(),
@@ -21694,6 +21908,68 @@ var VectorGetResultSchema = object({ items: array(object({
21694
21908
  id: string(),
21695
21909
  metadata: VectorMetadataSchema
21696
21910
  })) });
21911
+ /**
21912
+ * Ids to read back WITH their vectors.
21913
+ *
21914
+ * The sibling of {@link VectorGetResultSchema}, and deliberately a separate
21915
+ * method rather than a flag on it: `getByIds` promises no vectors and its one
21916
+ * caller depends on that promise. This one promises the opposite.
21917
+ *
21918
+ * It exists because a store cannot put its vectors here otherwise. An ArcFace
21919
+ * gallery is ranked IN PROCESS, per detection, against every enrolled sample —
21920
+ * a per-face cross-process KNN would be a network round trip inside the
21921
+ * recognition loop. So the gallery is loaded once and held in RAM, and loading
21922
+ * it requires the index to hand the floats back. Without this method the only
21923
+ * way to keep a readable vector is a JSON column, which is the thing this
21924
+ * capability exists to delete.
21925
+ *
21926
+ * BOUNDED BY THE CALLER: ids are named, never "everything". Enumerating an
21927
+ * index is {@link VectorScanInputSchema}'s job, and it returns no vectors.
21928
+ */
21929
+ var VectorFetchInputSchema = object({
21930
+ index: string(),
21931
+ ids: array(string())
21932
+ });
21933
+ var VectorFetchResultSchema = object({ items: array(object({
21934
+ id: string(),
21935
+ /** base64 Float32LE — the same wire form `upsert` accepts. */
21936
+ vector: string(),
21937
+ metadata: VectorMetadataSchema
21938
+ })) });
21939
+ /**
21940
+ * ENUMERATE an index: one page of rows in a stable order, no ranking.
21941
+ *
21942
+ * A reconcile does not want the nearest rows, it wants ALL of them, and asking
21943
+ * a KNN for "all" is the wrong question twice over. It hits the backend's `k`
21944
+ * ceiling — 4,096 on sqlite-vec against a 22,128-row index — and it needs a
21945
+ * probe vector it does not have, so the audit passed a ZERO vector whose cosine
21946
+ * distance to every row is degenerate. `examined: 4096` then read as "we
21947
+ * looked" for as long as anyone cared to read it.
21948
+ *
21949
+ * This is the primitive that question actually needs: a bounded page, ordered
21950
+ * by the backend's own row order, costing no distance computation at all.
21951
+ * Vectors are NOT returned — an enumeration that shipped 2 KB per row would be
21952
+ * the full-table read this capability was built to stop.
21953
+ */
21954
+ var VectorScanInputSchema = object({
21955
+ index: string(),
21956
+ /** Opaque resume point. `0` starts at the top; pass back `nextCursor`. */
21957
+ cursor: number().int().nonnegative().default(0),
21958
+ limit: number().int().positive()
21959
+ });
21960
+ var VectorScanResultSchema = object({
21961
+ items: array(object({
21962
+ id: string(),
21963
+ metadata: VectorMetadataSchema
21964
+ })),
21965
+ /**
21966
+ * Where the next page starts, or `null` when the walk reached the end.
21967
+ *
21968
+ * `null` is the ONLY end-of-index signal. A caller must not infer the end
21969
+ * from a short page: a backend is free to return fewer rows than asked.
21970
+ */
21971
+ nextCursor: number().int().nonnegative().nullable()
21972
+ });
21697
21973
  var VectorStatsInputSchema = object({ index: string() });
21698
21974
  var VectorStatsResultSchema = object({
21699
21975
  /** Provider id, so an operator can tell brute force from an ANN index. */
@@ -21712,7 +21988,7 @@ method(VectorDeclareIndexInputSchema, _void(), {
21712
21988
  }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, {
21713
21989
  kind: "mutation",
21714
21990
  auth: "admin"
21715
- }), method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }), method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }), method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
21991
+ }), method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }), method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }), method(VectorFetchInputSchema, VectorFetchResultSchema, { auth: "admin" }), method(VectorScanInputSchema, VectorScanResultSchema, { auth: "admin" }), method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
21716
21992
  kind: "mutation",
21717
21993
  auth: "admin"
21718
21994
  }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, {
@@ -28339,6 +28615,9 @@ method(object({
28339
28615
  }), method(object({}), array(RelocateJobSchema).readonly(), {
28340
28616
  kind: "query",
28341
28617
  auth: "admin"
28618
+ }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28619
+ kind: "query",
28620
+ auth: "admin"
28342
28621
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28343
28622
  kind: "mutation",
28344
28623
  auth: "admin"
@@ -34973,6 +35252,12 @@ Object.freeze({
34973
35252
  addonId: null,
34974
35253
  access: "create"
34975
35254
  },
35255
+ "pipelineAnalytics.countRelocatableMedia": {
35256
+ capName: "pipeline-analytics",
35257
+ capScope: "device",
35258
+ addonId: null,
35259
+ access: "view"
35260
+ },
34976
35261
  "pipelineAnalytics.countUnstampedEventMedia": {
34977
35262
  capName: "pipeline-analytics",
34978
35263
  capScope: "device",
@@ -36137,6 +36422,12 @@ Object.freeze({
36137
36422
  addonId: null,
36138
36423
  access: "view"
36139
36424
  },
36425
+ "recording.getRelocateResidue": {
36426
+ capName: "recording",
36427
+ capScope: "system",
36428
+ addonId: null,
36429
+ access: "view"
36430
+ },
36140
36431
  "recording.getStorageMigrationMoveStatus": {
36141
36432
  capName: "recording",
36142
36433
  capScope: "system",
@@ -36683,12 +36974,30 @@ Object.freeze({
36683
36974
  addonId: null,
36684
36975
  access: "create"
36685
36976
  },
36977
+ "storageMigration.drain": {
36978
+ capName: "storage-migration",
36979
+ capScope: "system",
36980
+ addonId: null,
36981
+ access: "create"
36982
+ },
36983
+ "storageMigration.movers": {
36984
+ capName: "storage-migration",
36985
+ capScope: "system",
36986
+ addonId: null,
36987
+ access: "view"
36988
+ },
36686
36989
  "storageMigration.plan": {
36687
36990
  capName: "storage-migration",
36688
36991
  capScope: "system",
36689
36992
  addonId: null,
36690
36993
  access: "view"
36691
36994
  },
36995
+ "storageMigration.residue": {
36996
+ capName: "storage-migration",
36997
+ capScope: "system",
36998
+ addonId: null,
36999
+ access: "view"
37000
+ },
36692
37001
  "storageMigration.start": {
36693
37002
  capName: "storage-migration",
36694
37003
  capScope: "system",
@@ -37523,6 +37832,12 @@ Object.freeze({
37523
37832
  addonId: null,
37524
37833
  access: "delete"
37525
37834
  },
37835
+ "vectorStore.fetchByIds": {
37836
+ capName: "vector-store",
37837
+ capScope: "system",
37838
+ addonId: null,
37839
+ access: "view"
37840
+ },
37526
37841
  "vectorStore.getByIds": {
37527
37842
  capName: "vector-store",
37528
37843
  capScope: "system",
@@ -37535,6 +37850,12 @@ Object.freeze({
37535
37850
  addonId: null,
37536
37851
  access: "view"
37537
37852
  },
37853
+ "vectorStore.scan": {
37854
+ capName: "vector-store",
37855
+ capScope: "system",
37856
+ addonId: null,
37857
+ access: "view"
37858
+ },
37538
37859
  "vectorStore.stats": {
37539
37860
  capName: "vector-store",
37540
37861
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-wyze",
3
- "version": "0.2.47",
3
+ "version": "0.2.49",
4
4
  "description": "Wyze camera device-provider addon for CamStack — wraps the @apocaliss92/wyze-bridge-js P2P/DTLS client, feeding the stream-broker via the pull-rfc4571 lazy-publish path (a structural twin of addon-provider-reolink)",
5
5
  "keywords": [
6
6
  "camstack",