@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.js CHANGED
@@ -5804,6 +5804,13 @@ var BaseAddon = class {
5804
5804
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5805
5805
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5806
5806
  _registeredCapNames = [];
5807
+ /**
5808
+ * True only after `readAddonStore` actually answered. Constructor
5809
+ * defaults look like stored config when the store is down — a forked
5810
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5811
+ * mode, 2026-08-25) is not "the operator chose this".
5812
+ */
5813
+ settingsStoreReady = false;
5807
5814
  /** Default config values. Provided via constructor. */
5808
5815
  defaults;
5809
5816
  constructor(defaults) {
@@ -6204,7 +6211,9 @@ var BaseAddon = class {
6204
6211
  ];
6205
6212
  let lastErr;
6206
6213
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6207
- return await settings.readAddonStore() ?? {};
6214
+ const stored = await settings.readAddonStore() ?? {};
6215
+ this.settingsStoreReady = true;
6216
+ return stored;
6208
6217
  } catch (err) {
6209
6218
  lastErr = err;
6210
6219
  const msg = err instanceof Error ? err.message : String(err);
@@ -6212,6 +6221,7 @@ var BaseAddon = class {
6212
6221
  if (attempt === delaysMs.length) break;
6213
6222
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6214
6223
  }
6224
+ this.settingsStoreReady = false;
6215
6225
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6216
6226
  return {};
6217
6227
  }
@@ -6623,7 +6633,7 @@ function method(input, output, options) {
6623
6633
  input,
6624
6634
  output,
6625
6635
  kind: options?.kind ?? "query",
6626
- auth: options?.auth ?? "protected",
6636
+ ...options?.auth !== void 0 ? { auth: options.auth } : {},
6627
6637
  ...options?.access !== void 0 ? { access: options.access } : {},
6628
6638
  ...options?.caller !== void 0 ? { caller: options.caller } : {},
6629
6639
  timeoutMs: options?.timeoutMs
@@ -6653,8 +6663,17 @@ var adminUiCapability = {
6653
6663
  mode: "singleton",
6654
6664
  internal: true,
6655
6665
  methods: {
6656
- getStaticDir: method(_void(), StaticDirOutputSchema$1),
6657
- getVersion: method(_void(), VersionOutputSchema$1)
6666
+ /**
6667
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
6668
+ * `data-store-provider`) — both methods were reachable on the AppRouter
6669
+ * by ANY authenticated session at the default `auth: 'protected'`, and
6670
+ * `getStaticDir` leaks a hub filesystem path. The only caller is
6671
+ * `main.ts`'s static-file bootstrap, via `capRegistry.getSingletonForNode`
6672
+ * — never tRPC — so `auth: 'admin'` costs it nothing. The actual admin-ui
6673
+ * SPA assets are served as plain static files, unaffected by this gate.
6674
+ */
6675
+ getStaticDir: method(_void(), StaticDirOutputSchema$1, { auth: "admin" }),
6676
+ getVersion: method(_void(), VersionOutputSchema$1, { auth: "admin" })
6658
6677
  }
6659
6678
  };
6660
6679
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
@@ -6990,8 +7009,23 @@ var viewerUiCapability = {
6990
7009
  mode: "singleton",
6991
7010
  internal: true,
6992
7011
  methods: {
6993
- getStaticDir: method(_void(), StaticDirOutputSchema),
6994
- getVersion: method(_void(), VersionOutputSchema)
7012
+ /**
7013
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
7014
+ * `data-store-provider`) — both methods were reachable on the AppRouter
7015
+ * by ANY authenticated session at the default `auth: 'protected'`, and
7016
+ * `getStaticDir` leaks a hub filesystem path.
7017
+ *
7018
+ * ⚠ Checked before gating: the VIEWER app IS used by a non-admin user
7019
+ * (`roberta`), but it never calls `viewerUi.*` over tRPC. The static SPA
7020
+ * it loads is served by the plain `GET /viewer/*` Fastify route in
7021
+ * `main.ts`, which reads `staticDir`/`indexPath` from a module-level
7022
+ * cache populated ONCE at boot via `capRegistry.getSingletonForNode`
7023
+ * (in-process, not tRPC) — the cap's tRPC procedure is never on that
7024
+ * request path. `auth: 'admin'` here does not block a single viewer
7025
+ * user from loading the app.
7026
+ */
7027
+ getStaticDir: method(_void(), StaticDirOutputSchema, { auth: "admin" }),
7028
+ getVersion: method(_void(), VersionOutputSchema, { auth: "admin" })
6995
7029
  }
6996
7030
  };
6997
7031
  /**
@@ -7666,24 +7700,6 @@ var RecordingRetentionSchema = object({
7666
7700
  maxSizeGb: number().min(0).optional()
7667
7701
  });
7668
7702
  /**
7669
- * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7670
- * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7671
- * previews at. Five graduated steps; absent on a config = `standard` (the
7672
- * shipped default, matching `sheet-geometry`/`sheet-composer`).
7673
- *
7674
- * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7675
- * Each window's index sidecar carries its own tile dims, so a camera whose
7676
- * preset changed over time renders every historical window at the dims it was
7677
- * written with.
7678
- */
7679
- var ScrubThumbnailPresetSchema = _enum([
7680
- "minimal",
7681
- "low",
7682
- "standard",
7683
- "high",
7684
- "max"
7685
- ]);
7686
- /**
7687
7703
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7688
7704
  *
7689
7705
  * `bands` is the ONLY authored recording intent: what to record, when, and on
@@ -7691,7 +7707,11 @@ var ScrubThumbnailPresetSchema = _enum([
7691
7707
  * other field is a storage knob (profiles, segment length, retention, scrub).
7692
7708
  *
7693
7709
  * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
7694
- * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
7710
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30,
7711
+ * and `scrubThumbnails` — a five-step fidelity knob for a sprite tier that was
7712
+ * deleted on 2026-07-24 and had ZERO consumers in the recorder — on 2026-08-25
7713
+ * (D62: a switch that writes a store nobody reads is worse than no switch).
7714
+ * Stored rows keep loading: the READ schema is `.strip()` (config-store.ts).
7695
7715
  * A stale caller must fail loudly — silently stripping its legacy intent would
7696
7716
  * persist a band-less config, i.e. silently stop recording the camera.
7697
7717
  */
@@ -7714,14 +7734,7 @@ var RecordingConfigSchema = object({
7714
7734
  * "off" is the absence of a covering band, never a band value.
7715
7735
  */
7716
7736
  bands: array(RecordingBandSchema).default([]),
7717
- retention: RecordingRetentionSchema.optional(),
7718
- /**
7719
- * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7720
- * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7721
- * windows only — existing sheets are immutable, and each window's index
7722
- * carries its own tile dims so mixed-preset history renders correctly.
7723
- */
7724
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7737
+ retention: RecordingRetentionSchema.optional()
7725
7738
  }).strict();
7726
7739
  /**
7727
7740
  * Entity-relocation job state (storage entity-routing spec, Phase 4).
@@ -7797,10 +7810,11 @@ var RelocateFootageInputSchema = object({
7797
7810
  * `RecordingConfig.enabled` or camera wrapper bindings. */
7798
7811
  var StorageMigrationLeaseInputSchema = object({ leaseId: string().min(1) });
7799
7812
  var StorageMigrationFootageMoveInputSchema = RelocateFootageInputSchema.extend({ leaseId: string().min(1) });
7800
- var StorageMigrationMediaMoveInputSchema = object({
7813
+ var RelocateMediaInputSchema = object({
7801
7814
  toLocationId: string(),
7802
7815
  throttleMbps: number().min(1).max(1e3).optional()
7803
- }).extend({ leaseId: string().min(1) });
7816
+ });
7817
+ var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
7804
7818
  /** The independently selectable logical storage classes. `recordings`
7805
7819
  * encompasses the high and mid segment profiles; `recordingsLow` is low
7806
7820
  * segments; `eventMedia` is post-analysis blobs. */
@@ -8095,7 +8109,26 @@ var LabelDefinitionSchema = object({
8095
8109
  description: string().optional(),
8096
8110
  icon: string().optional()
8097
8111
  });
8098
- var ClassMapDefinitionSchema = object({
8112
+ /**
8113
+ * Wire schema for a per-model CATALOG classMap override
8114
+ * (`ModelCatalogEntry.classMap` / `ModelConvertMetadata.classMap`) —
8115
+ * restricted to {@link CLASS_MAP_MACRO_TARGETS}, the only macros the
8116
+ * detection pipeline executor actually routes.
8117
+ *
8118
+ * This is deliberately a DIFFERENT, narrower shape than the general-purpose
8119
+ * `ClassMapDefinition` interface above (e.g. `IDetectionAddon.getClassMap()`
8120
+ * and the audio `YAMNET_TO_MACRO` catalog both use macro targets outside this
8121
+ * enum) — the two used to share the name `ClassMapDefinition`/
8122
+ * `ClassMapDefinitionSchema`, which made the schema-type-twin guard
8123
+ * (`scripts/check-schema-type-twins.ts`) flag them as a duplicated shape. They
8124
+ * are not: it is two different concepts colliding on a name. Keep this type
8125
+ * under its own name rather than reusing `ClassMapDefinition` — reusing it
8126
+ * would either narrow every `ClassMapDefinition` consumer to the four
8127
+ * detection macros (breaking `YAMNET_TO_MACRO`) or drop the validation this
8128
+ * schema exists for (see the "rejects a classMap whose target is not a
8129
+ * detection macro" test in `model-catalog-schema.test.ts`).
8130
+ */
8131
+ var DetectionCatalogClassMapSchema = object({
8099
8132
  mapping: record(string(), _enum([
8100
8133
  "person",
8101
8134
  "vehicle",
@@ -8187,6 +8220,12 @@ var ModelVariantGroupSchema = object({
8187
8220
  */
8188
8221
  resolution: number().int().positive().optional()
8189
8222
  });
8223
+ var ModelProviderIdSchema = _enum([
8224
+ "camstack",
8225
+ "frigate",
8226
+ "scrypted",
8227
+ "custom"
8228
+ ]);
8190
8229
  var ModelCatalogEntrySchema = object({
8191
8230
  id: string(),
8192
8231
  name: string(),
@@ -8284,11 +8323,17 @@ var ModelCatalogEntrySchema = object({
8284
8323
  */
8285
8324
  group: ModelVariantGroupSchema.optional(),
8286
8325
  /**
8326
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8327
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8328
+ * persisted before this field existed (`inferModelProvider` fills those).
8329
+ */
8330
+ provider: ModelProviderIdSchema.optional(),
8331
+ /**
8287
8332
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8288
8333
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8289
8334
  * labels already ARE the CamStack macros (Scrypted identity map).
8290
8335
  */
8291
- classMap: ClassMapDefinitionSchema.optional()
8336
+ classMap: DetectionCatalogClassMapSchema.optional()
8292
8337
  });
8293
8338
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8294
8339
  format: literal("openvino"),
@@ -8318,7 +8363,7 @@ var ModelConvertMetadataSchema = object({
8318
8363
  "segmentation"
8319
8364
  ]),
8320
8365
  faceAlignment: boolean().optional(),
8321
- classMap: ClassMapDefinitionSchema.optional()
8366
+ classMap: DetectionCatalogClassMapSchema.optional()
8322
8367
  });
8323
8368
  var ConvertResultSchema = object({
8324
8369
  entry: ModelCatalogEntrySchema,
@@ -9191,7 +9236,18 @@ var addonPagesSourceCapability = {
9191
9236
  scope: "system",
9192
9237
  mode: "collection",
9193
9238
  internal: true,
9194
- methods: { listPages: method(_void(), array(AddonPageDeclarationSchema).readonly()) }
9239
+ methods: {
9240
+ /**
9241
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
9242
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
9243
+ * authenticated session at the default `auth: 'protected'`. The only
9244
+ * consumer, the `addon-pages-aggregator` builtin, reads it via
9245
+ * `ctx.capabilities.getCollection('addon-pages-source')` — LOCAL-PROCESS
9246
+ * only, never tRPC (per `ctx.capabilities` seeing only the local
9247
+ * process). The public, enriched listing admin-ui actually consumes is
9248
+ * the separate `addon-pages` cap, unaffected by this change.
9249
+ */
9250
+ listPages: method(_void(), array(AddonPageDeclarationSchema).readonly(), { auth: "admin" }) }
9195
9251
  };
9196
9252
  var AddonHttpRouteSchema = object({
9197
9253
  method: _enum([
@@ -9488,7 +9544,20 @@ var addonWidgetsSourceCapability = {
9488
9544
  scope: "system",
9489
9545
  mode: "collection",
9490
9546
  internal: true,
9491
- methods: { listWidgets: method(_void(), array(WidgetMetadataSchema).readonly()) }
9547
+ methods: {
9548
+ /**
9549
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
9550
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
9551
+ * authenticated session at the default `auth: 'protected'`. The only
9552
+ * consumer, the `addon-widgets-aggregator` builtin, reads it via
9553
+ * `ctx.capabilities.getCollection('addon-widgets-source')` — LOCAL-PROCESS
9554
+ * only, never tRPC. The public, enriched listing admin-ui actually
9555
+ * consumes is the separate `addon-widgets` cap (`listWidgets` there is
9556
+ * gated separately, and a `preAuth: true` widget is surfaced through the
9557
+ * PUBLIC `auth.listLoginMethods` contribution channel instead) —
9558
+ * neither is affected by this change.
9559
+ */
9560
+ listWidgets: method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" }) }
9492
9561
  };
9493
9562
  /**
9494
9563
  * `addon-widgets` — system-scoped singleton aggregator cap. Public-facing
@@ -11652,7 +11721,17 @@ var customModelRegistryCapability = {
11652
11721
  scope: "system",
11653
11722
  mode: "collection",
11654
11723
  internal: true,
11655
- methods: { listModels: method(_void(), array(CustomModelDescriptorSchema).readonly()) }
11724
+ methods: {
11725
+ /**
11726
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
11727
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
11728
+ * authenticated session at the default `auth: 'protected'`. The only
11729
+ * consumer, the detection-pipeline's model resolution
11730
+ * (`addon-pipeline/src/detection-pipeline/provider.ts`), calls
11731
+ * `ctx.api.customModelRegistry.listModels.query()` — UDS/Moleculer,
11732
+ * never tRPC — so `auth: 'admin'` costs it nothing.
11733
+ */
11734
+ listModels: method(_void(), array(CustomModelDescriptorSchema).readonly(), { auth: "admin" }) }
11656
11735
  };
11657
11736
  /**
11658
11737
  * Query filter for settings-store collections.
@@ -11776,11 +11855,26 @@ var settingsStoreCapability = {
11776
11855
  key: string(),
11777
11856
  value: unknown()
11778
11857
  }), _void(), { kind: "mutation" }),
11779
- /** Get all entries matching an optional filter. */
11858
+ /**
11859
+ * Rows matching a filter.
11860
+ *
11861
+ * `columns` is a SQL-level projection: the engine SELECTs only those
11862
+ * columns (plus the primary key) instead of the whole row. It is the only
11863
+ * way to stop paying disk for a column that is discarded — a heavy JSON
11864
+ * column dominates the row, and dropping it in JS after the read pays every
11865
+ * byte first. Measured on the live `pipeline-analytics:tracks` table: a
11866
+ * 501-row page is 4.6 MB whole and 0.11 MB projected, same index, same
11867
+ * plan.
11868
+ *
11869
+ * The engine REFUSES a column it cannot serve rather than omitting it —
11870
+ * see `SettingsQueryInput.columns` for why a projection may not be
11871
+ * forgiving the way a predicate is.
11872
+ */
11780
11873
  query: method(object({
11781
11874
  namespace: string().optional(),
11782
11875
  collection: string(),
11783
- filter: QueryFilterSchema.optional()
11876
+ filter: QueryFilterSchema.optional(),
11877
+ columns: array(string()).readonly().optional()
11784
11878
  }), array(SettingsRecordSchema).readonly()),
11785
11879
  /** Insert a new record. */
11786
11880
  insert: method(object({
@@ -11906,6 +12000,23 @@ var EngineInfoSchema = object({
11906
12000
  * go through `settings-store`; they never see this cap, and an engine
11907
12001
  * never sees a caller.
11908
12002
  *
12003
+ * ⚠ **`internal: true` DOES NOT gate the mount, and until 2026-08-26 this
12004
+ * comment claimed otherwise.** `resolveCapMount` never reads the field, so this
12005
+ * cap was live on the AppRouter with every method at the default
12006
+ * `auth: 'protected'` — i.e. readable by ANY authenticated session, admin or
12007
+ * not. Verified against the live hub: `dataStoreProvider.query` returned raw
12008
+ * settings rows including a provider credential, bypassing the secret redaction
12009
+ * that `deviceManager.listAll` and friends go through.
12010
+ *
12011
+ * Every method is now `auth: 'admin'` EXPLICITLY, including the five reads —
12012
+ * they had no options object at all, which is exactly how they kept the
12013
+ * permissive default while the mutations looked deliberate. The explicit
12014
+ * annotation is the fix that works today; making `internal` mean something at
12015
+ * mount time is the systemic one, and it is not free: 22 caps declare it, only
12016
+ * 4 are `mount: 'skip'`, and forcing admin on all of them would change the auth
12017
+ * of surfaces the viewer and admin-ui call as non-admin users. That decision is
12018
+ * the operator's, not a side effect of this file.
12019
+ *
11909
12020
  * Design notes:
11910
12021
  * - **Stateless dispatch.** Every method carries its own
11911
12022
  * `namespace` + `collection`, so an engine keeps no per-caller state
@@ -11927,64 +12038,105 @@ var dataStoreProviderCapability = {
11927
12038
  internal: true,
11928
12039
  methods: {
11929
12040
  /** Self-description — how the orchestrator picks a registrant. */
11930
- getEngineInfo: method(_void(), EngineInfoSchema),
12041
+ getEngineInfo: method(_void(), EngineInfoSchema, { auth: "admin" }),
11931
12042
  /** Get a single value by key from a collection. */
11932
12043
  get: method(object({
11933
12044
  namespace: string().optional(),
11934
12045
  collection: string(),
11935
12046
  key: string()
11936
- }), unknown()),
12047
+ }), unknown(), { auth: "admin" }),
11937
12048
  /** Set a value by key in a collection (upsert). */
11938
12049
  set: method(object({
11939
12050
  namespace: string().optional(),
11940
12051
  collection: string(),
11941
12052
  key: string(),
11942
12053
  value: unknown()
11943
- }), _void(), { kind: "mutation" }),
12054
+ }), _void(), {
12055
+ kind: "mutation",
12056
+ auth: "admin"
12057
+ }),
11944
12058
  /** Get all entries matching an optional filter. */
11945
12059
  query: method(object({
11946
12060
  namespace: string().optional(),
11947
12061
  collection: string(),
11948
- filter: QueryFilterSchema.optional()
11949
- }), array(SettingsRecordSchema).readonly()),
12062
+ filter: QueryFilterSchema.optional(),
12063
+ /**
12064
+ * SQL-level column projection — MUST mirror `settings-store.query`.
12065
+ *
12066
+ * ⚠ An earlier version of this comment blamed Zod stripping, and that
12067
+ * was wrong — corrected 2026-08-26 after the hop map
12068
+ * (`docs/design/2026-08-26-mappa-hop-argomenti.md`) traced the path.
12069
+ * There is **no Zod parse at all** between the door and the engine: the
12070
+ * dispatcher forwards the payload verbatim and UDS carries it whole. A
12071
+ * field declared here reaches `SqliteSettingsBackend` either way.
12072
+ *
12073
+ * What actually lost `columns` was the THIRD declaration of this shape:
12074
+ * `SettingsQueryInput` in `interfaces/storage.ts`, a hand-written TS
12075
+ * interface the engine destructures from. The field existed on both
12076
+ * schemas and the engine still never read it, because nothing checks a
12077
+ * registered provider against `InferProvider<cap>` —
12078
+ * `ProviderRegistration.provider` is typed `object`.
12079
+ *
12080
+ * It is declared here anyway, and must stay in step with
12081
+ * `settings-store.query`: a caller reading only the cap definitions has
12082
+ * to be able to see that this call carries a projection.
12083
+ * `data-door-schema-parity.spec.ts` keeps the two aligned.
12084
+ */
12085
+ columns: array(string()).readonly().optional()
12086
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }),
11950
12087
  /** Insert a new record. */
11951
12088
  insert: method(object({
11952
12089
  namespace: string().optional(),
11953
12090
  collection: string(),
11954
12091
  record: SettingsRecordSchema
11955
- }), _void(), { kind: "mutation" }),
12092
+ }), _void(), {
12093
+ kind: "mutation",
12094
+ auth: "admin"
12095
+ }),
11956
12096
  /** Update an existing record by ID. */
11957
12097
  update: method(object({
11958
12098
  namespace: string().optional(),
11959
12099
  collection: string(),
11960
12100
  id: string(),
11961
12101
  data: record(string(), unknown())
11962
- }), _void(), { kind: "mutation" }),
12102
+ }), _void(), {
12103
+ kind: "mutation",
12104
+ auth: "admin"
12105
+ }),
11963
12106
  /** Delete a record by key/ID. */
11964
12107
  delete: method(object({
11965
12108
  namespace: string().optional(),
11966
12109
  collection: string(),
11967
12110
  key: string()
11968
- }), _void(), { kind: "mutation" }),
12111
+ }), _void(), {
12112
+ kind: "mutation",
12113
+ auth: "admin"
12114
+ }),
11969
12115
  /** Delete every record matching `filter`, in one statement. */
11970
12116
  deleteWhere: method(object({
11971
12117
  namespace: string().optional(),
11972
12118
  collection: string(),
11973
12119
  filter: MutationFilterSchema
11974
- }), object({ deleted: number().int() }), { kind: "mutation" }),
12120
+ }), object({ deleted: number().int() }), {
12121
+ kind: "mutation",
12122
+ auth: "admin"
12123
+ }),
11975
12124
  /** Apply `data` to every record matching `filter`, in one statement. */
11976
12125
  updateWhere: method(object({
11977
12126
  namespace: string().optional(),
11978
12127
  collection: string(),
11979
12128
  filter: MutationFilterSchema,
11980
12129
  data: record(string(), unknown())
11981
- }), object({ updated: number().int() }), { kind: "mutation" }),
12130
+ }), object({ updated: number().int() }), {
12131
+ kind: "mutation",
12132
+ auth: "admin"
12133
+ }),
11982
12134
  /** Count entries in a collection, optionally filtered. */
11983
12135
  count: method(object({
11984
12136
  namespace: string().optional(),
11985
12137
  collection: string(),
11986
12138
  filter: QueryFilterSchema.optional()
11987
- }), number()),
12139
+ }), number(), { auth: "admin" }),
11988
12140
  /** Grouped counts per ((field-origin)/bucketSize) bucket, filtered. */
11989
12141
  histogram: method(object({
11990
12142
  namespace: string().optional(),
@@ -11996,19 +12148,22 @@ var dataStoreProviderCapability = {
11996
12148
  }), array(object({
11997
12149
  bucket: number().int(),
11998
12150
  count: number().int()
11999
- })).readonly()),
12151
+ })).readonly(), { auth: "admin" }),
12000
12152
  /** Check if a collection is empty. */
12001
12153
  isEmpty: method(object({
12002
12154
  namespace: string().optional(),
12003
12155
  collection: string()
12004
- }), boolean()),
12156
+ }), boolean(), { auth: "admin" }),
12005
12157
  /** Declare a typed (SQL-backed) collection with columns + indexes. */
12006
12158
  declareCollection: method(object({
12007
12159
  namespace: string().optional(),
12008
12160
  collection: string(),
12009
12161
  columns: array(CollectionColumnSchema).readonly(),
12010
12162
  indexes: array(CollectionIndexSchema).readonly().optional()
12011
- }), _void(), { kind: "mutation" })
12163
+ }), _void(), {
12164
+ kind: "mutation",
12165
+ auth: "admin"
12166
+ })
12012
12167
  }
12013
12168
  };
12014
12169
  /**
@@ -12884,6 +13039,27 @@ var LinkedDeviceSchema = object({
12884
13039
  features: array(string()),
12885
13040
  producesTrackedEvents: boolean().optional()
12886
13041
  });
13042
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
13043
+ * The batch answer needs the tag; the single-device answer already has it
13044
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
13045
+ var LinkedDevicesForDeviceSchema = object({
13046
+ deviceId: number(),
13047
+ mode: LinkedDevicesModeSchema,
13048
+ devices: array(LinkedDeviceSchema)
13049
+ });
13050
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
13051
+ * `getAllBindings` all answer in. Declared once: three copies of the same
13052
+ * object literal is exactly how the three drift apart. */
13053
+ var DeviceBindingsForDeviceSchema = object({
13054
+ deviceId: number(),
13055
+ entries: array(object({
13056
+ capName: string(),
13057
+ kind: _enum(["native", "wrapped"]),
13058
+ providerAddonId: string(),
13059
+ providerNodeId: string(),
13060
+ nativeAddonId: string()
13061
+ }))
13062
+ });
12887
13063
  var SavedDeviceRowSchema = object({
12888
13064
  /** Numeric id reserved at allocateDeviceId time. */
12889
13065
  id: number(),
@@ -13246,7 +13422,21 @@ var deviceManagerCapability = {
13246
13422
  projection: _enum(["full", "slim"]).optional(),
13247
13423
  /** Return only camera devices. Filtering server-side instead of
13248
13424
  * shipping 293 rows to find 12. */
13249
- isCamera: boolean().optional()
13425
+ isCamera: boolean().optional(),
13426
+ /**
13427
+ * Return only these device ids. For the caller that already KNOWS the
13428
+ * handful it wants and needs a field the id-bearing answer does not
13429
+ * carry — the viewer's linked-devices panel joins `type` and `online`
13430
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
13431
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
13432
+ * refetches on the reconcile interval, on a phone.
13433
+ *
13434
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
13435
+ * keys rather than rejecting them (verified against the live hub
13436
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
13437
+ * it answers today and the caller filters as it already does.
13438
+ */
13439
+ deviceIds: array(number()).optional()
13250
13440
  }), array(DeviceInfoSchema)),
13251
13441
  /** Get a single device by numeric deviceId. */
13252
13442
  getDevice: method(object({ deviceId: number() }), DeviceInfoSchema.nullable()),
@@ -13265,6 +13455,23 @@ var deviceManagerCapability = {
13265
13455
  mode: LinkedDevicesModeSchema,
13266
13456
  devices: array(LinkedDeviceSchema)
13267
13457
  })),
13458
+ /**
13459
+ * `getLinkedDevices` for MANY cameras, in one call.
13460
+ *
13461
+ * Not a convenience wrapper — a cost fix. Resolving one camera's linked set
13462
+ * needs the whole fleet as candidates (children ∪ same-location), so the
13463
+ * single-device path runs a full `listAll` per call. Asked per camera that
13464
+ * is N full-fleet sweeps of device-manager's event loop, and they do not
13465
+ * overlap: measured on the live hub 2026-08-25, 30 concurrent
13466
+ * `getLinkedDevices` took 4110 ms wall (median 4060 ms each) to return
13467
+ * 7260 bytes in total. The batch takes ONE sweep and one settings read per
13468
+ * camera.
13469
+ *
13470
+ * A device id that resolves to nothing still gets a row (`devices: []`) —
13471
+ * a caller that asked for thirty and got twenty-eight cannot tell which two
13472
+ * are missing, or that any are.
13473
+ */
13474
+ getLinkedDevicesBatch: method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)),
13268
13475
  /** Get stream sources for a camera device. */
13269
13476
  getStreamSources: method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)),
13270
13477
  /** Get config entries (key + value + description) for a device. */
@@ -13322,16 +13529,22 @@ var deviceManagerCapability = {
13322
13529
  * currently-active provider (native or wrapper) + the underlying native
13323
13530
  * addon id, so consumers can decide routing without re-running discovery.
13324
13531
  */
13325
- getBindings: method(object({ deviceId: number() }), object({
13326
- deviceId: number(),
13327
- entries: array(object({
13328
- capName: string(),
13329
- kind: _enum(["native", "wrapped"]),
13330
- providerAddonId: string(),
13331
- providerNodeId: string(),
13332
- nativeAddonId: string()
13333
- }))
13334
- })),
13532
+ getBindings: method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema),
13533
+ /**
13534
+ * `getBindings` for a NAMED set of devices, in one call.
13535
+ *
13536
+ * Between `getBindings` (one device) and `getAllBindings` (all 988) there
13537
+ * was nothing, so a caller that needs seventy-five either pays
13538
+ * seventy-five round-trips or drags the whole fleet across. Measured on
13539
+ * the live hub 2026-08-25: 75 concurrent `getBindings` = 1211 ms / 118 KB,
13540
+ * `getAllBindings({})` = 511 ms / 679 KB. Neither is the right answer to
13541
+ * "these seventy-five".
13542
+ *
13543
+ * Same resolver, same routing rules, same per-device shape as
13544
+ * `getBindings`; ids are deduped and a device with no bindings answers
13545
+ * `entries: []` rather than dropping out.
13546
+ */
13547
+ getBindingsBatch: method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)),
13335
13548
  /**
13336
13549
  * Return the binding map for every device known to the hub. Used by
13337
13550
  * `SystemManager` warm-boot: a single round-trip resolves the
@@ -13340,16 +13553,7 @@ var deviceManagerCapability = {
13340
13553
  * device add/remove) — clients invalidate via the
13341
13554
  * `capability.binding-changed` event.
13342
13555
  */
13343
- getAllBindings: method(object({}), array(object({
13344
- deviceId: number(),
13345
- entries: array(object({
13346
- capName: string(),
13347
- kind: _enum(["native", "wrapped"]),
13348
- providerAddonId: string(),
13349
- providerNodeId: string(),
13350
- nativeAddonId: string()
13351
- }))
13352
- }))),
13556
+ getAllBindings: method(object({}), array(DeviceBindingsForDeviceSchema)),
13353
13557
  /**
13354
13558
  * Activate (or deactivate) a wrapper addon for a (device, cap) pair.
13355
13559
  * Persists the binding via ctx.settings. active=false clears the wrapper
@@ -13759,13 +13963,20 @@ var embeddingEncoderCapability = {
13759
13963
  mode: "collection",
13760
13964
  internal: true,
13761
13965
  methods: {
13966
+ /**
13967
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
13968
+ * `data-store-provider`) — every method below was reachable on the
13969
+ * AppRouter by ANY authenticated session at the default
13970
+ * `auth: 'protected'`. Consumers reach the encoder via `ctx.api` from
13971
+ * `addon-post-analysis`, never tRPC.
13972
+ */
13762
13973
  encode: method(object({
13763
13974
  crop: _instanceof(Uint8Array),
13764
13975
  width: number(),
13765
13976
  height: number()
13766
- }), EmbeddingResultSchema),
13767
- encodeText: method(object({ text: string() }), EmbeddingResultSchema),
13768
- getInfo: method(_void(), EmbeddingInfoSchema)
13977
+ }), EmbeddingResultSchema, { auth: "admin" }),
13978
+ encodeText: method(object({ text: string() }), EmbeddingResultSchema, { auth: "admin" }),
13979
+ getInfo: method(_void(), EmbeddingInfoSchema, { auth: "admin" })
13769
13980
  }
13770
13981
  };
13771
13982
  /**
@@ -14074,12 +14285,28 @@ var llmRuntimeCapability = {
14074
14285
  mode: "singleton",
14075
14286
  internal: true,
14076
14287
  methods: {
14288
+ /**
14289
+ * `complete`, `status`, `listLocalModels` and `getDiskUsage` had NO
14290
+ * `auth` override — `internal: true` did not gate the mount (see the
14291
+ * 2026-08-26 note on `data-store-provider`), so they were reachable on
14292
+ * the AppRouter by ANY authenticated session at the default
14293
+ * `auth: 'protected'`, inconsistent with the rest of this cap (already
14294
+ * `auth: 'admin'`) and with this file's own docblock: "the operator
14295
+ * reaches this only through the `llm` cap's methods." The sole caller,
14296
+ * `addon-ai`'s `runtime-client.ts`, reaches every method here via
14297
+ * `ctx.api` + `nodePin` — never tRPC — so `auth: 'admin'` costs it
14298
+ * nothing; the public `llm` cap (which end users and non-admin sessions
14299
+ * legitimately call for chat/generation) is untouched.
14300
+ */
14077
14301
  complete: method(LlmGenerateBaseInputSchema.extend({
14078
14302
  images: array(LlmImageSchema).optional(),
14079
14303
  runtime: ManagedRuntimeConfigSchema,
14080
14304
  /** The managed profile's timeout, threaded by the hub provider. */
14081
14305
  timeoutMs: number().int().positive().optional()
14082
- }), LlmGenerateResultSchema, { kind: "mutation" }),
14306
+ }), LlmGenerateResultSchema, {
14307
+ kind: "mutation",
14308
+ auth: "admin"
14309
+ }),
14083
14310
  ensureStarted: method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
14084
14311
  kind: "mutation",
14085
14312
  auth: "admin"
@@ -14088,7 +14315,7 @@ var llmRuntimeCapability = {
14088
14315
  kind: "mutation",
14089
14316
  auth: "admin"
14090
14317
  }),
14091
- status: method(object({}), LlmRuntimeStatusSchema),
14318
+ status: method(object({}), LlmRuntimeStatusSchema, { auth: "admin" }),
14092
14319
  installModel: method(object({ model: ManagedModelRefSchema }), _void(), {
14093
14320
  kind: "mutation",
14094
14321
  auth: "admin"
@@ -14097,8 +14324,8 @@ var llmRuntimeCapability = {
14097
14324
  kind: "mutation",
14098
14325
  auth: "admin"
14099
14326
  }),
14100
- listLocalModels: method(object({}), array(LlmNodeModelSchema)),
14101
- getDiskUsage: method(object({}), LlmRuntimeDiskUsageSchema)
14327
+ listLocalModels: method(object({}), array(LlmNodeModelSchema), { auth: "admin" }),
14328
+ getDiskUsage: method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" })
14102
14329
  }
14103
14330
  };
14104
14331
  /**
@@ -16237,12 +16464,15 @@ var NcOccupancyConditionSchema = object({
16237
16464
  * there is no second switch that can disagree with the first and every rule
16238
16465
  * authored before the decision migrates for free (`audioModeOf`):
16239
16466
  *
16240
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
16241
- * classifier labels with one of them. No window, no percentage:
16242
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
16243
- * `throttle` cooldown is the only brake. The per-label confidence floor is
16244
- * the analyzer's (`classificationMinScore`, per device) a label only
16245
- * reaches this condition if the classifier was already confident enough.
16467
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
16468
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
16469
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
16470
+ * frames is the wrong question for a classifier that labels 1–3 frames
16471
+ * per episode. The count window is the brake that drops a single-frame
16472
+ * false positive; the rule's own `throttle` cooldown is the other. The
16473
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
16474
+ * per device) — a label only reaches this condition if the classifier was
16475
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
16246
16476
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
16247
16477
  * the condition: at least `hitPercent`% of the samples over
16248
16478
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -16269,14 +16499,22 @@ var NcOccupancyConditionSchema = object({
16269
16499
  * an operator who typed `dog` mean the same thing.
16270
16500
  */
16271
16501
  var NcAudioConditionSchema = object({
16272
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
16502
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
16273
16503
  labels: array(string().min(1)).min(1).optional(),
16274
16504
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
16275
16505
  dbThreshold: number().min(-96).max(0).optional(),
16276
16506
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
16277
16507
  hitPercent: number().int().min(1).max(100).default(60),
16278
16508
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
16279
- samplingSeconds: number().int().min(1).max(300).default(10)
16509
+ samplingSeconds: number().int().min(1).max(300).default(10),
16510
+ /**
16511
+ * LABEL MODE: how many labelled frames must land inside
16512
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
16513
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
16514
+ */
16515
+ confirmHits: number().int().min(1).max(20).optional(),
16516
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
16517
+ confirmWindowSec: number().int().min(1).max(60).optional()
16280
16518
  });
16281
16519
  /**
16282
16520
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -18353,7 +18591,17 @@ var oauthIntegrationCapability = {
18353
18591
  scope: "system",
18354
18592
  mode: "collection",
18355
18593
  internal: true,
18356
- methods: { getDescriptor: method(_void(), OauthIntegrationDescriptorSchema) }
18594
+ methods: {
18595
+ /**
18596
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
18597
+ * `data-store-provider`) — `getDescriptor` was reachable on the AppRouter
18598
+ * by ANY authenticated session at the default `auth: 'protected'`. The
18599
+ * real caller is `/api/oauth2/authorize` and `/api/oauth2/integrations`
18600
+ * (`oauth2-routes.ts`), which resolve the provider directly off the
18601
+ * capability registry — never through tRPC. `auth: 'admin'` closes the
18602
+ * tRPC surface without touching that path.
18603
+ */
18604
+ getDescriptor: method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" }) }
18357
18605
  };
18358
18606
  /**
18359
18607
  * pipeline-analytics — device-scoped wrapper cap. Refines raw
@@ -19201,6 +19449,46 @@ var RecentTracksPageSchema = object({
19201
19449
  /** Cursor for the next page, or null when this page is the last. */
19202
19450
  nextCursor: string().nullable()
19203
19451
  });
19452
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
19453
+ var LIST_GROUPS_MAX_LIMIT = 100;
19454
+ var AnalyticsGroupRecordSchema = object({
19455
+ id: string(),
19456
+ deviceId: number().int(),
19457
+ openedAt: number().int(),
19458
+ closedAt: number().int(),
19459
+ timestamp: number().int(),
19460
+ memberCount: number().int(),
19461
+ memberTrackIds: array(string()).readonly(),
19462
+ className: string(),
19463
+ classes: array(string()).readonly(),
19464
+ /** Relative event-media path, or null when the group has no picture yet. */
19465
+ mediaUrl: string().nullable(),
19466
+ singleton: boolean()
19467
+ });
19468
+ var AnalyticsGroupMemberSchema = object({
19469
+ trackId: string(),
19470
+ deviceId: number().int(),
19471
+ className: string(),
19472
+ firstSeen: number().int(),
19473
+ lastSeen: number().int(),
19474
+ mediaUrl: string().nullable()
19475
+ });
19476
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
19477
+ var ListGroupsQueryInput = object({
19478
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
19479
+ deviceIds: array(number()),
19480
+ /** Window lower bound on `closedAt` (inclusive). */
19481
+ since: number().optional(),
19482
+ /** Window upper bound on `openedAt` (inclusive). */
19483
+ until: number().optional(),
19484
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
19485
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
19486
+ cursor: string().optional()
19487
+ });
19488
+ var ListGroupsPageSchema = object({
19489
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
19490
+ nextCursor: string().nullable()
19491
+ });
19204
19492
  var KeyEventQueryInput = object({
19205
19493
  deviceId: number(),
19206
19494
  /** Window lower bound (track firstSeen ≥ since). */
@@ -19276,7 +19564,9 @@ var TrackCascadeCountsSchema = object({
19276
19564
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19277
19565
  plates: number().int(),
19278
19566
  /** Per-track CLIP search vectors removed (best-effort). */
19279
- embeddings: number().int()
19567
+ embeddings: number().int(),
19568
+ /** Group membership + group rows removed with their last member (best-effort). */
19569
+ groups: number().int()
19280
19570
  });
19281
19571
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19282
19572
  var DiskReconcileCountsSchema = object({
@@ -19448,6 +19738,16 @@ var pipelineAnalyticsCapability = {
19448
19738
  * are not included (same contract as `listTracks`).
19449
19739
  */
19450
19740
  listRecentTracks: method(RecentTracksQueryInput, RecentTracksPageSchema),
19741
+ /**
19742
+ * Batched co-moving group listing — the Groups feed. Same merge/cursor
19743
+ * contract as {@link listRecentTracks}. A group is a sealed partition of
19744
+ * one session; `getGroup` is the detail with members.
19745
+ */
19746
+ listGroups: method(ListGroupsQueryInput, ListGroupsPageSchema),
19747
+ getGroup: method(object({
19748
+ deviceId: number(),
19749
+ groupId: string().min(1)
19750
+ }), AnalyticsGroupDetailSchema.nullable()),
19451
19751
  clearTracks: method(object({ deviceId: number() }), _void(), {
19452
19752
  kind: "mutation",
19453
19753
  auth: "admin"
@@ -19700,6 +20000,29 @@ var pipelineAnalyticsCapability = {
19700
20000
  kind: "mutation",
19701
20001
  auth: "admin"
19702
20002
  }),
20003
+ /**
20004
+ * Moves event media between locations — the OPERATOR's mover, without the
20005
+ * coordinator's lease. Twin of `recording.relocateFootage`: the same
20006
+ * engine the lease-gated coordinated migration uses
20007
+ * (`startStorageMigrationMove`), armable in the background and WITHOUT
20008
+ * pausing anything. Exists because `eventMedia` was the only storage
20009
+ * class whose only move path went through the recorder's global pause.
20010
+ */
20011
+ relocateMedia: method(RelocateMediaInputSchema, object({ jobId: string() }), {
20012
+ kind: "mutation",
20013
+ auth: "admin"
20014
+ }),
20015
+ /** Every relocate job this addon knows about, newest first (in RAM: the
20016
+ * move is resumable, so a lost list costs nothing but the display). */
20017
+ listRelocateMediaJobs: method(object({}), array(RelocateJobSchema).readonly(), {
20018
+ kind: "query",
20019
+ auth: "admin"
20020
+ }),
20021
+ /** Cancel a running or queued relocate job. */
20022
+ cancelRelocateMedia: method(object({ jobId: string() }), object({ cancelled: boolean() }), {
20023
+ kind: "mutation",
20024
+ auth: "admin"
20025
+ }),
19703
20026
  listOpsLog: method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19704
20027
  kind: "query",
19705
20028
  auth: "admin"
@@ -20059,6 +20382,11 @@ object({
20059
20382
  "jpeg"
20060
20383
  ])
20061
20384
  });
20385
+ /**
20386
+ * Process-local frame identity. It is serializable so it can ride an in-process
20387
+ * capability call, but `registryId` deliberately prevents resolution in any
20388
+ * other process or execution group.
20389
+ */
20062
20390
  var FrameRefSchema = object({
20063
20391
  registryId: string().min(1),
20064
20392
  id: string().min(1),
@@ -20133,7 +20461,8 @@ var PipelineModelOptionSchema = object({
20133
20461
  sizeMB: number()
20134
20462
  })),
20135
20463
  group: ModelVariantGroupSchema.optional(),
20136
- legacy: boolean().optional()
20464
+ legacy: boolean().optional(),
20465
+ provider: ModelProviderIdSchema.optional()
20137
20466
  });
20138
20467
  var ConfigFieldBridge = custom();
20139
20468
  var PipelineAddonSchemaSchema = object({
@@ -23011,6 +23340,20 @@ var ssoBridgeCapability = {
23011
23340
  mode: "singleton",
23012
23341
  internal: true,
23013
23342
  methods: {
23343
+ /**
23344
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
23345
+ * `data-store-provider`) — `signBridgeToken` was reachable on the
23346
+ * AppRouter by ANY authenticated session at the default
23347
+ * `auth: 'protected'`. The claims (including `isAdmin`) are CALLER-
23348
+ * SUPPLIED: an unprivileged session could have minted its own
23349
+ * `isAdmin: true` bridge token and redeemed it at
23350
+ * `/api/auth/sso/finish` to become admin — a full privilege escalation
23351
+ * through the exact surface this cap exists to close (see the module
23352
+ * docblock). Every real caller (`oauth-grants.ts`, `main.ts`
23353
+ * registration, the OIDC/magic-link addons) reaches this through
23354
+ * `ctx.api` / the in-process registry — never through tRPC — so
23355
+ * `auth: 'admin'` costs those callers nothing.
23356
+ */
23014
23357
  signBridgeToken: method(object({
23015
23358
  claims: SsoBridgeClaimsSchema,
23016
23359
  /**
@@ -23023,8 +23366,8 @@ var ssoBridgeCapability = {
23023
23366
  * linking rather than produce an eternal token.
23024
23367
  */
23025
23368
  ttlSec: union([number().int().positive(), literal("never")]).optional()
23026
- }), object({ token: string() })),
23027
- verifyBridgeToken: method(object({ token: string() }), SsoBridgeClaimsSchema.nullable())
23369
+ }), object({ token: string() }), { auth: "admin" }),
23370
+ verifyBridgeToken: method(object({ token: string() }), SsoBridgeClaimsSchema.nullable(), { auth: "admin" })
23028
23371
  }
23029
23372
  };
23030
23373
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
@@ -23173,8 +23516,15 @@ var storageEvictableCapability = {
23173
23516
  mode: "collection",
23174
23517
  internal: true,
23175
23518
  methods: {
23519
+ /**
23520
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
23521
+ * `data-store-provider`) — this was reachable on the AppRouter by ANY
23522
+ * authenticated session at the default `auth: 'protected'`. The only
23523
+ * consumer is the orchestrator's `StoragePressureManager`, which calls
23524
+ * `ctx.api` — never tRPC — so `auth: 'admin'` costs it nothing.
23525
+ */
23176
23526
  /** Bytes this provider holds on the given location — drives proportional fan-out. */
23177
- getEvictableUsage: method(object({ locationId: string() }), EvictableUsageSchema),
23527
+ getEvictableUsage: method(object({ locationId: string() }), EvictableUsageSchema, { auth: "admin" }),
23178
23528
  /**
23179
23529
  * Free approximately `targetBytes` of this provider's OWN least-valuable
23180
23530
  * data on the location (oldest footage, expired clips, …). Returns what it
@@ -23183,7 +23533,10 @@ var storageEvictableCapability = {
23183
23533
  evict: method(object({
23184
23534
  locationId: string(),
23185
23535
  targetBytes: number().int().positive()
23186
- }), EvictResultSchema, { kind: "mutation" })
23536
+ }), EvictResultSchema, {
23537
+ kind: "mutation",
23538
+ auth: "admin"
23539
+ })
23187
23540
  }
23188
23541
  };
23189
23542
  /**
@@ -23286,43 +23639,82 @@ var storageProviderCapability = {
23286
23639
  mode: "collection",
23287
23640
  internal: true,
23288
23641
  methods: {
23642
+ /**
23643
+ * Every method below except `testLocation` had NO `auth` override —
23644
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
23645
+ * `data-store-provider`), so this was reachable on the AppRouter by ANY
23646
+ * authenticated session at the default `auth: 'protected'`: arbitrary
23647
+ * read/write/delete/list against any storage location the operator has
23648
+ * configured, keyed only by a caller-supplied `location` + `relativePath`
23649
+ * — the same "public consumers never see this cap" claim that was false
23650
+ * for `data-store-provider`, here gating raw file I/O instead of raw
23651
+ * settings rows. The only legitimate caller,
23652
+ * `addon-registry.service.ts`'s `activeStorageProvider`, calls the
23653
+ * provider object directly (in-process) — never tRPC — so `auth: 'admin'`
23654
+ * on every method costs it nothing. Public consumers already go through
23655
+ * the separate `storage` singleton cap.
23656
+ */
23289
23657
  /** Self-description for the "Add location" wizard. */
23290
- getProviderInfo: method(_void(), ProviderInfoSchema),
23658
+ getProviderInfo: method(_void(), ProviderInfoSchema, { auth: "admin" }),
23291
23659
  /** Validate config + probe connectivity. Called on add and on edit. */
23292
23660
  testLocation: method(object({ config: record(string(), unknown()) }), TestLocationResultSchema, { auth: "admin" }),
23293
23661
  resolve: method(object({
23294
23662
  location: StorageLocationSchema,
23295
23663
  relativePath: string()
23296
- }), string()),
23664
+ }), string(), { auth: "admin" }),
23297
23665
  write: method(object({
23298
23666
  location: StorageLocationSchema,
23299
23667
  relativePath: string(),
23300
23668
  data: _instanceof(Uint8Array)
23301
- }), _void(), { kind: "mutation" }),
23669
+ }), _void(), {
23670
+ kind: "mutation",
23671
+ auth: "admin"
23672
+ }),
23302
23673
  read: method(object({
23303
23674
  location: StorageLocationSchema,
23304
23675
  relativePath: string()
23305
- }), _instanceof(Uint8Array)),
23676
+ }), _instanceof(Uint8Array), { auth: "admin" }),
23306
23677
  exists: method(object({
23307
23678
  location: StorageLocationSchema,
23308
23679
  relativePath: string()
23309
- }), boolean()),
23680
+ }), boolean(), { auth: "admin" }),
23310
23681
  list: method(object({
23311
23682
  location: StorageLocationSchema,
23312
23683
  prefix: string().optional()
23313
- }), array(string()).readonly()),
23684
+ }), array(string()).readonly(), { auth: "admin" }),
23314
23685
  delete: method(object({
23315
23686
  location: StorageLocationSchema,
23316
23687
  relativePath: string()
23317
- }), _void(), { kind: "mutation" }),
23318
- getAvailableSpace: method(object({ location: StorageLocationSchema }), number().nullable()),
23319
- beginUpload: method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }),
23320
- writeChunk: method(WriteChunkInputSchema, _void(), { kind: "mutation" }),
23321
- finalizeUpload: method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }),
23322
- abortUpload: method(AbortUploadInputSchema, _void(), { kind: "mutation" }),
23323
- beginDownload: method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }),
23324
- readChunk: method(ReadChunkInputSchema, _instanceof(Uint8Array)),
23325
- endDownload: method(EndDownloadInputSchema, _void(), { kind: "mutation" })
23688
+ }), _void(), {
23689
+ kind: "mutation",
23690
+ auth: "admin"
23691
+ }),
23692
+ getAvailableSpace: method(object({ location: StorageLocationSchema }), number().nullable(), { auth: "admin" }),
23693
+ beginUpload: method(BeginUploadInputSchema, BeginUploadResultSchema, {
23694
+ kind: "mutation",
23695
+ auth: "admin"
23696
+ }),
23697
+ writeChunk: method(WriteChunkInputSchema, _void(), {
23698
+ kind: "mutation",
23699
+ auth: "admin"
23700
+ }),
23701
+ finalizeUpload: method(FinalizeUploadInputSchema, _void(), {
23702
+ kind: "mutation",
23703
+ auth: "admin"
23704
+ }),
23705
+ abortUpload: method(AbortUploadInputSchema, _void(), {
23706
+ kind: "mutation",
23707
+ auth: "admin"
23708
+ }),
23709
+ beginDownload: method(BeginDownloadInputSchema, BeginDownloadResultSchema, {
23710
+ kind: "mutation",
23711
+ auth: "admin"
23712
+ }),
23713
+ readChunk: method(ReadChunkInputSchema, _instanceof(Uint8Array), { auth: "admin" }),
23714
+ endDownload: method(EndDownloadInputSchema, _void(), {
23715
+ kind: "mutation",
23716
+ auth: "admin"
23717
+ })
23326
23718
  }
23327
23719
  };
23328
23720
  /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
@@ -23655,9 +24047,28 @@ var userPasskeysCapability = {
23655
24047
  auth: "admin",
23656
24048
  access: "create"
23657
24049
  }),
24050
+ /**
24051
+ * The four methods below had NO `auth` override — `internal: true` did
24052
+ * not gate the mount (see the 2026-08-26 note on `data-store-provider`),
24053
+ * so they were reachable on the AppRouter at the default
24054
+ * `auth: 'protected'` (any authenticated session), inconsistent with
24055
+ * every other method on this cap (all already `auth: 'admin'`).
24056
+ *
24057
+ * ⚠ Checked before gating: these are also the PRE-AUTH login-ceremony
24058
+ * methods, so admin-gating them here must not break login for an
24059
+ * unauthenticated principal. It doesn't — the real login path is the
24060
+ * PUBLIC `auth.beginAuthentication` / `auth.finishAuthentication` /
24061
+ * `auth.beginDiscoverableAuthentication` / `auth.finishDiscoverableAuthentication`
24062
+ * procedures in `server/backend/src/api/core/auth.router.ts`, which call
24063
+ * `provider.beginAuthentication(...)` etc. directly against the
24064
+ * capability-registry-resolved provider — never through this cap's own
24065
+ * tRPC route. `auth: 'admin'` here only closes a redundant, unused
24066
+ * second entry point onto the same ceremony.
24067
+ */
23658
24068
  beginAuthentication: method(object({ userId: string().optional() }), object({ optionsJSON: record(string(), unknown()) }), {
23659
24069
  kind: "mutation",
23660
- access: "view"
24070
+ access: "view",
24071
+ auth: "admin"
23661
24072
  }),
23662
24073
  finishAuthentication: method(object({
23663
24074
  /** Required — the user the assertion belongs to (verified). */
@@ -23666,11 +24077,13 @@ var userPasskeysCapability = {
23666
24077
  response: record(string(), unknown())
23667
24078
  }), object({ verified: boolean() }), {
23668
24079
  kind: "mutation",
23669
- access: "view"
24080
+ access: "view",
24081
+ auth: "admin"
23670
24082
  }),
23671
24083
  beginDiscoverableAuthentication: method(object({}), object({ optionsJSON: record(string(), unknown()) }), {
23672
24084
  kind: "mutation",
23673
- access: "view"
24085
+ access: "view",
24086
+ auth: "admin"
23674
24087
  }),
23675
24088
  finishDiscoverableAuthentication: method(object({
23676
24089
  /** AuthenticationResponseJSON from the browser. */
@@ -23679,7 +24092,8 @@ response: record(string(), unknown()) }), object({
23679
24092
  userId: string().nullable()
23680
24093
  }), {
23681
24094
  kind: "mutation",
23682
- access: "view"
24095
+ access: "view",
24096
+ auth: "admin"
23683
24097
  }),
23684
24098
  listPasskeys: method(object({ userId: string() }), array(PasskeySummarySchema), { auth: "admin" }),
23685
24099
  removePasskey: method(object({
@@ -23857,6 +24271,14 @@ var VectorStatsResultSchema = object({
23857
24271
  /** False when the backend ranks approximately. */
23858
24272
  exact: boolean()
23859
24273
  });
24274
+ /**
24275
+ * `internal: true` did not gate the mount (see the 2026-08-26 note on
24276
+ * `data-store-provider`) — every method below was reachable on the AppRouter
24277
+ * by ANY authenticated session at the default `auth: 'protected'`. The only
24278
+ * consumers (`addon-post-analysis`'s embedding stores) reach this cap via
24279
+ * `ctx.api` — UDS/Moleculer, never tRPC — so `auth: 'admin'` on every method
24280
+ * costs them nothing. No client (viewer or admin-ui) calls `vectorStore.*`.
24281
+ */
23860
24282
  var vectorStoreCapability = {
23861
24283
  name: "vector-store",
23862
24284
  scope: "system",
@@ -23864,14 +24286,26 @@ var vectorStoreCapability = {
23864
24286
  internal: true,
23865
24287
  methods: {
23866
24288
  /** Idempotent. Re-declaring with a different `dim` is an error, not a wipe. */
23867
- declareIndex: method(VectorDeclareIndexInputSchema, _void(), { kind: "mutation" }),
23868
- upsert: method(VectorUpsertInputSchema, VectorUpsertResultSchema, { kind: "mutation" }),
23869
- query: method(VectorQueryInputSchema, VectorQueryResultSchema),
24289
+ declareIndex: method(VectorDeclareIndexInputSchema, _void(), {
24290
+ kind: "mutation",
24291
+ auth: "admin"
24292
+ }),
24293
+ upsert: method(VectorUpsertInputSchema, VectorUpsertResultSchema, {
24294
+ kind: "mutation",
24295
+ auth: "admin"
24296
+ }),
24297
+ query: method(VectorQueryInputSchema, VectorQueryResultSchema, { auth: "admin" }),
23870
24298
  /** Metadata by id, no vectors — see {@link VectorGetResultSchema}. */
23871
- getByIds: method(VectorGetInputSchema, VectorGetResultSchema),
23872
- deleteByIds: method(VectorDeleteInputSchema, VectorDeleteResultSchema, { kind: "mutation" }),
23873
- deleteByFilter: method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, { kind: "mutation" }),
23874
- stats: method(VectorStatsInputSchema, VectorStatsResultSchema)
24299
+ getByIds: method(VectorGetInputSchema, VectorGetResultSchema, { auth: "admin" }),
24300
+ deleteByIds: method(VectorDeleteInputSchema, VectorDeleteResultSchema, {
24301
+ kind: "mutation",
24302
+ auth: "admin"
24303
+ }),
24304
+ deleteByFilter: method(VectorDeleteByFilterInputSchema, VectorDeleteResultSchema, {
24305
+ kind: "mutation",
24306
+ auth: "admin"
24307
+ }),
24308
+ stats: method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" })
23875
24309
  }
23876
24310
  };
23877
24311
  var ClipSchema = object({
@@ -26720,7 +27154,27 @@ var faceGalleryCapability = {
26720
27154
  scope: "system",
26721
27155
  mode: "singleton",
26722
27156
  methods: {
26723
- listIdentities: method(_void(), array(IdentitySchema).readonly()),
27157
+ listIdentities: method(object({
27158
+ /**
27159
+ * Inline {@link IdentitySchema.coverBase64} on every row.
27160
+ *
27161
+ * Default `false`, the same inversion `listRecentFaces` and
27162
+ * `listPlates` took on 2026-08-25 (see `include-crops-default.ts` for
27163
+ * why the burden belongs on the caller that WANTS the bytes). Measured
27164
+ * on the live hub the same day: four identities cost 40,979 B with the
27165
+ * covers inline, ~10 KB of base64 per row, on a query this UI mounts
27166
+ * four times and the viewer holds at `staleTime: 30_000`.
27167
+ *
27168
+ * Nothing loses its avatar: `coverMediaKey` is already on every row and
27169
+ * the `event-media` plane serves that key `immutable` with an ETag.
27170
+ *
27171
+ * **This is an INPUT field, so it does not reach the addon until the
27172
+ * next train** — the hub router validates cap inputs against its own
27173
+ * compiled Zod and strips a key it does not know. Until then the
27174
+ * provider sees `undefined`, which resolves to `false`: the cheap shape
27175
+ * is what ships, and the opt-in becomes reachable when the train lands.
27176
+ */
27177
+ includeCrops: boolean().optional() }).optional(), array(IdentitySchema).readonly()),
26724
27178
  createIdentity: method(object({ name: string().min(1) }), IdentitySchema, {
26725
27179
  kind: "mutation",
26726
27180
  auth: "admin"
@@ -29971,7 +30425,14 @@ var PlateInfoSchema = object({
29971
30425
  plateBbox: BoundingBoxSchema.optional(),
29972
30426
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
29973
30427
  keyFrameMediaKey: string().optional(),
29974
- base64: string().optional()
30428
+ base64: string().optional(),
30429
+ /**
30430
+ * Same crop as a data-plane URL, always present when the plate has a stored
30431
+ * crop. `getPlateByTrack` returns this and never inlines JPEG; `listPlates`
30432
+ * and `searchPlates` inline the crop only when their `includeCrops` input is
30433
+ * left at its `true` default.
30434
+ */
30435
+ cropUrl: string().optional()
29975
30436
  });
29976
30437
  var MediaFileLiteSchema = object({
29977
30438
  key: string(),
@@ -30002,7 +30463,25 @@ var plateGalleryCapability = {
30002
30463
  methods: {
30003
30464
  listPlates: method(object({
30004
30465
  deviceId: number().int().optional(),
30005
- limit: number().int().positive().optional()
30466
+ limit: number().int().positive().optional(),
30467
+ /**
30468
+ * Inline the base64 crop on every row. Default `true` — the existing
30469
+ * behaviour, kept so no caller breaks.
30470
+ *
30471
+ * Set `false` once the caller renders {@link PlateInfo.cropUrl}.
30472
+ * Measured on the live hub at the 500 rows the Plates view asks for:
30473
+ * 879,403 B and 27.7 s with the crops inline, against a few KiB of
30474
+ * metadata without them — and the browser then caches the images.
30475
+ *
30476
+ * This is the plate twin of `faceGallery.listRecentFaces`'s option;
30477
+ * plates were the one gallery list left without it.
30478
+ *
30479
+ * **This is an INPUT field, so it does not reach the addon until the
30480
+ * next train.** The hub router validates cap inputs against its own
30481
+ * compiled Zod and strips a key it does not know. Until the train
30482
+ * ships, sending `false` is harmless and keeps the crops inline.
30483
+ */
30484
+ includeCrops: boolean().optional()
30006
30485
  }).optional(), array(PlateInfoSchema).readonly()),
30007
30486
  getPlateByTrack: method(object({
30008
30487
  deviceId: number().int(),
@@ -30013,7 +30492,9 @@ var plateGalleryCapability = {
30013
30492
  searchPlates: method(object({
30014
30493
  text: string().min(1),
30015
30494
  maxDistance: number().int().min(0).optional(),
30016
- limit: number().int().positive().optional()
30495
+ limit: number().int().positive().optional(),
30496
+ /** See `listPlates.includeCrops`. Default `true`. */
30497
+ includeCrops: boolean().optional()
30017
30498
  }), array(PlateInfoSchema).readonly()),
30018
30499
  /** Suggest groups of near-identical reads (same physical plate) for review/merge. */
30019
30500
  suggestPlateClusters: method(object({
@@ -30033,7 +30514,13 @@ var plateGalleryCapability = {
30033
30514
  kind: "mutation",
30034
30515
  auth: "admin"
30035
30516
  }),
30036
- listVehicles: method(_void(), array(VehicleSchema).readonly()),
30517
+ listVehicles: method(object({
30518
+ /** Inline {@link VehicleSchema.coverBase64} on every row. Default
30519
+ * `false` — the vehicle twin of `faceGallery.listIdentities`'s
30520
+ * option; `coverMediaKey` + the `event-media` plane carry the picture.
30521
+ * INPUT field: stripped by the hub router until the train ships, which
30522
+ * resolves to `false` and is exactly the intended default. */
30523
+ includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()),
30037
30524
  createVehicle: method(object({ name: string().min(1) }), VehicleSchema, {
30038
30525
  kind: "mutation",
30039
30526
  auth: "admin"
@@ -35001,6 +35488,12 @@ Object.freeze({
35001
35488
  addonId: null,
35002
35489
  access: "view"
35003
35490
  },
35491
+ "deviceManager.getBindingsBatch": {
35492
+ capName: "device-manager",
35493
+ capScope: "system",
35494
+ addonId: null,
35495
+ access: "view"
35496
+ },
35004
35497
  "deviceManager.getChildren": {
35005
35498
  capName: "device-manager",
35006
35499
  capScope: "system",
@@ -35061,6 +35554,12 @@ Object.freeze({
35061
35554
  addonId: null,
35062
35555
  access: "view"
35063
35556
  },
35557
+ "deviceManager.getLinkedDevicesBatch": {
35558
+ capName: "device-manager",
35559
+ capScope: "system",
35560
+ addonId: null,
35561
+ access: "view"
35562
+ },
35064
35563
  "deviceManager.getRoleDisplayDefaults": {
35065
35564
  capName: "device-manager",
35066
35565
  capScope: "system",
@@ -36765,6 +37264,12 @@ Object.freeze({
36765
37264
  addonId: null,
36766
37265
  access: "create"
36767
37266
  },
37267
+ "pipelineAnalytics.cancelRelocateMedia": {
37268
+ capName: "pipeline-analytics",
37269
+ capScope: "device",
37270
+ addonId: null,
37271
+ access: "create"
37272
+ },
36768
37273
  "pipelineAnalytics.cancelStorageMigrationMove": {
36769
37274
  capName: "pipeline-analytics",
36770
37275
  capScope: "device",
@@ -36831,6 +37336,12 @@ Object.freeze({
36831
37336
  addonId: null,
36832
37337
  access: "view"
36833
37338
  },
37339
+ "pipelineAnalytics.getGroup": {
37340
+ capName: "pipeline-analytics",
37341
+ capScope: "device",
37342
+ addonId: null,
37343
+ access: "view"
37344
+ },
36834
37345
  "pipelineAnalytics.getKeyEvents": {
36835
37346
  capName: "pipeline-analytics",
36836
37347
  capScope: "device",
@@ -36915,6 +37426,12 @@ Object.freeze({
36915
37426
  addonId: null,
36916
37427
  access: "view"
36917
37428
  },
37429
+ "pipelineAnalytics.listGroups": {
37430
+ capName: "pipeline-analytics",
37431
+ capScope: "device",
37432
+ addonId: null,
37433
+ access: "view"
37434
+ },
36918
37435
  "pipelineAnalytics.listOpsLog": {
36919
37436
  capName: "pipeline-analytics",
36920
37437
  capScope: "device",
@@ -36927,6 +37444,12 @@ Object.freeze({
36927
37444
  addonId: null,
36928
37445
  access: "view"
36929
37446
  },
37447
+ "pipelineAnalytics.listRelocateMediaJobs": {
37448
+ capName: "pipeline-analytics",
37449
+ capScope: "device",
37450
+ addonId: null,
37451
+ access: "view"
37452
+ },
36930
37453
  "pipelineAnalytics.listRetrainAnnotations": {
36931
37454
  capName: "pipeline-analytics",
36932
37455
  capScope: "device",
@@ -37005,6 +37528,12 @@ Object.freeze({
37005
37528
  addonId: null,
37006
37529
  access: "create"
37007
37530
  },
37531
+ "pipelineAnalytics.relocateMedia": {
37532
+ capName: "pipeline-analytics",
37533
+ capScope: "device",
37534
+ addonId: null,
37535
+ access: "create"
37536
+ },
37008
37537
  "pipelineAnalytics.restageRetrainTrack": {
37009
37538
  capName: "pipeline-analytics",
37010
37539
  capScope: "device",
@@ -39696,6 +40225,11 @@ Object.freeze({
39696
40225
  form: "single",
39697
40226
  optional: false
39698
40227
  }],
40228
+ "deviceManager.getBindingsBatch": [{
40229
+ name: "deviceIds",
40230
+ form: "array",
40231
+ optional: false
40232
+ }],
39699
40233
  "deviceManager.getChildren": [{
39700
40234
  name: "parentDeviceId",
39701
40235
  form: "single",
@@ -39741,6 +40275,11 @@ Object.freeze({
39741
40275
  form: "single",
39742
40276
  optional: false
39743
40277
  }],
40278
+ "deviceManager.getLinkedDevicesBatch": [{
40279
+ name: "deviceIds",
40280
+ form: "array",
40281
+ optional: false
40282
+ }],
39744
40283
  "deviceManager.getSettingsSchema": [{
39745
40284
  name: "deviceId",
39746
40285
  form: "single",
@@ -39761,6 +40300,11 @@ Object.freeze({
39761
40300
  form: "single",
39762
40301
  optional: false
39763
40302
  }],
40303
+ "deviceManager.listAll": [{
40304
+ name: "deviceIds",
40305
+ form: "array",
40306
+ optional: true
40307
+ }],
39764
40308
  "deviceManager.loadConfig": [{
39765
40309
  name: "deviceId",
39766
40310
  form: "single",
@@ -40334,6 +40878,11 @@ Object.freeze({
40334
40878
  form: "single",
40335
40879
  optional: false
40336
40880
  }],
40881
+ "pipelineAnalytics.getGroup": [{
40882
+ name: "deviceId",
40883
+ form: "single",
40884
+ optional: false
40885
+ }],
40337
40886
  "pipelineAnalytics.getKeyEvents": [{
40338
40887
  name: "deviceId",
40339
40888
  form: "single",
@@ -40389,6 +40938,11 @@ Object.freeze({
40389
40938
  form: "array",
40390
40939
  optional: false
40391
40940
  }],
40941
+ "pipelineAnalytics.listGroups": [{
40942
+ name: "deviceIds",
40943
+ form: "array",
40944
+ optional: false
40945
+ }],
40392
40946
  "pipelineAnalytics.listOpsLog": [{
40393
40947
  name: "deviceId",
40394
40948
  form: "single",