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