@camstack/addon-osd-manager 0.1.20 → 0.1.22

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 (18) hide show
  1. package/dist/{MotionZonesSettings-Dhh1AqWY.mjs → MotionZonesSettings-DhYwaRz3.mjs} +2 -2
  2. package/dist/{PrivacyMaskSettings-CPABuSH1.mjs → PrivacyMaskSettings-BquvldBM.mjs} +4 -4
  3. package/dist/{SceneMonitorEditor-CyVjkJI2.mjs → SceneMonitorEditor-wK0ulSnf.mjs} +3 -3
  4. package/dist/_stub.js +11 -11
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-5jby0ZM9.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-CG-BhZBB.mjs} +4 -4
  6. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6_-m0FOQ.mjs +26 -0
  7. package/dist/{hostInit-5GIsHCyh.mjs → hostInit-8p7aKRSc.mjs} +3 -3
  8. package/dist/index.js +685 -131
  9. package/dist/index.mjs +685 -131
  10. package/dist/{player-overlays-A11g0xRn.mjs → player-overlays-CS4sZzYZ.mjs} +1 -1
  11. package/dist/remoteEntry.js +1 -1
  12. package/dist/{responsive-B_W10135.mjs → responsive-VdLah82L.mjs} +1 -1
  13. package/dist/{square-BqxtsJJ4.mjs → square-a4rMgWr9.mjs} +1 -1
  14. package/dist/{trash-2-xxP-WwG0.mjs → trash-2-BCaHwGDI.mjs} +1 -1
  15. package/dist/{use-device-snapshot-B6sowK2s.mjs → use-device-snapshot-CTWHDX3F.mjs} +1 -1
  16. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-0ziNXHDT.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-BdfKcHRf.mjs} +1 -1
  17. package/package.json +1 -1
  18. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DO3S4jGF.mjs +0 -26
package/dist/index.mjs CHANGED
@@ -5800,6 +5800,13 @@ var BaseAddon = class {
5800
5800
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5801
5801
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5802
5802
  _registeredCapNames = [];
5803
+ /**
5804
+ * True only after `readAddonStore` actually answered. Constructor
5805
+ * defaults look like stored config when the store is down — a forked
5806
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5807
+ * mode, 2026-08-25) is not "the operator chose this".
5808
+ */
5809
+ settingsStoreReady = false;
5803
5810
  /** Default config values. Provided via constructor. */
5804
5811
  defaults;
5805
5812
  constructor(defaults) {
@@ -6200,7 +6207,9 @@ var BaseAddon = class {
6200
6207
  ];
6201
6208
  let lastErr;
6202
6209
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6203
- return await settings.readAddonStore() ?? {};
6210
+ const stored = await settings.readAddonStore() ?? {};
6211
+ this.settingsStoreReady = true;
6212
+ return stored;
6204
6213
  } catch (err) {
6205
6214
  lastErr = err;
6206
6215
  const msg = err instanceof Error ? err.message : String(err);
@@ -6208,6 +6217,7 @@ var BaseAddon = class {
6208
6217
  if (attempt === delaysMs.length) break;
6209
6218
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6210
6219
  }
6220
+ this.settingsStoreReady = false;
6211
6221
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6212
6222
  return {};
6213
6223
  }
@@ -6619,7 +6629,7 @@ function method(input, output, options) {
6619
6629
  input,
6620
6630
  output,
6621
6631
  kind: options?.kind ?? "query",
6622
- auth: options?.auth ?? "protected",
6632
+ ...options?.auth !== void 0 ? { auth: options.auth } : {},
6623
6633
  ...options?.access !== void 0 ? { access: options.access } : {},
6624
6634
  ...options?.caller !== void 0 ? { caller: options.caller } : {},
6625
6635
  timeoutMs: options?.timeoutMs
@@ -6649,8 +6659,17 @@ var adminUiCapability = {
6649
6659
  mode: "singleton",
6650
6660
  internal: true,
6651
6661
  methods: {
6652
- getStaticDir: method(_void(), StaticDirOutputSchema$1),
6653
- getVersion: method(_void(), VersionOutputSchema$1)
6662
+ /**
6663
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
6664
+ * `data-store-provider`) — both methods were reachable on the AppRouter
6665
+ * by ANY authenticated session at the default `auth: 'protected'`, and
6666
+ * `getStaticDir` leaks a hub filesystem path. The only caller is
6667
+ * `main.ts`'s static-file bootstrap, via `capRegistry.getSingletonForNode`
6668
+ * — never tRPC — so `auth: 'admin'` costs it nothing. The actual admin-ui
6669
+ * SPA assets are served as plain static files, unaffected by this gate.
6670
+ */
6671
+ getStaticDir: method(_void(), StaticDirOutputSchema$1, { auth: "admin" }),
6672
+ getVersion: method(_void(), VersionOutputSchema$1, { auth: "admin" })
6654
6673
  }
6655
6674
  };
6656
6675
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
@@ -6986,8 +7005,23 @@ var viewerUiCapability = {
6986
7005
  mode: "singleton",
6987
7006
  internal: true,
6988
7007
  methods: {
6989
- getStaticDir: method(_void(), StaticDirOutputSchema),
6990
- getVersion: method(_void(), VersionOutputSchema)
7008
+ /**
7009
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
7010
+ * `data-store-provider`) — both methods were reachable on the AppRouter
7011
+ * by ANY authenticated session at the default `auth: 'protected'`, and
7012
+ * `getStaticDir` leaks a hub filesystem path.
7013
+ *
7014
+ * ⚠ Checked before gating: the VIEWER app IS used by a non-admin user
7015
+ * (`roberta`), but it never calls `viewerUi.*` over tRPC. The static SPA
7016
+ * it loads is served by the plain `GET /viewer/*` Fastify route in
7017
+ * `main.ts`, which reads `staticDir`/`indexPath` from a module-level
7018
+ * cache populated ONCE at boot via `capRegistry.getSingletonForNode`
7019
+ * (in-process, not tRPC) — the cap's tRPC procedure is never on that
7020
+ * request path. `auth: 'admin'` here does not block a single viewer
7021
+ * user from loading the app.
7022
+ */
7023
+ getStaticDir: method(_void(), StaticDirOutputSchema, { auth: "admin" }),
7024
+ getVersion: method(_void(), VersionOutputSchema, { auth: "admin" })
6991
7025
  }
6992
7026
  };
6993
7027
  /**
@@ -7662,24 +7696,6 @@ var RecordingRetentionSchema = object({
7662
7696
  maxSizeGb: number().min(0).optional()
7663
7697
  });
7664
7698
  /**
7665
- * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7666
- * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7667
- * previews at. Five graduated steps; absent on a config = `standard` (the
7668
- * shipped default, matching `sheet-geometry`/`sheet-composer`).
7669
- *
7670
- * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7671
- * Each window's index sidecar carries its own tile dims, so a camera whose
7672
- * preset changed over time renders every historical window at the dims it was
7673
- * written with.
7674
- */
7675
- var ScrubThumbnailPresetSchema = _enum([
7676
- "minimal",
7677
- "low",
7678
- "standard",
7679
- "high",
7680
- "max"
7681
- ]);
7682
- /**
7683
7699
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7684
7700
  *
7685
7701
  * `bands` is the ONLY authored recording intent: what to record, when, and on
@@ -7687,7 +7703,11 @@ var ScrubThumbnailPresetSchema = _enum([
7687
7703
  * other field is a storage knob (profiles, segment length, retention, scrub).
7688
7704
  *
7689
7705
  * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
7690
- * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
7706
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30,
7707
+ * and `scrubThumbnails` — a five-step fidelity knob for a sprite tier that was
7708
+ * deleted on 2026-07-24 and had ZERO consumers in the recorder — on 2026-08-25
7709
+ * (D62: a switch that writes a store nobody reads is worse than no switch).
7710
+ * Stored rows keep loading: the READ schema is `.strip()` (config-store.ts).
7691
7711
  * A stale caller must fail loudly — silently stripping its legacy intent would
7692
7712
  * persist a band-less config, i.e. silently stop recording the camera.
7693
7713
  */
@@ -7710,14 +7730,7 @@ var RecordingConfigSchema = object({
7710
7730
  * "off" is the absence of a covering band, never a band value.
7711
7731
  */
7712
7732
  bands: array(RecordingBandSchema).default([]),
7713
- retention: RecordingRetentionSchema.optional(),
7714
- /**
7715
- * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7716
- * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7717
- * windows only — existing sheets are immutable, and each window's index
7718
- * carries its own tile dims so mixed-preset history renders correctly.
7719
- */
7720
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7733
+ retention: RecordingRetentionSchema.optional()
7721
7734
  }).strict();
7722
7735
  /**
7723
7736
  * Entity-relocation job state (storage entity-routing spec, Phase 4).
@@ -7793,10 +7806,11 @@ var RelocateFootageInputSchema = object({
7793
7806
  * `RecordingConfig.enabled` or camera wrapper bindings. */
7794
7807
  var StorageMigrationLeaseInputSchema = object({ leaseId: string().min(1) });
7795
7808
  var StorageMigrationFootageMoveInputSchema = RelocateFootageInputSchema.extend({ leaseId: string().min(1) });
7796
- var StorageMigrationMediaMoveInputSchema = object({
7809
+ var RelocateMediaInputSchema = object({
7797
7810
  toLocationId: string(),
7798
7811
  throttleMbps: number().min(1).max(1e3).optional()
7799
- }).extend({ leaseId: string().min(1) });
7812
+ });
7813
+ var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
7800
7814
  /** The independently selectable logical storage classes. `recordings`
7801
7815
  * encompasses the high and mid segment profiles; `recordingsLow` is low
7802
7816
  * segments; `eventMedia` is post-analysis blobs. */
@@ -8091,7 +8105,26 @@ var LabelDefinitionSchema = object({
8091
8105
  description: string().optional(),
8092
8106
  icon: string().optional()
8093
8107
  });
8094
- var ClassMapDefinitionSchema = object({
8108
+ /**
8109
+ * Wire schema for a per-model CATALOG classMap override
8110
+ * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
8111
+ * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
8112
+ * detection pipeline executor actually routes.
8113
+ *
8114
+ * This is deliberately a DIFFERENT, narrower shape than the general-purpose
8115
+ * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
8116
+ * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
8117
+ * enum) — the two used to share the name `ClassMapDefinition`/
8118
+ * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
8119
+ * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
8120
+ * are not: it is two different concepts colliding on a name. Keep this type
8121
+ * under its own name rather than reusing `ClassMapDefinition` — reusing it
8122
+ * would either narrow every `ClassMapDefinition` consumer to the four
8123
+ * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
8124
+ * schema exists for (see the "rejects a classMap whose target is not a
8125
+ * detection macro" test in `model-catalog-schema.test.ts`).
8126
+ */
8127
+ var DetectionCatalogClassMapSchema = object({
8095
8128
  mapping: record(string(), _enum([
8096
8129
  "person",
8097
8130
  "vehicle",
@@ -8183,6 +8216,12 @@ var ModelVariantGroupSchema = object({
8183
8216
  */
8184
8217
  resolution: number().int().positive().optional()
8185
8218
  });
8219
+ var ModelProviderIdSchema = _enum([
8220
+ "camstack",
8221
+ "frigate",
8222
+ "scrypted",
8223
+ "custom"
8224
+ ]);
8186
8225
  var ModelCatalogEntrySchema = object({
8187
8226
  id: string(),
8188
8227
  name: string(),
@@ -8280,11 +8319,17 @@ var ModelCatalogEntrySchema = object({
8280
8319
  */
8281
8320
  group: ModelVariantGroupSchema.optional(),
8282
8321
  /**
8322
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8323
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8324
+ * persisted before this field existed (`inferModelProvider` fills those).
8325
+ */
8326
+ provider: ModelProviderIdSchema.optional(),
8327
+ /**
8283
8328
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8284
8329
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8285
8330
  * labels already ARE the CamStack macros (Scrypted identity map).
8286
8331
  */
8287
- classMap: ClassMapDefinitionSchema.optional()
8332
+ classMap: DetectionCatalogClassMapSchema.optional()
8288
8333
  });
8289
8334
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8290
8335
  format: literal("openvino"),
@@ -8314,7 +8359,7 @@ var ModelConvertMetadataSchema = object({
8314
8359
  "segmentation"
8315
8360
  ]),
8316
8361
  faceAlignment: boolean().optional(),
8317
- classMap: ClassMapDefinitionSchema.optional()
8362
+ classMap: DetectionCatalogClassMapSchema.optional()
8318
8363
  });
8319
8364
  var ConvertResultSchema = object({
8320
8365
  entry: ModelCatalogEntrySchema,
@@ -9187,7 +9232,18 @@ var addonPagesSourceCapability = {
9187
9232
  scope: "system",
9188
9233
  mode: "collection",
9189
9234
  internal: true,
9190
- methods: { listPages: method(_void(), array(AddonPageDeclarationSchema).readonly()) }
9235
+ methods: {
9236
+ /**
9237
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
9238
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
9239
+ * authenticated session at the default `auth: 'protected'`. The only
9240
+ * consumer, the `addon-pages-aggregator` builtin, reads it via
9241
+ * `ctx.capabilities.getCollection('addon-pages-source')` — LOCAL-PROCESS
9242
+ * only, never tRPC (per `ctx.capabilities` seeing only the local
9243
+ * process). The public, enriched listing admin-ui actually consumes is
9244
+ * the separate `addon-pages` cap, unaffected by this change.
9245
+ */
9246
+ listPages: method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" }) }
9191
9247
  };
9192
9248
  var AddonHttpRouteSchema = object({
9193
9249
  method: _enum([
@@ -9484,7 +9540,20 @@ var addonWidgetsSourceCapability = {
9484
9540
  scope: "system",
9485
9541
  mode: "collection",
9486
9542
  internal: true,
9487
- methods: { listWidgets: method(_void(), array(WidgetMetadataSchema).readonly()) }
9543
+ methods: {
9544
+ /**
9545
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
9546
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
9547
+ * authenticated session at the default `auth: 'protected'`. The only
9548
+ * consumer, the `addon-widgets-aggregator` builtin, reads it via
9549
+ * `ctx.capabilities.getCollection('addon-widgets-source')` — LOCAL-PROCESS
9550
+ * only, never tRPC. The public, enriched listing admin-ui actually
9551
+ * consumes is the separate `addon-widgets` cap (`listWidgets` there is
9552
+ * gated separately, and a `preAuth: true` widget is surfaced through the
9553
+ * PUBLIC `auth.listLoginMethods` contribution channel instead) —
9554
+ * neither is affected by this change.
9555
+ */
9556
+ listWidgets: method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" }) }
9488
9557
  };
9489
9558
  /**
9490
9559
  * `addon-widgets` — system-scoped singleton aggregator cap. Public-facing
@@ -11648,7 +11717,17 @@ var customModelRegistryCapability = {
11648
11717
  scope: "system",
11649
11718
  mode: "collection",
11650
11719
  internal: true,
11651
- methods: { listModels: method(_void(), array(CustomModelDescriptorSchema).readonly()) }
11720
+ methods: {
11721
+ /**
11722
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
11723
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
11724
+ * authenticated session at the default `auth: 'protected'`. The only
11725
+ * consumer, the detection-pipeline's model resolution
11726
+ * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
11727
+ * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
11728
+ * never tRPC — so `auth: 'admin'` costs it nothing.
11729
+ */
11730
+ listModels: method(_void(), array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }) }
11652
11731
  };
11653
11732
  /**
11654
11733
  * Query filter for settings-store collections.
@@ -11772,11 +11851,26 @@ var settingsStoreCapability = {
11772
11851
  key: string(),
11773
11852
  value: unknown()
11774
11853
  }), _void(), { kind: "mutation" }),
11775
- /** Get all entries matching an optional filter. */
11854
+ /**
11855
+ * Rows matching a filter.
11856
+ *
11857
+ * `columns` is a SQL-level projection: the engine SELECTs only those
11858
+ * columns (plus the primary key) instead of the whole row. It is the only
11859
+ * way to stop paying disk for a column that is discarded — a heavy JSON
11860
+ * column dominates the row, and dropping it in JS after the read pays every
11861
+ * byte first. Measured on the live `pipeline-analytics:tracks` table: a
11862
+ * 501-row page is 4.6 MB whole and 0.11 MB projected, same index, same
11863
+ * plan.
11864
+ *
11865
+ * The engine REFUSES a column it cannot serve rather than omitting it —
11866
+ * see `SettingsQueryInput.columns` for why a projection may not be
11867
+ * forgiving the way a predicate is.
11868
+ */
11776
11869
  query: method(object({
11777
11870
  namespace: string().optional(),
11778
11871
  collection: string(),
11779
- filter: QueryFilterSchema.optional()
11872
+ filter: QueryFilterSchema.optional(),
11873
+ columns: array(string()).readonly().optional()
11780
11874
  }), array(SettingsRecordSchema).readonly()),
11781
11875
  /** Insert a new record. */
11782
11876
  insert: method(object({
@@ -11902,6 +11996,23 @@ var EngineInfoSchema = object({
11902
11996
  * go through `settings-store`; they never see this cap, and an engine
11903
11997
  * never sees a caller.
11904
11998
  *
11999
+ * ⚠ **`internal: true` DOES NOT gate the mount, and until 2026-08-26 this
12000
+ * comment claimed otherwise.** `resolveCapMount` never reads the field, so this
12001
+ * cap was live on the AppRouter with every method at the default
12002
+ * `auth: 'protected'` — i.e. readable by ANY authenticated session, admin or
12003
+ * not. Verified against the live hub: `dataStoreProvider.query` returned raw
12004
+ * settings rows including a provider credential, bypassing the secret redaction
12005
+ * that `deviceManager.listAll` and friends go through.
12006
+ *
12007
+ * Every method is now `auth: 'admin'` EXPLICITLY, including the five reads —
12008
+ * they had no options object at all, which is exactly how they kept the
12009
+ * permissive default while the mutations looked deliberate. The explicit
12010
+ * annotation is the fix that works today; making `internal` mean something at
12011
+ * mount time is the systemic one, and it is not free: 22 caps declare it, only
12012
+ * 4 are `mount: 'skip'`, and forcing admin on all of them would change the auth
12013
+ * of surfaces the viewer and admin-ui call as non-admin users. That decision is
12014
+ * the operator's, not a side effect of this file.
12015
+ *
11905
12016
  * Design notes:
11906
12017
  * - **Stateless dispatch.** Every method carries its own
11907
12018
  * `namespace` + `collection`, so an engine keeps no per-caller state
@@ -11923,64 +12034,105 @@ var dataStoreProviderCapability = {
11923
12034
  internal: true,
11924
12035
  methods: {
11925
12036
  /** Self-description — how the orchestrator picks a registrant. */
11926
- getEngineInfo: method(_void(), EngineInfoSchema),
12037
+ getEngineInfo: method(_void(), EngineInfoSchema, { auth: "admin" }),
11927
12038
  /** Get a single value by key from a collection. */
11928
12039
  get: method(object({
11929
12040
  namespace: string().optional(),
11930
12041
  collection: string(),
11931
12042
  key: string()
11932
- }), unknown()),
12043
+ }), unknown(), { auth: "admin" }),
11933
12044
  /** Set a value by key in a collection (upsert). */
11934
12045
  set: method(object({
11935
12046
  namespace: string().optional(),
11936
12047
  collection: string(),
11937
12048
  key: string(),
11938
12049
  value: unknown()
11939
- }), _void(), { kind: "mutation" }),
12050
+ }), _void(), {
12051
+ kind: "mutation",
12052
+ auth: "admin"
12053
+ }),
11940
12054
  /** Get all entries matching an optional filter. */
11941
12055
  query: method(object({
11942
12056
  namespace: string().optional(),
11943
12057
  collection: string(),
11944
- filter: QueryFilterSchema.optional()
11945
- }), array(SettingsRecordSchema).readonly()),
12058
+ filter: QueryFilterSchema.optional(),
12059
+ /**
12060
+ * SQL-level column projection — MUST mirror `settings-store.query`.
12061
+ *
12062
+ * ⚠ An earlier version of this comment blamed Zod stripping, and that
12063
+ * was wrong — corrected 2026-08-26 after the hop map
12064
+ * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
12065
+ * There is **no Zod parse at all** between the door and the engine: the
12066
+ * dispatcher forwards the payload verbatim and UDS carries it whole. A
12067
+ * field declared here reaches `SqliteSettingsBackend` either way.
12068
+ *
12069
+ * What actually lost `columns` was the THIRD declaration of this shape:
12070
+ * `SettingsQueryInput` in `interfaces/storage.ts`, a hand-written TS
12071
+ * interface the engine destructures from. The field existed on both
12072
+ * schemas and the engine still never read it, because nothing checks a
12073
+ * registered provider against `InferProvider<cap>` —
12074
+ * `ProviderRegistration.provider` is typed `object`.
12075
+ *
12076
+ * It is declared here anyway, and must stay in step with
12077
+ * `settings-store.query`: a caller reading only the cap definitions has
12078
+ * to be able to see that this call carries a projection.
12079
+ * `data-door-schema-parity.spec.ts` keeps the two aligned.
12080
+ */
12081
+ columns: array(string()).readonly().optional()
12082
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }),
11946
12083
  /** Insert a new record. */
11947
12084
  insert: method(object({
11948
12085
  namespace: string().optional(),
11949
12086
  collection: string(),
11950
12087
  record: SettingsRecordSchema
11951
- }), _void(), { kind: "mutation" }),
12088
+ }), _void(), {
12089
+ kind: "mutation",
12090
+ auth: "admin"
12091
+ }),
11952
12092
  /** Update an existing record by ID. */
11953
12093
  update: method(object({
11954
12094
  namespace: string().optional(),
11955
12095
  collection: string(),
11956
12096
  id: string(),
11957
12097
  data: record(string(), unknown())
11958
- }), _void(), { kind: "mutation" }),
12098
+ }), _void(), {
12099
+ kind: "mutation",
12100
+ auth: "admin"
12101
+ }),
11959
12102
  /** Delete a record by key/ID. */
11960
12103
  delete: method(object({
11961
12104
  namespace: string().optional(),
11962
12105
  collection: string(),
11963
12106
  key: string()
11964
- }), _void(), { kind: "mutation" }),
12107
+ }), _void(), {
12108
+ kind: "mutation",
12109
+ auth: "admin"
12110
+ }),
11965
12111
  /** Delete every record matching `filter`, in one statement. */
11966
12112
  deleteWhere: method(object({
11967
12113
  namespace: string().optional(),
11968
12114
  collection: string(),
11969
12115
  filter: MutationFilterSchema
11970
- }), object({ deleted: number().int() }), { kind: "mutation" }),
12116
+ }), object({ deleted: number().int() }), {
12117
+ kind: "mutation",
12118
+ auth: "admin"
12119
+ }),
11971
12120
  /** Apply `data` to every record matching `filter`, in one statement. */
11972
12121
  updateWhere: method(object({
11973
12122
  namespace: string().optional(),
11974
12123
  collection: string(),
11975
12124
  filter: MutationFilterSchema,
11976
12125
  data: record(string(), unknown())
11977
- }), object({ updated: number().int() }), { kind: "mutation" }),
12126
+ }), object({ updated: number().int() }), {
12127
+ kind: "mutation",
12128
+ auth: "admin"
12129
+ }),
11978
12130
  /** Count entries in a collection, optionally filtered. */
11979
12131
  count: method(object({
11980
12132
  namespace: string().optional(),
11981
12133
  collection: string(),
11982
12134
  filter: QueryFilterSchema.optional()
11983
- }), number()),
12135
+ }), number(), { auth: "admin" }),
11984
12136
  /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
11985
12137
  histogram: method(object({
11986
12138
  namespace: string().optional(),
@@ -11992,19 +12144,22 @@ var dataStoreProviderCapability = {
11992
12144
  }), array(object({
11993
12145
  bucket: number().int(),
11994
12146
  count: number().int()
11995
- })).readonly()),
12147
+ })).readonly(), { auth: "admin" }),
11996
12148
  /** Check if a collection is empty. */
11997
12149
  isEmpty: method(object({
11998
12150
  namespace: string().optional(),
11999
12151
  collection: string()
12000
- }), boolean()),
12152
+ }), boolean(), { auth: "admin" }),
12001
12153
  /** Declare a typed (SQL-backed) collection with columns + indexes. */
12002
12154
  declareCollection: method(object({
12003
12155
  namespace: string().optional(),
12004
12156
  collection: string(),
12005
12157
  columns: array(CollectionColumnSchema).readonly(),
12006
12158
  indexes: array(CollectionIndexSchema).readonly().optional()
12007
- }), _void(), { kind: "mutation" })
12159
+ }), _void(), {
12160
+ kind: "mutation",
12161
+ auth: "admin"
12162
+ })
12008
12163
  }
12009
12164
  };
12010
12165
  /**
@@ -12880,6 +13035,27 @@ var LinkedDeviceSchema = object({
12880
13035
  features: array(string()),
12881
13036
  producesTrackedEvents: boolean().optional()
12882
13037
  });
13038
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
13039
+ * The batch answer needs the tag; the single-device answer already has it
13040
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
13041
+ var LinkedDevicesForDeviceSchema = object({
13042
+ deviceId: number(),
13043
+ mode: LinkedDevicesModeSchema,
13044
+ devices: array(LinkedDeviceSchema)
13045
+ });
13046
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
13047
+ * `getAllBindings` all answer in. Declared once: three copies of the same
13048
+ * object literal is exactly how the three drift apart. */
13049
+ var DeviceBindingsForDeviceSchema = object({
13050
+ deviceId: number(),
13051
+ entries: array(object({
13052
+ capName: string(),
13053
+ kind: _enum(["native", "wrapped"]),
13054
+ providerAddonId: string(),
13055
+ providerNodeId: string(),
13056
+ nativeAddonId: string()
13057
+ }))
13058
+ });
12883
13059
  var SavedDeviceRowSchema = object({
12884
13060
  /** Numeric id reserved at allocateDeviceId time. */
12885
13061
  id: number(),
@@ -13242,7 +13418,21 @@ var deviceManagerCapability = {
13242
13418
  projection: _enum(["full", "slim"]).optional(),
13243
13419
  /** Return only camera devices. Filtering server-side instead of
13244
13420
  * shipping 293 rows to find 12. */
13245
- isCamera: boolean().optional()
13421
+ isCamera: boolean().optional(),
13422
+ /**
13423
+ * Return only these device ids. For the caller that already KNOWS the
13424
+ * handful it wants and needs a field the id-bearing answer does not
13425
+ * carry — the viewer's linked-devices panel joins `type` and `online`
13426
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
13427
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
13428
+ * refetches on the reconcile interval, on a phone.
13429
+ *
13430
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
13431
+ * keys rather than rejecting them (verified against the live hub
13432
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
13433
+ * it answers today and the caller filters as it already does.
13434
+ */
13435
+ deviceIds: array(number()).optional()
13246
13436
  }), array(DeviceInfoSchema)),
13247
13437
  /** Get a single device by numeric deviceId. */
13248
13438
  getDevice: method(object({ deviceId: number() }), DeviceInfoSchema.nullable()),
@@ -13261,6 +13451,23 @@ var deviceManagerCapability = {
13261
13451
  mode: LinkedDevicesModeSchema,
13262
13452
  devices: array(LinkedDeviceSchema)
13263
13453
  })),
13454
+ /**
13455
+ * `getLinkedDevices` for MANY cameras, in one call.
13456
+ *
13457
+ * Not a convenience wrapper — a cost fix. Resolving one camera's linked set
13458
+ * needs the whole fleet as candidates (children ∪ same-location), so the
13459
+ * single-device path runs a full `listAll` per call. Asked per camera that
13460
+ * is N full-fleet sweeps of device-manager's event loop, and they do not
13461
+ * overlap: measured on the live hub 2026-08-25, 30 concurrent
13462
+ * `getLinkedDevices` took 4110 ms wall (median 4060 ms each) to return
13463
+ * 7260 bytes in total. The batch takes ONE sweep and one settings read per
13464
+ * camera.
13465
+ *
13466
+ * A device id that resolves to nothing still gets a row (`devices: []`) —
13467
+ * a caller that asked for thirty and got twenty-eight cannot tell which two
13468
+ * are missing, or that any are.
13469
+ */
13470
+ getLinkedDevicesBatch: method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)),
13264
13471
  /** Get stream sources for a camera device. */
13265
13472
  getStreamSources: method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)),
13266
13473
  /** Get config entries (key + value + description) for a device. */
@@ -13318,16 +13525,22 @@ var deviceManagerCapability = {
13318
13525
  * currently-active provider (native or wrapper) + the underlying native
13319
13526
  * addon id, so consumers can decide routing without re-running discovery.
13320
13527
  */
13321
- getBindings: method(object({ deviceId: number() }), object({
13322
- deviceId: number(),
13323
- entries: array(object({
13324
- capName: string(),
13325
- kind: _enum(["native", "wrapped"]),
13326
- providerAddonId: string(),
13327
- providerNodeId: string(),
13328
- nativeAddonId: string()
13329
- }))
13330
- })),
13528
+ getBindings: method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema),
13529
+ /**
13530
+ * `getBindings` for a NAMED set of devices, in one call.
13531
+ *
13532
+ * Between `getBindings` (one device) and `getAllBindings` (all 988) there
13533
+ * was nothing, so a caller that needs seventy-five either pays
13534
+ * seventy-five round-trips or drags the whole fleet across. Measured on
13535
+ * the live hub 2026-08-25: 75 concurrent `getBindings` = 1211 ms / 118 KB,
13536
+ * `getAllBindings({})` = 511 ms / 679 KB. Neither is the right answer to
13537
+ * "these seventy-five".
13538
+ *
13539
+ * Same resolver, same routing rules, same per-device shape as
13540
+ * `getBindings`; ids are deduped and a device with no bindings answers
13541
+ * `entries: []` rather than dropping out.
13542
+ */
13543
+ getBindingsBatch: method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)),
13331
13544
  /**
13332
13545
  * Return the binding map for every device known to the hub. Used by
13333
13546
  * `SystemManager` warm-boot: a single round-trip resolves the
@@ -13336,16 +13549,7 @@ var deviceManagerCapability = {
13336
13549
  * device add/remove) — clients invalidate via the
13337
13550
  * `capability.binding-changed` event.
13338
13551
  */
13339
- getAllBindings: method(object({}), array(object({
13340
- deviceId: number(),
13341
- entries: array(object({
13342
- capName: string(),
13343
- kind: _enum(["native", "wrapped"]),
13344
- providerAddonId: string(),
13345
- providerNodeId: string(),
13346
- nativeAddonId: string()
13347
- }))
13348
- }))),
13552
+ getAllBindings: method(object({}), array(DeviceBindingsForDeviceSchema)),
13349
13553
  /**
13350
13554
  * Activate (or deactivate) a wrapper addon for a (device, cap) pair.
13351
13555
  * Persists the binding via ctx.settings. active=false clears the wrapper
@@ -13755,13 +13959,20 @@ var embeddingEncoderCapability = {
13755
13959
  mode: "collection",
13756
13960
  internal: true,
13757
13961
  methods: {
13962
+ /**
13963
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
13964
+ * `data-store-provider`) — every method below was reachable on the
13965
+ * AppRouter by ANY authenticated session at the default
13966
+ * `auth: 'protected'`. Consumers reach the encoder via `ctx.api` from
13967
+ * `addon-post-analysis`, never tRPC.
13968
+ */
13758
13969
  encode: method(object({
13759
13970
  crop: _instanceof(Uint8Array),
13760
13971
  width: number(),
13761
13972
  height: number()
13762
- }), EmbeddingResultSchema),
13763
- encodeText: method(object({ text: string() }), EmbeddingResultSchema),
13764
- getInfo: method(_void(), EmbeddingInfoSchema)
13973
+ }), EmbeddingResultSchema, { auth: "admin" }),
13974
+ encodeText: method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }),
13975
+ getInfo: method(_void(), EmbeddingInfoSchema, { auth: "admin" })
13765
13976
  }
13766
13977
  };
13767
13978
  /**
@@ -14070,12 +14281,28 @@ var llmRuntimeCapability = {
14070
14281
  mode: "singleton",
14071
14282
  internal: true,
14072
14283
  methods: {
14284
+ /**
14285
+ * `complete`, `status`, `listLocalModels` and `getDiskUsage` had NO
14286
+ * `auth` override — `internal: true` did not gate the mount (see the
14287
+ * 2026-08-26 note on `data-store-provider`), so they were reachable on
14288
+ * the AppRouter by ANY authenticated session at the default
14289
+ * `auth: 'protected'`, inconsistent with the rest of this cap (already
14290
+ * `auth: 'admin'`) and with this file's own docblock: "the operator
14291
+ * reaches this only through the `llm` cap's methods." The sole caller,
14292
+ * `addon-ai`'s `runtime-client.ts`, reaches every method here via
14293
+ * `ctx.api` + `nodePin` — never tRPC — so `auth: 'admin'` costs it
14294
+ * nothing; the public `llm` cap (which end users and non-admin sessions
14295
+ * legitimately call for chat/generation) is untouched.
14296
+ */
14073
14297
  complete: method(LlmGenerateBaseInputSchema.extend({
14074
14298
  images: array(LlmImageSchema).optional(),
14075
14299
  runtime: ManagedRuntimeConfigSchema,
14076
14300
  /** The managed profile's timeout, threaded by the hub provider. */
14077
14301
  timeoutMs: number().int().positive().optional()
14078
- }), LlmGenerateResultSchema, { kind: "mutation" }),
14302
+ }), LlmGenerateResultSchema, {
14303
+ kind: "mutation",
14304
+ auth: "admin"
14305
+ }),
14079
14306
  ensureStarted: method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
14080
14307
  kind: "mutation",
14081
14308
  auth: "admin"
@@ -14084,7 +14311,7 @@ var llmRuntimeCapability = {
14084
14311
  kind: "mutation",
14085
14312
  auth: "admin"
14086
14313
  }),
14087
- status: method(object({}), LlmRuntimeStatusSchema),
14314
+ status: method(object({}), LlmRuntimeStatusSchema, { auth: "admin" }),
14088
14315
  installModel: method(object({ model: ManagedModelRefSchema }), _void(), {
14089
14316
  kind: "mutation",
14090
14317
  auth: "admin"
@@ -14093,8 +14320,8 @@ var llmRuntimeCapability = {
14093
14320
  kind: "mutation",
14094
14321
  auth: "admin"
14095
14322
  }),
14096
- listLocalModels: method(object({}), array(LlmNodeModelSchema)),
14097
- getDiskUsage: method(object({}), LlmRuntimeDiskUsageSchema)
14323
+ listLocalModels: method(object({}), array(LlmNodeModelSchema), { auth: "admin" }),
14324
+ getDiskUsage: method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" })
14098
14325
  }
14099
14326
  };
14100
14327
  /**
@@ -16233,12 +16460,15 @@ var NcOccupancyConditionSchema = object({
16233
16460
  * there is no second switch that can disagree with the first and every rule
16234
16461
  * authored before the decision migrates for free (`audioModeOf`):
16235
16462
  *
16236
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
16237
- * classifier labels with one of them. No window, no percentage:
16238
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
16239
- * `throttle` cooldown is the only brake. The per-label confidence floor is
16240
- * the analyzer's (`classificationMinScore`, per device) a label only
16241
- * reaches this condition if the classifier was already confident enough.
16463
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
16464
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
16465
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
16466
+ * frames is the wrong question for a classifier that labels 1–3 frames
16467
+ * per episode. The count window is the brake that drops a single-frame
16468
+ * false positive; the rule's own `throttle` cooldown is the other. The
16469
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
16470
+ * per device) — a label only reaches this condition if the classifier was
16471
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
16242
16472
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
16243
16473
  * the condition: at least `hitPercent`% of the samples over
16244
16474
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -16265,14 +16495,22 @@ var NcOccupancyConditionSchema = object({
16265
16495
  * an operator who typed `dog` mean the same thing.
16266
16496
  */
16267
16497
  var NcAudioConditionSchema = object({
16268
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
16498
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
16269
16499
  labels: array(string().min(1)).min(1).optional(),
16270
16500
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
16271
16501
  dbThreshold: number().min(-96).max(0).optional(),
16272
16502
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
16273
16503
  hitPercent: number().int().min(1).max(100).default(60),
16274
16504
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
16275
- samplingSeconds: number().int().min(1).max(300).default(10)
16505
+ samplingSeconds: number().int().min(1).max(300).default(10),
16506
+ /**
16507
+ * LABEL MODE: how many labelled frames must land inside
16508
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
16509
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
16510
+ */
16511
+ confirmHits: number().int().min(1).max(20).optional(),
16512
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
16513
+ confirmWindowSec: number().int().min(1).max(60).optional()
16276
16514
  });
16277
16515
  /**
16278
16516
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -18349,7 +18587,17 @@ var oauthIntegrationCapability = {
18349
18587
  scope: "system",
18350
18588
  mode: "collection",
18351
18589
  internal: true,
18352
- methods: { getDescriptor: method(_void(), OauthIntegrationDescriptorSchema) }
18590
+ methods: {
18591
+ /**
18592
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
18593
+ * `data-store-provider`) — `getDescriptor` was reachable on the AppRouter
18594
+ * by ANY authenticated session at the default `auth: 'protected'`. The
18595
+ * real caller is `/api/oauth2/authorize` and `/api/oauth2/integrations`
18596
+ * (`oauth2-routes.ts`), which resolve the provider directly off the
18597
+ * capability registry — never through tRPC. `auth: 'admin'` closes the
18598
+ * tRPC surface without touching that path.
18599
+ */
18600
+ getDescriptor: method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" }) }
18353
18601
  };
18354
18602
  /**
18355
18603
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
@@ -19197,6 +19445,46 @@ var RecentTracksPageSchema = object({
19197
19445
  /** Cursor for the next page, or null when this page is the last. */
19198
19446
  nextCursor: string().nullable()
19199
19447
  });
19448
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
19449
+ var LIST_GROUPS_MAX_LIMIT = 100;
19450
+ var AnalyticsGroupRecordSchema = object({
19451
+ id: string(),
19452
+ deviceId: number().int(),
19453
+ openedAt: number().int(),
19454
+ closedAt: number().int(),
19455
+ timestamp: number().int(),
19456
+ memberCount: number().int(),
19457
+ memberTrackIds: array(string()).readonly(),
19458
+ className: string(),
19459
+ classes: array(string()).readonly(),
19460
+ /** Relative event-media path, or null when the group has no picture yet. */
19461
+ mediaUrl: string().nullable(),
19462
+ singleton: boolean()
19463
+ });
19464
+ var AnalyticsGroupMemberSchema = object({
19465
+ trackId: string(),
19466
+ deviceId: number().int(),
19467
+ className: string(),
19468
+ firstSeen: number().int(),
19469
+ lastSeen: number().int(),
19470
+ mediaUrl: string().nullable()
19471
+ });
19472
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
19473
+ var ListGroupsQueryInput = object({
19474
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
19475
+ deviceIds: array(number()),
19476
+ /** Window lower bound on `closedAt` (inclusive). */
19477
+ since: number().optional(),
19478
+ /** Window upper bound on `openedAt` (inclusive). */
19479
+ until: number().optional(),
19480
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
19481
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
19482
+ cursor: string().optional()
19483
+ });
19484
+ var ListGroupsPageSchema = object({
19485
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
19486
+ nextCursor: string().nullable()
19487
+ });
19200
19488
  var KeyEventQueryInput = object({
19201
19489
  deviceId: number(),
19202
19490
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19272,7 +19560,9 @@ var TrackCascadeCountsSchema = object({
19272
19560
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19273
19561
  plates: number().int(),
19274
19562
  /** Per-track CLIP search vectors removed (best-effort). */
19275
- embeddings: number().int()
19563
+ embeddings: number().int(),
19564
+ /** Group membership + group rows removed with their last member (best-effort). */
19565
+ groups: number().int()
19276
19566
  });
19277
19567
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19278
19568
  var DiskReconcileCountsSchema = object({
@@ -19444,6 +19734,16 @@ var pipelineAnalyticsCapability = {
19444
19734
  * are not included (same contract as `listTracks`).
19445
19735
  */
19446
19736
  listRecentTracks: method(RecentTracksQueryInput, RecentTracksPageSchema),
19737
+ /**
19738
+ * Batched co-moving group listing — the Groups feed. Same merge/cursor
19739
+ * contract as {@link listRecentTracks}. A group is a sealed partition of
19740
+ * one session; `getGroup` is the detail with members.
19741
+ */
19742
+ listGroups: method(ListGroupsQueryInput, ListGroupsPageSchema),
19743
+ getGroup: method(object({
19744
+ deviceId: number(),
19745
+ groupId: string().min(1)
19746
+ }), AnalyticsGroupDetailSchema.nullable()),
19447
19747
  clearTracks: method(object({ deviceId: number() }), _void(), {
19448
19748
  kind: "mutation",
19449
19749
  auth: "admin"
@@ -19696,6 +19996,29 @@ var pipelineAnalyticsCapability = {
19696
19996
  kind: "mutation",
19697
19997
  auth: "admin"
19698
19998
  }),
19999
+ /**
20000
+ * Moves event media between locations — the OPERATOR's mover, without the
20001
+ * coordinator's lease. Twin of `recording.relocateFootage`: the same
20002
+ * engine the lease-gated coordinated migration uses
20003
+ * (`startStorageMigrationMove`), armable in the background and WITHOUT
20004
+ * pausing anything. Exists because `eventMedia` was the only storage
20005
+ * class whose only move path went through the recorder's global pause.
20006
+ */
20007
+ relocateMedia: method(RelocateMediaInputSchema, object({ jobId: string() }), {
20008
+ kind: "mutation",
20009
+ auth: "admin"
20010
+ }),
20011
+ /** Every relocate job this addon knows about, newest first (in RAM: the
20012
+ * move is resumable, so a lost list costs nothing but the display). */
20013
+ listRelocateMediaJobs: method(object({}), array(RelocateJobSchema).readonly(), {
20014
+ kind: "query",
20015
+ auth: "admin"
20016
+ }),
20017
+ /** Cancel a running or queued relocate job. */
20018
+ cancelRelocateMedia: method(object({ jobId: string() }), object({ cancelled: boolean() }), {
20019
+ kind: "mutation",
20020
+ auth: "admin"
20021
+ }),
19699
20022
  listOpsLog: method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19700
20023
  kind: "query",
19701
20024
  auth: "admin"
@@ -20055,6 +20378,11 @@ object({
20055
20378
  "jpeg"
20056
20379
  ])
20057
20380
  });
20381
+ /**
20382
+ * Process-local frame identity. It is serializable so it can ride an in-process
20383
+ * capability call, but `registryId` deliberately prevents resolution in any
20384
+ * other process or execution group.
20385
+ */
20058
20386
  var FrameRefSchema = object({
20059
20387
  registryId: string().min(1),
20060
20388
  id: string().min(1),
@@ -20129,7 +20457,8 @@ var PipelineModelOptionSchema = object({
20129
20457
  sizeMB: number()
20130
20458
  })),
20131
20459
  group: ModelVariantGroupSchema.optional(),
20132
- legacy: boolean().optional()
20460
+ legacy: boolean().optional(),
20461
+ provider: ModelProviderIdSchema.optional()
20133
20462
  });
20134
20463
  var ConfigFieldBridge = custom();
20135
20464
  var PipelineAddonSchemaSchema = object({
@@ -23007,6 +23336,20 @@ var ssoBridgeCapability = {
23007
23336
  mode: "singleton",
23008
23337
  internal: true,
23009
23338
  methods: {
23339
+ /**
23340
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
23341
+ * `data-store-provider`) — `signBridgeToken` was reachable on the
23342
+ * AppRouter by ANY authenticated session at the default
23343
+ * `auth: 'protected'`. The claims (including `isAdmin`) are CALLER-
23344
+ * SUPPLIED: an unprivileged session could have minted its own
23345
+ * `isAdmin: true` bridge token and redeemed it at
23346
+ * `/api/auth/sso/finish` to become admin — a full privilege escalation
23347
+ * through the exact surface this cap exists to close (see the module
23348
+ * docblock). Every real caller (`oauth-grants.ts`, `main.ts`
23349
+ * registration, the OIDC/magic-link addons) reaches this through
23350
+ * `ctx.api` / the in-process registry — never through tRPC — so
23351
+ * `auth: 'admin'` costs those callers nothing.
23352
+ */
23010
23353
  signBridgeToken: method(object({
23011
23354
  claims: SsoBridgeClaimsSchema,
23012
23355
  /**
@@ -23019,8 +23362,8 @@ var ssoBridgeCapability = {
23019
23362
  * linking rather than produce an eternal token.
23020
23363
  */
23021
23364
  ttlSec: union([number().int().positive(), literal("never")]).optional()
23022
- }), object({ token: string() })),
23023
- verifyBridgeToken: method(object({ token: string() }), SsoBridgeClaimsSchema.nullable())
23365
+ }), object({ token: string() }), { auth: "admin" }),
23366
+ verifyBridgeToken: method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" })
23024
23367
  }
23025
23368
  };
23026
23369
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
@@ -23169,8 +23512,15 @@ var storageEvictableCapability = {
23169
23512
  mode: "collection",
23170
23513
  internal: true,
23171
23514
  methods: {
23515
+ /**
23516
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
23517
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
23518
+ * authenticated session at the default `auth: 'protected'`. The only
23519
+ * consumer is the orchestrator's `StoragePressureManager`, which calls
23520
+ * `ctx.api` — never tRPC — so `auth: 'admin'` costs it nothing.
23521
+ */
23172
23522
  /** Bytes this provider holds on the given location — drives proportional fan-out. */
23173
- getEvictableUsage: method(object({ locationId: string() }), EvictableUsageSchema),
23523
+ getEvictableUsage: method(object({ locationId: string() }), EvictableUsageSchema, { auth: "admin" }),
23174
23524
  /**
23175
23525
  * Free approximately `targetBytes` of this provider's OWN least-valuable
23176
23526
  * data on the location (oldest footage, expired clips, …). Returns what it
@@ -23179,7 +23529,10 @@ var storageEvictableCapability = {
23179
23529
  evict: method(object({
23180
23530
  locationId: string(),
23181
23531
  targetBytes: number().int().positive()
23182
- }), EvictResultSchema, { kind: "mutation" })
23532
+ }), EvictResultSchema, {
23533
+ kind: "mutation",
23534
+ auth: "admin"
23535
+ })
23183
23536
  }
23184
23537
  };
23185
23538
  /**
@@ -23282,43 +23635,82 @@ var storageProviderCapability = {
23282
23635
  mode: "collection",
23283
23636
  internal: true,
23284
23637
  methods: {
23638
+ /**
23639
+ * Every method below except `testLocation` had NO `auth` override —
23640
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
23641
+ * `data-store-provider`), so this was reachable on the AppRouter by ANY
23642
+ * authenticated session at the default `auth: 'protected'`: arbitrary
23643
+ * read/write/delete/list against any storage location the operator has
23644
+ * configured, keyed only by a caller-supplied `location` + `relativePath`
23645
+ * — the same "public consumers never see this cap" claim that was false
23646
+ * for `data-store-provider`, here gating raw file I/O instead of raw
23647
+ * settings rows. The only legitimate caller,
23648
+ * `addon-registry.service.ts`'s `activeStorageProvider`, calls the
23649
+ * provider object directly (in-process) — never tRPC — so `auth: 'admin'`
23650
+ * on every method costs it nothing. Public consumers already go through
23651
+ * the separate `storage` singleton cap.
23652
+ */
23285
23653
  /** Self-description for the "Add location" wizard. */
23286
- getProviderInfo: method(_void(), ProviderInfoSchema),
23654
+ getProviderInfo: method(_void(), ProviderInfoSchema, { auth: "admin" }),
23287
23655
  /** Validate config + probe connectivity. Called on add and on edit. */
23288
23656
  testLocation: method(object({ config: record(string(), unknown()) }), TestLocationResultSchema, { auth: "admin" }),
23289
23657
  resolve: method(object({
23290
23658
  location: StorageLocationSchema,
23291
23659
  relativePath: string()
23292
- }), string()),
23660
+ }), string(), { auth: "admin" }),
23293
23661
  write: method(object({
23294
23662
  location: StorageLocationSchema,
23295
23663
  relativePath: string(),
23296
23664
  data: _instanceof(Uint8Array)
23297
- }), _void(), { kind: "mutation" }),
23665
+ }), _void(), {
23666
+ kind: "mutation",
23667
+ auth: "admin"
23668
+ }),
23298
23669
  read: method(object({
23299
23670
  location: StorageLocationSchema,
23300
23671
  relativePath: string()
23301
- }), _instanceof(Uint8Array)),
23672
+ }), _instanceof(Uint8Array), { auth: "admin" }),
23302
23673
  exists: method(object({
23303
23674
  location: StorageLocationSchema,
23304
23675
  relativePath: string()
23305
- }), boolean()),
23676
+ }), boolean(), { auth: "admin" }),
23306
23677
  list: method(object({
23307
23678
  location: StorageLocationSchema,
23308
23679
  prefix: string().optional()
23309
- }), array(string()).readonly()),
23680
+ }), array(string()).readonly(), { auth: "admin" }),
23310
23681
  delete: method(object({
23311
23682
  location: StorageLocationSchema,
23312
23683
  relativePath: string()
23313
- }), _void(), { kind: "mutation" }),
23314
- getAvailableSpace: method(object({ location: StorageLocationSchema }), number().nullable()),
23315
- beginUpload: method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }),
23316
- writeChunk: method(WriteChunkInputSchema, _void(), { kind: "mutation" }),
23317
- finalizeUpload: method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }),
23318
- abortUpload: method(AbortUploadInputSchema, _void(), { kind: "mutation" }),
23319
- beginDownload: method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }),
23320
- readChunk: method(ReadChunkInputSchema, _instanceof(Uint8Array)),
23321
- endDownload: method(EndDownloadInputSchema, _void(), { kind: "mutation" })
23684
+ }), _void(), {
23685
+ kind: "mutation",
23686
+ auth: "admin"
23687
+ }),
23688
+ getAvailableSpace: method(object({ location: StorageLocationSchema }), number().nullable(), { auth: "admin" }),
23689
+ beginUpload: method(BeginUploadInputSchema, BeginUploadResultSchema, {
23690
+ kind: "mutation",
23691
+ auth: "admin"
23692
+ }),
23693
+ writeChunk: method(WriteChunkInputSchema, _void(), {
23694
+ kind: "mutation",
23695
+ auth: "admin"
23696
+ }),
23697
+ finalizeUpload: method(FinalizeUploadInputSchema, _void(), {
23698
+ kind: "mutation",
23699
+ auth: "admin"
23700
+ }),
23701
+ abortUpload: method(AbortUploadInputSchema, _void(), {
23702
+ kind: "mutation",
23703
+ auth: "admin"
23704
+ }),
23705
+ beginDownload: method(BeginDownloadInputSchema, BeginDownloadResultSchema, {
23706
+ kind: "mutation",
23707
+ auth: "admin"
23708
+ }),
23709
+ readChunk: method(ReadChunkInputSchema, _instanceof(Uint8Array), { auth: "admin" }),
23710
+ endDownload: method(EndDownloadInputSchema, _void(), {
23711
+ kind: "mutation",
23712
+ auth: "admin"
23713
+ })
23322
23714
  }
23323
23715
  };
23324
23716
  /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
@@ -23651,9 +24043,28 @@ var userPasskeysCapability = {
23651
24043
  auth: "admin",
23652
24044
  access: "create"
23653
24045
  }),
24046
+ /**
24047
+ * The four methods below had NO `auth` override — `internal: true` did
24048
+ * not gate the mount (see the 2026-08-26 note on `data-store-provider`),
24049
+ * so they were reachable on the AppRouter at the default
24050
+ * `auth: 'protected'` (any authenticated session), inconsistent with
24051
+ * every other method on this cap (all already `auth: 'admin'`).
24052
+ *
24053
+ * ⚠ Checked before gating: these are also the PRE-AUTH login-ceremony
24054
+ * methods, so admin-gating them here must not break login for an
24055
+ * unauthenticated principal. It doesn't — the real login path is the
24056
+ * PUBLIC `auth.beginAuthentication` / `auth.finishAuthentication` /
24057
+ * `auth.beginDiscoverableAuthentication` / `auth.finishDiscoverableAuthentication`
24058
+ * procedures in `server/backend/src/api/core/auth.router.ts`, which call
24059
+ * `provider.beginAuthentication(...)` etc. directly against the
24060
+ * capability-registry-resolved provider — never through this cap's own
24061
+ * tRPC route. `auth: 'admin'` here only closes a redundant, unused
24062
+ * second entry point onto the same ceremony.
24063
+ */
23654
24064
  beginAuthentication: method(object({ userId: string().optional() }), object({ optionsJSON: record(string(), unknown()) }), {
23655
24065
  kind: "mutation",
23656
- access: "view"
24066
+ access: "view",
24067
+ auth: "admin"
23657
24068
  }),
23658
24069
  finishAuthentication: method(object({
23659
24070
  /** Required — the user the assertion belongs to (verified). */
@@ -23662,11 +24073,13 @@ var userPasskeysCapability = {
23662
24073
  response: record(string(), unknown())
23663
24074
  }), object({ verified: boolean() }), {
23664
24075
  kind: "mutation",
23665
- access: "view"
24076
+ access: "view",
24077
+ auth: "admin"
23666
24078
  }),
23667
24079
  beginDiscoverableAuthentication: method(object({}), object({ optionsJSON: record(string(), unknown()) }), {
23668
24080
  kind: "mutation",
23669
- access: "view"
24081
+ access: "view",
24082
+ auth: "admin"
23670
24083
  }),
23671
24084
  finishDiscoverableAuthentication: method(object({
23672
24085
  /** AuthenticationResponseJSON from the browser. */
@@ -23675,7 +24088,8 @@ response: record(string(), unknown()) }), object({
23675
24088
  userId: string().nullable()
23676
24089
  }), {
23677
24090
  kind: "mutation",
23678
- access: "view"
24091
+ access: "view",
24092
+ auth: "admin"
23679
24093
  }),
23680
24094
  listPasskeys: method(object({ userId: string() }), array(PasskeySummarySchema), { auth: "admin" }),
23681
24095
  removePasskey: method(object({
@@ -23853,6 +24267,14 @@ var VectorStatsResultSchema = object({
23853
24267
  /** False when the backend ranks approximately. */
23854
24268
  exact: boolean()
23855
24269
  });
24270
+ /**
24271
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
24272
+ * `data-store-provider`) — every method below was reachable on the AppRouter
24273
+ * by ANY authenticated session at the default `auth: 'protected'`. The only
24274
+ * consumers (`addon-post-analysis`'s embedding stores) reach this cap via
24275
+ * `ctx.api` — UDS/Moleculer, never tRPC — so `auth: 'admin'` on every method
24276
+ * costs them nothing. No client (viewer or admin-ui) calls `vectorStore.*`.
24277
+ */
23856
24278
  var vectorStoreCapability = {
23857
24279
  name: "vector-store",
23858
24280
  scope: "system",
@@ -23860,14 +24282,26 @@ var vectorStoreCapability = {
23860
24282
  internal: true,
23861
24283
  methods: {
23862
24284
  /** Idempotent. Re-declaring with a different `dim` is an error, not a wipe. */
23863
- declareIndex: method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }),
23864
- upsert: method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }),
23865
- query: method(VectorQueryInputSchema, VectorQueryResultSchema),
24285
+ declareIndex: method(VectorDeclareIndexInputSchema, _void(), {
24286
+ kind: "mutation",
24287
+ auth: "admin"
24288
+ }),
24289
+ upsert: method(VectorUpsertInputSchema, VectorUpsertResultSchema, {
24290
+ kind: "mutation",
24291
+ auth: "admin"
24292
+ }),
24293
+ query: method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }),
23866
24294
  /** Metadata by id, no vectors — see {@link VectorGetResultSchema}. */
23867
- getByIds: method(VectorGetInputSchema, VectorGetResultSchema),
23868
- deleteByIds: method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }),
23869
- deleteByFilter: method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }),
23870
- stats: method(VectorStatsInputSchema, VectorStatsResultSchema)
24295
+ getByIds: method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }),
24296
+ deleteByIds: method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
24297
+ kind: "mutation",
24298
+ auth: "admin"
24299
+ }),
24300
+ deleteByFilter: method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, {
24301
+ kind: "mutation",
24302
+ auth: "admin"
24303
+ }),
24304
+ stats: method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" })
23871
24305
  }
23872
24306
  };
23873
24307
  var ClipSchema = object({
@@ -26716,7 +27150,27 @@ var faceGalleryCapability = {
26716
27150
  scope: "system",
26717
27151
  mode: "singleton",
26718
27152
  methods: {
26719
- listIdentities: method(_void(), array(IdentitySchema).readonly()),
27153
+ listIdentities: method(object({
27154
+ /**
27155
+ * Inline {@link IdentitySchema.coverBase64} on every row.
27156
+ *
27157
+ * Default `false`, the same inversion `listRecentFaces` and
27158
+ * `listPlates` took on 2026-08-25 (see `include-crops-default.ts` for
27159
+ * why the burden belongs on the caller that WANTS the bytes). Measured
27160
+ * on the live hub the same day: four identities cost 40,979 B with the
27161
+ * covers inline, ~10 KB of base64 per row, on a query this UI mounts
27162
+ * four times and the viewer holds at `staleTime: 30_000`.
27163
+ *
27164
+ * Nothing loses its avatar: `coverMediaKey` is already on every row and
27165
+ * the `event-media` plane serves that key `immutable` with an ETag.
27166
+ *
27167
+ * **This is an INPUT field, so it does not reach the addon until the
27168
+ * next train** — the hub router validates cap inputs against its own
27169
+ * compiled Zod and strips a key it does not know. Until then the
27170
+ * provider sees `undefined`, which resolves to `false`: the cheap shape
27171
+ * is what ships, and the opt-in becomes reachable when the train lands.
27172
+ */
27173
+ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly()),
26720
27174
  createIdentity: method(object({ name: string().min(1) }), IdentitySchema, {
26721
27175
  kind: "mutation",
26722
27176
  auth: "admin"
@@ -29967,7 +30421,14 @@ var PlateInfoSchema = object({
29967
30421
  plateBbox: BoundingBoxSchema.optional(),
29968
30422
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
29969
30423
  keyFrameMediaKey: string().optional(),
29970
- base64: string().optional()
30424
+ base64: string().optional(),
30425
+ /**
30426
+ * Same crop as a data-plane URL, always present when the plate has a stored
30427
+ * crop. `getPlateByTrack` returns this and never inlines JPEG; `listPlates`
30428
+ * and `searchPlates` inline the crop only when their `includeCrops` input is
30429
+ * left at its `true` default.
30430
+ */
30431
+ cropUrl: string().optional()
29971
30432
  });
29972
30433
  var MediaFileLiteSchema = object({
29973
30434
  key: string(),
@@ -29998,7 +30459,25 @@ var plateGalleryCapability = {
29998
30459
  methods: {
29999
30460
  listPlates: method(object({
30000
30461
  deviceId: number().int().optional(),
30001
- limit: number().int().positive().optional()
30462
+ limit: number().int().positive().optional(),
30463
+ /**
30464
+ * Inline the base64 crop on every row. Default `true` — the existing
30465
+ * behaviour, kept so no caller breaks.
30466
+ *
30467
+ * Set `false` once the caller renders {@link PlateInfo.cropUrl}.
30468
+ * Measured on the live hub at the 500 rows the Plates view asks for:
30469
+ * 879,403 B and 27.7 s with the crops inline, against a few KiB of
30470
+ * metadata without them — and the browser then caches the images.
30471
+ *
30472
+ * This is the plate twin of `faceGallery.listRecentFaces`'s option;
30473
+ * plates were the one gallery list left without it.
30474
+ *
30475
+ * **This is an INPUT field, so it does not reach the addon until the
30476
+ * next train.** The hub router validates cap inputs against its own
30477
+ * compiled Zod and strips a key it does not know. Until the train
30478
+ * ships, sending `false` is harmless and keeps the crops inline.
30479
+ */
30480
+ includeCrops: boolean().optional()
30002
30481
  }).optional(), array(PlateInfoSchema).readonly()),
30003
30482
  getPlateByTrack: method(object({
30004
30483
  deviceId: number().int(),
@@ -30009,7 +30488,9 @@ var plateGalleryCapability = {
30009
30488
  searchPlates: method(object({
30010
30489
  text: string().min(1),
30011
30490
  maxDistance: number().int().min(0).optional(),
30012
- limit: number().int().positive().optional()
30491
+ limit: number().int().positive().optional(),
30492
+ /** See `listPlates.includeCrops`. Default `true`. */
30493
+ includeCrops: boolean().optional()
30013
30494
  }), array(PlateInfoSchema).readonly()),
30014
30495
  /** Suggest groups of near-identical reads (same physical plate) for review/merge. */
30015
30496
  suggestPlateClusters: method(object({
@@ -30029,7 +30510,13 @@ var plateGalleryCapability = {
30029
30510
  kind: "mutation",
30030
30511
  auth: "admin"
30031
30512
  }),
30032
- listVehicles: method(_void(), array(VehicleSchema).readonly()),
30513
+ listVehicles: method(object({
30514
+ /** Inline {@link VehicleSchema.coverBase64} on every row. Default
30515
+ * `false` — the vehicle twin of `faceGallery.listIdentities`'s
30516
+ * option; `coverMediaKey` + the `event-media` plane carry the picture.
30517
+ * INPUT field: stripped by the hub router until the train ships, which
30518
+ * resolves to `false` and is exactly the intended default. */
30519
+ includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()),
30033
30520
  createVehicle: method(object({ name: string().min(1) }), VehicleSchema, {
30034
30521
  kind: "mutation",
30035
30522
  auth: "admin"
@@ -34997,6 +35484,12 @@ Object.freeze({
34997
35484
  addonId: null,
34998
35485
  access: "view"
34999
35486
  },
35487
+ "deviceManager.getBindingsBatch": {
35488
+ capName: "device-manager",
35489
+ capScope: "system",
35490
+ addonId: null,
35491
+ access: "view"
35492
+ },
35000
35493
  "deviceManager.getChildren": {
35001
35494
  capName: "device-manager",
35002
35495
  capScope: "system",
@@ -35057,6 +35550,12 @@ Object.freeze({
35057
35550
  addonId: null,
35058
35551
  access: "view"
35059
35552
  },
35553
+ "deviceManager.getLinkedDevicesBatch": {
35554
+ capName: "device-manager",
35555
+ capScope: "system",
35556
+ addonId: null,
35557
+ access: "view"
35558
+ },
35060
35559
  "deviceManager.getRoleDisplayDefaults": {
35061
35560
  capName: "device-manager",
35062
35561
  capScope: "system",
@@ -36761,6 +37260,12 @@ Object.freeze({
36761
37260
  addonId: null,
36762
37261
  access: "create"
36763
37262
  },
37263
+ "pipelineAnalytics.cancelRelocateMedia": {
37264
+ capName: "pipeline-analytics",
37265
+ capScope: "device",
37266
+ addonId: null,
37267
+ access: "create"
37268
+ },
36764
37269
  "pipelineAnalytics.cancelStorageMigrationMove": {
36765
37270
  capName: "pipeline-analytics",
36766
37271
  capScope: "device",
@@ -36827,6 +37332,12 @@ Object.freeze({
36827
37332
  addonId: null,
36828
37333
  access: "view"
36829
37334
  },
37335
+ "pipelineAnalytics.getGroup": {
37336
+ capName: "pipeline-analytics",
37337
+ capScope: "device",
37338
+ addonId: null,
37339
+ access: "view"
37340
+ },
36830
37341
  "pipelineAnalytics.getKeyEvents": {
36831
37342
  capName: "pipeline-analytics",
36832
37343
  capScope: "device",
@@ -36911,6 +37422,12 @@ Object.freeze({
36911
37422
  addonId: null,
36912
37423
  access: "view"
36913
37424
  },
37425
+ "pipelineAnalytics.listGroups": {
37426
+ capName: "pipeline-analytics",
37427
+ capScope: "device",
37428
+ addonId: null,
37429
+ access: "view"
37430
+ },
36914
37431
  "pipelineAnalytics.listOpsLog": {
36915
37432
  capName: "pipeline-analytics",
36916
37433
  capScope: "device",
@@ -36923,6 +37440,12 @@ Object.freeze({
36923
37440
  addonId: null,
36924
37441
  access: "view"
36925
37442
  },
37443
+ "pipelineAnalytics.listRelocateMediaJobs": {
37444
+ capName: "pipeline-analytics",
37445
+ capScope: "device",
37446
+ addonId: null,
37447
+ access: "view"
37448
+ },
36926
37449
  "pipelineAnalytics.listRetrainAnnotations": {
36927
37450
  capName: "pipeline-analytics",
36928
37451
  capScope: "device",
@@ -37001,6 +37524,12 @@ Object.freeze({
37001
37524
  addonId: null,
37002
37525
  access: "create"
37003
37526
  },
37527
+ "pipelineAnalytics.relocateMedia": {
37528
+ capName: "pipeline-analytics",
37529
+ capScope: "device",
37530
+ addonId: null,
37531
+ access: "create"
37532
+ },
37004
37533
  "pipelineAnalytics.restageRetrainTrack": {
37005
37534
  capName: "pipeline-analytics",
37006
37535
  capScope: "device",
@@ -39692,6 +40221,11 @@ Object.freeze({
39692
40221
  form: "single",
39693
40222
  optional: false
39694
40223
  }],
40224
+ "deviceManager.getBindingsBatch": [{
40225
+ name: "deviceIds",
40226
+ form: "array",
40227
+ optional: false
40228
+ }],
39695
40229
  "deviceManager.getChildren": [{
39696
40230
  name: "parentDeviceId",
39697
40231
  form: "single",
@@ -39737,6 +40271,11 @@ Object.freeze({
39737
40271
  form: "single",
39738
40272
  optional: false
39739
40273
  }],
40274
+ "deviceManager.getLinkedDevicesBatch": [{
40275
+ name: "deviceIds",
40276
+ form: "array",
40277
+ optional: false
40278
+ }],
39740
40279
  "deviceManager.getSettingsSchema": [{
39741
40280
  name: "deviceId",
39742
40281
  form: "single",
@@ -39757,6 +40296,11 @@ Object.freeze({
39757
40296
  form: "single",
39758
40297
  optional: false
39759
40298
  }],
40299
+ "deviceManager.listAll": [{
40300
+ name: "deviceIds",
40301
+ form: "array",
40302
+ optional: true
40303
+ }],
39760
40304
  "deviceManager.loadConfig": [{
39761
40305
  name: "deviceId",
39762
40306
  form: "single",
@@ -40330,6 +40874,11 @@ Object.freeze({
40330
40874
  form: "single",
40331
40875
  optional: false
40332
40876
  }],
40877
+ "pipelineAnalytics.getGroup": [{
40878
+ name: "deviceId",
40879
+ form: "single",
40880
+ optional: false
40881
+ }],
40333
40882
  "pipelineAnalytics.getKeyEvents": [{
40334
40883
  name: "deviceId",
40335
40884
  form: "single",
@@ -40385,6 +40934,11 @@ Object.freeze({
40385
40934
  form: "array",
40386
40935
  optional: false
40387
40936
  }],
40937
+ "pipelineAnalytics.listGroups": [{
40938
+ name: "deviceIds",
40939
+ form: "array",
40940
+ optional: false
40941
+ }],
40388
40942
  "pipelineAnalytics.listOpsLog": [{
40389
40943
  name: "deviceId",
40390
40944
  form: "single",