@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.
- package/dist/{MotionZonesSettings-Dhh1AqWY.mjs → MotionZonesSettings-DhYwaRz3.mjs} +2 -2
- package/dist/{PrivacyMaskSettings-CPABuSH1.mjs → PrivacyMaskSettings-BquvldBM.mjs} +4 -4
- package/dist/{SceneMonitorEditor-CyVjkJI2.mjs → SceneMonitorEditor-wK0ulSnf.mjs} +3 -3
- package/dist/_stub.js +11 -11
- 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
- package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-6_-m0FOQ.mjs +26 -0
- package/dist/{hostInit-5GIsHCyh.mjs → hostInit-8p7aKRSc.mjs} +3 -3
- package/dist/index.js +685 -131
- package/dist/index.mjs +685 -131
- package/dist/{player-overlays-A11g0xRn.mjs → player-overlays-CS4sZzYZ.mjs} +1 -1
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-B_W10135.mjs → responsive-VdLah82L.mjs} +1 -1
- package/dist/{square-BqxtsJJ4.mjs → square-a4rMgWr9.mjs} +1 -1
- package/dist/{trash-2-xxP-WwG0.mjs → trash-2-BCaHwGDI.mjs} +1 -1
- package/dist/{use-device-snapshot-B6sowK2s.mjs → use-device-snapshot-CTWHDX3F.mjs} +1 -1
- 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
- package/package.json +1 -1
- 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
|
-
|
|
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
|
|
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
|
-
|
|
6657
|
-
|
|
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
|
-
|
|
6994
|
-
|
|
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
|
|
7813
|
+
var RelocateMediaInputSchema = object({
|
|
7801
7814
|
toLocationId: string(),
|
|
7802
7815
|
throttleMbps: number().min(1).max(1e3).optional()
|
|
7803
|
-
})
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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
|
-
/**
|
|
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(), {
|
|
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
|
-
|
|
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(), {
|
|
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(), {
|
|
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(), {
|
|
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() }), {
|
|
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() }), {
|
|
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(), {
|
|
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() }),
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
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(
|
|
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, {
|
|
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
|
|
16241
|
-
*
|
|
16242
|
-
* `hitPercent` and `samplingSeconds` are ignored
|
|
16243
|
-
*
|
|
16244
|
-
*
|
|
16245
|
-
*
|
|
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 ⇒
|
|
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: {
|
|
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, {
|
|
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(), {
|
|
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(), {
|
|
23318
|
-
|
|
23319
|
-
|
|
23320
|
-
|
|
23321
|
-
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
23325
|
-
|
|
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(), {
|
|
23868
|
-
|
|
23869
|
-
|
|
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, {
|
|
23873
|
-
|
|
23874
|
-
|
|
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(
|
|
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(
|
|
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",
|