@camstack/system 1.2.22 → 1.2.23

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 (60) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +0 -9
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -40
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -40
  12. package/dist/builtins/console-logging/index.js +1 -1
  13. package/dist/builtins/console-logging/index.mjs +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  18. package/dist/builtins/hub-forwarder/index.js +1 -1
  19. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  20. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  21. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  22. package/dist/builtins/local-network/local-network.addon.d.ts +16 -0
  23. package/dist/builtins/local-network/local-network.addon.js +30 -2
  24. package/dist/builtins/local-network/local-network.addon.mjs +30 -2
  25. package/dist/builtins/loki-logging/index.js +34 -12
  26. package/dist/builtins/loki-logging/index.mjs +34 -12
  27. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +19 -0
  28. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  30. package/dist/builtins/platform-probe/index.js +1 -1
  31. package/dist/builtins/platform-probe/index.mjs +1 -1
  32. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  34. package/dist/builtins/snapshot/index.js +1 -1
  35. package/dist/builtins/snapshot/index.mjs +1 -1
  36. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  38. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  40. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +7 -0
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +108 -4
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +108 -4
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +11 -1
  44. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +5 -0
  45. package/dist/builtins/system-config/system-config.addon.js +1 -1
  46. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  47. package/dist/builtins/winston-logging/index.js +1 -1
  48. package/dist/builtins/winston-logging/index.mjs +1 -1
  49. package/dist/{dist-C9_XdXl5.mjs → dist-CIek6F9X.mjs} +436 -72
  50. package/dist/{dist-j49ewCPY.js → dist-CS0pdC8l.js} +436 -72
  51. package/dist/index.js +58 -8
  52. package/dist/index.mjs +58 -8
  53. package/dist/kernel/capability-registry.d.ts +9 -0
  54. package/dist/kernel/transport/local-child-registry.d.ts +18 -0
  55. package/dist/kernel/transport/parent-unowned-call.d.ts +22 -0
  56. package/dist/logging/log-manager.d.ts +32 -1
  57. package/dist/logging/partitioned-log-buffer.d.ts +15 -3
  58. package/dist/{manifest-python-deps-B30qVAuW.mjs → manifest-python-deps-57PA4rHm.mjs} +15 -6
  59. package/dist/{manifest-python-deps-CnRW2Utv.js → manifest-python-deps-CqDPWqEE.js} +14 -5
  60. package/package.json +1 -1
@@ -3228,35 +3228,22 @@ var ConvertResultSchema = zod.z.object({
3228
3228
  */
3229
3229
  var RecordingWeekdaySchema = zod.z.number().int().min(0).max(6);
3230
3230
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
3231
- var RecordingScheduleSchema = zod.z.discriminatedUnion("kind", [zod.z.object({ kind: zod.z.literal("always") }), zod.z.object({
3232
- kind: zod.z.literal("timeOfDay"),
3233
- start: zod.z.string().regex(HHMM),
3234
- end: zod.z.string().regex(HHMM),
3235
- /** Restrict to these weekdays; omit = every day. */
3236
- days: zod.z.array(RecordingWeekdaySchema).optional()
3237
- })]);
3238
- var RecordingModeSchema = zod.z.enum([
3239
- "continuous",
3240
- "onMotion",
3241
- "onAudioThreshold"
3242
- ]);
3243
3231
  /**
3244
- * First-class, authoritative per-camera storage mode — the explicit choice the
3245
- * UI reads directly (never inferred from `rules`):
3246
- * - `off` — not recording.
3247
- * - `events` — record only around triggers (motion / audio threshold),
3248
- * with pre/post-buffer.
3249
- * - `continuous` — record 24/7 within the schedule.
3232
+ * DERIVED per-camera storage summary — the single field cheap consumers read
3233
+ * (the viewer's status dot, the camera list) instead of walking `bands`:
3234
+ * - `off` — no band covers the camera (or it is disabled).
3235
+ * - `events` — every band records around triggers only.
3236
+ * - `continuous` — at least one band records continuously.
3250
3237
  *
3251
- * `mode` compiles one-way to the internal `rules[]` consumed by the policy
3252
- * engine (see `compileRules`); `rules[]` is never authored directly anymore.
3238
+ * NEVER authored: the recorder stamps it from the authoritative `bands` on
3239
+ * every save (`activeModeForConfig`). Writing it has no effect.
3253
3240
  */
3254
3241
  var RecordingStorageModeSchema = zod.z.enum([
3255
3242
  "off",
3256
3243
  "events",
3257
3244
  "continuous"
3258
3245
  ]);
3259
- /** Which detectors trigger an `events`-mode recording. */
3246
+ /** Which detectors trigger an `events`-mode band. */
3260
3247
  var RecordingTriggersSchema = zod.z.object({
3261
3248
  motion: zod.z.boolean().optional(),
3262
3249
  audioThresholdDbfs: zod.z.number().optional()
@@ -3292,18 +3279,6 @@ var RecordingBandSchema = zod.z.object({
3292
3279
  preBufferSec: zod.z.number().min(0).optional(),
3293
3280
  postBufferSec: zod.z.number().min(0).optional()
3294
3281
  });
3295
- var RecordingRuleSchema = zod.z.object({
3296
- schedule: RecordingScheduleSchema,
3297
- mode: RecordingModeSchema,
3298
- /** Seconds of footage to retain BEFORE a trigger (applied at keep/discard). */
3299
- preBufferSec: zod.z.number().min(0).default(0),
3300
- /** Keep recording until this many seconds after the last trigger. */
3301
- postBufferSec: zod.z.number().min(0).default(0),
3302
- /** Each new trigger restarts the post-buffer window. */
3303
- resetTimeoutOnNewEvent: zod.z.boolean().default(true),
3304
- /** onAudioThreshold only — dBFS level that counts as a trigger. */
3305
- thresholdDbfs: zod.z.number().optional()
3306
- });
3307
3282
  /**
3308
3283
  * Per-device retention overrides. Every field is optional; an unset or `0`
3309
3284
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -3337,40 +3312,28 @@ var ScrubThumbnailPresetSchema = zod.z.enum([
3337
3312
  /**
3338
3313
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
3339
3314
  *
3340
- * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
3341
- * are its mode-specific parameters. `rules` is a DEPRECATED authoring input kept
3342
- * only for transition + migration (`migrateRulesToMode`); the policy engine
3343
- * consumes the compiled output of `compileRules(config)`, never `rules` directly.
3315
+ * `bands` is the ONLY authored recording intent: what to record, when, and on
3316
+ * which trigger. `mode` is a derived summary the recorder stamps on save; every
3317
+ * other field is a storage knob (profiles, segment length, retention, scrub).
3318
+ *
3319
+ * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
3320
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
3321
+ * A stale caller must fail loudly — silently stripping its legacy intent would
3322
+ * persist a band-less config, i.e. silently stop recording the camera.
3344
3323
  */
3345
3324
  var RecordingConfigSchema = zod.z.object({
3346
3325
  enabled: zod.z.boolean(),
3347
- /** Authoritative storage mode. Absent on legacy targets derived once via
3348
- * `migrateRulesToMode`, then persisted. */
3326
+ /** DERIVED summary of `bands`, stamped by the recorder on every save.
3327
+ * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
3349
3328
  mode: RecordingStorageModeSchema.optional(),
3350
3329
  profiles: zod.z.array(CamProfileSchema).optional(),
3351
3330
  segmentSeconds: zod.z.number().int().positive().optional(),
3352
- /** Shared recording time-bands for `events` & `continuous` — record only when
3353
- * the wall-clock falls inside one of these windows. Omit or empty = always.
3354
- * `continuous` compiles to one rule per band; `events` to band × trigger. */
3355
- schedules: zod.z.array(RecordingScheduleSchema).optional(),
3356
- /** Legacy single-band predecessor of `schedules`. Read-compat only — it is
3357
- * normalized into `schedules` on read and never written going forward. (Not
3358
- * tagged `@deprecated`: the normalization paths must read it cast-free.) */
3359
- schedule: RecordingScheduleSchema.optional(),
3360
- /** `events`-mode only — which detectors trigger a recording. */
3361
- triggers: RecordingTriggersSchema.optional(),
3362
- /** `events`-mode only — seconds retained before / after a trigger. */
3363
- preBufferSec: zod.z.number().min(0).optional(),
3364
- postBufferSec: zod.z.number().min(0).optional(),
3365
- /** DEPRECATED authoring input; retained for migration/transition. */
3366
- rules: zod.z.array(RecordingRuleSchema).optional(),
3367
3331
  /**
3368
- * AUTHORITATIVE mode-per-band recording model (recorder). When present it
3369
- * is the single source of truth; the legacy `mode`/`schedules`/`schedule`/
3370
- * `triggers`/`rules` fields above are kept for READ-COMPAT only and are
3371
- * derived into bands once via `migrateConfigToBands`.
3332
+ * AUTHORITATIVE mode-per-band recording model the single source of truth
3333
+ * the recorder's band engine consumes. An empty array = record nothing;
3334
+ * "off" is the absence of a covering band, never a band value.
3372
3335
  */
3373
- bands: zod.z.array(RecordingBandSchema).optional(),
3336
+ bands: zod.z.array(RecordingBandSchema).default([]),
3374
3337
  retention: RecordingRetentionSchema.optional(),
3375
3338
  /**
3376
3339
  * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
@@ -3378,8 +3341,15 @@ var RecordingConfigSchema = zod.z.object({
3378
3341
  * windows only — existing sheets are immutable, and each window's index
3379
3342
  * carries its own tile dims so mixed-preset history renders correctly.
3380
3343
  */
3381
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
3382
- });
3344
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional(),
3345
+ /**
3346
+ * OPT-IN thumbnail-strip generation for this camera: every keyframe of the
3347
+ * low recording saved as a JPEG (the fast-drag scrub depth), a derived
3348
+ * cache that eviction reclaims with the footage. Absent/false = no strips
3349
+ * are written and scrub reads exact keyframes at every velocity.
3350
+ */
3351
+ stripsEnabled: zod.z.boolean().optional()
3352
+ }).strict();
3383
3353
  /**
3384
3354
  * Ops-log — the durable, append-only operations audit shared by the
3385
3355
  * recordings and events management surfaces.
@@ -3398,7 +3368,8 @@ var OpsLogOpSchema = zod.z.enum([
3398
3368
  "prune",
3399
3369
  "manual-delete",
3400
3370
  "rescan",
3401
- "retention-run"
3371
+ "retention-run",
3372
+ "relocate"
3402
3373
  ]);
3403
3374
  /** Why the operation ran. */
3404
3375
  var OpsLogReasonSchema = zod.z.enum([
@@ -3437,6 +3408,55 @@ var OpsLogQueryInputSchema = zod.z.object({
3437
3408
  limit: zod.z.number().int().min(1).max(1e3).optional()
3438
3409
  });
3439
3410
  /**
3411
+ * Entity-relocation job state (storage entity-routing spec, Phase 4).
3412
+ *
3413
+ * One shape shared by the recorder's `relocateFootage` (segments + strips) and
3414
+ * pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
3415
+ * page renders both movers with one component. Jobs are in-RAM (a restart
3416
+ * forgets them — re-running is safe by construction: copy-if-absent, delete
3417
+ * after verify) and each completed/failed run also lands one durable ops-log
3418
+ * row on the owning addon's surface.
3419
+ */
3420
+ var RelocateJobStateSchema = zod.z.enum([
3421
+ "running",
3422
+ "done",
3423
+ "failed",
3424
+ "cancelled"
3425
+ ]);
3426
+ var RelocateJobSchema = zod.z.object({
3427
+ jobId: zod.z.string(),
3428
+ state: RelocateJobStateSchema,
3429
+ /** Source location — for media relocation this is informational ('*': rows
3430
+ * move from wherever they are to the target). */
3431
+ fromLocationId: zod.z.string(),
3432
+ toLocationId: zod.z.string(),
3433
+ /** Scoped device, or null = every device. */
3434
+ deviceId: zod.z.number().nullable(),
3435
+ /** What the job moves (owner-addon specific: segments/strips or media). */
3436
+ entities: zod.z.array(zod.z.string()),
3437
+ filesMoved: zod.z.number().int(),
3438
+ bytesMoved: zod.z.number().int(),
3439
+ /** Total files discovered up front; null while (or when) unknown. */
3440
+ filesTotal: zod.z.number().int().nullable(),
3441
+ startedAt: zod.z.number(),
3442
+ finishedAt: zod.z.number().nullable(),
3443
+ error: zod.z.string().nullable()
3444
+ });
3445
+ var RelocateFootageInputSchema = zod.z.object({
3446
+ deviceId: zod.z.number().optional(),
3447
+ fromLocationId: zod.z.string(),
3448
+ toLocationId: zod.z.string(),
3449
+ entities: zod.z.array(zod.z.enum(["segments", "strips"])).optional(),
3450
+ /** Copy throttle in MB/s (default 40) — the drain is a background chore,
3451
+ * never allowed to starve live writers. */
3452
+ throttleMbps: zod.z.number().min(1).max(1e3).optional()
3453
+ });
3454
+ var RelocateMediaInputSchema = zod.z.object({
3455
+ deviceId: zod.z.number().optional(),
3456
+ toLocationId: zod.z.string(),
3457
+ throttleMbps: zod.z.number().min(1).max(1e3).optional()
3458
+ });
3459
+ /**
3440
3460
  * Severity ordering. Declared once here because it had already been re-declared
3441
3461
  * three times independently (`kernel/transport/uds-logger.ts`,
3442
3462
  * `builtins/loki-logging/loki-destination.ts`, and the log buffer) — four copies
@@ -3506,6 +3526,13 @@ var StorageLocationSchema = zod.z.object({
3506
3526
  nodeId: zod.z.string().optional(),
3507
3527
  isDefault: zod.z.boolean().default(false),
3508
3528
  isSystem: zod.z.boolean().default(false),
3529
+ /** COMPUTED at read time by the orchestrator (statfs of the backing volume
3530
+ * for node-local locations it can reach) — never persisted, absent when the
3531
+ * volume is remote/unreachable. The single capacity truth every UI reads. */
3532
+ capacity: zod.z.object({
3533
+ totalBytes: zod.z.number(),
3534
+ availableBytes: zod.z.number()
3535
+ }).nullable().optional(),
3509
3536
  createdAt: zod.z.number(),
3510
3537
  updatedAt: zod.z.number()
3511
3538
  });
@@ -4334,7 +4361,8 @@ var NcTaxonomyEntrySchema = zod.z.object({
4334
4361
  /** Macro/category parent for grouping ('car' → 'vehicle'); null for a top. */
4335
4362
  parentKind: zod.z.string().nullable()
4336
4363
  });
4337
- zod.z.object({
4364
+ /** The complete NC picker taxonomy — three grouped buckets. */
4365
+ var NcTaxonomySchema = zod.z.object({
4338
4366
  videoClasses: zod.z.array(NcTaxonomyEntrySchema),
4339
4367
  audioKinds: zod.z.array(NcTaxonomyEntrySchema),
4340
4368
  labels: zod.z.array(NcTaxonomyEntrySchema)
@@ -5135,6 +5163,7 @@ var AccessoryKind = {
5135
5163
  };
5136
5164
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
5137
5165
  DeviceFeature.BatteryOperated;
5166
+ new Set(["devices", "classes"]);
5138
5167
  /**
5139
5168
  * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
5140
5169
  * motion-zones, and the detection zones/lines editor all speak this one
@@ -5455,12 +5484,60 @@ var NcRuleTargetSchema = zod.z.object({
5455
5484
  * - `keyFrame` — the clean scene frame (no subject box).
5456
5485
  * - `none` — no attachment.
5457
5486
  */
5458
- var NcMediaPolicySchema = zod.z.object({ attach: zod.z.enum([
5459
- "best",
5460
- "best-matching",
5461
- "keyFrame",
5462
- "none"
5463
- ]).default("best") });
5487
+ /**
5488
+ * WHAT THE PICTURE SHOWS — chosen explicitly, because the implicit ladders
5489
+ * conflate it with the selection strategy and betray the request: asking for
5490
+ * the clean scene frame on an object-event owner used to start at
5491
+ * `fullFrameBoxed`, i.e. the annotated frame (2026-07-30).
5492
+ * - `cropped` — the subject, tight. What "who is at the door" wants.
5493
+ * - `full` — the clean scene, no annotation. What "what is going on" wants.
5494
+ * - `boxed` — the scene WITH the detection boxes drawn, for verifying what
5495
+ * the pipeline actually saw.
5496
+ */
5497
+ var NcMediaFrameSchema = zod.z.enum([
5498
+ "cropped",
5499
+ "full",
5500
+ "boxed"
5501
+ ]);
5502
+ var NcMediaPolicySchema = zod.z.object({
5503
+ attach: zod.z.enum([
5504
+ "best",
5505
+ "best-matching",
5506
+ "keyFrame",
5507
+ "none"
5508
+ ]).default("best"),
5509
+ /** What the still shows. Absent = `cropped` (the historical `best` shape). */
5510
+ frame: NcMediaFrameSchema.optional(),
5511
+ /** Crop the attached still to the rule's condition-zone bbox (padded) —
5512
+ * "show me the ZONE", not the whole scene or the subject crop. */
5513
+ zoneCrop: zod.z.boolean().optional(),
5514
+ /**
5515
+ * Also attach a short GIF cut from the stream broker's clip ring around the
5516
+ * event — NOT from the recording, so the camera does not have to be
5517
+ * recording, and the window sits AROUND the moment instead of a segment
5518
+ * behind it. Fail-closed: a window the ring does not cover contributes no
5519
+ * gif, never a failed notification.
5520
+ */
5521
+ gif: zod.z.boolean().optional(),
5522
+ /**
5523
+ * Also attach a short MP4 CLIP of the same cut. Same source and the same
5524
+ * fail-closed rule as `gif`. Prefer it where the backend takes video
5525
+ * (telegram, discord, zentik, webhook); backends that do not are handled by
5526
+ * the degrade engine, which drops the video and keeps the still.
5527
+ */
5528
+ clip: zod.z.boolean().optional(),
5529
+ /** Seconds of footage BEFORE / AFTER the event instant. Defaults 3 / 7. */
5530
+ clipPreRollSec: zod.z.number().int().min(0).max(30).optional(),
5531
+ clipPostRollSec: zod.z.number().int().min(0).max(30).optional(),
5532
+ /**
5533
+ * Which stream profile the footage is cut from. Absent = the CHEAPEST
5534
+ * assigned profile: a notification is watched on a phone, so the 4K
5535
+ * rendition would burn CPU to produce a file the client downscales anyway.
5536
+ * A profile that is not assigned falls back to the cheapest, and the render
5537
+ * reports which one actually ran.
5538
+ */
5539
+ profile: CamProfileSchema.optional()
5540
+ });
5464
5541
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
5465
5542
  var NcThrottleSchema = zod.z.object({
5466
5543
  cooldownSec: zod.z.number().int().min(0).max(86400).default(60),
@@ -5474,7 +5551,19 @@ var NcRuleInputSchema = zod.z.object({
5474
5551
  delivery: NcDeliverySchema,
5475
5552
  conditions: NcConditionsSchema.default({}),
5476
5553
  schedule: NcScheduleSchema.optional(),
5477
- targets: zod.z.array(NcRuleTargetSchema).min(1),
5554
+ /** May be empty when `targetUsers` addresses at least one user — the
5555
+ * "at least one addressee" invariant is enforced by the provider, because
5556
+ * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
5557
+ targets: zod.z.array(NcRuleTargetSchema),
5558
+ /**
5559
+ * USERS this rule addresses in addition to `targets` (Phase 4). At fire
5560
+ * time each user fans out to the personal targets they own
5561
+ * (`config.ownerUserId`), filtered by that user's `allowedDevices` for the
5562
+ * firing camera — a user is never notified about a device they cannot open.
5563
+ * Per-user opt-outs (`disabledTargetIds`) still apply to the fanned-out
5564
+ * targets.
5565
+ */
5566
+ targetUsers: zod.z.array(zod.z.string()).optional(),
5478
5567
  media: NcMediaPolicySchema.default({ attach: "best" }),
5479
5568
  throttle: NcThrottleSchema.default({
5480
5569
  cooldownSec: 60,
@@ -5704,7 +5793,20 @@ var notificationRulesCapability = {
5704
5793
  kind: "mutation",
5705
5794
  auth: "admin"
5706
5795
  }),
5707
- getConditionCatalog: method(zod.z.object({}), zod.z.object({ catalog: zod.z.array(NcConditionDescriptorSchema) })),
5796
+ /**
5797
+ * The machine-readable condition surface a rule editor renders from, plus
5798
+ * the picker VOCABULARY those conditions draw on.
5799
+ *
5800
+ * `taxonomy` is optional so an older provider that only returns `catalog`
5801
+ * still validates; a client that receives none keeps free-text inputs.
5802
+ * It was previously served ONLY on the `nc.getConditionCatalog` bridge
5803
+ * action, which is why the viewer had grouped pickers and the admin UI —
5804
+ * which reaches this cap — was stuck typing `person` from memory.
5805
+ */
5806
+ getConditionCatalog: method(zod.z.object({}), zod.z.object({
5807
+ catalog: zod.z.array(NcConditionDescriptorSchema),
5808
+ taxonomy: NcTaxonomySchema.optional()
5809
+ })),
5708
5810
  /**
5709
5811
  * Queryable delivery history — a read-only view over the durable outbox
5710
5812
  * (fired rule, subject summary, target, status, timestamps, error on a
@@ -6752,6 +6854,48 @@ var streamBrokerCapability = {
6752
6854
  kind: "mutation",
6753
6855
  auth: "admin"
6754
6856
  }),
6857
+ /**
6858
+ * Render a short GIF or MP4 from the broker's PRE-BUFFER around an instant.
6859
+ *
6860
+ * The pre-buffer is the only source that already holds the seconds BEFORE
6861
+ * an event (it is what it exists for — "when an addon needs the last N
6862
+ * seconds of video for clip creation"). Cutting from recorded segments
6863
+ * cannot: only finalized segments are addressable and the one covering the
6864
+ * moment is still being written, so a segment-sourced clip is either
6865
+ * delayed or shows the approach instead of the event (operator,
6866
+ * 2026-07-30). It also means a camera does not have to be RECORDING to
6867
+ * produce a notification clip.
6868
+ *
6869
+ * Returns muxed BYTES, never packets: the frame plane never crosses a
6870
+ * process boundary at frame rate (D9/D18) — this is one compressed,
6871
+ * on-demand payload per notification.
6872
+ *
6873
+ * Waits up to `postRollSec` for the tail to accumulate, so the window is
6874
+ * CENTRED on `aroundMs`.
6875
+ */
6876
+ renderPreBufferClip: method(zod.z.object({
6877
+ deviceId: zod.z.number(),
6878
+ /** Absent = the LOWEST assigned profile — a notification attachment is
6879
+ * watched on a phone, and the cheap rendition is the right default. */
6880
+ profile: CamProfileSchema.optional(),
6881
+ aroundMs: zod.z.number(),
6882
+ preRollSec: zod.z.number().min(0).max(20).default(3),
6883
+ postRollSec: zod.z.number().min(0).max(20).default(5),
6884
+ format: zod.z.enum(["gif", "mp4"]).default("gif"),
6885
+ maxWidth: zod.z.number().int().min(120).max(1920).default(480),
6886
+ /** GIF only — MP4 keeps the source cadence. */
6887
+ fps: zod.z.number().int().min(1).max(15).default(5)
6888
+ }), zod.z.object({
6889
+ base64: zod.z.string(),
6890
+ mime: zod.z.string(),
6891
+ bytes: zod.z.number().int(),
6892
+ /** The profile actually rendered (what the default resolved to). */
6893
+ profile: CamProfileSchema,
6894
+ durationMs: zod.z.number()
6895
+ }), {
6896
+ kind: "mutation",
6897
+ auth: "admin"
6898
+ }),
6755
6899
  listAllCameraStreams: method(zod.z.void(), zod.z.array(CameraStreamSchema).readonly()),
6756
6900
  listAllProfileSlots: method(zod.z.void(), zod.z.array(ProfileSlotSchema).readonly()),
6757
6901
  getBrokerStats: method(zod.z.object({ brokerId: zod.z.string() }), BrokerStatsSchema),
@@ -16758,6 +16902,36 @@ var TargetKindSchema = zod.z.object({
16758
16902
  icon: zod.z.string(),
16759
16903
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
16760
16904
  addonId: zod.z.string(),
16905
+ /**
16906
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
16907
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16908
+ * when the addon bundles no icon for that kind — the client then falls back
16909
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16910
+ *
16911
+ * Root-relative on purpose: it resolves against whatever origin serves a web
16912
+ * client, and a native client joins it onto its own hub base.
16913
+ *
16914
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
16915
+ * field that survived only because the runtime cap-router forwards provider
16916
+ * output verbatim — so every consumer had to re-declare it by hand to stop
16917
+ * its own Zod parse from stripping it, and the whole arrangement would have
16918
+ * broken silently the moment output validation was tightened anywhere.
16919
+ */
16920
+ iconUrl: zod.z.string().optional(),
16921
+ /**
16922
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16923
+ *
16924
+ * The server knows this and therefore says it, because the client cannot
16925
+ * safely guess: a React-Native client renders SVG and raster through two
16926
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16927
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
16928
+ * placeholder glyph for every vector icon while the web build looked fine.
16929
+ *
16930
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
16931
+ * not been updated — a client that cannot determine the type should prefer
16932
+ * its raster path, which is the safe default for an unknown image.
16933
+ */
16934
+ iconMediaType: zod.z.string().optional(),
16761
16935
  configSchema: ConfigSchemaPassthrough,
16762
16936
  supportsDiscovery: zod.z.boolean(),
16763
16937
  caps: TargetKindCapsSchema
@@ -17630,6 +17804,23 @@ var pipelineAnalyticsCapability = {
17630
17804
  }),
17631
17805
  /** The events ops-log rows (newest-first), optionally scoped to one camera.
17632
17806
  * Backed by a declared pipeline-analytics SQLite collection. */
17807
+ /**
17808
+ * Move event-media blobs onto `toLocationId` (entity-routing spec Phase
17809
+ * 4): per-row copy → stamp row.locationId → delete old blob. Rows already
17810
+ * at the target are skipped, so a re-run resumes. Single-flight.
17811
+ */
17812
+ relocateMedia: method(RelocateMediaInputSchema, zod.z.object({ jobId: zod.z.string() }), {
17813
+ kind: "mutation",
17814
+ auth: "admin"
17815
+ }),
17816
+ getMediaRelocateStatus: method(zod.z.object({}), zod.z.array(RelocateJobSchema).readonly(), {
17817
+ kind: "query",
17818
+ auth: "admin"
17819
+ }),
17820
+ cancelMediaRelocate: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
17821
+ kind: "mutation",
17822
+ auth: "admin"
17823
+ }),
17633
17824
  listOpsLog: method(OpsLogQueryInputSchema, zod.z.array(OpsLogEntrySchema).readonly(), {
17634
17825
  kind: "query",
17635
17826
  auth: "admin"
@@ -21187,6 +21378,17 @@ var ConnectionEndpointSchema = zod.z.object({
21187
21378
  priority: zod.z.number()
21188
21379
  });
21189
21380
  var GetConnectionEndpointsResultSchema = zod.z.object({ endpoints: zod.z.array(ConnectionEndpointSchema).readonly() });
21381
+ /**
21382
+ * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
21383
+ * AUTO (resolved from the candidate ranking at send time); `resolved` reports
21384
+ * what AUTO currently picks, so the UI can show the effective value either way.
21385
+ */
21386
+ var NotificationEndpointSchema = zod.z.object({
21387
+ /** The operator's explicit choice, or null for AUTO. */
21388
+ baseUrl: zod.z.string().nullable(),
21389
+ /** What the ranking currently resolves to (null when nothing is reachable). */
21390
+ resolved: zod.z.string().nullable()
21391
+ });
21190
21392
  var AllowedAddressesSchema = zod.z.object({
21191
21393
  /**
21192
21394
  * Allowlist of interface addresses operators have explicitly opted
@@ -21238,6 +21440,26 @@ var localNetworkCapability = {
21238
21440
  scheme: zod.z.enum(["http", "https"]).optional()
21239
21441
  }), GetConnectionEndpointsResultSchema),
21240
21442
  /**
21443
+ * The endpoint OUTBOUND artifacts must be addressed by — notification
21444
+ * attachment links and deep links. `baseUrl: null` means AUTO: the first
21445
+ * public candidate (a configured origin, then a connected tunnel, then the
21446
+ * LAN), never loopback.
21447
+ *
21448
+ * Why an explicit marker: the automatic ranking cannot know which of
21449
+ * several reachable addresses the RECIPIENT can actually open. A link is
21450
+ * consumed on someone's phone, off the LAN, possibly hours later — and a
21451
+ * link nobody can open is worse than no link, because the notification
21452
+ * arrives advertising media that 404s.
21453
+ */
21454
+ getNotificationEndpoint: method(zod.z.void(), NotificationEndpointSchema),
21455
+ /**
21456
+ * Mark which endpoint notification artifacts use. `baseUrl: null` restores
21457
+ * AUTO. The value is stored verbatim (not an index into the candidate
21458
+ * list): interfaces come and go, and an index would silently re-point at a
21459
+ * different address after a reboot.
21460
+ */
21461
+ setNotificationEndpoint: method(zod.z.object({ baseUrl: zod.z.string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }),
21462
+ /**
21241
21463
  * Read the operator's allowlist. Empty = "auto" (no filter). Used
21242
21464
  * by the admin UI's address selector to seed its checkbox state.
21243
21465
  */
@@ -22331,7 +22553,12 @@ var RecordingDeviceUsageSchema = zod.z.object({
22331
22553
  var RecordingLocationUsageSchema = zod.z.object({
22332
22554
  /** StorageLocation id; null for the legacy/degraded single-root fallback. */
22333
22555
  locationId: zod.z.string().nullable(),
22334
- /** Bytes of recordings stored on this location. */
22556
+ /** Every location id sharing this row's PHYSICAL volume (aliases). One row
22557
+ * is emitted per physical disk (2026-07-29): two locations on one root
22558
+ * previously rendered as two identical "disks" with a nonsensical used
22559
+ * split — hydrate attribution across aliases is arbitrary by nature. */
22560
+ locationIds: zod.z.array(zod.z.string()).optional(),
22561
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
22335
22562
  usedBytes: zod.z.number(),
22336
22563
  /** Free bytes on the location's volume; null when capacity is unknown (remote). */
22337
22564
  availableBytes: zod.z.number().nullable(),
@@ -22487,6 +22714,77 @@ var recordingCapability = {
22487
22714
  listOpsLog: method(OpsLogQueryInputSchema, zod.z.array(OpsLogEntrySchema).readonly(), {
22488
22715
  kind: "query",
22489
22716
  auth: "admin"
22717
+ }),
22718
+ /**
22719
+ * Move footage (segments and/or strips) from one recordings location to
22720
+ * another — the drain workflow's mover (entity-routing spec Phase 4).
22721
+ * Throttled copy → size-verify → delete source → index refresh; resumable
22722
+ * by construction (copy-if-absent). Single-flight: one job at a time.
22723
+ */
22724
+ /**
22725
+ * Render a short GIF from recorded footage around `aroundMs` (low profile,
22726
+ * palette-free ffmpeg gif). Synchronous — the window is a few seconds of
22727
+ * `low`, rendering in ~1-2s. Built for notification attachments: the
22728
+ * caller gets bytes, nothing persists. Fails when no footage covers the
22729
+ * window (camera not recording → caller skips the attachment).
22730
+ */
22731
+ renderGif: method(zod.z.object({
22732
+ deviceId: zod.z.number(),
22733
+ aroundMs: zod.z.number(),
22734
+ preRollSec: zod.z.number().min(0).max(30).default(2),
22735
+ postRollSec: zod.z.number().min(0).max(30).default(5),
22736
+ maxWidth: zod.z.number().int().min(120).max(1280).default(480),
22737
+ fps: zod.z.number().int().min(1).max(15).default(5)
22738
+ }), zod.z.object({
22739
+ gifBase64: zod.z.string(),
22740
+ fromMs: zod.z.number(),
22741
+ toMs: zod.z.number()
22742
+ }), {
22743
+ kind: "mutation",
22744
+ auth: "admin"
22745
+ }),
22746
+ /**
22747
+ * Render a short MP4 clip from recorded footage around `aroundMs` — the
22748
+ * "send a clip, not a gif" notification attachment. Same window, source
22749
+ * and fail-closed contract as {@link renderGif}: no footage covering the
22750
+ * window ⇒ it throws and the caller ships no attachment. Re-encoded (never
22751
+ * stream-copied) so a concat across segments with different encoder
22752
+ * parameters still plays. Silent by contract.
22753
+ *
22754
+ * The returned window is SEGMENT-GRANULAR: the source playlist is built
22755
+ * from whole segments, so the clip starts at or before `fromMs` and ends
22756
+ * at or after `toMs` — overshoot bounded by one segment per side (an 8 s
22757
+ * ask over 4 s segments measured 12 s). `fromMs`/`toMs` report the window
22758
+ * ASKED for, not the encoded duration.
22759
+ */
22760
+ renderClip: method(zod.z.object({
22761
+ deviceId: zod.z.number(),
22762
+ aroundMs: zod.z.number(),
22763
+ preRollSec: zod.z.number().min(0).max(30).default(3),
22764
+ postRollSec: zod.z.number().min(0).max(30).default(7),
22765
+ maxWidth: zod.z.number().int().min(160).max(1920).default(640)
22766
+ }), zod.z.object({
22767
+ clipBase64: zod.z.string(),
22768
+ mime: zod.z.string(),
22769
+ fromMs: zod.z.number(),
22770
+ toMs: zod.z.number(),
22771
+ bytes: zod.z.number().int()
22772
+ }), {
22773
+ kind: "mutation",
22774
+ auth: "admin"
22775
+ }),
22776
+ relocateFootage: method(RelocateFootageInputSchema, zod.z.object({ jobId: zod.z.string() }), {
22777
+ kind: "mutation",
22778
+ auth: "admin"
22779
+ }),
22780
+ /** Every known relocate job (in-RAM, newest-first). */
22781
+ getRelocateStatus: method(zod.z.object({}), zod.z.array(RelocateJobSchema).readonly(), {
22782
+ kind: "query",
22783
+ auth: "admin"
22784
+ }),
22785
+ cancelRelocate: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
22786
+ kind: "mutation",
22787
+ auth: "admin"
22490
22788
  })
22491
22789
  }
22492
22790
  };
@@ -25554,6 +25852,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
25554
25852
  addonId: null,
25555
25853
  access: "view"
25556
25854
  },
25855
+ "localNetwork.getNotificationEndpoint": {
25856
+ capName: "local-network",
25857
+ capScope: "system",
25858
+ addonId: null,
25859
+ access: "view"
25860
+ },
25557
25861
  "localNetwork.getPreferred": {
25558
25862
  capName: "local-network",
25559
25863
  capScope: "system",
@@ -25578,6 +25882,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
25578
25882
  addonId: null,
25579
25883
  access: "create"
25580
25884
  },
25885
+ "localNetwork.setNotificationEndpoint": {
25886
+ capName: "local-network",
25887
+ capScope: "system",
25888
+ addonId: null,
25889
+ access: "create"
25890
+ },
25581
25891
  "lockControl.lock": {
25582
25892
  capName: "lock-control",
25583
25893
  capScope: "device",
@@ -26220,6 +26530,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
26220
26530
  addonId: null,
26221
26531
  access: "create"
26222
26532
  },
26533
+ "pipelineAnalytics.cancelMediaRelocate": {
26534
+ capName: "pipeline-analytics",
26535
+ capScope: "device",
26536
+ addonId: null,
26537
+ access: "create"
26538
+ },
26223
26539
  "pipelineAnalytics.clearTracks": {
26224
26540
  capName: "pipeline-analytics",
26225
26541
  capScope: "device",
@@ -26274,6 +26590,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
26274
26590
  addonId: null,
26275
26591
  access: "view"
26276
26592
  },
26593
+ "pipelineAnalytics.getMediaRelocateStatus": {
26594
+ capName: "pipeline-analytics",
26595
+ capScope: "device",
26596
+ addonId: null,
26597
+ access: "view"
26598
+ },
26277
26599
  "pipelineAnalytics.getMotionEvents": {
26278
26600
  capName: "pipeline-analytics",
26279
26601
  capScope: "device",
@@ -26346,6 +26668,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
26346
26668
  addonId: null,
26347
26669
  access: "create"
26348
26670
  },
26671
+ "pipelineAnalytics.relocateMedia": {
26672
+ capName: "pipeline-analytics",
26673
+ capScope: "device",
26674
+ addonId: null,
26675
+ access: "create"
26676
+ },
26349
26677
  "pipelineAnalytics.searchObjectEvents": {
26350
26678
  capName: "pipeline-analytics",
26351
26679
  capScope: "device",
@@ -27108,6 +27436,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
27108
27436
  addonId: null,
27109
27437
  access: "create"
27110
27438
  },
27439
+ "recording.cancelRelocate": {
27440
+ capName: "recording",
27441
+ capScope: "system",
27442
+ addonId: null,
27443
+ access: "create"
27444
+ },
27111
27445
  "recording.deleteFootprint": {
27112
27446
  capName: "recording",
27113
27447
  capScope: "system",
@@ -27138,6 +27472,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
27138
27472
  addonId: null,
27139
27473
  access: "view"
27140
27474
  },
27475
+ "recording.getRelocateStatus": {
27476
+ capName: "recording",
27477
+ capScope: "system",
27478
+ addonId: null,
27479
+ access: "view"
27480
+ },
27141
27481
  "recording.getStorageUsage": {
27142
27482
  capName: "recording",
27143
27483
  capScope: "system",
@@ -27168,6 +27508,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
27168
27508
  addonId: null,
27169
27509
  access: "view"
27170
27510
  },
27511
+ "recording.relocateFootage": {
27512
+ capName: "recording",
27513
+ capScope: "system",
27514
+ addonId: null,
27515
+ access: "create"
27516
+ },
27517
+ "recording.renderClip": {
27518
+ capName: "recording",
27519
+ capScope: "system",
27520
+ addonId: null,
27521
+ access: "create"
27522
+ },
27523
+ "recording.renderGif": {
27524
+ capName: "recording",
27525
+ capScope: "system",
27526
+ addonId: null,
27527
+ access: "create"
27528
+ },
27171
27529
  "recording.rescanStorage": {
27172
27530
  capName: "recording",
27173
27531
  capScope: "system",
@@ -27762,6 +28120,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
27762
28120
  addonId: null,
27763
28121
  access: "create"
27764
28122
  },
28123
+ "streamBroker.renderPreBufferClip": {
28124
+ capName: "stream-broker",
28125
+ capScope: "system",
28126
+ addonId: null,
28127
+ access: "create"
28128
+ },
27765
28129
  "streamBroker.restartProfile": {
27766
28130
  capName: "stream-broker",
27767
28131
  capScope: "system",