@camstack/addon-post-analysis 1.2.148 → 1.2.150

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.
@@ -8403,13 +8403,49 @@ var StorageMigrationParticipantSchema = _enum([
8403
8403
  "recorder",
8404
8404
  "analytics"
8405
8405
  ]);
8406
+ /**
8407
+ * The mover's own numbers, folded onto the coordinator's durable move record.
8408
+ *
8409
+ * The long half of a non-blocking migration is `draining`, and it is measured
8410
+ * in hours: 136 885 files at ~4 MB/s is about five of them. Before this shape
8411
+ * existed the only place those numbers appeared was a Loki line, so an operator
8412
+ * watching the Admin UI saw `phase: draining` and nothing else for a whole
8413
+ * afternoon.
8414
+ *
8415
+ * It is POLLED, never pushed. Events are telemetry and may be dropped
8416
+ * (D8/D11), and a dropped progress event is indistinguishable from a stalled
8417
+ * mover — which is the exact failure this is meant to end. The coordinator's
8418
+ * `waitForMoves` already fetches the whole {@link RelocateJob} on every tick to
8419
+ * read `state`; folding the counters costs no extra read and makes the durable
8420
+ * record say afterwards how far a move actually got.
8421
+ *
8422
+ * `filesTotal` is `null` for "no honest denominator" and is never zero-filled:
8423
+ * a windowed footage job (`sinceMs`) and a node with no ledger both genuinely
8424
+ * cannot say M, and a 0 there would render as "100 % done".
8425
+ */
8426
+ var StorageMigrationMoveProgressSchema = object({
8427
+ filesMoved: number().int().nonnegative(),
8428
+ /** The archive census — the **M** of "N of M" (D295). `null` = unknowable. */
8429
+ filesTotal: number().int().nonnegative().nullable(),
8430
+ bytesMoved: number().int().nonnegative(),
8431
+ /** The MOVER's start, not the migration's: a drain restarted after an addon
8432
+ * crash gets a new mover, and a rate computed from the migration's start
8433
+ * would silently average in the time nothing was running. */
8434
+ startedAt: number(),
8435
+ /** When the coordinator last read these numbers. Paired with `startedAt` it
8436
+ * is the only honest rate: both clocks are the hub's, so a UI never has to
8437
+ * subtract its own. */
8438
+ observedAt: number()
8439
+ });
8406
8440
  var StorageMigrationMoveSchema = object({
8407
8441
  storageClass: StorageMigrationClassSchema,
8408
8442
  fromLocationId: string(),
8409
8443
  toLocationId: string(),
8410
8444
  moverJobId: string().nullable(),
8411
8445
  state: RelocateJobStateSchema.nullable(),
8412
- error: string().nullable()
8446
+ error: string().nullable(),
8447
+ /** Last observed mover counters; `null` until the mover has been polled once. */
8448
+ progress: StorageMigrationMoveProgressSchema.nullable()
8413
8449
  });
8414
8450
  var StorageMigrationJobSchema = object({
8415
8451
  jobId: string(),
@@ -8455,6 +8491,98 @@ var StorageMigrationPlanSchema = object({
8455
8491
  findings: array(StorageMigrationFindingSchema)
8456
8492
  });
8457
8493
  /**
8494
+ * A mover as it exists RIGHT NOW, whether or not a migration job owns it.
8495
+ *
8496
+ * The coordinator's job record is the state of record for a migration, and its
8497
+ * moves carry {@link StorageMigrationMoveProgress}. But the movers are usable
8498
+ * standalone — `recording.relocateFootage` and `pipelineAnalytics.relocateMedia`
8499
+ * are both operator-callable, and on 2026-08-29 a five-hour drain was armed that
8500
+ * way because no supported UI path existed. A mover armed like that has no job
8501
+ * to fold progress into, so it has to be readable on its own or it is invisible.
8502
+ *
8503
+ * `migrationJobId` is what tells the two apart: `null` means nothing here
8504
+ * orchestrated it.
8505
+ */
8506
+ var StorageMigrationMoverSchema = object({
8507
+ lane: _enum(["footage", "media"]),
8508
+ job: RelocateJobSchema,
8509
+ /** The coordinator job that armed this mover, or `null` for a mover armed
8510
+ * directly against the owning addon. */
8511
+ migrationJobId: string().nullable(),
8512
+ /** When the hub read these counters. Stamped here so a rate is `bytesMoved`
8513
+ * over (`observedAt` − `job.startedAt`) with BOTH ends on the hub's clock —
8514
+ * a browser subtracting its own `Date.now()` from a server `startedAt` is a
8515
+ * rate made of two different clocks. */
8516
+ observedAt: number()
8517
+ });
8518
+ /**
8519
+ * What a SOURCE still holds for one storage class — the number that makes a
8520
+ * "drain remaining" action honest rather than hopeful.
8521
+ *
8522
+ * It comes from the archive (`SegmentHourLedger.census` for footage, the media
8523
+ * engine's own selection count for media), never from the resident index: a
8524
+ * drain sized off `RecordingIndex` is what reported `done` over 80.3 GB it had
8525
+ * never been told about (D295).
8526
+ *
8527
+ * `items`/`bytes` are `null` for "the archive could not be asked", which is
8528
+ * deliberately NOT zero: a drain is still offered for an unknown residue,
8529
+ * because refusing on an unanswerable read would hide exactly the case an
8530
+ * operator needs to act on.
8531
+ */
8532
+ var StorageMigrationResidueSchema = object({
8533
+ storageClass: StorageMigrationClassSchema,
8534
+ /** The location still holding the data. `'*'` for the media lane, whose rows
8535
+ * move from wherever they are rather than from one named source. */
8536
+ fromLocationId: string(),
8537
+ /** Where a drain would move it — the class's CURRENT default. */
8538
+ toLocationId: string(),
8539
+ /** Segments (footage lane) or rows (media lane) still on the source. */
8540
+ items: number().int().nonnegative().nullable(),
8541
+ /** Bytes on the source; `null` when the lane counts rows rather than bytes. */
8542
+ bytes: number().int().nonnegative().nullable()
8543
+ });
8544
+ /**
8545
+ * Run the DRAIN half and nothing else.
8546
+ *
8547
+ * A migration that reached `done` has already repointed, so `start` correctly
8548
+ * refuses its destination ("already the default") — there is nothing left to
8549
+ * repoint. But the drain can fail, be cancelled, be interrupted by a restart,
8550
+ * or finish against a work list that was a tenth of the archive (D295), and
8551
+ * before this there was no supported way to run only that half: the only way
8552
+ * through was calling `recording.relocateFootage` by hand over admin tRPC.
8553
+ *
8554
+ * `drain` NEVER calls `setDefaultLocations`. That is what keeps `start`'s
8555
+ * refusal meaningful: the two verbs are disjoint, so nothing here can silently
8556
+ * re-repoint a class that is already migrated.
8557
+ */
8558
+ var StorageMigrationDrainInputSchema = object({
8559
+ /** The classes to drain. Each must appear in `storageMigration.residue`, so
8560
+ * a class whose source is already empty is refused rather than started. */
8561
+ classes: array(StorageMigrationClassSchema).min(1),
8562
+ throttleMbps: number().min(1).max(1e3).optional()
8563
+ });
8564
+ /** What a footage source still holds, asked of the durable hour ledger. */
8565
+ var RelocateResidueInputSchema = object({
8566
+ fromLocationId: string().min(1),
8567
+ /** Narrow to one logical class; omit for every profile on the location. */
8568
+ footageClass: RelocateFootageClassSchema.optional()
8569
+ });
8570
+ /** `null` = the archive could not answer (no ledger on this node, or the
8571
+ * aggregate failed). Never conflated with an empty source. */
8572
+ var RelocateResidueSchema = object({
8573
+ segments: number().int().nonnegative(),
8574
+ bytes: number().int().nonnegative()
8575
+ }).nullable();
8576
+ /** How many rows a media pass would still act on against a given target — the
8577
+ * media lane's denominator AND its residue, from ONE derivation so the two can
8578
+ * never disagree. `null` = the count could not be taken. */
8579
+ var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8580
+ var RelocatableMediaCountInputSchema = object({
8581
+ toLocationId: string().min(1),
8582
+ /** Omitted = `move`. */
8583
+ mode: MediaRelocateModeSchema.optional()
8584
+ });
8585
+ /**
8458
8586
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8459
8587
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8460
8588
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8558,6 +8686,32 @@ var StorageLocationRefSchema = union([StorageLocationTypeSchema, string().regex(
8558
8686
  * two addons declaring the same `id` must agree on `cardinality` (validated
8559
8687
  * at kernel aggregation time, not here).
8560
8688
  */
8689
+ /**
8690
+ * `StorageAccess` — how the service that DECLARED a storage-location kind
8691
+ * actually reaches the bytes. It is the constraint that decides which
8692
+ * `storage-provider`s may back a location of that kind.
8693
+ *
8694
+ * - `'local-path'` — the service asks `storage.resolve` for a path string and
8695
+ * then does its own `node:fs` I/O on it (the recorder's segment writer, the
8696
+ * post-analysis media roots). Only a provider that serves a genuine local
8697
+ * filesystem (`getProviderInfo().nodeLocal === true`) can satisfy that: a
8698
+ * remote provider's `resolve` returns a path on the REMOTE host, and
8699
+ * `fs.readdir` of it on this node either fails or — far worse — succeeds
8700
+ * against a same-named local directory that is something else entirely.
8701
+ *
8702
+ * - `'cap-mediated'` — every byte travels through the `storage` cap
8703
+ * (`read`/`write`, or `beginUpload`/`writeChunk`/`finalizeUpload`). The
8704
+ * service never sees a path, so any provider can back it. `backups` is the
8705
+ * one kind that qualifies today.
8706
+ *
8707
+ * Before this existed, `recordings` was unreachable by SFTP/S3/WebDAV only as
8708
+ * an EMERGENT property of how the recorder happened to be written. Nothing
8709
+ * refused the configuration; the first write simply went somewhere wrong, and
8710
+ * a recording write that goes wrong surfaces as a silent black window rather
8711
+ * than an error (the read path does not `stat`). This turns that accident into
8712
+ * a declared, enforced, testable refusal.
8713
+ */
8714
+ var StorageAccessSchema = _enum(["local-path", "cap-mediated"]);
8561
8715
  var StorageLocationDeclarationSchema = object({
8562
8716
  /**
8563
8717
  * Global location identifier, e.g. `recordings` or `recordingsLow`.
@@ -8577,6 +8731,19 @@ var StorageLocationDeclarationSchema = object({
8577
8731
  */
8578
8732
  cardinality: _enum(["single", "multi"]),
8579
8733
  /**
8734
+ * HOW the declaring service reaches the bytes — and therefore WHICH
8735
+ * providers may back a location of this kind. See {@link StorageAccessSchema}
8736
+ * and {@link STORAGE_ACCESS_FALLBACK}.
8737
+ *
8738
+ * Absent means `'local-path'`. That default is FAIL-CLOSED on purpose: it
8739
+ * can only over-restrict (refuse a remote provider for a kind that might
8740
+ * have coped) and never under-restrict. Declaring `'cap-mediated'` is the
8741
+ * permissive direction and is therefore never inferred — a repo guard
8742
+ * (`scripts/check-storage-access-declarations.ts`) refuses to let it be
8743
+ * reached by omission.
8744
+ */
8745
+ access: StorageAccessSchema.optional(),
8746
+ /**
8580
8747
  * When set, the default instance for this location inherits its resolved
8581
8748
  * root from the named location's default instance. Useful for derivative
8582
8749
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
@@ -19204,8 +19371,10 @@ var TrackSchema = object({
19204
19371
  lastSeen: number(),
19205
19372
  /** Frame-rate position history (subject to maxPositionHistory cap). */
19206
19373
  positions: array(TrackPositionSchema).readonly(),
19207
- /** Periodic snapshots at snapshotIntervalMs cadence (subject to
19208
- * saveThumbnails policy). */
19374
+ /** Periodic snapshots at snapshotIntervalMs cadence — DEBUG media, produced
19375
+ * only while `MediaSettings.debugMediaEnabled` is on for the camera (D299;
19376
+ * the retired `saveThumbnails` used to gate this and the rolling
19377
+ * `lastFrame` together). Empty is the healthy default, not a capture gap. */
19209
19378
  snapshots: array(TrackSnapshotSchema).readonly(),
19210
19379
  /** Deduplicated zones the track has entered at least once. Zone IDS. */
19211
19380
  zonesVisited: array(string()).readonly(),
@@ -20288,6 +20457,21 @@ var pipelineAnalyticsCapability = {
20288
20457
  * it to zero.
20289
20458
  */
20290
20459
  countUnstampedEventMedia: method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }),
20460
+ /**
20461
+ * How many rows a pass would STILL act on against `toLocationId`.
20462
+ *
20463
+ * One derivation, two consumers: it is the media lane's denominator (the
20464
+ * **M** the footage lane gets from the ledger census — D295) and it is the
20465
+ * residue behind "drain remaining". Deriving them separately is how "N of M"
20466
+ * ends up comparing two different populations.
20467
+ *
20468
+ * `null` means the count could not be taken; it is never zero-filled,
20469
+ * because a zero here reads as "nothing left to move".
20470
+ */
20471
+ countRelocatableMedia: method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
20472
+ kind: "query",
20473
+ auth: "admin"
20474
+ }),
20291
20475
  /** Every relocate job this addon knows about, newest first (in RAM: the
20292
20476
  * move is resumable, so a lost list costs nothing but the display). */
20293
20477
  listRelocateMediaJobs: method(object({}), array(RelocateJobSchema).readonly(), {
@@ -22503,6 +22687,9 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
22503
22687
  }), method(object({ jobId: string().optional() }), StorageMigrationJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
22504
22688
  kind: "mutation",
22505
22689
  auth: "admin"
22690
+ }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
22691
+ kind: "mutation",
22692
+ auth: "admin"
22506
22693
  });
22507
22694
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
22508
22695
  providerId: string().min(1),
@@ -22901,12 +23088,38 @@ response: record(string(), unknown()) }), object({
22901
23088
  *
22902
23089
  * ## Why this is a capability and not a helper
22903
23090
  *
22904
- * Six stores in `addon-post-analysis` already hold vectors — object CLIP, face,
22905
- * plate, vehicle, identity, and the event store's derivatives — and every one of
22906
- * them keeps its vectors in a `JSON` settings-store column and ranks them by
22907
- * brute-force cosine in JS. Measured on the live hub that costs ~11.7 KB per row
22908
- * (512 floats as TEXT, `JSON.parse`d on every search) and made semantic search
22909
- * load 5,000 rows before ranking anything.
23091
+ * This capability was introduced with the claim that SIX stores in
23092
+ * `addon-post-analysis` held vectors in a `JSON` settings-store column — object
23093
+ * CLIP, face, plate, vehicle, identity, and the event store's derivatives. That
23094
+ * claim was never true, and leaving it here made five stores look like pending
23095
+ * work when three of them have no vector at all. Counted column by column on
23096
+ * 2026-08-30, exactly THREE ever held one:
23097
+ *
23098
+ * - `object-clip` — 512-dim CLIP image embedding, migrated 2026-08-06.
23099
+ * - `faces.embedding` — 512-dim ArcFace face embedding, migrated 2026-08-30.
23100
+ * - `identity-samples.embedding` — the same ArcFace vector for an ENROLLED
23101
+ * face, migrated 2026-08-30 into its OWN index (see below).
23102
+ *
23103
+ * `plates` and `vehicle-samples` store a plate STRING and a score; `vehicles`
23104
+ * and `identities` store a name; the event store stores no derivative vector.
23105
+ * They are not migration candidates and never were.
23106
+ *
23107
+ * Measured on the live hub the JSON encoding cost ~11.7 KB per row (512 floats
23108
+ * as TEXT, `JSON.parse`d on every search) and made semantic search load 5,000
23109
+ * rows before ranking anything.
23110
+ *
23111
+ * ## One index per COMPARISON, never per encoder
23112
+ *
23113
+ * `faces` and `identity-samples` hold the same 512 ArcFace dims from the same
23114
+ * model, and they still get two indexes. An index is a set of things that are
23115
+ * ranked against each other and that live and die together, and these two are
23116
+ * neither: a `faces` row is TRACK-OWNED and cascades away with its track under
23117
+ * a per-camera capacity cap, an `identity-samples` row is retention-EXEMPT
23118
+ * forever and is the gallery every recognition ranks against. One index would
23119
+ * mean every gallery load and every reconcile carried a filter whose failure
23120
+ * mode is either ranking a candidate against itself or reclaiming an enrolled
23121
+ * person's only sample. The dimension they share is not a reason to share an
23122
+ * index; the question they answer is, and it differs.
22910
23123
  *
22911
23124
  * The fix is not a faster loop, it is a different backend — and the backend
22912
23125
  * should be replaceable without touching six callers. So: a singleton
@@ -23011,7 +23224,20 @@ var VectorQueryResultSchema = object({
23011
23224
  */
23012
23225
  scanned: number(),
23013
23226
  /** True when the backend could not consider every row that passed the filter. */
23014
- truncated: boolean()
23227
+ truncated: boolean(),
23228
+ /**
23229
+ * The `topK` the backend actually ran with.
23230
+ *
23231
+ * Every backend has a ceiling — sqlite-vec's is 4,096 — and a caller asking
23232
+ * past it used to learn nothing but a boolean, from a WARN in the provider's
23233
+ * own log rather than in its answer. That is how an audit asking for 20,000
23234
+ * consumed 4,096 and reported `examined: 4096` as if it had walked the index,
23235
+ * for weeks. `truncated` says THAT the answer was short; this says BY HOW
23236
+ * MUCH, in the return value, where the caller cannot fail to see it.
23237
+ *
23238
+ * Equals the requested `topK` whenever nothing was lowered.
23239
+ */
23240
+ effectiveTopK: number().int().positive()
23015
23241
  });
23016
23242
  var VectorDeleteInputSchema = object({
23017
23243
  index: string(),
@@ -23040,6 +23266,68 @@ var VectorGetResultSchema = object({ items: array(object({
23040
23266
  id: string(),
23041
23267
  metadata: VectorMetadataSchema
23042
23268
  })) });
23269
+ /**
23270
+ * Ids to read back WITH their vectors.
23271
+ *
23272
+ * The sibling of {@link VectorGetResultSchema}, and deliberately a separate
23273
+ * method rather than a flag on it: `getByIds` promises no vectors and its one
23274
+ * caller depends on that promise. This one promises the opposite.
23275
+ *
23276
+ * It exists because a store cannot put its vectors here otherwise. An ArcFace
23277
+ * gallery is ranked IN PROCESS, per detection, against every enrolled sample —
23278
+ * a per-face cross-process KNN would be a network round trip inside the
23279
+ * recognition loop. So the gallery is loaded once and held in RAM, and loading
23280
+ * it requires the index to hand the floats back. Without this method the only
23281
+ * way to keep a readable vector is a JSON column, which is the thing this
23282
+ * capability exists to delete.
23283
+ *
23284
+ * BOUNDED BY THE CALLER: ids are named, never "everything". Enumerating an
23285
+ * index is {@link VectorScanInputSchema}'s job, and it returns no vectors.
23286
+ */
23287
+ var VectorFetchInputSchema = object({
23288
+ index: string(),
23289
+ ids: array(string())
23290
+ });
23291
+ var VectorFetchResultSchema = object({ items: array(object({
23292
+ id: string(),
23293
+ /** base64 Float32LE — the same wire form `upsert` accepts. */
23294
+ vector: string(),
23295
+ metadata: VectorMetadataSchema
23296
+ })) });
23297
+ /**
23298
+ * ENUMERATE an index: one page of rows in a stable order, no ranking.
23299
+ *
23300
+ * A reconcile does not want the nearest rows, it wants ALL of them, and asking
23301
+ * a KNN for "all" is the wrong question twice over. It hits the backend's `k`
23302
+ * ceiling — 4,096 on sqlite-vec against a 22,128-row index — and it needs a
23303
+ * probe vector it does not have, so the audit passed a ZERO vector whose cosine
23304
+ * distance to every row is degenerate. `examined: 4096` then read as "we
23305
+ * looked" for as long as anyone cared to read it.
23306
+ *
23307
+ * This is the primitive that question actually needs: a bounded page, ordered
23308
+ * by the backend's own row order, costing no distance computation at all.
23309
+ * Vectors are NOT returned — an enumeration that shipped 2 KB per row would be
23310
+ * the full-table read this capability was built to stop.
23311
+ */
23312
+ var VectorScanInputSchema = object({
23313
+ index: string(),
23314
+ /** Opaque resume point. `0` starts at the top; pass back `nextCursor`. */
23315
+ cursor: number().int().nonnegative().default(0),
23316
+ limit: number().int().positive()
23317
+ });
23318
+ var VectorScanResultSchema = object({
23319
+ items: array(object({
23320
+ id: string(),
23321
+ metadata: VectorMetadataSchema
23322
+ })),
23323
+ /**
23324
+ * Where the next page starts, or `null` when the walk reached the end.
23325
+ *
23326
+ * `null` is the ONLY end-of-index signal. A caller must not infer the end
23327
+ * from a short page: a backend is free to return fewer rows than asked.
23328
+ */
23329
+ nextCursor: number().int().nonnegative().nullable()
23330
+ });
23043
23331
  var VectorStatsInputSchema = object({ index: string() });
23044
23332
  var VectorStatsResultSchema = object({
23045
23333
  /** Provider id, so an operator can tell brute force from an ANN index. */
@@ -23058,7 +23346,7 @@ method(VectorDeclareIndexInputSchema, _void(), {
23058
23346
  }), method(VectorUpsertInputSchema, VectorUpsertResultSchema, {
23059
23347
  kind: "mutation",
23060
23348
  auth: "admin"
23061
- }), method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }), method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }), method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
23349
+ }), method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }), method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }), method(VectorFetchInputSchema, VectorFetchResultSchema, { auth: "admin" }), method(VectorScanInputSchema, VectorScanResultSchema, { auth: "admin" }), method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
23062
23350
  kind: "mutation",
23063
23351
  auth: "admin"
23064
23352
  }), method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, {
@@ -29751,6 +30039,9 @@ method(object({
29751
30039
  }), method(object({}), array(RelocateJobSchema).readonly(), {
29752
30040
  kind: "query",
29753
30041
  auth: "admin"
30042
+ }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
30043
+ kind: "query",
30044
+ auth: "admin"
29754
30045
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29755
30046
  kind: "mutation",
29756
30047
  auth: "admin"
@@ -32241,6 +32532,146 @@ function readDeviceStateFrom(runtimeState) {
32241
32532
  }
32242
32533
  }
32243
32534
  /**
32535
+ * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
32536
+ * valutatore puro del suo FRONTE.
32537
+ *
32538
+ * Viveva dentro il builtin virtual-doorbell
32539
+ * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
32540
+ * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
32541
+ * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
32542
+ * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
32543
+ * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
32544
+ * SPOSTA qui e il doorbell la ri-esporta.
32545
+ *
32546
+ * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
32547
+ * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
32548
+ * regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
32549
+ * booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
32550
+ */
32551
+ /**
32552
+ * Known binary / switch source caps → the boolean slice field whose
32553
+ * false→true rise counts as ACTIVE. Every entry is "fire on active".
32554
+ * Sensors whose "active" reading is not a plain boolean (presence's string
32555
+ * state, connectivity's connected flag) are deliberately excluded — a
32556
+ * reconnect is not a doorbell press, and it is not a recording either.
32557
+ */
32558
+ var SOURCE_CAP_ACTIVE_FIELD = {
32559
+ contact: "entryOpen",
32560
+ binary: "on",
32561
+ switch: "on",
32562
+ motion: "detected",
32563
+ flood: "flooded",
32564
+ gas: "detected",
32565
+ smoke: "detected",
32566
+ "carbon-monoxide": "detected",
32567
+ vibration: "detected",
32568
+ tamper: "tampered"
32569
+ };
32570
+ /**
32571
+ * The same caps → the slice field carrying the ms-epoch timestamp of the
32572
+ * last transition. Every source cap MUST appear here (guarded by a spec):
32573
+ * without a transition timestamp the evaluator cannot tell a genuine rise
32574
+ * from a boot-time hydration when the FIRST slice it ever sees is already
32575
+ * active, and errs towards silence — swallowing the rise.
32576
+ *
32577
+ * These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
32578
+ * provider derives them from `state.last_changed`, so they survive our own
32579
+ * restarts and correctly read as "hours ago" for a state that has been
32580
+ * active for hours. `motion` names its rise timestamp `lastDetectedAt`.
32581
+ */
32582
+ var SOURCE_CAP_CHANGED_AT_FIELD = {
32583
+ contact: "lastChangedAt",
32584
+ binary: "lastChangedAt",
32585
+ switch: "lastChangedAt",
32586
+ motion: "lastDetectedAt",
32587
+ flood: "lastChangedAt",
32588
+ gas: "lastChangedAt",
32589
+ smoke: "lastChangedAt",
32590
+ "carbon-monoxide": "lastChangedAt",
32591
+ vibration: "lastChangedAt",
32592
+ tamper: "lastChangedAt"
32593
+ };
32594
+ /** True when a cap is a recognised binary/switch source. */
32595
+ function isSourceCap(capName) {
32596
+ return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
32597
+ }
32598
+ /** Extract the "active" boolean a source cap's slice carries, or null when
32599
+ * the cap is unknown or the field is missing / non-boolean. */
32600
+ function sliceActiveValue(capName, slice) {
32601
+ const field = SOURCE_CAP_ACTIVE_FIELD[capName];
32602
+ if (field === void 0) return null;
32603
+ const raw = slice[field];
32604
+ return typeof raw === "boolean" ? raw : null;
32605
+ }
32606
+ /** Ms-epoch transition timestamp a source cap's slice carries, or null when
32607
+ * it is absent, non-numeric or the zero "never observed" sentinel. */
32608
+ function sliceChangedAt(capName, slice) {
32609
+ const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
32610
+ if (field === void 0) return null;
32611
+ const raw = slice[field];
32612
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
32613
+ return raw;
32614
+ }
32615
+ /**
32616
+ * How recent a source's own transition timestamp must be for a FIRST
32617
+ * sighting that is already active to count as a genuine rise rather than a
32618
+ * hydration of long-standing state.
32619
+ */
32620
+ var DEFAULT_FIRST_SIGHTING_FRESHNESS_MS = 3e4;
32621
+ /**
32622
+ * IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
32623
+ * porta `prior`, `nowMs` e il proprio `startedAtMs`.
32624
+ *
32625
+ * Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
32626
+ * fronte solo se il timestamp UPSTREAM della transizione è posteriore a
32627
+ * `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
32628
+ * servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
32629
+ * stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
32630
+ * iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
32631
+ * adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
32632
+ * SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
32633
+ */
32634
+ function evaluateSensorEdge(input) {
32635
+ const value = sliceActiveValue(input.capName, input.slice);
32636
+ if (value === null) return {
32637
+ edge: "none",
32638
+ value: null,
32639
+ reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
32640
+ };
32641
+ if (input.prior === void 0) {
32642
+ if (!value) return {
32643
+ edge: "none",
32644
+ value,
32645
+ reason: "baseline-seeded-inactive"
32646
+ };
32647
+ const changedAt = sliceChangedAt(input.capName, input.slice);
32648
+ const floor = Math.max(input.startedAtMs, input.nowMs - input.firstSightingFreshnessMs);
32649
+ if (changedAt === null || changedAt < floor) return {
32650
+ edge: "none",
32651
+ value,
32652
+ reason: "baseline-seeded-stale-active"
32653
+ };
32654
+ return {
32655
+ edge: "rising",
32656
+ value: true
32657
+ };
32658
+ }
32659
+ if (input.prior === value) return {
32660
+ edge: "none",
32661
+ value,
32662
+ reason: "no-change"
32663
+ };
32664
+ if (!value) return {
32665
+ edge: "none",
32666
+ value,
32667
+ reason: "falling-edge"
32668
+ };
32669
+ return {
32670
+ edge: "rising",
32671
+ value: true
32672
+ };
32673
+ }
32674
+ /**
32244
32675
  * Accessory device helpers — shared across drivers.
32245
32676
  *
32246
32677
  * Many vendor-specific drivers register accessory child devices on
@@ -36462,6 +36893,12 @@ Object.freeze({
36462
36893
  addonId: null,
36463
36894
  access: "create"
36464
36895
  },
36896
+ "pipelineAnalytics.countRelocatableMedia": {
36897
+ capName: "pipeline-analytics",
36898
+ capScope: "device",
36899
+ addonId: null,
36900
+ access: "view"
36901
+ },
36465
36902
  "pipelineAnalytics.countUnstampedEventMedia": {
36466
36903
  capName: "pipeline-analytics",
36467
36904
  capScope: "device",
@@ -37626,6 +38063,12 @@ Object.freeze({
37626
38063
  addonId: null,
37627
38064
  access: "view"
37628
38065
  },
38066
+ "recording.getRelocateResidue": {
38067
+ capName: "recording",
38068
+ capScope: "system",
38069
+ addonId: null,
38070
+ access: "view"
38071
+ },
37629
38072
  "recording.getStorageMigrationMoveStatus": {
37630
38073
  capName: "recording",
37631
38074
  capScope: "system",
@@ -38172,12 +38615,30 @@ Object.freeze({
38172
38615
  addonId: null,
38173
38616
  access: "create"
38174
38617
  },
38618
+ "storageMigration.drain": {
38619
+ capName: "storage-migration",
38620
+ capScope: "system",
38621
+ addonId: null,
38622
+ access: "create"
38623
+ },
38624
+ "storageMigration.movers": {
38625
+ capName: "storage-migration",
38626
+ capScope: "system",
38627
+ addonId: null,
38628
+ access: "view"
38629
+ },
38175
38630
  "storageMigration.plan": {
38176
38631
  capName: "storage-migration",
38177
38632
  capScope: "system",
38178
38633
  addonId: null,
38179
38634
  access: "view"
38180
38635
  },
38636
+ "storageMigration.residue": {
38637
+ capName: "storage-migration",
38638
+ capScope: "system",
38639
+ addonId: null,
38640
+ access: "view"
38641
+ },
38181
38642
  "storageMigration.start": {
38182
38643
  capName: "storage-migration",
38183
38644
  capScope: "system",
@@ -39012,6 +39473,12 @@ Object.freeze({
39012
39473
  addonId: null,
39013
39474
  access: "delete"
39014
39475
  },
39476
+ "vectorStore.fetchByIds": {
39477
+ capName: "vector-store",
39478
+ capScope: "system",
39479
+ addonId: null,
39480
+ access: "view"
39481
+ },
39015
39482
  "vectorStore.getByIds": {
39016
39483
  capName: "vector-store",
39017
39484
  capScope: "system",
@@ -39024,6 +39491,12 @@ Object.freeze({
39024
39491
  addonId: null,
39025
39492
  access: "view"
39026
39493
  },
39494
+ "vectorStore.scan": {
39495
+ capName: "vector-store",
39496
+ capScope: "system",
39497
+ addonId: null,
39498
+ access: "view"
39499
+ },
39027
39500
  "vectorStore.stats": {
39028
39501
  capName: "vector-store",
39029
39502
  capScope: "system",
@@ -42059,4 +42532,4 @@ function vectorDimFromBase64(encoded) {
42059
42532
  return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
42060
42533
  }
42061
42534
  //#endregion
42062
- export { buildEventKindDescriptor as $, NcSnoozeSuppressedSchema as A, hydrateSchema as At, SCENE_DIVERGED as B, object as Bt, NcRuleInputSchema as C, vectorDimFromBase64 as Ct, NcScheduleSchema as D, BaseAddon as Dt, NcRuleTargetSchema as E, errMsg as Et, RECORDING_EXPORT_MAX_READ_BYTES as F, array as Ft, TimelapseRuleSchema as G, EventCategory as Gt, TIMELAPSE_DENSE_FLOOR_SEC as H, record as Ht, RetrainStatusSchema as I, boolean as It, addonWidgetsSourceCapability as J, TrackSourceSchema as K, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as L, discriminatedUnion as Lt, NcTaxonomySchema as M, nodePin as Mt, OpsLogEntrySchema as N, sleep as Nt, NcSnoozeInputSchema as O, DeviceType as Ot, PoolMemoryWatchdog as P, _enum as Pt, audioModeOf as Q, SCENE_DEFAULT_ANCHOR_THRESHOLD as R, literal as Rt, NcConditionDescriptorSchema as S, systemEventFilterApplies as St, NcRuleSchema as T, zoneAnalyticsCapability as Tt, TimelapseRuleInputSchema as U, string as Ut, SceneMonitorSchema as V, partialRecord as Vt, TimelapseRulePatchSchema as W, unknown as Wt, assertTimelapseCadences as X, alarmPanelCapability as Y, audioMetricsCapability as Z, NC_ALARM_SYSTEM_EVENT_KINDS as _, readDeviceStateFrom as _t, COCO_TO_MACRO as a, encodeVectorBase64 as at, NC_SNOOZE_MAX_MINUTES as b, sceneMonitorCapability as bt, DETECTION_MACRO_CLASSES as c, hfModelUrl as ct, EVENT_KIND_BY_CAP as d, kebabToCamel as dt, cosineSimilarity as et, EVENT_PAD_MS as f, notificationRulesCapability as ft, MACRO_LABELS as g, plateGalleryCapability as gt, LabelAttributionSchema as h, pipelineAnalyticsCapability as ht, CLUSTER_MODEL_SCOPED_STEPS as i, embeddingEncoderCapability as it, NcSystemEventKindSchema as j, isDeviceScopedCap as jt, NcSnoozeSchema as k, createEvent as kt, DETECTION_PIPELINE_CAP_NAME as l, isDetectionMacroClass as lt, FailureCounters as m, pickClusterStepModels as mt, AnalyticsGroupRecordSchema as n, defineCustomActions as nt, DEFAULT_EVENT_COLOR as o, faceGalleryCapability as ot, FULL_IMAGE_BBOX as p, parseProcStatus as pt, VISIT_MERGE_GAP_MS as q, BaseDevice as r, deriveRecordingMode as rt, DEFAULT_TIMELAPSE_PREVIEW_TEXT as s, failureContributionCapability as st, AnalyticsGroupDetailSchema as t, customAction as tt, DeclaredDevices as u, isScheduleActive as ut, NC_CONDITION_CATALOG as v, readTimelapseGeneratedAt as vt, NcRulePatchSchema as w, videoclipsCapability as wt, NC_TAXONOMY as x, subKindsOf as xt, NC_DEFAULT_SNOOZE_MINUTES as y, resolvePoolMemoryPolicy as yt, SCENE_DEFAULT_UNCOVERED_POLICY as z, number as zt };
42535
+ export { audioModeOf as $, NcSnoozeSchema as A, BaseAddon as At, SCENE_DEFAULT_UNCOVERED_POLICY as B, discriminatedUnion as Bt, NcConditionDescriptorSchema as C, sceneMonitorCapability as Ct, NcRuleTargetSchema as D, videoclipsCapability as Dt, NcRuleSchema as E, vectorDimFromBase64 as Et, PoolMemoryWatchdog as F, nodePin as Ft, TimelapseRulePatchSchema as G, record as Gt, SceneMonitorSchema as H, number as Ht, RECORDING_EXPORT_MAX_READ_BYTES as I, sleep as It, VISIT_MERGE_GAP_MS as J, EventCategory as Jt, TimelapseRuleSchema as K, string as Kt, RetrainStatusSchema as L, _enum as Lt, NcSystemEventKindSchema as M, createEvent as Mt, NcTaxonomySchema as N, hydrateSchema as Nt, NcScheduleSchema as O, zoneAnalyticsCapability as Ot, OpsLogEntrySchema as P, isDeviceScopedCap as Pt, audioMetricsCapability as Q, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as R, array as Rt, NC_TAXONOMY as S, resolvePoolMemoryPolicy as St, NcRulePatchSchema as T, systemEventFilterApplies as Tt, TIMELAPSE_DENSE_FLOOR_SEC as U, object as Ut, SCENE_DIVERGED as V, literal as Vt, TimelapseRuleInputSchema as W, partialRecord as Wt, alarmPanelCapability as X, addonWidgetsSourceCapability as Y, assertTimelapseCadences as Z, MACRO_LABELS as _, pickClusterStepModels as _t, COCO_TO_MACRO as a, embeddingEncoderCapability as at, NC_DEFAULT_SNOOZE_MINUTES as b, readDeviceStateFrom as bt, DEFAULT_TIMELAPSE_PREVIEW_TEXT as c, faceGalleryCapability as ct, DeclaredDevices as d, isDetectionMacroClass as dt, buildEventKindDescriptor as et, EVENT_KIND_BY_CAP as f, isScheduleActive as ft, LabelAttributionSchema as g, parseProcStatus as gt, FailureCounters as h, notificationRulesCapability as ht, CLUSTER_MODEL_SCOPED_STEPS as i, deriveRecordingMode as it, NcSnoozeSuppressedSchema as j, DeviceType as jt, NcSnoozeInputSchema as k, errMsg as kt, DETECTION_MACRO_CLASSES as l, failureContributionCapability as lt, FULL_IMAGE_BBOX as m, kebabToCamel as mt, AnalyticsGroupRecordSchema as n, customAction as nt, DEFAULT_EVENT_COLOR as o, encodeVectorBase64 as ot, EVENT_PAD_MS as p, isSourceCap as pt, TrackSourceSchema as q, unknown as qt, BaseDevice as r, defineCustomActions as rt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as s, evaluateSensorEdge as st, AnalyticsGroupDetailSchema as t, cosineSimilarity as tt, DETECTION_PIPELINE_CAP_NAME as u, hfModelUrl as ut, NC_ALARM_SYSTEM_EVENT_KINDS as v, pipelineAnalyticsCapability as vt, NcRuleInputSchema as w, subKindsOf as wt, NC_SNOOZE_MAX_MINUTES as x, readTimelapseGeneratedAt as xt, NC_CONDITION_CATALOG as y, plateGalleryCapability as yt, SCENE_DEFAULT_ANCHOR_THRESHOLD as z, boolean as zt };
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-1xYuXmDS.js");
5
+ const require_dist = require("../dist-BTviHGUC.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs);
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { Dt as BaseAddon, P as PoolMemoryWatchdog, ct as hfModelUrl, it as embeddingEncoderCapability, pt as parseProcStatus, yt as resolvePoolMemoryPolicy } from "../dist-4eP-xmjY.mjs";
1
+ import { At as BaseAddon, F as PoolMemoryWatchdog, St as resolvePoolMemoryPolicy, at as embeddingEncoderCapability, gt as parseProcStatus, ut as hfModelUrl } from "../dist-C8cA7PX_.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -1,6 +1,6 @@
1
1
  import { a as e, i as t, n, o as r, r as i, t as a } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.js-C0AuF9av.mjs";
2
2
  import { t as o } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-B3Wx5J80.mjs";
3
- import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C7KsMvFR.mjs";
3
+ import { a as s, i as c, n as l, o as u, r as d, s as f, t as p } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D8CtX8cU.mjs";
4
4
  import { n as m, r as h, t as g } from "./_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Bm-iyjmq.mjs";
5
5
  //#region ../../node_modules/lucide-react/dist/esm/shared/src/utils.js
6
6
  var _ = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), v = (e) => e.replace(/^([A-Z])|[\s-_]+(\w)/g, (e, t, n) => n ? n.toUpperCase() : t.toLowerCase()), y = (e) => {