@camstack/addon-notifiers 1.2.11 → 1.2.13
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/addon.js +1226 -486
- package/dist/addon.mjs +1226 -486
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -29,7 +29,7 @@ let node_fs = require("node:fs");
|
|
|
29
29
|
let node_path = require("node:path");
|
|
30
30
|
node_path = __toESM(node_path);
|
|
31
31
|
let node_url = require("node:url");
|
|
32
|
-
//#region ../types/dist/event-category-
|
|
32
|
+
//#region ../types/dist/event-category-41fKf-q9.mjs
|
|
33
33
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
34
34
|
EventCategory["SystemBoot"] = "system.boot";
|
|
35
35
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -89,6 +89,26 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
89
89
|
*/
|
|
90
90
|
EventCategory["AddonRetryScheduled"] = "addon.retry-scheduled";
|
|
91
91
|
/**
|
|
92
|
+
* A liveness invariant of this node is false — it has no devices, every
|
|
93
|
+
* camera is offline, or a camera that was recording has stopped producing
|
|
94
|
+
* segments. Emitted by the `liveness-monitor` builtin, once per fault (the
|
|
95
|
+
* `findingId` is stable across ticks), only after its boot grace period.
|
|
96
|
+
* AlertCenter raises a persistent operator-visible alert.
|
|
97
|
+
*
|
|
98
|
+
* It exists because on 2026-08-03 the hub ran four hours with zero devices
|
|
99
|
+
* and zero recordings while every surface stayed quiet.
|
|
100
|
+
*
|
|
101
|
+
* Payload: `{ findingId, severity, title, message, deviceId? }`.
|
|
102
|
+
*/
|
|
103
|
+
EventCategory["SystemLivenessFailed"] = "system.liveness-failed";
|
|
104
|
+
/**
|
|
105
|
+
* A previously reported liveness fault is true again. AlertCenter dismisses
|
|
106
|
+
* the matching `SystemLivenessFailed` alert.
|
|
107
|
+
*
|
|
108
|
+
* Payload: `{ findingId }`.
|
|
109
|
+
*/
|
|
110
|
+
EventCategory["SystemLivenessRecovered"] = "system.liveness-recovered";
|
|
111
|
+
/**
|
|
92
112
|
* Monitor is attempting to reload a failed addon NOW. UI uses this
|
|
93
113
|
* to show a spinner during the retry attempt. Same transient nature
|
|
94
114
|
* as AddonRetryScheduled.
|
|
@@ -7456,14 +7476,7 @@ var RecordingConfigSchema = object({
|
|
|
7456
7476
|
* windows only — existing sheets are immutable, and each window's index
|
|
7457
7477
|
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7458
7478
|
*/
|
|
7459
|
-
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7460
|
-
/**
|
|
7461
|
-
* OPT-IN thumbnail-strip generation for this camera: every keyframe of the
|
|
7462
|
-
* low recording saved as a JPEG (the fast-drag scrub depth), a derived
|
|
7463
|
-
* cache that eviction reclaims with the footage. Absent/false = no strips
|
|
7464
|
-
* are written and scrub reads exact keyframes at every velocity.
|
|
7465
|
-
*/
|
|
7466
|
-
stripsEnabled: boolean().optional()
|
|
7479
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7467
7480
|
}).strict();
|
|
7468
7481
|
/**
|
|
7469
7482
|
* Ops-log — the durable, append-only operations audit shared by the
|
|
@@ -7525,7 +7538,7 @@ var OpsLogQueryInputSchema = object({
|
|
|
7525
7538
|
/**
|
|
7526
7539
|
* Entity-relocation job state (storage entity-routing spec, Phase 4).
|
|
7527
7540
|
*
|
|
7528
|
-
* One shape shared by the recorder's `relocateFootage` (segments
|
|
7541
|
+
* One shape shared by the recorder's `relocateFootage` (segments) and
|
|
7529
7542
|
* pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
|
|
7530
7543
|
* page renders both movers with one component. Jobs are in-RAM (a restart
|
|
7531
7544
|
* forgets them — re-running is safe by construction: copy-if-absent, delete
|
|
@@ -7547,7 +7560,7 @@ var RelocateJobSchema = object({
|
|
|
7547
7560
|
toLocationId: string(),
|
|
7548
7561
|
/** Scoped device, or null = every device. */
|
|
7549
7562
|
deviceId: number().nullable(),
|
|
7550
|
-
/** What the job moves (owner-addon specific: segments
|
|
7563
|
+
/** What the job moves (owner-addon specific: segments or media). */
|
|
7551
7564
|
entities: array(string()),
|
|
7552
7565
|
filesMoved: number().int(),
|
|
7553
7566
|
bytesMoved: number().int(),
|
|
@@ -7561,7 +7574,7 @@ var RelocateFootageInputSchema = object({
|
|
|
7561
7574
|
deviceId: number().optional(),
|
|
7562
7575
|
fromLocationId: string(),
|
|
7563
7576
|
toLocationId: string(),
|
|
7564
|
-
entities: array(_enum(["segments"
|
|
7577
|
+
entities: array(_enum(["segments"])).optional(),
|
|
7565
7578
|
/** Copy throttle in MB/s (default 40) — the drain is a background chore,
|
|
7566
7579
|
* never allowed to starve live writers. */
|
|
7567
7580
|
throttleMbps: number().min(1).max(1e3).optional()
|
|
@@ -9223,8 +9236,11 @@ function prepareNotification(caps, n) {
|
|
|
9223
9236
|
});
|
|
9224
9237
|
}
|
|
9225
9238
|
const inActions = n.actions ?? [];
|
|
9226
|
-
const
|
|
9227
|
-
if (inActions.length >
|
|
9239
|
+
const kept = inActions.slice(0, Math.max(0, caps.actions));
|
|
9240
|
+
if (inActions.length > kept.length) dropped.push("actions");
|
|
9241
|
+
const iconsSupported = caps.actionIcons === true;
|
|
9242
|
+
if (!iconsSupported && kept.some((a) => a.icon !== void 0)) dropped.push("actionIcons");
|
|
9243
|
+
const actions = iconsSupported ? kept : kept.map(({ icon: _icon, ...rest }) => rest);
|
|
9228
9244
|
let clickUrl = null;
|
|
9229
9245
|
if (n.clickUrl !== void 0) if (caps.clickUrl) clickUrl = n.clickUrl;
|
|
9230
9246
|
else dropped.push("clickUrl");
|
|
@@ -9323,160 +9339,552 @@ var MaskGridDimsSchema = object({
|
|
|
9323
9339
|
height: number()
|
|
9324
9340
|
});
|
|
9325
9341
|
/**
|
|
9326
|
-
* notification-
|
|
9327
|
-
*
|
|
9328
|
-
* Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
|
|
9329
|
-
* (operator decisions D-1/D-2/D-3 are binding):
|
|
9342
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
9330
9343
|
*
|
|
9331
|
-
*
|
|
9332
|
-
*
|
|
9333
|
-
*
|
|
9334
|
-
*
|
|
9335
|
-
*
|
|
9336
|
-
*
|
|
9337
|
-
* `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
|
|
9338
|
-
* `delivery: 'track-end'` evaluates the finalized track record at close.
|
|
9339
|
-
* - DISPATCH stays behind `notification-output` (rules reference targets
|
|
9340
|
-
* by id; per-backend params are a passthrough blob capped by the
|
|
9341
|
-
* target kind's own caps/degrade engine).
|
|
9344
|
+
* Apprise-derived model (see
|
|
9345
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
9346
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
9347
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
9348
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
9349
|
+
* message to what the kind supports — callers never special-case a service.
|
|
9342
9350
|
*
|
|
9343
|
-
*
|
|
9344
|
-
*
|
|
9345
|
-
*
|
|
9346
|
-
*
|
|
9347
|
-
*
|
|
9348
|
-
*
|
|
9349
|
-
*
|
|
9351
|
+
* DESIGN DECISIONS (locked):
|
|
9352
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
9353
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
9354
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
9355
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
9356
|
+
* alternative would fork the UI per addon and cannot host the
|
|
9357
|
+
* discovery→adopt flow.
|
|
9358
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
9359
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
9360
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
9361
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
9362
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
9363
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
9364
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
9365
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
9366
|
+
* base64 fallback needed.
|
|
9350
9367
|
*
|
|
9351
|
-
*
|
|
9352
|
-
* `
|
|
9353
|
-
*
|
|
9368
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
9369
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
9370
|
+
* admin "Integrations" page.
|
|
9354
9371
|
*/
|
|
9355
9372
|
/**
|
|
9356
|
-
*
|
|
9357
|
-
*
|
|
9358
|
-
* - `immediate` ↔ object-event persist (lowest-latency detection burst)
|
|
9359
|
-
* - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
|
|
9360
|
-
* - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
|
|
9361
|
-
* change of a LINKED device, one row per linked camera)
|
|
9362
|
-
* - `package-event` ↔ PackageDropDetector object-event insert (a `package`
|
|
9363
|
-
* delivery / pick-up)
|
|
9364
|
-
*
|
|
9365
|
-
* `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
|
|
9366
|
-
* `package-event` are pure trigger kinds (no urgency dimension). Extending
|
|
9367
|
-
* this one field keeps the schema additive — a rule still declares exactly
|
|
9368
|
-
* one trigger.
|
|
9373
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
9374
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
9369
9375
|
*/
|
|
9370
|
-
var
|
|
9371
|
-
"
|
|
9372
|
-
"
|
|
9373
|
-
"
|
|
9374
|
-
"
|
|
9376
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
9377
|
+
"image",
|
|
9378
|
+
"video",
|
|
9379
|
+
"gif",
|
|
9380
|
+
"audio",
|
|
9381
|
+
"icon"
|
|
9375
9382
|
]);
|
|
9376
|
-
/** Weekly schedule — OR of windows; absence on the rule = always active. */
|
|
9377
|
-
var NcScheduleSchema = object({
|
|
9378
|
-
windows: array(object({
|
|
9379
|
-
/** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
|
|
9380
|
-
days: array(number().int().min(0).max(6)).min(1),
|
|
9381
|
-
startMinute: number().int().min(0).max(1439),
|
|
9382
|
-
endMinute: number().int().min(0).max(1439)
|
|
9383
|
-
})).min(1),
|
|
9384
|
-
/** IANA timezone; default = hub host timezone. */
|
|
9385
|
-
timezone: string().optional(),
|
|
9386
|
-
/** Active OUTSIDE the windows (e.g. "only outside business hours"). */
|
|
9387
|
-
invert: boolean().optional()
|
|
9388
|
-
});
|
|
9389
|
-
/** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
|
|
9390
|
-
var NcPlateMatcherSchema = object({
|
|
9391
|
-
values: array(string().min(1)).min(1),
|
|
9392
|
-
/** Max Levenshtein distance after normalization (uppercase alphanumeric). */
|
|
9393
|
-
maxDistance: number().int().min(0).max(3).default(1)
|
|
9394
|
-
});
|
|
9395
9383
|
/**
|
|
9396
|
-
*
|
|
9397
|
-
*
|
|
9398
|
-
*
|
|
9399
|
-
*
|
|
9400
|
-
* - `became-free` — count crossed ≥ `count` → below it
|
|
9401
|
-
* - `>=` / `<=` — count is at/over or at/under `count`
|
|
9402
|
-
* `sustainSeconds` requires the condition hold continuously that long
|
|
9403
|
-
* before firing (debounces flicker; 0 = fire on the first matching edge).
|
|
9404
|
-
* Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
|
|
9405
|
-
* the condition never matches. Confirmed edge-state survives addon restarts
|
|
9406
|
-
* (declared SQLite collection, reseeded on boot).
|
|
9384
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
9385
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
9386
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
9387
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
9407
9388
|
*/
|
|
9408
|
-
var
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
sustainSeconds: number().int().min(0).max(3600).default(15)
|
|
9421
|
-
});
|
|
9389
|
+
var AttachmentSchema = object({
|
|
9390
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
9391
|
+
url: string().optional(),
|
|
9392
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
9393
|
+
mime: string().optional(),
|
|
9394
|
+
name: string().optional()
|
|
9395
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
9396
|
+
var NotificationFormatSchema = _enum([
|
|
9397
|
+
"text",
|
|
9398
|
+
"markdown",
|
|
9399
|
+
"html"
|
|
9400
|
+
]);
|
|
9422
9401
|
/**
|
|
9423
|
-
*
|
|
9402
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
9424
9403
|
*
|
|
9425
|
-
*
|
|
9426
|
-
*
|
|
9427
|
-
*
|
|
9428
|
-
*
|
|
9429
|
-
*
|
|
9430
|
-
* an operator who never asked for exits must not start receiving them.
|
|
9431
|
-
* - `exit` — ONLY an exit crossing. A record that is not a crossing at all
|
|
9432
|
-
* fails closed, because "the car left the drive" is a question about a
|
|
9433
|
-
* boundary, not about a detection.
|
|
9434
|
-
* - `any` — no direction filter; entries, exits and non-crossings alike.
|
|
9404
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
9405
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
9406
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
9407
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
9408
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
9435
9409
|
*
|
|
9436
|
-
*
|
|
9437
|
-
*
|
|
9438
|
-
*
|
|
9410
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
9411
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
9412
|
+
* differently.
|
|
9439
9413
|
*/
|
|
9440
|
-
var
|
|
9441
|
-
"
|
|
9442
|
-
"
|
|
9443
|
-
"
|
|
9414
|
+
var NotificationActionIconSchema = _enum([
|
|
9415
|
+
"acknowledge",
|
|
9416
|
+
"dismiss",
|
|
9417
|
+
"silence",
|
|
9418
|
+
"view",
|
|
9419
|
+
"play",
|
|
9420
|
+
"open",
|
|
9421
|
+
"close",
|
|
9422
|
+
"lock",
|
|
9423
|
+
"unlock",
|
|
9424
|
+
"arm",
|
|
9425
|
+
"disarm",
|
|
9426
|
+
"light",
|
|
9427
|
+
"alert"
|
|
9444
9428
|
]);
|
|
9445
|
-
/**
|
|
9446
|
-
var
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9429
|
+
/** A single tap-through action button. */
|
|
9430
|
+
var NotificationActionSchema = object({
|
|
9431
|
+
id: string(),
|
|
9432
|
+
label: string(),
|
|
9433
|
+
url: string().optional(),
|
|
9434
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
9435
|
+
icon: NotificationActionIconSchema.optional(),
|
|
9436
|
+
/**
|
|
9437
|
+
* Renders in a warning style where the notifier supports it.
|
|
9438
|
+
*
|
|
9439
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
9440
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
9441
|
+
* does not buy.
|
|
9442
|
+
*/
|
|
9443
|
+
destructive: boolean().optional()
|
|
9450
9444
|
});
|
|
9451
9445
|
/**
|
|
9452
|
-
* The
|
|
9453
|
-
*
|
|
9446
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
9447
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
9448
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
9449
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
9450
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
9451
|
+
* `priority` for that one target.
|
|
9454
9452
|
*/
|
|
9455
|
-
var
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9453
|
+
var NotificationSchema = object({
|
|
9454
|
+
body: string(),
|
|
9455
|
+
title: string().optional(),
|
|
9456
|
+
format: NotificationFormatSchema.default("text"),
|
|
9457
|
+
priority: number().int().min(1).max(5).default(3),
|
|
9458
|
+
level: string().optional(),
|
|
9459
|
+
attachments: array(AttachmentSchema).optional(),
|
|
9460
|
+
clickUrl: string().optional(),
|
|
9461
|
+
actions: array(NotificationActionSchema).optional(),
|
|
9462
|
+
sound: string().optional(),
|
|
9463
|
+
ttl: number().optional(),
|
|
9464
|
+
tag: string().optional(),
|
|
9465
|
+
deviceId: number().optional(),
|
|
9466
|
+
eventId: string().optional(),
|
|
9467
|
+
metadata: record(string(), unknown()).optional()
|
|
9468
|
+
});
|
|
9469
|
+
/** One declared native severity/priority level for a kind. */
|
|
9470
|
+
var TargetKindLevelSchema = object({
|
|
9471
|
+
id: string(),
|
|
9472
|
+
label: string(),
|
|
9473
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
9474
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
9475
|
+
flags: object({
|
|
9476
|
+
critical: boolean().optional(),
|
|
9477
|
+
silent: boolean().optional(),
|
|
9478
|
+
noPush: boolean().optional()
|
|
9479
|
+
}).optional(),
|
|
9480
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
9481
|
+
requires: array(string()).optional(),
|
|
9482
|
+
description: string().optional()
|
|
9483
|
+
});
|
|
9484
|
+
/** The full capability block consulted before dispatch. */
|
|
9485
|
+
var TargetKindCapsSchema = object({
|
|
9486
|
+
attachments: object({
|
|
9487
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
9488
|
+
mode: _enum([
|
|
9489
|
+
"url",
|
|
9490
|
+
"bytes",
|
|
9491
|
+
"both"
|
|
9492
|
+
]),
|
|
9493
|
+
max: number().int().nonnegative(),
|
|
9494
|
+
maxBytes: number().int().positive().optional()
|
|
9495
|
+
}),
|
|
9496
|
+
/** Max action buttons (0 = none). */
|
|
9497
|
+
actions: number().int().nonnegative(),
|
|
9475
9498
|
/**
|
|
9476
|
-
*
|
|
9477
|
-
*
|
|
9499
|
+
* Whether this kind renders a per-action ICON.
|
|
9500
|
+
*
|
|
9501
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
9502
|
+
* run on the addon cap path — three production failures in one day taught
|
|
9503
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
9504
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
9505
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
9478
9506
|
*/
|
|
9479
|
-
|
|
9507
|
+
actionIcons: boolean().optional(),
|
|
9508
|
+
levels: array(TargetKindLevelSchema),
|
|
9509
|
+
format: array(NotificationFormatSchema),
|
|
9510
|
+
clickUrl: boolean(),
|
|
9511
|
+
sound: boolean(),
|
|
9512
|
+
ttl: boolean(),
|
|
9513
|
+
bodyMaxLen: number().int().positive()
|
|
9514
|
+
});
|
|
9515
|
+
/**
|
|
9516
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
9517
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
9518
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
9519
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
9520
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
9521
|
+
*/
|
|
9522
|
+
var ConfigSchemaPassthrough$1 = unknown();
|
|
9523
|
+
var TargetKindSchema = object({
|
|
9524
|
+
kind: string(),
|
|
9525
|
+
label: string(),
|
|
9526
|
+
icon: string(),
|
|
9527
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
9528
|
+
addonId: string(),
|
|
9529
|
+
/**
|
|
9530
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
9531
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
9532
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
9533
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
9534
|
+
*
|
|
9535
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
9536
|
+
* client, and a native client joins it onto its own hub base.
|
|
9537
|
+
*
|
|
9538
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
9539
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
9540
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
9541
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
9542
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
9543
|
+
*/
|
|
9544
|
+
iconUrl: string().optional(),
|
|
9545
|
+
/**
|
|
9546
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
9547
|
+
*
|
|
9548
|
+
* The server knows this and therefore says it, because the client cannot
|
|
9549
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
9550
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
9551
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
9552
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
9553
|
+
*
|
|
9554
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
9555
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
9556
|
+
* its raster path, which is the safe default for an unknown image.
|
|
9557
|
+
*/
|
|
9558
|
+
iconMediaType: string().optional(),
|
|
9559
|
+
configSchema: ConfigSchemaPassthrough$1,
|
|
9560
|
+
supportsDiscovery: boolean(),
|
|
9561
|
+
caps: TargetKindCapsSchema
|
|
9562
|
+
});
|
|
9563
|
+
/**
|
|
9564
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
9565
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
9566
|
+
* round-trip a stored secret to the UI.
|
|
9567
|
+
*/
|
|
9568
|
+
var TargetSchema = object({
|
|
9569
|
+
id: string(),
|
|
9570
|
+
name: string(),
|
|
9571
|
+
kind: string(),
|
|
9572
|
+
addonId: string(),
|
|
9573
|
+
enabled: boolean(),
|
|
9574
|
+
config: record(string(), unknown())
|
|
9575
|
+
});
|
|
9576
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
9577
|
+
var DiscoveredTargetSchema = object({
|
|
9578
|
+
kind: string(),
|
|
9579
|
+
suggestedName: string(),
|
|
9580
|
+
config: record(string(), unknown())
|
|
9581
|
+
});
|
|
9582
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
9583
|
+
var RenderedAsSchema = object({
|
|
9584
|
+
level: string(),
|
|
9585
|
+
format: NotificationFormatSchema,
|
|
9586
|
+
attachmentsSent: number().int().nonnegative(),
|
|
9587
|
+
actionsSent: number().int().nonnegative(),
|
|
9588
|
+
truncated: boolean(),
|
|
9589
|
+
dropped: array(string())
|
|
9590
|
+
});
|
|
9591
|
+
var SendResultSchema = object({
|
|
9592
|
+
success: boolean(),
|
|
9593
|
+
error: string().optional(),
|
|
9594
|
+
renderedAs: RenderedAsSchema.optional()
|
|
9595
|
+
});
|
|
9596
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
9597
|
+
var TestResultSchema = SendResultSchema;
|
|
9598
|
+
var notificationOutputCapability = {
|
|
9599
|
+
name: "notification-output",
|
|
9600
|
+
scope: "system",
|
|
9601
|
+
mode: "collection",
|
|
9602
|
+
methods: {
|
|
9603
|
+
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
9604
|
+
listTargets: method(object({}), array(TargetSchema)),
|
|
9605
|
+
discoverTargets: method(object({
|
|
9606
|
+
kind: string(),
|
|
9607
|
+
config: record(string(), unknown()).optional()
|
|
9608
|
+
}), array(DiscoveredTargetSchema)),
|
|
9609
|
+
send: method(object({
|
|
9610
|
+
targetId: string(),
|
|
9611
|
+
notification: NotificationSchema
|
|
9612
|
+
}), SendResultSchema, { kind: "mutation" }),
|
|
9613
|
+
testTarget: method(object({
|
|
9614
|
+
targetId: string(),
|
|
9615
|
+
sample: NotificationSchema.optional()
|
|
9616
|
+
}), TestResultSchema, { kind: "mutation" }),
|
|
9617
|
+
upsertTarget: method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }),
|
|
9618
|
+
deleteTarget: method(object({ targetId: string() }), _void(), { kind: "mutation" }),
|
|
9619
|
+
setTargetEnabled: method(object({
|
|
9620
|
+
targetId: string(),
|
|
9621
|
+
enabled: boolean()
|
|
9622
|
+
}), _void(), { kind: "mutation" })
|
|
9623
|
+
}
|
|
9624
|
+
};
|
|
9625
|
+
/**
|
|
9626
|
+
* notification-rules — the Notification Center rule surface (P1 core).
|
|
9627
|
+
*
|
|
9628
|
+
* Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
|
|
9629
|
+
* (operator decisions D-1/D-2/D-3 are binding):
|
|
9630
|
+
*
|
|
9631
|
+
* - D-2: rule EVALUATION lives in `addon-post-analysis` (the
|
|
9632
|
+
* `notification-center` module), hooked on the durable persistence
|
|
9633
|
+
* moments (object-event insert, TrackCloser.closeExpired) with a
|
|
9634
|
+
* persisted outbox + retry — never the lossy telemetry bus (D8).
|
|
9635
|
+
* - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
|
|
9636
|
+
* FIRST persisted detection matching the conditions (per-track dedup,
|
|
9637
|
+
* `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
|
|
9638
|
+
* `delivery: 'track-end'` evaluates the finalized track record at close.
|
|
9639
|
+
* - DISPATCH stays behind `notification-output` (rules reference targets
|
|
9640
|
+
* by id; per-backend params are a passthrough blob capped by the
|
|
9641
|
+
* target kind's own caps/degrade engine).
|
|
9642
|
+
*
|
|
9643
|
+
* P1 scope: admin-authored rules only (`createdBy` stamped from the
|
|
9644
|
+
* server-injected caller identity — the first `caller: 'required'`
|
|
9645
|
+
* adopter). The P1 condition subset is: devices, classes(+exclude),
|
|
9646
|
+
* minConfidence, admin zones (any/all + exclude), weekly schedule
|
|
9647
|
+
* windows, and the optional label/identity/plate matchers. User rules,
|
|
9648
|
+
* private zones, per-recipient fan-out and the wider condition table are
|
|
9649
|
+
* P2+ (see spec §7).
|
|
9650
|
+
*
|
|
9651
|
+
* All schemas here are the single source of truth — `NcRule` etc. are
|
|
9652
|
+
* `z.infer` exports; no duplicate interfaces (the advanced-notifier
|
|
9653
|
+
* schema/interface drift is explicitly not repeated).
|
|
9654
|
+
*/
|
|
9655
|
+
/**
|
|
9656
|
+
* D-3: the trigger/urgency of a rule — which persistence moment evaluates it.
|
|
9657
|
+
* The value maps 1:1 onto the evaluated record kind:
|
|
9658
|
+
* - `immediate` ↔ object-event persist (lowest-latency detection burst)
|
|
9659
|
+
* - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
|
|
9660
|
+
* - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
|
|
9661
|
+
* change of a LINKED device, one row per linked camera)
|
|
9662
|
+
* - `package-event` ↔ PackageDropDetector object-event insert (a `package`
|
|
9663
|
+
* delivery / pick-up)
|
|
9664
|
+
*
|
|
9665
|
+
* `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
|
|
9666
|
+
* `package-event` are pure trigger kinds (no urgency dimension). Extending
|
|
9667
|
+
* this one field keeps the schema additive — a rule still declares exactly
|
|
9668
|
+
* one trigger.
|
|
9669
|
+
*/
|
|
9670
|
+
var NcDeliverySchema = _enum([
|
|
9671
|
+
"immediate",
|
|
9672
|
+
"track-end",
|
|
9673
|
+
"device-event",
|
|
9674
|
+
"package-event"
|
|
9675
|
+
]);
|
|
9676
|
+
/** Weekly schedule — OR of windows; absence on the rule = always active. */
|
|
9677
|
+
var NcScheduleSchema = object({
|
|
9678
|
+
windows: array(object({
|
|
9679
|
+
/** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
|
|
9680
|
+
days: array(number().int().min(0).max(6)).min(1),
|
|
9681
|
+
startMinute: number().int().min(0).max(1439),
|
|
9682
|
+
endMinute: number().int().min(0).max(1439)
|
|
9683
|
+
})).min(1),
|
|
9684
|
+
/** IANA timezone; default = hub host timezone. */
|
|
9685
|
+
timezone: string().optional(),
|
|
9686
|
+
/** Active OUTSIDE the windows (e.g. "only outside business hours"). */
|
|
9687
|
+
invert: boolean().optional()
|
|
9688
|
+
});
|
|
9689
|
+
/** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
|
|
9690
|
+
var NcPlateMatcherSchema = object({
|
|
9691
|
+
values: array(string().min(1)).min(1),
|
|
9692
|
+
/** Max Levenshtein distance after normalization (uppercase alphanumeric). */
|
|
9693
|
+
maxDistance: number().int().min(0).max(3).default(1)
|
|
9694
|
+
});
|
|
9695
|
+
/**
|
|
9696
|
+
* Occupancy condition (DEVICE-EVENT trigger). Fires on a ZoneAnalytics
|
|
9697
|
+
* occupancy edge for a device — optionally narrowed to a single admin
|
|
9698
|
+
* `zoneId` and/or object `className`. `op` selects the edge/threshold:
|
|
9699
|
+
* - `became-occupied` (default) — count crossed 0 → ≥ `count`
|
|
9700
|
+
* - `became-free` — count crossed ≥ `count` → below it
|
|
9701
|
+
* - `>=` / `<=` — count is at/over or at/under `count`
|
|
9702
|
+
* `sustainSeconds` requires the condition hold continuously that long
|
|
9703
|
+
* before firing (debounces flicker; 0 = fire on the first matching edge).
|
|
9704
|
+
* Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
|
|
9705
|
+
* the condition never matches. Confirmed edge-state survives addon restarts
|
|
9706
|
+
* (declared SQLite collection, reseeded on boot).
|
|
9707
|
+
*/
|
|
9708
|
+
var NcOccupancyConditionSchema = object({
|
|
9709
|
+
/** Admin zone id to scope the count to; absent = whole-frame occupancy. */
|
|
9710
|
+
zoneId: string().optional(),
|
|
9711
|
+
/** Object class to count; absent = any class. */
|
|
9712
|
+
className: string().optional(),
|
|
9713
|
+
op: _enum([
|
|
9714
|
+
"became-occupied",
|
|
9715
|
+
"became-free",
|
|
9716
|
+
">=",
|
|
9717
|
+
"<="
|
|
9718
|
+
]).default("became-occupied"),
|
|
9719
|
+
count: number().int().min(0).default(1),
|
|
9720
|
+
sustainSeconds: number().int().min(0).max(3600).default(15)
|
|
9721
|
+
});
|
|
9722
|
+
/**
|
|
9723
|
+
* Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
|
|
9724
|
+
*
|
|
9725
|
+
* The values are not symmetric, and deliberately so — the absent value has to
|
|
9726
|
+
* mean exactly what every rule authored before this condition existed already
|
|
9727
|
+
* does:
|
|
9728
|
+
* - `enter` — entries and every NON-crossing record (movement state,
|
|
9729
|
+
* package, sensor). Exits are rejected. **This is the absent behaviour**:
|
|
9730
|
+
* an operator who never asked for exits must not start receiving them.
|
|
9731
|
+
* - `exit` — ONLY an exit crossing. A record that is not a crossing at all
|
|
9732
|
+
* fails closed, because "the car left the drive" is a question about a
|
|
9733
|
+
* boundary, not about a detection.
|
|
9734
|
+
* - `any` — no direction filter; entries, exits and non-crossings alike.
|
|
9735
|
+
*
|
|
9736
|
+
* A rule asking for a direction should normally also scope `zones`, which the
|
|
9737
|
+
* engine evaluates against the crossed zone as well as the current membership
|
|
9738
|
+
* (an exit's membership no longer contains the zone it just left).
|
|
9739
|
+
*/
|
|
9740
|
+
var NcCrossingSchema = _enum([
|
|
9741
|
+
"enter",
|
|
9742
|
+
"exit",
|
|
9743
|
+
"any"
|
|
9744
|
+
]);
|
|
9745
|
+
/** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
|
|
9746
|
+
var NcZoneConditionSchema = object({
|
|
9747
|
+
ids: array(string().min(1)).min(1),
|
|
9748
|
+
/** Quantifier over `ids` — at least one / every one visited. */
|
|
9749
|
+
match: _enum(["any", "all"]).default("any")
|
|
9750
|
+
});
|
|
9751
|
+
/**
|
|
9752
|
+
* The P1 condition set — a flat AND of groups; absent group = pass;
|
|
9753
|
+
* membership lists are OR within the list (spec §2.3).
|
|
9754
|
+
*/
|
|
9755
|
+
/**
|
|
9756
|
+
* What a rule may actuate.
|
|
9757
|
+
*
|
|
9758
|
+
* **No hand-maintained allowlist** (operator decision, and the right one — a
|
|
9759
|
+
* written list of methods is a third parallel map to keep aligned, and this
|
|
9760
|
+
* repo has paid for those). The boundary instead comes from a property the
|
|
9761
|
+
* capabilities already carry: an action may target only a **device-scoped**
|
|
9762
|
+
* capability method.
|
|
9763
|
+
*
|
|
9764
|
+
* That is not decoration. A rule can be authored by a NON-ADMIN — personal
|
|
9765
|
+
* rules are a supported flow — and the executor runs with the addon's
|
|
9766
|
+
* privileges, so an unbounded action is an arbitrary RPC channel with a
|
|
9767
|
+
* privilege escalation attached. Restricting to device scope excludes the
|
|
9768
|
+
* system caps (`device-manager.removeDevice` and friends) by construction,
|
|
9769
|
+
* costs nothing to maintain, and cannot rot: a cap that stops being
|
|
9770
|
+
* device-scoped stops being actuatable in the same change.
|
|
9771
|
+
*
|
|
9772
|
+
* The executor enforces it; {@link NcRuleActionSchema} carries the intent.
|
|
9773
|
+
*/
|
|
9774
|
+
/**
|
|
9775
|
+
* One step of a sequence.
|
|
9776
|
+
*
|
|
9777
|
+
* `wait` is a first-class step rather than a property of the next action: it is
|
|
9778
|
+
* what makes a sequence a SEQUENCE and not a list — "unlock, wait 5s, open"
|
|
9779
|
+
* cannot be expressed otherwise.
|
|
9780
|
+
*/
|
|
9781
|
+
var NcRuleActionSchema = discriminatedUnion("kind", [object({
|
|
9782
|
+
kind: literal("wait"),
|
|
9783
|
+
seconds: number().min(0).max(300)
|
|
9784
|
+
}), object({
|
|
9785
|
+
kind: literal("cap"),
|
|
9786
|
+
deviceId: number().int(),
|
|
9787
|
+
/** Capability name, e.g. `alarm-panel`. */
|
|
9788
|
+
cap: string().min(1),
|
|
9789
|
+
/** Method on it. The executor refuses a non-device-scoped cap. */
|
|
9790
|
+
method: string().min(1),
|
|
9791
|
+
/** Method arguments, minus `deviceId` (the executor injects it). */
|
|
9792
|
+
args: record(string(), unknown()).optional()
|
|
9793
|
+
})]);
|
|
9794
|
+
/**
|
|
9795
|
+
* A named, ordered run of steps with its own throttle.
|
|
9796
|
+
*
|
|
9797
|
+
* `minDelaySec` exists because a noisy rule otherwise hammers a physical
|
|
9798
|
+
* actuator — the rule's own cooldown governs NOTIFICATIONS, which is a
|
|
9799
|
+
* different budget from "how often may this gate actually open".
|
|
9800
|
+
*/
|
|
9801
|
+
var NcRuleActionSequenceSchema = object({
|
|
9802
|
+
name: string().min(1).max(120),
|
|
9803
|
+
enabled: boolean(),
|
|
9804
|
+
minDelaySec: number().int().min(0).max(86400).optional(),
|
|
9805
|
+
actions: array(NcRuleActionSchema).min(1)
|
|
9806
|
+
});
|
|
9807
|
+
/**
|
|
9808
|
+
* One button carried by the notification, running a named sequence on tap.
|
|
9809
|
+
*
|
|
9810
|
+
* **Read this before adding a button that does something physical.** The tap
|
|
9811
|
+
* arrives over a link that travelled through third-party infrastructure — ntfy,
|
|
9812
|
+
* a push relay, whatever forwarded the message — and the callback's ONLY
|
|
9813
|
+
* authority is the token in that link: single-use, short-lived, bound to this
|
|
9814
|
+
* one action of this one notification. It does not identify who tapped.
|
|
9815
|
+
* Whoever holds the notification can run the button, once, inside the window.
|
|
9816
|
+
* That is the operator's explicit choice (2026-08-05), and `destructive` is a
|
|
9817
|
+
* rendering hint, not a second gate. [D47](decisions/adr-0047.md).
|
|
9818
|
+
*/
|
|
9819
|
+
var NcRuleNotificationButtonSchema = object({
|
|
9820
|
+
/** Stable id — travels in the callback and identifies the button in logs. */
|
|
9821
|
+
id: string().min(1).max(64),
|
|
9822
|
+
label: string().min(1).max(40),
|
|
9823
|
+
/** Name of a sequence in `onTrigger`. The dispatcher drops a button whose
|
|
9824
|
+
* sequence does not exist rather than minting a token for nothing. */
|
|
9825
|
+
sequence: string().min(1).max(120),
|
|
9826
|
+
icon: NotificationActionIconSchema.optional(),
|
|
9827
|
+
destructive: boolean().optional()
|
|
9828
|
+
});
|
|
9829
|
+
/**
|
|
9830
|
+
* Sequences a rule runs, by hook point.
|
|
9831
|
+
*
|
|
9832
|
+
* ONLY `onTrigger` is here, deliberately. The reference also has activation /
|
|
9833
|
+
* deactivation / reset / post-generation hooks, and they are wanted — but this
|
|
9834
|
+
* repo's expensive failure mode is declaring a surface nothing produces, so a
|
|
9835
|
+
* hook appears here in the same change that produces its edge, never before.
|
|
9836
|
+
*/
|
|
9837
|
+
var NcRuleActionsSchema = object({
|
|
9838
|
+
/** Runs when the rule MATCHES. */
|
|
9839
|
+
onTrigger: array(NcRuleActionSequenceSchema).optional(),
|
|
9840
|
+
/**
|
|
9841
|
+
* Buttons the NOTIFICATION carries, each running one of this rule's
|
|
9842
|
+
* sequences when tapped.
|
|
9843
|
+
*
|
|
9844
|
+
* Deliberately a REFERENCE to a sequence rather than a second place to
|
|
9845
|
+
* author steps. A button that could define its own actions would be a
|
|
9846
|
+
* parallel actuation vocabulary — the executor's device-scope check, the
|
|
9847
|
+
* stop-at-first-failure rule and the per-sequence throttle all live on
|
|
9848
|
+
* sequences, and a second authoring surface would drift from every one of
|
|
9849
|
+
* them.
|
|
9850
|
+
*
|
|
9851
|
+
* A sequence reachable ONLY by a button simply appears in `onTrigger` with
|
|
9852
|
+
* `enabled: false`: it is then authored, throttled and validated like the
|
|
9853
|
+
* rest, and nothing runs it automatically.
|
|
9854
|
+
*/
|
|
9855
|
+
buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
|
|
9856
|
+
});
|
|
9857
|
+
var NcConditionsSchema = object({
|
|
9858
|
+
/** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
|
|
9859
|
+
deviceState: object({
|
|
9860
|
+
deviceId: number().int(),
|
|
9861
|
+
/** Any of these matches. */
|
|
9862
|
+
states: array(string().min(1)).min(1)
|
|
9863
|
+
}).optional(),
|
|
9864
|
+
/** Device scope — absent = all devices. */
|
|
9865
|
+
devices: array(number()).optional(),
|
|
9866
|
+
/** Detector class names (any overlap with the record's class set). */
|
|
9867
|
+
classes: array(string().min(1)).optional(),
|
|
9868
|
+
/** Veto classes — any overlap fails the rule. */
|
|
9869
|
+
classesExclude: array(string().min(1)).optional(),
|
|
9870
|
+
/** Minimum detection confidence 0–1 (fails when the record has none). */
|
|
9871
|
+
minConfidence: number().min(0).max(1).optional(),
|
|
9872
|
+
/** Admin zone membership over event `zones` / track `zonesVisited`. */
|
|
9873
|
+
zones: NcZoneConditionSchema.optional(),
|
|
9874
|
+
/** Veto zones — any hit fails the rule. */
|
|
9875
|
+
zonesExclude: array(string().min(1)).optional(),
|
|
9876
|
+
/**
|
|
9877
|
+
* Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
|
|
9878
|
+
* and a closed track carries none, so a `track-end` rule asking for one
|
|
9879
|
+
* fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
|
|
9880
|
+
* which is exactly today's behaviour. See {@link NcCrossingSchema}.
|
|
9881
|
+
*/
|
|
9882
|
+
crossing: NcCrossingSchema.optional(),
|
|
9883
|
+
/**
|
|
9884
|
+
* Exact (case-insensitive) match on the record's collapsed `label`
|
|
9885
|
+
* (identity name / plate text / subclass).
|
|
9886
|
+
*/
|
|
9887
|
+
labelEquals: array(string().min(1)).optional(),
|
|
9480
9888
|
/**
|
|
9481
9889
|
* Identity matcher. P1 boundary: matched against the record's collapsed
|
|
9482
9890
|
* `label` (the identity display name propagated by the face pipeline) —
|
|
@@ -9653,6 +10061,14 @@ var NcMediaPolicySchema = object({
|
|
|
9653
10061
|
clipPreRollSec: number().int().min(0).max(30).optional(),
|
|
9654
10062
|
clipPostRollSec: number().int().min(0).max(30).optional(),
|
|
9655
10063
|
/**
|
|
10064
|
+
* Playback rate of the attached gif / clip. Absent = 2x.
|
|
10065
|
+
*
|
|
10066
|
+
* A notification clip is GLANCED at on a lock screen, not watched: at real
|
|
10067
|
+
* time an eight-second passage is eight seconds of the recipient's attention
|
|
10068
|
+
* and twice the bytes. 1 is real time for the operator who wants it.
|
|
10069
|
+
*/
|
|
10070
|
+
clipSpeed: number().min(1).max(8).optional(),
|
|
10071
|
+
/**
|
|
9656
10072
|
* Which stream profile the footage is cut from. Absent = the CHEAPEST
|
|
9657
10073
|
* assigned profile: a notification is watched on a phone, so the 4K
|
|
9658
10074
|
* rendition would burn CPU to produce a file the client downscales anyway.
|
|
@@ -9724,7 +10140,30 @@ var NcRuleInputSchema = object({
|
|
|
9724
10140
|
* behaviour, visible to all, read-only in the viewer). Present = personal
|
|
9725
10141
|
* rule owned by this userId. Server-stamped; never trusted from a client.
|
|
9726
10142
|
*/
|
|
9727
|
-
ownerUserId: string().optional()
|
|
10143
|
+
ownerUserId: string().optional(),
|
|
10144
|
+
/**
|
|
10145
|
+
* May a non-admin snooze this rule for EVERYONE, not just themselves?
|
|
10146
|
+
*
|
|
10147
|
+
* A snooze is personal by default — it silences the person who set it. This
|
|
10148
|
+
* opts THIS rule into the "the gardener is here all afternoon" case, where
|
|
10149
|
+
* silencing the camera for the whole household is legitimate. It silences
|
|
10150
|
+
* other people, so it is off unless a rule deliberately allows it.
|
|
10151
|
+
*
|
|
10152
|
+
* `.optional()`, deliberately NOT `.default()`: a Zod default does not run on
|
|
10153
|
+
* the addon cap path (three production failures in one day), so absent is
|
|
10154
|
+
* read as `false` by {@link canSetGlobal} in the engine. Admins are not bound
|
|
10155
|
+
* by this flag — see the scope rules on that function.
|
|
10156
|
+
*/
|
|
10157
|
+
snoozeAllowGlobal: boolean().optional(),
|
|
10158
|
+
/**
|
|
10159
|
+
* Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
|
|
10160
|
+
*
|
|
10161
|
+
* This is what makes the rule set the alarm's trigger set without the alarm
|
|
10162
|
+
* being a special case: arming is
|
|
10163
|
+
* `{ cap: 'alarm-panel', method: 'arm', args: { mode: 'away' } }`, the same
|
|
10164
|
+
* shape as every other actuation.
|
|
10165
|
+
*/
|
|
10166
|
+
actions: NcRuleActionsSchema.optional()
|
|
9728
10167
|
});
|
|
9729
10168
|
/**
|
|
9730
10169
|
* Partial patch for `updateRule` — any subset of the input fields, plus the
|
|
@@ -9795,7 +10234,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
9795
10234
|
"packagePhase",
|
|
9796
10235
|
"crossingSelect",
|
|
9797
10236
|
"polygonDraw",
|
|
9798
|
-
"occupancy"
|
|
10237
|
+
"occupancy",
|
|
10238
|
+
"deviceState"
|
|
9799
10239
|
]),
|
|
9800
10240
|
operator: _enum([
|
|
9801
10241
|
"in",
|
|
@@ -9809,7 +10249,28 @@ var NcConditionDescriptorSchema = object({
|
|
|
9809
10249
|
/** Which delivery kinds the condition applies to. */
|
|
9810
10250
|
appliesTo: array(NcDeliverySchema),
|
|
9811
10251
|
phase: string(),
|
|
9812
|
-
description: string().optional()
|
|
10252
|
+
description: string().optional(),
|
|
10253
|
+
/**
|
|
10254
|
+
* The CHOICES for a single-choice widget (`sourceSelect`, `crossingSelect`,
|
|
10255
|
+
* `packagePhase`, …), served with the descriptor.
|
|
10256
|
+
*
|
|
10257
|
+
* Before this the descriptor said which widget to render and not what to put
|
|
10258
|
+
* in it, so every option list lived in three places: this file's enums, the
|
|
10259
|
+
* admin's `NC_*_OPTIONS` and the viewer's `NC_*_VALUES`. That triple mirror
|
|
10260
|
+
* is the drift that emptied the viewer's rule editor on 2026-08-04 — the app
|
|
10261
|
+
* mirrors the cap by hand, so it is only ever as current as its last build.
|
|
10262
|
+
*
|
|
10263
|
+
* With the options on the wire, a condition of an EXISTING `valueType` costs
|
|
10264
|
+
* zero client changes. Clients keep a local fallback for an older hub that
|
|
10265
|
+
* does not send them; absent here is "use your own list", not "no choices".
|
|
10266
|
+
*/
|
|
10267
|
+
options: array(object({
|
|
10268
|
+
/** Written to the rule verbatim. `''` means the ABSENT state. */
|
|
10269
|
+
value: string(),
|
|
10270
|
+
label: string(),
|
|
10271
|
+
/** What THIS choice matches — shown one at a time, under the control. */
|
|
10272
|
+
hint: string().optional()
|
|
10273
|
+
})).readonly().optional()
|
|
9813
10274
|
});
|
|
9814
10275
|
/**
|
|
9815
10276
|
* The delivery lifecycle status of a history row — a straight read of the
|
|
@@ -9894,6 +10355,74 @@ var NcHistoryFilterSchema = object({
|
|
|
9894
10355
|
until: number().optional(),
|
|
9895
10356
|
limit: number().int().min(1).max(500).default(100)
|
|
9896
10357
|
});
|
|
10358
|
+
/**
|
|
10359
|
+
* What a snooze covers. Broader scopes win when several overlap, so one window
|
|
10360
|
+
* leaves ONE digest rather than a rule snooze and a whole-feed snooze both
|
|
10361
|
+
* summarising the same silence.
|
|
10362
|
+
*/
|
|
10363
|
+
var NcSnoozeScopeSchema = _enum([
|
|
10364
|
+
"rule",
|
|
10365
|
+
"device",
|
|
10366
|
+
"all"
|
|
10367
|
+
]);
|
|
10368
|
+
/**
|
|
10369
|
+
* Client-authored snooze. The server stamps `userId`, `startedAt` and
|
|
10370
|
+
* `expiresAt` — a DURATION is sent rather than an instant so a client with a
|
|
10371
|
+
* skewed clock cannot author a window that is already over, or never ends.
|
|
10372
|
+
*/
|
|
10373
|
+
var NcSnoozeInputSchema = object({
|
|
10374
|
+
scope: NcSnoozeScopeSchema,
|
|
10375
|
+
/** Required when `scope: 'rule'` — a scoped snooze with no id matches
|
|
10376
|
+
* NOTHING rather than degrading to "everything". */
|
|
10377
|
+
ruleId: string().optional(),
|
|
10378
|
+
/** Required when `scope: 'device'`. */
|
|
10379
|
+
deviceId: number().int().optional(),
|
|
10380
|
+
durationMinutes: number().int().min(1).max(1440),
|
|
10381
|
+
/**
|
|
10382
|
+
* Silence this for EVERY recipient, not just the caller. Permission is
|
|
10383
|
+
* checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
|
|
10384
|
+
* broader scopes). Absent = personal.
|
|
10385
|
+
*/
|
|
10386
|
+
global: boolean().optional(),
|
|
10387
|
+
/**
|
|
10388
|
+
* Deliver a summary of what was suppressed when the window ends. Absent =
|
|
10389
|
+
* ON: someone silencing a nuisance camera wants it off, someone silencing a
|
|
10390
|
+
* SECURITY camera wants to know what they missed, and choosing "off" for
|
|
10391
|
+
* everybody is how a snooze becomes an outage. Resolved to a concrete
|
|
10392
|
+
* boolean by the server at create time — never left to a Zod default, which
|
|
10393
|
+
* does not run on the addon cap path.
|
|
10394
|
+
*/
|
|
10395
|
+
summary: boolean().optional()
|
|
10396
|
+
});
|
|
10397
|
+
/** A persisted snooze window. */
|
|
10398
|
+
var NcSnoozeSchema = object({
|
|
10399
|
+
id: string(),
|
|
10400
|
+
/** Who set it. Also who it silences, unless `global`. */
|
|
10401
|
+
userId: string(),
|
|
10402
|
+
scope: NcSnoozeScopeSchema,
|
|
10403
|
+
ruleId: string().optional(),
|
|
10404
|
+
deviceId: number().int().optional(),
|
|
10405
|
+
startedAt: number(),
|
|
10406
|
+
/** Exclusive: at exactly this instant the snooze is over. Expiry is a
|
|
10407
|
+
* COMPARISON, not a job — no sweeper can leave the operator silenced. */
|
|
10408
|
+
expiresAt: number(),
|
|
10409
|
+
global: boolean(),
|
|
10410
|
+
summary: boolean(),
|
|
10411
|
+
/** When the end-of-window digest went out. Absent = not sent (yet, or the
|
|
10412
|
+
* window has not closed, or `summary` is false). */
|
|
10413
|
+
digestSentAt: number().optional()
|
|
10414
|
+
});
|
|
10415
|
+
object({
|
|
10416
|
+
snoozeId: string(),
|
|
10417
|
+
targetId: string(),
|
|
10418
|
+
ruleId: string(),
|
|
10419
|
+
ruleName: string(),
|
|
10420
|
+
deviceId: number().int(),
|
|
10421
|
+
/** How many notifications this snooze hid for that pair. */
|
|
10422
|
+
count: number().int(),
|
|
10423
|
+
firstAt: number(),
|
|
10424
|
+
lastAt: number()
|
|
10425
|
+
});
|
|
9897
10426
|
method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), method(object({ ruleId: string() }), object({ rule: NcRuleSchema.nullable() }), { auth: "admin" }), method(object({ rule: NcRuleInputSchema }), object({ rule: NcRuleSchema }), {
|
|
9898
10427
|
kind: "mutation",
|
|
9899
10428
|
auth: "admin",
|
|
@@ -9923,7 +10452,13 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
9923
10452
|
}), method(object({}), object({
|
|
9924
10453
|
catalog: array(NcConditionDescriptorSchema),
|
|
9925
10454
|
taxonomy: NcTaxonomySchema.optional()
|
|
9926
|
-
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" })
|
|
10455
|
+
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
10456
|
+
kind: "mutation",
|
|
10457
|
+
caller: "required"
|
|
10458
|
+
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
10459
|
+
kind: "mutation",
|
|
10460
|
+
caller: "required"
|
|
10461
|
+
});
|
|
9927
10462
|
/**
|
|
9928
10463
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
9929
10464
|
*
|
|
@@ -10671,7 +11206,15 @@ method(object({
|
|
|
10671
11206
|
format: _enum(["gif", "mp4"]).default("gif"),
|
|
10672
11207
|
maxWidth: number().int().min(120).max(1920).default(480),
|
|
10673
11208
|
/** GIF only — MP4 keeps the source cadence. */
|
|
10674
|
-
fps: number().int().min(1).max(15).default(5)
|
|
11209
|
+
fps: number().int().min(1).max(15).default(5),
|
|
11210
|
+
/**
|
|
11211
|
+
* Playback rate. A notification clip is GLANCED at on a lock screen,
|
|
11212
|
+
* not watched, so 2x is the default: the recipient sees the whole
|
|
11213
|
+
* passage in half the time and the GIF is half the bytes. `1` is real
|
|
11214
|
+
* time. Applies to MP4 as well — the operator set a speed, not a GIF
|
|
11215
|
+
* speed.
|
|
11216
|
+
*/
|
|
11217
|
+
speed: number().min(1).max(8).default(2)
|
|
10675
11218
|
}), object({
|
|
10676
11219
|
base64: string(),
|
|
10677
11220
|
mime: string(),
|
|
@@ -15457,6 +16000,210 @@ method(object({ codec: string() }), boolean()), method(_void(), object({
|
|
|
15457
16000
|
kind: "mutation",
|
|
15458
16001
|
auth: "admin"
|
|
15459
16002
|
});
|
|
16003
|
+
/**
|
|
16004
|
+
* Query filter for settings-store collections.
|
|
16005
|
+
*/
|
|
16006
|
+
var QueryFilterSchema = object({
|
|
16007
|
+
where: record(string(), unknown()).optional(),
|
|
16008
|
+
whereIn: record(string(), array(unknown())).optional(),
|
|
16009
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
16010
|
+
orderBy: object({
|
|
16011
|
+
field: string(),
|
|
16012
|
+
direction: _enum(["asc", "desc"])
|
|
16013
|
+
}).optional(),
|
|
16014
|
+
limit: number().optional(),
|
|
16015
|
+
offset: number().optional()
|
|
16016
|
+
});
|
|
16017
|
+
/**
|
|
16018
|
+
* The predicate half of a filter, for BULK MUTATIONS.
|
|
16019
|
+
*
|
|
16020
|
+
* Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
|
|
16021
|
+
* meaning for a statement that rewrites a set, and accepting them would invite
|
|
16022
|
+
* a caller to believe `limit` bounds the damage. Every field is optional here
|
|
16023
|
+
* only so the shape stays composable — the implementation REJECTS a filter
|
|
16024
|
+
* that compiles to no predicate, because that is the whole collection.
|
|
16025
|
+
*/
|
|
16026
|
+
var MutationFilterSchema = object({
|
|
16027
|
+
where: record(string(), unknown()).optional(),
|
|
16028
|
+
whereIn: record(string(), array(unknown())).optional(),
|
|
16029
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
|
|
16030
|
+
});
|
|
16031
|
+
/** A single stored record: `{ id, data }`. */
|
|
16032
|
+
var SettingsRecordSchema = object({
|
|
16033
|
+
id: string(),
|
|
16034
|
+
data: record(string(), unknown())
|
|
16035
|
+
});
|
|
16036
|
+
/**
|
|
16037
|
+
* Column declaration for a structured (SQL-backed) collection.
|
|
16038
|
+
*
|
|
16039
|
+
* Logical types — the backend translates each to the matching SQLite
|
|
16040
|
+
* storage class and handles per-type marshaling:
|
|
16041
|
+
* - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
|
|
16042
|
+
* - `JSON` — TEXT under the hood; serialised on write, parsed on read
|
|
16043
|
+
* - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
|
|
16044
|
+
*/
|
|
16045
|
+
var CollectionColumnSchema = object({
|
|
16046
|
+
name: string(),
|
|
16047
|
+
type: _enum([
|
|
16048
|
+
"TEXT",
|
|
16049
|
+
"INTEGER",
|
|
16050
|
+
"REAL",
|
|
16051
|
+
"JSON",
|
|
16052
|
+
"BOOLEAN"
|
|
16053
|
+
]),
|
|
16054
|
+
primaryKey: boolean().optional(),
|
|
16055
|
+
notNull: boolean().optional(),
|
|
16056
|
+
unique: boolean().optional(),
|
|
16057
|
+
/**
|
|
16058
|
+
* Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
|
|
16059
|
+
* columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
|
|
16060
|
+
* collection surface simply cannot express their existing tables — which is
|
|
16061
|
+
* why they were still on the uncapped `table*` API.
|
|
16062
|
+
*/
|
|
16063
|
+
defaultValue: union([
|
|
16064
|
+
string(),
|
|
16065
|
+
number(),
|
|
16066
|
+
boolean()
|
|
16067
|
+
]).optional()
|
|
16068
|
+
});
|
|
16069
|
+
var CollectionIndexSchema = object({
|
|
16070
|
+
name: string(),
|
|
16071
|
+
columns: array(string()).readonly(),
|
|
16072
|
+
unique: boolean().optional()
|
|
16073
|
+
});
|
|
16074
|
+
method(object({
|
|
16075
|
+
namespace: string().optional(),
|
|
16076
|
+
collection: string(),
|
|
16077
|
+
key: string()
|
|
16078
|
+
}), unknown()), method(object({
|
|
16079
|
+
namespace: string().optional(),
|
|
16080
|
+
collection: string(),
|
|
16081
|
+
key: string(),
|
|
16082
|
+
value: unknown()
|
|
16083
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16084
|
+
namespace: string().optional(),
|
|
16085
|
+
collection: string(),
|
|
16086
|
+
filter: QueryFilterSchema.optional()
|
|
16087
|
+
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
16088
|
+
namespace: string().optional(),
|
|
16089
|
+
collection: string(),
|
|
16090
|
+
record: SettingsRecordSchema
|
|
16091
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16092
|
+
namespace: string().optional(),
|
|
16093
|
+
collection: string(),
|
|
16094
|
+
id: string(),
|
|
16095
|
+
data: record(string(), unknown())
|
|
16096
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16097
|
+
namespace: string().optional(),
|
|
16098
|
+
collection: string(),
|
|
16099
|
+
key: string()
|
|
16100
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16101
|
+
namespace: string().optional(),
|
|
16102
|
+
collection: string(),
|
|
16103
|
+
filter: MutationFilterSchema
|
|
16104
|
+
}), object({ deleted: number().int() }), { kind: "mutation" }), method(object({
|
|
16105
|
+
namespace: string().optional(),
|
|
16106
|
+
collection: string(),
|
|
16107
|
+
filter: MutationFilterSchema,
|
|
16108
|
+
data: record(string(), unknown())
|
|
16109
|
+
}), object({ updated: number().int() }), { kind: "mutation" }), method(object({
|
|
16110
|
+
namespace: string().optional(),
|
|
16111
|
+
collection: string(),
|
|
16112
|
+
filter: QueryFilterSchema.optional()
|
|
16113
|
+
}), number()), method(object({
|
|
16114
|
+
namespace: string().optional(),
|
|
16115
|
+
collection: string(),
|
|
16116
|
+
field: string(),
|
|
16117
|
+
bucketSize: number().int().positive(),
|
|
16118
|
+
origin: number().int(),
|
|
16119
|
+
filter: QueryFilterSchema.optional()
|
|
16120
|
+
}), array(object({
|
|
16121
|
+
bucket: number().int(),
|
|
16122
|
+
count: number().int()
|
|
16123
|
+
})).readonly()), method(object({
|
|
16124
|
+
namespace: string().optional(),
|
|
16125
|
+
collection: string()
|
|
16126
|
+
}), boolean()), method(object({
|
|
16127
|
+
namespace: string().optional(),
|
|
16128
|
+
collection: string(),
|
|
16129
|
+
columns: array(CollectionColumnSchema).readonly(),
|
|
16130
|
+
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
16131
|
+
}), _void(), { kind: "mutation" });
|
|
16132
|
+
/**
|
|
16133
|
+
* What one engine says about itself. The orchestrator uses `kind` to pick
|
|
16134
|
+
* a registrant for a collection; `engineId` is what a log line names when
|
|
16135
|
+
* a call is routed or refused.
|
|
16136
|
+
*/
|
|
16137
|
+
var EngineInfoSchema = object({
|
|
16138
|
+
engineId: string(),
|
|
16139
|
+
/**
|
|
16140
|
+
* `relational` — rows, columns, indexes, the surface `settings-store`
|
|
16141
|
+
* has always described. `vector` — an embedding store answering
|
|
16142
|
+
* similarity queries. A registrant declares exactly one; an engine that
|
|
16143
|
+
* does both registers twice, because "both" would make the routing
|
|
16144
|
+
* decision ambiguous at exactly the point it must not be.
|
|
16145
|
+
*/
|
|
16146
|
+
kind: _enum(["relational", "vector"]),
|
|
16147
|
+
displayName: string()
|
|
16148
|
+
});
|
|
16149
|
+
method(_void(), EngineInfoSchema), method(object({
|
|
16150
|
+
namespace: string().optional(),
|
|
16151
|
+
collection: string(),
|
|
16152
|
+
key: string()
|
|
16153
|
+
}), unknown()), method(object({
|
|
16154
|
+
namespace: string().optional(),
|
|
16155
|
+
collection: string(),
|
|
16156
|
+
key: string(),
|
|
16157
|
+
value: unknown()
|
|
16158
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16159
|
+
namespace: string().optional(),
|
|
16160
|
+
collection: string(),
|
|
16161
|
+
filter: QueryFilterSchema.optional()
|
|
16162
|
+
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
16163
|
+
namespace: string().optional(),
|
|
16164
|
+
collection: string(),
|
|
16165
|
+
record: SettingsRecordSchema
|
|
16166
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16167
|
+
namespace: string().optional(),
|
|
16168
|
+
collection: string(),
|
|
16169
|
+
id: string(),
|
|
16170
|
+
data: record(string(), unknown())
|
|
16171
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16172
|
+
namespace: string().optional(),
|
|
16173
|
+
collection: string(),
|
|
16174
|
+
key: string()
|
|
16175
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16176
|
+
namespace: string().optional(),
|
|
16177
|
+
collection: string(),
|
|
16178
|
+
filter: MutationFilterSchema
|
|
16179
|
+
}), object({ deleted: number().int() }), { kind: "mutation" }), method(object({
|
|
16180
|
+
namespace: string().optional(),
|
|
16181
|
+
collection: string(),
|
|
16182
|
+
filter: MutationFilterSchema,
|
|
16183
|
+
data: record(string(), unknown())
|
|
16184
|
+
}), object({ updated: number().int() }), { kind: "mutation" }), method(object({
|
|
16185
|
+
namespace: string().optional(),
|
|
16186
|
+
collection: string(),
|
|
16187
|
+
filter: QueryFilterSchema.optional()
|
|
16188
|
+
}), number()), method(object({
|
|
16189
|
+
namespace: string().optional(),
|
|
16190
|
+
collection: string(),
|
|
16191
|
+
field: string(),
|
|
16192
|
+
bucketSize: number().int().positive(),
|
|
16193
|
+
origin: number().int(),
|
|
16194
|
+
filter: QueryFilterSchema.optional()
|
|
16195
|
+
}), array(object({
|
|
16196
|
+
bucket: number().int(),
|
|
16197
|
+
count: number().int()
|
|
16198
|
+
})).readonly()), method(object({
|
|
16199
|
+
namespace: string().optional(),
|
|
16200
|
+
collection: string()
|
|
16201
|
+
}), boolean()), method(object({
|
|
16202
|
+
namespace: string().optional(),
|
|
16203
|
+
collection: string(),
|
|
16204
|
+
columns: array(CollectionColumnSchema).readonly(),
|
|
16205
|
+
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
16206
|
+
}), _void(), { kind: "mutation" });
|
|
15460
16207
|
DeviceType.Camera;
|
|
15461
16208
|
/**
|
|
15462
16209
|
* `device-adoption` — generic discovery + adoption surface,
|
|
@@ -16135,7 +16882,20 @@ method(object({
|
|
|
16135
16882
|
}), _void(), {
|
|
16136
16883
|
kind: "mutation",
|
|
16137
16884
|
auth: "admin"
|
|
16138
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
|
|
16885
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
|
|
16886
|
+
addonId: string().optional(),
|
|
16887
|
+
/**
|
|
16888
|
+
* `slim` omits `config` (returned `{}`), `metadata` (null) and the
|
|
16889
|
+
* `sourceInfo` derived from config — and skips the per-device settings
|
|
16890
|
+
* read that produces them. Everything identifying a device (id, name,
|
|
16891
|
+
* type, online, features, isCamera, parent/link ids) is unchanged.
|
|
16892
|
+
* Do not use it for dispatch routing, which needs `sourceInfo`.
|
|
16893
|
+
*/
|
|
16894
|
+
projection: _enum(["full", "slim"]).optional(),
|
|
16895
|
+
/** Return only camera devices. Filtering server-side instead of
|
|
16896
|
+
* shipping 293 rows to find 12. */
|
|
16897
|
+
isCamera: boolean().optional()
|
|
16898
|
+
}), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
16139
16899
|
mode: DeviceLinkModeSchema,
|
|
16140
16900
|
devices: array(LinkedDeviceSchema)
|
|
16141
16901
|
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
@@ -16574,14 +17334,14 @@ var LlmProfileSchema = object({
|
|
|
16574
17334
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
16575
17335
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
16576
17336
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
16577
|
-
var ConfigSchemaPassthrough
|
|
17337
|
+
var ConfigSchemaPassthrough = unknown();
|
|
16578
17338
|
var LlmProfileKindDescriptorSchema = object({
|
|
16579
17339
|
kind: LlmProfileKindSchema,
|
|
16580
17340
|
label: string(),
|
|
16581
17341
|
icon: string(),
|
|
16582
17342
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16583
17343
|
addonId: string(),
|
|
16584
|
-
configSchema: ConfigSchemaPassthrough
|
|
17344
|
+
configSchema: ConfigSchemaPassthrough
|
|
16585
17345
|
});
|
|
16586
17346
|
var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
|
|
16587
17347
|
var LlmDefaultSchema = object({
|
|
@@ -17112,241 +17872,102 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
17112
17872
|
});
|
|
17113
17873
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
17114
17874
|
/**
|
|
17115
|
-
*
|
|
17116
|
-
*
|
|
17117
|
-
*
|
|
17118
|
-
* `docs/superpowers/specs/2026-
|
|
17119
|
-
*
|
|
17120
|
-
*
|
|
17121
|
-
*
|
|
17122
|
-
*
|
|
17123
|
-
*
|
|
17124
|
-
*
|
|
17125
|
-
*
|
|
17126
|
-
*
|
|
17127
|
-
*
|
|
17128
|
-
*
|
|
17129
|
-
*
|
|
17130
|
-
*
|
|
17131
|
-
*
|
|
17132
|
-
|
|
17133
|
-
|
|
17134
|
-
*
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
*
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
|
|
17141
|
-
|
|
17142
|
-
|
|
17143
|
-
* admin "Integrations" page.
|
|
17144
|
-
*/
|
|
17145
|
-
/**
|
|
17146
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
17147
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
17148
|
-
*/
|
|
17149
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
17150
|
-
"image",
|
|
17151
|
-
"video",
|
|
17152
|
-
"gif",
|
|
17153
|
-
"audio",
|
|
17154
|
-
"icon"
|
|
17155
|
-
]);
|
|
17156
|
-
/**
|
|
17157
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
17158
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
17159
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
17160
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
17161
|
-
*/
|
|
17162
|
-
var AttachmentSchema = object({
|
|
17163
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
17164
|
-
url: string().optional(),
|
|
17165
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
17166
|
-
mime: string().optional(),
|
|
17167
|
-
name: string().optional()
|
|
17168
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
17169
|
-
var NotificationFormatSchema = _enum([
|
|
17170
|
-
"text",
|
|
17171
|
-
"markdown",
|
|
17172
|
-
"html"
|
|
17875
|
+
* core-blocks — user-authored TypeScript, stored in the kernel and executed in
|
|
17876
|
+
* its own process.
|
|
17877
|
+
*
|
|
17878
|
+
* Spec: `docs/superpowers/specs/2026-08-04-core-blocks-and-synthetic-devices-design.md`.
|
|
17879
|
+
*
|
|
17880
|
+
* The first use is **owning devices without being a device provider**: a block
|
|
17881
|
+
* declares devices under a system or custom integration and drives their state,
|
|
17882
|
+
* with the same `ctx` an addon gets. Automations come later; nothing here
|
|
17883
|
+
* models a trigger.
|
|
17884
|
+
*
|
|
17885
|
+
* **Stated plainly, because it does not change by being true:** a block has an
|
|
17886
|
+
* addon's powers — devices, storage, the event bus, `ctx.api`. It is a plugin
|
|
17887
|
+
* with no review step. What makes that survivable is not a sandbox, it is
|
|
17888
|
+
* PROCESS ISOLATION: one process per block, supervised by `CrashSupervisor`,
|
|
17889
|
+
* so a block that throws or never returns is marked `failed` and visible
|
|
17890
|
+
* instead of taking the hub with it (D6). Every method here is admin-only, and
|
|
17891
|
+
* must stay so.
|
|
17892
|
+
*/
|
|
17893
|
+
/** Where a block runs. The operator chooses — a block driving a device on an
|
|
17894
|
+
* agent is the reason placement is not fixed to the hub. */
|
|
17895
|
+
var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
|
|
17896
|
+
/** What a block's process is doing. Mirrors the addon runner's own lifecycle so
|
|
17897
|
+
* a failing block reads the same way a failing addon does. */
|
|
17898
|
+
var CoreBlockStatusSchema = _enum([
|
|
17899
|
+
"stopped",
|
|
17900
|
+
"starting",
|
|
17901
|
+
"running",
|
|
17902
|
+
"failed"
|
|
17173
17903
|
]);
|
|
17174
|
-
/**
|
|
17175
|
-
var
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
17184
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
17185
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
17186
|
-
* `priority` for that one target.
|
|
17187
|
-
*/
|
|
17188
|
-
var NotificationSchema = object({
|
|
17189
|
-
body: string(),
|
|
17190
|
-
title: string().optional(),
|
|
17191
|
-
format: NotificationFormatSchema.default("text"),
|
|
17192
|
-
priority: number().int().min(1).max(5).default(3),
|
|
17193
|
-
level: string().optional(),
|
|
17194
|
-
attachments: array(AttachmentSchema).optional(),
|
|
17195
|
-
clickUrl: string().optional(),
|
|
17196
|
-
actions: array(NotificationActionSchema).optional(),
|
|
17197
|
-
sound: string().optional(),
|
|
17198
|
-
ttl: number().optional(),
|
|
17199
|
-
tag: string().optional(),
|
|
17200
|
-
deviceId: number().optional(),
|
|
17201
|
-
eventId: string().optional(),
|
|
17202
|
-
metadata: record(string(), unknown()).optional()
|
|
17203
|
-
});
|
|
17204
|
-
/** One declared native severity/priority level for a kind. */
|
|
17205
|
-
var TargetKindLevelSchema = object({
|
|
17206
|
-
id: string(),
|
|
17207
|
-
label: string(),
|
|
17208
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
17209
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
17210
|
-
flags: object({
|
|
17211
|
-
critical: boolean().optional(),
|
|
17212
|
-
silent: boolean().optional(),
|
|
17213
|
-
noPush: boolean().optional()
|
|
17214
|
-
}).optional(),
|
|
17215
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
17216
|
-
requires: array(string()).optional(),
|
|
17217
|
-
description: string().optional()
|
|
17218
|
-
});
|
|
17219
|
-
/** The full capability block consulted before dispatch. */
|
|
17220
|
-
var TargetKindCapsSchema = object({
|
|
17221
|
-
attachments: object({
|
|
17222
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
17223
|
-
mode: _enum([
|
|
17224
|
-
"url",
|
|
17225
|
-
"bytes",
|
|
17226
|
-
"both"
|
|
17227
|
-
]),
|
|
17228
|
-
max: number().int().nonnegative(),
|
|
17229
|
-
maxBytes: number().int().positive().optional()
|
|
17230
|
-
}),
|
|
17231
|
-
/** Max action buttons (0 = none). */
|
|
17232
|
-
actions: number().int().nonnegative(),
|
|
17233
|
-
levels: array(TargetKindLevelSchema),
|
|
17234
|
-
format: array(NotificationFormatSchema),
|
|
17235
|
-
clickUrl: boolean(),
|
|
17236
|
-
sound: boolean(),
|
|
17237
|
-
ttl: boolean(),
|
|
17238
|
-
bodyMaxLen: number().int().positive()
|
|
17239
|
-
});
|
|
17240
|
-
/**
|
|
17241
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
17242
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
17243
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
17244
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
17245
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
17246
|
-
*/
|
|
17247
|
-
var ConfigSchemaPassthrough = unknown();
|
|
17248
|
-
var TargetKindSchema = object({
|
|
17249
|
-
kind: string(),
|
|
17250
|
-
label: string(),
|
|
17251
|
-
icon: string(),
|
|
17252
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
17253
|
-
addonId: string(),
|
|
17254
|
-
/**
|
|
17255
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
17256
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
17257
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
17258
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
17259
|
-
*
|
|
17260
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
17261
|
-
* client, and a native client joins it onto its own hub base.
|
|
17262
|
-
*
|
|
17263
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
17264
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
17265
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
17266
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
17267
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
17268
|
-
*/
|
|
17269
|
-
iconUrl: string().optional(),
|
|
17904
|
+
/** Client-authored fields. */
|
|
17905
|
+
var CoreBlockInputSchema = object({
|
|
17906
|
+
name: string().min(1).max(120),
|
|
17907
|
+
/** TypeScript source. Compiled server-side before it is ever stored — a
|
|
17908
|
+
* block that does not compile is a fork failure the operator would meet
|
|
17909
|
+
* minutes later, in a log, instead of in the editor. */
|
|
17910
|
+
code: string().max(2e5),
|
|
17911
|
+
enabled: boolean(),
|
|
17912
|
+
placement: CoreBlockPlacementSchema,
|
|
17270
17913
|
/**
|
|
17271
|
-
*
|
|
17272
|
-
*
|
|
17273
|
-
* The server knows this and therefore says it, because the client cannot
|
|
17274
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
17275
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
17276
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
17277
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
17278
|
-
*
|
|
17279
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
17280
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
17281
|
-
* its raster path, which is the safe default for an unknown image.
|
|
17914
|
+
* Integration the block's devices hang from. Absent = the system integration
|
|
17915
|
+
* blocks share. A block may declare its own instead.
|
|
17282
17916
|
*/
|
|
17283
|
-
|
|
17284
|
-
configSchema: ConfigSchemaPassthrough,
|
|
17285
|
-
supportsDiscovery: boolean(),
|
|
17286
|
-
caps: TargetKindCapsSchema
|
|
17917
|
+
integrationId: string().optional()
|
|
17287
17918
|
});
|
|
17288
|
-
/**
|
|
17289
|
-
|
|
17290
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
17291
|
-
* round-trip a stored secret to the UI.
|
|
17292
|
-
*/
|
|
17293
|
-
var TargetSchema = object({
|
|
17919
|
+
/** A stored block. */
|
|
17920
|
+
var CoreBlockSchema = CoreBlockInputSchema.extend({
|
|
17294
17921
|
id: string(),
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17337
|
-
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
setTargetEnabled: method(object({
|
|
17345
|
-
targetId: string(),
|
|
17346
|
-
enabled: boolean()
|
|
17347
|
-
}), _void(), { kind: "mutation" })
|
|
17348
|
-
}
|
|
17349
|
-
};
|
|
17922
|
+
createdAt: number(),
|
|
17923
|
+
updatedAt: number(),
|
|
17924
|
+
/** Server-stamped author. */
|
|
17925
|
+
createdBy: string(),
|
|
17926
|
+
status: CoreBlockStatusSchema,
|
|
17927
|
+
/**
|
|
17928
|
+
* Why the block is not running, when it is not. The operator's ONLY window
|
|
17929
|
+
* into a block that failed at load — a block that is silently absent is the
|
|
17930
|
+
* failure mode this whole feature has to avoid.
|
|
17931
|
+
*/
|
|
17932
|
+
lastError: string().optional(),
|
|
17933
|
+
/** Ms epoch of the last state change. */
|
|
17934
|
+
lastChangedAt: number()
|
|
17935
|
+
});
|
|
17936
|
+
/** What a compile attempt produced. */
|
|
17937
|
+
var CoreBlockCompileResultSchema = object({
|
|
17938
|
+
ok: boolean(),
|
|
17939
|
+
/** Present when `ok` is false — the first error, in the author's words. */
|
|
17940
|
+
error: string().optional(),
|
|
17941
|
+
line: number().optional(),
|
|
17942
|
+
column: number().optional()
|
|
17943
|
+
});
|
|
17944
|
+
method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }), method(object({ blockId: string() }), object({ block: CoreBlockSchema.nullable() }), { auth: "admin" }), method(object({ block: CoreBlockInputSchema }), object({ block: CoreBlockSchema }), {
|
|
17945
|
+
kind: "mutation",
|
|
17946
|
+
auth: "admin",
|
|
17947
|
+
caller: "required"
|
|
17948
|
+
}), method(object({
|
|
17949
|
+
blockId: string(),
|
|
17950
|
+
block: CoreBlockInputSchema.partial()
|
|
17951
|
+
}), object({ block: CoreBlockSchema }), {
|
|
17952
|
+
kind: "mutation",
|
|
17953
|
+
auth: "admin",
|
|
17954
|
+
caller: "required"
|
|
17955
|
+
}), method(object({ blockId: string() }), object({ success: literal(true) }), {
|
|
17956
|
+
kind: "mutation",
|
|
17957
|
+
auth: "admin"
|
|
17958
|
+
}), method(object({
|
|
17959
|
+
blockId: string(),
|
|
17960
|
+
enabled: boolean()
|
|
17961
|
+
}), object({ block: CoreBlockSchema }), {
|
|
17962
|
+
kind: "mutation",
|
|
17963
|
+
auth: "admin"
|
|
17964
|
+
}), method(object({ code: string() }), CoreBlockCompileResultSchema, {
|
|
17965
|
+
kind: "mutation",
|
|
17966
|
+
auth: "admin"
|
|
17967
|
+
}), method(object({}), object({ libs: array(object({
|
|
17968
|
+
filePath: string(),
|
|
17969
|
+
content: string()
|
|
17970
|
+
})) }), { auth: "admin" });
|
|
17350
17971
|
/**
|
|
17351
17972
|
* Zod schemas for persisted record types.
|
|
17352
17973
|
*
|
|
@@ -17594,6 +18215,11 @@ var EventKindDescriptorSchema = object({
|
|
|
17594
18215
|
deviceId: number()
|
|
17595
18216
|
})
|
|
17596
18217
|
});
|
|
18218
|
+
/** One camera's event vocabulary, as returned by `listEventKindsBatch`. */
|
|
18219
|
+
var EventKindsForDeviceSchema = object({
|
|
18220
|
+
deviceId: number(),
|
|
18221
|
+
kinds: array(EventKindDescriptorSchema).readonly()
|
|
18222
|
+
});
|
|
17597
18223
|
var SensorEventSchema = object({
|
|
17598
18224
|
id: string(),
|
|
17599
18225
|
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
@@ -17850,6 +18476,19 @@ var MediaFileSchema = object({
|
|
|
17850
18476
|
sizeBytes: number(),
|
|
17851
18477
|
timestamp: number()
|
|
17852
18478
|
});
|
|
18479
|
+
/**
|
|
18480
|
+
* One media row WITHOUT its bytes.
|
|
18481
|
+
*
|
|
18482
|
+
* A track's media is 5-8 MB of base64 (measured: 7.67 MB across 23 files for a
|
|
18483
|
+
* 140 s track), and a client that renders tiles from the media data plane needs
|
|
18484
|
+
* to know only WHAT EXISTS — the bytes then arrive per tile, lazily, over HTTP
|
|
18485
|
+
* with an immutable cache, instead of all at once inside a tRPC response that
|
|
18486
|
+
* blocks the whole view.
|
|
18487
|
+
*
|
|
18488
|
+
* `sizeBytes` is carried because it is what lets a client decide between the
|
|
18489
|
+
* stored blob and a `?variant=thumb` rendering without fetching either.
|
|
18490
|
+
*/
|
|
18491
|
+
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
17853
18492
|
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
17854
18493
|
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
17855
18494
|
var DeviceEventQueryInput = object({
|
|
@@ -17999,7 +18638,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
17999
18638
|
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
18000
18639
|
kind: "mutation",
|
|
18001
18640
|
auth: "admin"
|
|
18002
|
-
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
18641
|
+
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
|
|
18003
18642
|
deviceId: number(),
|
|
18004
18643
|
since: number().optional(),
|
|
18005
18644
|
until: number().optional(),
|
|
@@ -18073,7 +18712,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
18073
18712
|
}), array(MediaFileSchema).readonly()), method(object({
|
|
18074
18713
|
trackId: string(),
|
|
18075
18714
|
kinds: array(MediaFileKindEnum).optional()
|
|
18076
|
-
}), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
|
|
18715
|
+
}), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
|
|
18077
18716
|
deviceId: number(),
|
|
18078
18717
|
timestamp: number(),
|
|
18079
18718
|
frameWidth: number(),
|
|
@@ -18703,6 +19342,28 @@ var ServerUpdateStateSchema = _enum([
|
|
|
18703
19342
|
"pending-restart",
|
|
18704
19343
|
"awaiting-confirmation"
|
|
18705
19344
|
]);
|
|
19345
|
+
var ImageContractSchema = object({
|
|
19346
|
+
state: _enum([
|
|
19347
|
+
"in-sync",
|
|
19348
|
+
"behind-patch",
|
|
19349
|
+
"behind-series",
|
|
19350
|
+
"ahead",
|
|
19351
|
+
"unknown"
|
|
19352
|
+
]),
|
|
19353
|
+
/** The baked seed closure version — the image/app-bundle fingerprint. */
|
|
19354
|
+
seedVersion: string().nullable(),
|
|
19355
|
+
/** Best-known version the deployment contract delivers today. */
|
|
19356
|
+
contractVersion: string().nullable(),
|
|
19357
|
+
/**
|
|
19358
|
+
* Where `contractVersion` came from: a real registry check (`registry`), or
|
|
19359
|
+
* the node's own running version (`running` — a node can never run code
|
|
19360
|
+
* newer than the newest release, so `seed < running` proves image staleness
|
|
19361
|
+
* even before any registry check has run).
|
|
19362
|
+
*/
|
|
19363
|
+
contractSource: _enum(["registry", "running"]).nullable(),
|
|
19364
|
+
/** One operator-grade sentence: this node runs image X; the contract says Y. */
|
|
19365
|
+
message: string()
|
|
19366
|
+
});
|
|
18706
19367
|
var ServerRollbackInfoSchema = object({
|
|
18707
19368
|
/** The version that failed (or was manually rolled back). */
|
|
18708
19369
|
fromVersion: string(),
|
|
@@ -18739,7 +19400,12 @@ var ServerPackageStatusSchema = object({
|
|
|
18739
19400
|
* versions are being IGNORED. Surfaced as a warning in the UI.
|
|
18740
19401
|
*/
|
|
18741
19402
|
stateFileCorrupt: boolean(),
|
|
18742
|
-
lastCheckedAtMs: number().nullable()
|
|
19403
|
+
lastCheckedAtMs: number().nullable(),
|
|
19404
|
+
/**
|
|
19405
|
+
* Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
|
|
19406
|
+
* version skew: an older provider's payload simply omits it.
|
|
19407
|
+
*/
|
|
19408
|
+
imageContract: ImageContractSchema.optional()
|
|
18743
19409
|
});
|
|
18744
19410
|
var ServerUpdateCheckResultSchema = object({
|
|
18745
19411
|
packageName: string(),
|
|
@@ -18774,101 +19440,6 @@ version: string().optional() }), ServerUpdateActionResultSchema, {
|
|
|
18774
19440
|
auth: "admin"
|
|
18775
19441
|
});
|
|
18776
19442
|
/**
|
|
18777
|
-
* Query filter for settings-store collections.
|
|
18778
|
-
*/
|
|
18779
|
-
var QueryFilterSchema = object({
|
|
18780
|
-
where: record(string(), unknown()).optional(),
|
|
18781
|
-
whereIn: record(string(), array(unknown())).optional(),
|
|
18782
|
-
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
18783
|
-
orderBy: object({
|
|
18784
|
-
field: string(),
|
|
18785
|
-
direction: _enum(["asc", "desc"])
|
|
18786
|
-
}).optional(),
|
|
18787
|
-
limit: number().optional(),
|
|
18788
|
-
offset: number().optional()
|
|
18789
|
-
});
|
|
18790
|
-
/** A single stored record: `{ id, data }`. */
|
|
18791
|
-
var SettingsRecordSchema = object({
|
|
18792
|
-
id: string(),
|
|
18793
|
-
data: record(string(), unknown())
|
|
18794
|
-
});
|
|
18795
|
-
/**
|
|
18796
|
-
* Column declaration for a structured (SQL-backed) collection.
|
|
18797
|
-
*
|
|
18798
|
-
* Logical types — the backend translates each to the matching SQLite
|
|
18799
|
-
* storage class and handles per-type marshaling:
|
|
18800
|
-
* - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
|
|
18801
|
-
* - `JSON` — TEXT under the hood; serialised on write, parsed on read
|
|
18802
|
-
* - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
|
|
18803
|
-
*/
|
|
18804
|
-
var CollectionColumnSchema = object({
|
|
18805
|
-
name: string(),
|
|
18806
|
-
type: _enum([
|
|
18807
|
-
"TEXT",
|
|
18808
|
-
"INTEGER",
|
|
18809
|
-
"REAL",
|
|
18810
|
-
"JSON",
|
|
18811
|
-
"BOOLEAN"
|
|
18812
|
-
]),
|
|
18813
|
-
primaryKey: boolean().optional(),
|
|
18814
|
-
notNull: boolean().optional(),
|
|
18815
|
-
unique: boolean().optional()
|
|
18816
|
-
});
|
|
18817
|
-
var CollectionIndexSchema = object({
|
|
18818
|
-
name: string(),
|
|
18819
|
-
columns: array(string()).readonly(),
|
|
18820
|
-
unique: boolean().optional()
|
|
18821
|
-
});
|
|
18822
|
-
method(object({
|
|
18823
|
-
namespace: string().optional(),
|
|
18824
|
-
collection: string(),
|
|
18825
|
-
key: string()
|
|
18826
|
-
}), unknown()), method(object({
|
|
18827
|
-
namespace: string().optional(),
|
|
18828
|
-
collection: string(),
|
|
18829
|
-
key: string(),
|
|
18830
|
-
value: unknown()
|
|
18831
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18832
|
-
namespace: string().optional(),
|
|
18833
|
-
collection: string(),
|
|
18834
|
-
filter: QueryFilterSchema.optional()
|
|
18835
|
-
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
18836
|
-
namespace: string().optional(),
|
|
18837
|
-
collection: string(),
|
|
18838
|
-
record: SettingsRecordSchema
|
|
18839
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18840
|
-
namespace: string().optional(),
|
|
18841
|
-
collection: string(),
|
|
18842
|
-
id: string(),
|
|
18843
|
-
data: record(string(), unknown())
|
|
18844
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18845
|
-
namespace: string().optional(),
|
|
18846
|
-
collection: string(),
|
|
18847
|
-
key: string()
|
|
18848
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18849
|
-
namespace: string().optional(),
|
|
18850
|
-
collection: string(),
|
|
18851
|
-
filter: QueryFilterSchema.optional()
|
|
18852
|
-
}), number()), method(object({
|
|
18853
|
-
namespace: string().optional(),
|
|
18854
|
-
collection: string(),
|
|
18855
|
-
field: string(),
|
|
18856
|
-
bucketSize: number().int().positive(),
|
|
18857
|
-
origin: number().int(),
|
|
18858
|
-
filter: QueryFilterSchema.optional()
|
|
18859
|
-
}), array(object({
|
|
18860
|
-
bucket: number().int(),
|
|
18861
|
-
count: number().int()
|
|
18862
|
-
})).readonly()), method(object({
|
|
18863
|
-
namespace: string().optional(),
|
|
18864
|
-
collection: string()
|
|
18865
|
-
}), boolean()), method(object({
|
|
18866
|
-
namespace: string().optional(),
|
|
18867
|
-
collection: string(),
|
|
18868
|
-
columns: array(CollectionColumnSchema).readonly(),
|
|
18869
|
-
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
18870
|
-
}), _void(), { kind: "mutation" });
|
|
18871
|
-
/**
|
|
18872
19443
|
* `smtp-provider` — pluggable email delivery surface.
|
|
18873
19444
|
*
|
|
18874
19445
|
* Collection cap: a deployment may install multiple SMTP relays (e.g.
|
|
@@ -22731,6 +23302,54 @@ Object.freeze({
|
|
|
22731
23302
|
addonId: null,
|
|
22732
23303
|
access: "create"
|
|
22733
23304
|
},
|
|
23305
|
+
"coreBlocks.compile": {
|
|
23306
|
+
capName: "core-blocks",
|
|
23307
|
+
capScope: "system",
|
|
23308
|
+
addonId: null,
|
|
23309
|
+
access: "create"
|
|
23310
|
+
},
|
|
23311
|
+
"coreBlocks.create": {
|
|
23312
|
+
capName: "core-blocks",
|
|
23313
|
+
capScope: "system",
|
|
23314
|
+
addonId: null,
|
|
23315
|
+
access: "create"
|
|
23316
|
+
},
|
|
23317
|
+
"coreBlocks.delete": {
|
|
23318
|
+
capName: "core-blocks",
|
|
23319
|
+
capScope: "system",
|
|
23320
|
+
addonId: null,
|
|
23321
|
+
access: "delete"
|
|
23322
|
+
},
|
|
23323
|
+
"coreBlocks.get": {
|
|
23324
|
+
capName: "core-blocks",
|
|
23325
|
+
capScope: "system",
|
|
23326
|
+
addonId: null,
|
|
23327
|
+
access: "view"
|
|
23328
|
+
},
|
|
23329
|
+
"coreBlocks.getTypeDefs": {
|
|
23330
|
+
capName: "core-blocks",
|
|
23331
|
+
capScope: "system",
|
|
23332
|
+
addonId: null,
|
|
23333
|
+
access: "view"
|
|
23334
|
+
},
|
|
23335
|
+
"coreBlocks.list": {
|
|
23336
|
+
capName: "core-blocks",
|
|
23337
|
+
capScope: "system",
|
|
23338
|
+
addonId: null,
|
|
23339
|
+
access: "view"
|
|
23340
|
+
},
|
|
23341
|
+
"coreBlocks.setEnabled": {
|
|
23342
|
+
capName: "core-blocks",
|
|
23343
|
+
capScope: "system",
|
|
23344
|
+
addonId: null,
|
|
23345
|
+
access: "create"
|
|
23346
|
+
},
|
|
23347
|
+
"coreBlocks.update": {
|
|
23348
|
+
capName: "core-blocks",
|
|
23349
|
+
capScope: "system",
|
|
23350
|
+
addonId: null,
|
|
23351
|
+
access: "create"
|
|
23352
|
+
},
|
|
22734
23353
|
"cover.close": {
|
|
22735
23354
|
capName: "cover",
|
|
22736
23355
|
capScope: "device",
|
|
@@ -22767,6 +23386,84 @@ Object.freeze({
|
|
|
22767
23386
|
addonId: null,
|
|
22768
23387
|
access: "view"
|
|
22769
23388
|
},
|
|
23389
|
+
"dataStoreProvider.count": {
|
|
23390
|
+
capName: "data-store-provider",
|
|
23391
|
+
capScope: "system",
|
|
23392
|
+
addonId: null,
|
|
23393
|
+
access: "view"
|
|
23394
|
+
},
|
|
23395
|
+
"dataStoreProvider.declareCollection": {
|
|
23396
|
+
capName: "data-store-provider",
|
|
23397
|
+
capScope: "system",
|
|
23398
|
+
addonId: null,
|
|
23399
|
+
access: "create"
|
|
23400
|
+
},
|
|
23401
|
+
"dataStoreProvider.delete": {
|
|
23402
|
+
capName: "data-store-provider",
|
|
23403
|
+
capScope: "system",
|
|
23404
|
+
addonId: null,
|
|
23405
|
+
access: "delete"
|
|
23406
|
+
},
|
|
23407
|
+
"dataStoreProvider.deleteWhere": {
|
|
23408
|
+
capName: "data-store-provider",
|
|
23409
|
+
capScope: "system",
|
|
23410
|
+
addonId: null,
|
|
23411
|
+
access: "delete"
|
|
23412
|
+
},
|
|
23413
|
+
"dataStoreProvider.get": {
|
|
23414
|
+
capName: "data-store-provider",
|
|
23415
|
+
capScope: "system",
|
|
23416
|
+
addonId: null,
|
|
23417
|
+
access: "view"
|
|
23418
|
+
},
|
|
23419
|
+
"dataStoreProvider.getEngineInfo": {
|
|
23420
|
+
capName: "data-store-provider",
|
|
23421
|
+
capScope: "system",
|
|
23422
|
+
addonId: null,
|
|
23423
|
+
access: "view"
|
|
23424
|
+
},
|
|
23425
|
+
"dataStoreProvider.histogram": {
|
|
23426
|
+
capName: "data-store-provider",
|
|
23427
|
+
capScope: "system",
|
|
23428
|
+
addonId: null,
|
|
23429
|
+
access: "view"
|
|
23430
|
+
},
|
|
23431
|
+
"dataStoreProvider.insert": {
|
|
23432
|
+
capName: "data-store-provider",
|
|
23433
|
+
capScope: "system",
|
|
23434
|
+
addonId: null,
|
|
23435
|
+
access: "create"
|
|
23436
|
+
},
|
|
23437
|
+
"dataStoreProvider.isEmpty": {
|
|
23438
|
+
capName: "data-store-provider",
|
|
23439
|
+
capScope: "system",
|
|
23440
|
+
addonId: null,
|
|
23441
|
+
access: "view"
|
|
23442
|
+
},
|
|
23443
|
+
"dataStoreProvider.query": {
|
|
23444
|
+
capName: "data-store-provider",
|
|
23445
|
+
capScope: "system",
|
|
23446
|
+
addonId: null,
|
|
23447
|
+
access: "view"
|
|
23448
|
+
},
|
|
23449
|
+
"dataStoreProvider.set": {
|
|
23450
|
+
capName: "data-store-provider",
|
|
23451
|
+
capScope: "system",
|
|
23452
|
+
addonId: null,
|
|
23453
|
+
access: "create"
|
|
23454
|
+
},
|
|
23455
|
+
"dataStoreProvider.update": {
|
|
23456
|
+
capName: "data-store-provider",
|
|
23457
|
+
capScope: "system",
|
|
23458
|
+
addonId: null,
|
|
23459
|
+
access: "create"
|
|
23460
|
+
},
|
|
23461
|
+
"dataStoreProvider.updateWhere": {
|
|
23462
|
+
capName: "data-store-provider",
|
|
23463
|
+
capScope: "system",
|
|
23464
|
+
addonId: null,
|
|
23465
|
+
access: "create"
|
|
23466
|
+
},
|
|
22770
23467
|
"dayNight.getOptions": {
|
|
22771
23468
|
capName: "day-night",
|
|
22772
23469
|
capScope: "device",
|
|
@@ -24561,12 +25258,24 @@ Object.freeze({
|
|
|
24561
25258
|
addonId: null,
|
|
24562
25259
|
access: "create"
|
|
24563
25260
|
},
|
|
25261
|
+
"notificationRules.cancelSnooze": {
|
|
25262
|
+
capName: "notification-rules",
|
|
25263
|
+
capScope: "system",
|
|
25264
|
+
addonId: null,
|
|
25265
|
+
access: "create"
|
|
25266
|
+
},
|
|
24564
25267
|
"notificationRules.createRule": {
|
|
24565
25268
|
capName: "notification-rules",
|
|
24566
25269
|
capScope: "system",
|
|
24567
25270
|
addonId: null,
|
|
24568
25271
|
access: "create"
|
|
24569
25272
|
},
|
|
25273
|
+
"notificationRules.createSnooze": {
|
|
25274
|
+
capName: "notification-rules",
|
|
25275
|
+
capScope: "system",
|
|
25276
|
+
addonId: null,
|
|
25277
|
+
access: "create"
|
|
25278
|
+
},
|
|
24570
25279
|
"notificationRules.deleteRule": {
|
|
24571
25280
|
capName: "notification-rules",
|
|
24572
25281
|
capScope: "system",
|
|
@@ -24597,6 +25306,12 @@ Object.freeze({
|
|
|
24597
25306
|
addonId: null,
|
|
24598
25307
|
access: "view"
|
|
24599
25308
|
},
|
|
25309
|
+
"notificationRules.listSnoozes": {
|
|
25310
|
+
capName: "notification-rules",
|
|
25311
|
+
capScope: "system",
|
|
25312
|
+
addonId: null,
|
|
25313
|
+
access: "view"
|
|
25314
|
+
},
|
|
24600
25315
|
"notificationRules.setRuleEnabled": {
|
|
24601
25316
|
capName: "notification-rules",
|
|
24602
25317
|
capScope: "system",
|
|
@@ -24801,6 +25516,12 @@ Object.freeze({
|
|
|
24801
25516
|
addonId: null,
|
|
24802
25517
|
access: "view"
|
|
24803
25518
|
},
|
|
25519
|
+
"pipelineAnalytics.listEventKindsBatch": {
|
|
25520
|
+
capName: "pipeline-analytics",
|
|
25521
|
+
capScope: "device",
|
|
25522
|
+
addonId: null,
|
|
25523
|
+
access: "view"
|
|
25524
|
+
},
|
|
24804
25525
|
"pipelineAnalytics.listOpsLog": {
|
|
24805
25526
|
capName: "pipeline-analytics",
|
|
24806
25527
|
capScope: "device",
|
|
@@ -24813,6 +25534,12 @@ Object.freeze({
|
|
|
24813
25534
|
addonId: null,
|
|
24814
25535
|
access: "view"
|
|
24815
25536
|
},
|
|
25537
|
+
"pipelineAnalytics.listTrackMedia": {
|
|
25538
|
+
capName: "pipeline-analytics",
|
|
25539
|
+
capScope: "device",
|
|
25540
|
+
addonId: null,
|
|
25541
|
+
access: "view"
|
|
25542
|
+
},
|
|
24816
25543
|
"pipelineAnalytics.listTracks": {
|
|
24817
25544
|
capName: "pipeline-analytics",
|
|
24818
25545
|
capScope: "device",
|
|
@@ -25845,6 +26572,12 @@ Object.freeze({
|
|
|
25845
26572
|
addonId: null,
|
|
25846
26573
|
access: "delete"
|
|
25847
26574
|
},
|
|
26575
|
+
"settingsStore.deleteWhere": {
|
|
26576
|
+
capName: "settings-store",
|
|
26577
|
+
capScope: "system",
|
|
26578
|
+
addonId: null,
|
|
26579
|
+
access: "delete"
|
|
26580
|
+
},
|
|
25848
26581
|
"settingsStore.get": {
|
|
25849
26582
|
capName: "settings-store",
|
|
25850
26583
|
capScope: "system",
|
|
@@ -25887,6 +26620,12 @@ Object.freeze({
|
|
|
25887
26620
|
addonId: null,
|
|
25888
26621
|
access: "create"
|
|
25889
26622
|
},
|
|
26623
|
+
"settingsStore.updateWhere": {
|
|
26624
|
+
capName: "settings-store",
|
|
26625
|
+
capScope: "system",
|
|
26626
|
+
addonId: null,
|
|
26627
|
+
access: "create"
|
|
26628
|
+
},
|
|
25890
26629
|
"smtpProvider.getStatus": {
|
|
25891
26630
|
capName: "smtp-provider",
|
|
25892
26631
|
capScope: "system",
|
|
@@ -28613,6 +29352,7 @@ var WEBHOOK_CAPS = {
|
|
|
28613
29352
|
max: 99
|
|
28614
29353
|
},
|
|
28615
29354
|
actions: 99,
|
|
29355
|
+
actionIcons: true,
|
|
28616
29356
|
levels: [
|
|
28617
29357
|
{
|
|
28618
29358
|
id: "p1",
|
|
@@ -29945,7 +30685,7 @@ function createMemorySettingsStorePort() {
|
|
|
29945
30685
|
* the store persists whatever it is handed verbatim.
|
|
29946
30686
|
*/
|
|
29947
30687
|
/** The default collection name (see `settings-store` addonId-prefix note). */
|
|
29948
|
-
var TARGETS_COLLECTION = "notification-targets";
|
|
30688
|
+
var TARGETS_COLLECTION = "notifiers:notification-targets";
|
|
29949
30689
|
/** KV JSON-blob shape — a single `data` column routes the row through the
|
|
29950
30690
|
* settings backend's canonical key/value path (id TEXT PK, data TEXT). */
|
|
29951
30691
|
var KV_BLOB_COLUMNS = [{
|