@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.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
//#region ../types/dist/event-category-
|
|
5
|
+
//#region ../types/dist/event-category-41fKf-q9.mjs
|
|
6
6
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
7
7
|
EventCategory["SystemBoot"] = "system.boot";
|
|
8
8
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -62,6 +62,26 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
62
62
|
*/
|
|
63
63
|
EventCategory["AddonRetryScheduled"] = "addon.retry-scheduled";
|
|
64
64
|
/**
|
|
65
|
+
* A liveness invariant of this node is false — it has no devices, every
|
|
66
|
+
* camera is offline, or a camera that was recording has stopped producing
|
|
67
|
+
* segments. Emitted by the `liveness-monitor` builtin, once per fault (the
|
|
68
|
+
* `findingId` is stable across ticks), only after its boot grace period.
|
|
69
|
+
* AlertCenter raises a persistent operator-visible alert.
|
|
70
|
+
*
|
|
71
|
+
* It exists because on 2026-08-03 the hub ran four hours with zero devices
|
|
72
|
+
* and zero recordings while every surface stayed quiet.
|
|
73
|
+
*
|
|
74
|
+
* Payload: `{ findingId, severity, title, message, deviceId? }`.
|
|
75
|
+
*/
|
|
76
|
+
EventCategory["SystemLivenessFailed"] = "system.liveness-failed";
|
|
77
|
+
/**
|
|
78
|
+
* A previously reported liveness fault is true again. AlertCenter dismisses
|
|
79
|
+
* the matching `SystemLivenessFailed` alert.
|
|
80
|
+
*
|
|
81
|
+
* Payload: `{ findingId }`.
|
|
82
|
+
*/
|
|
83
|
+
EventCategory["SystemLivenessRecovered"] = "system.liveness-recovered";
|
|
84
|
+
/**
|
|
65
85
|
* Monitor is attempting to reload a failed addon NOW. UI uses this
|
|
66
86
|
* to show a spinner during the retry attempt. Same transient nature
|
|
67
87
|
* as AddonRetryScheduled.
|
|
@@ -7429,14 +7449,7 @@ var RecordingConfigSchema = object({
|
|
|
7429
7449
|
* windows only — existing sheets are immutable, and each window's index
|
|
7430
7450
|
* carries its own tile dims so mixed-preset history renders correctly.
|
|
7431
7451
|
*/
|
|
7432
|
-
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7433
|
-
/**
|
|
7434
|
-
* OPT-IN thumbnail-strip generation for this camera: every keyframe of the
|
|
7435
|
-
* low recording saved as a JPEG (the fast-drag scrub depth), a derived
|
|
7436
|
-
* cache that eviction reclaims with the footage. Absent/false = no strips
|
|
7437
|
-
* are written and scrub reads exact keyframes at every velocity.
|
|
7438
|
-
*/
|
|
7439
|
-
stripsEnabled: boolean().optional()
|
|
7452
|
+
scrubThumbnails: ScrubThumbnailPresetSchema.optional()
|
|
7440
7453
|
}).strict();
|
|
7441
7454
|
/**
|
|
7442
7455
|
* Ops-log — the durable, append-only operations audit shared by the
|
|
@@ -7498,7 +7511,7 @@ var OpsLogQueryInputSchema = object({
|
|
|
7498
7511
|
/**
|
|
7499
7512
|
* Entity-relocation job state (storage entity-routing spec, Phase 4).
|
|
7500
7513
|
*
|
|
7501
|
-
* One shape shared by the recorder's `relocateFootage` (segments
|
|
7514
|
+
* One shape shared by the recorder's `relocateFootage` (segments) and
|
|
7502
7515
|
* pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
|
|
7503
7516
|
* page renders both movers with one component. Jobs are in-RAM (a restart
|
|
7504
7517
|
* forgets them — re-running is safe by construction: copy-if-absent, delete
|
|
@@ -7520,7 +7533,7 @@ var RelocateJobSchema = object({
|
|
|
7520
7533
|
toLocationId: string(),
|
|
7521
7534
|
/** Scoped device, or null = every device. */
|
|
7522
7535
|
deviceId: number().nullable(),
|
|
7523
|
-
/** What the job moves (owner-addon specific: segments
|
|
7536
|
+
/** What the job moves (owner-addon specific: segments or media). */
|
|
7524
7537
|
entities: array(string()),
|
|
7525
7538
|
filesMoved: number().int(),
|
|
7526
7539
|
bytesMoved: number().int(),
|
|
@@ -7534,7 +7547,7 @@ var RelocateFootageInputSchema = object({
|
|
|
7534
7547
|
deviceId: number().optional(),
|
|
7535
7548
|
fromLocationId: string(),
|
|
7536
7549
|
toLocationId: string(),
|
|
7537
|
-
entities: array(_enum(["segments"
|
|
7550
|
+
entities: array(_enum(["segments"])).optional(),
|
|
7538
7551
|
/** Copy throttle in MB/s (default 40) — the drain is a background chore,
|
|
7539
7552
|
* never allowed to starve live writers. */
|
|
7540
7553
|
throttleMbps: number().min(1).max(1e3).optional()
|
|
@@ -9196,8 +9209,11 @@ function prepareNotification(caps, n) {
|
|
|
9196
9209
|
});
|
|
9197
9210
|
}
|
|
9198
9211
|
const inActions = n.actions ?? [];
|
|
9199
|
-
const
|
|
9200
|
-
if (inActions.length >
|
|
9212
|
+
const kept = inActions.slice(0, Math.max(0, caps.actions));
|
|
9213
|
+
if (inActions.length > kept.length) dropped.push("actions");
|
|
9214
|
+
const iconsSupported = caps.actionIcons === true;
|
|
9215
|
+
if (!iconsSupported && kept.some((a) => a.icon !== void 0)) dropped.push("actionIcons");
|
|
9216
|
+
const actions = iconsSupported ? kept : kept.map(({ icon: _icon, ...rest }) => rest);
|
|
9201
9217
|
let clickUrl = null;
|
|
9202
9218
|
if (n.clickUrl !== void 0) if (caps.clickUrl) clickUrl = n.clickUrl;
|
|
9203
9219
|
else dropped.push("clickUrl");
|
|
@@ -9296,160 +9312,552 @@ var MaskGridDimsSchema = object({
|
|
|
9296
9312
|
height: number()
|
|
9297
9313
|
});
|
|
9298
9314
|
/**
|
|
9299
|
-
* notification-
|
|
9300
|
-
*
|
|
9301
|
-
* Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
|
|
9302
|
-
* (operator decisions D-1/D-2/D-3 are binding):
|
|
9315
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
9303
9316
|
*
|
|
9304
|
-
*
|
|
9305
|
-
*
|
|
9306
|
-
*
|
|
9307
|
-
*
|
|
9308
|
-
*
|
|
9309
|
-
*
|
|
9310
|
-
* `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
|
|
9311
|
-
* `delivery: 'track-end'` evaluates the finalized track record at close.
|
|
9312
|
-
* - DISPATCH stays behind `notification-output` (rules reference targets
|
|
9313
|
-
* by id; per-backend params are a passthrough blob capped by the
|
|
9314
|
-
* target kind's own caps/degrade engine).
|
|
9317
|
+
* Apprise-derived model (see
|
|
9318
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
9319
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
9320
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
9321
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
9322
|
+
* message to what the kind supports — callers never special-case a service.
|
|
9315
9323
|
*
|
|
9316
|
-
*
|
|
9317
|
-
*
|
|
9318
|
-
*
|
|
9319
|
-
*
|
|
9320
|
-
*
|
|
9321
|
-
*
|
|
9322
|
-
*
|
|
9324
|
+
* DESIGN DECISIONS (locked):
|
|
9325
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
9326
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
9327
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
9328
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
9329
|
+
* alternative would fork the UI per addon and cannot host the
|
|
9330
|
+
* discovery→adopt flow.
|
|
9331
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
9332
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
9333
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
9334
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
9335
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
9336
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
9337
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
9338
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
9339
|
+
* base64 fallback needed.
|
|
9323
9340
|
*
|
|
9324
|
-
*
|
|
9325
|
-
* `
|
|
9326
|
-
*
|
|
9341
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
9342
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
9343
|
+
* admin "Integrations" page.
|
|
9327
9344
|
*/
|
|
9328
9345
|
/**
|
|
9329
|
-
*
|
|
9330
|
-
*
|
|
9331
|
-
* - `immediate` ↔ object-event persist (lowest-latency detection burst)
|
|
9332
|
-
* - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
|
|
9333
|
-
* - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
|
|
9334
|
-
* change of a LINKED device, one row per linked camera)
|
|
9335
|
-
* - `package-event` ↔ PackageDropDetector object-event insert (a `package`
|
|
9336
|
-
* delivery / pick-up)
|
|
9337
|
-
*
|
|
9338
|
-
* `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
|
|
9339
|
-
* `package-event` are pure trigger kinds (no urgency dimension). Extending
|
|
9340
|
-
* this one field keeps the schema additive — a rule still declares exactly
|
|
9341
|
-
* one trigger.
|
|
9346
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
9347
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
9342
9348
|
*/
|
|
9343
|
-
var
|
|
9344
|
-
"
|
|
9345
|
-
"
|
|
9346
|
-
"
|
|
9347
|
-
"
|
|
9349
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
9350
|
+
"image",
|
|
9351
|
+
"video",
|
|
9352
|
+
"gif",
|
|
9353
|
+
"audio",
|
|
9354
|
+
"icon"
|
|
9348
9355
|
]);
|
|
9349
|
-
/** Weekly schedule — OR of windows; absence on the rule = always active. */
|
|
9350
|
-
var NcScheduleSchema = object({
|
|
9351
|
-
windows: array(object({
|
|
9352
|
-
/** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
|
|
9353
|
-
days: array(number().int().min(0).max(6)).min(1),
|
|
9354
|
-
startMinute: number().int().min(0).max(1439),
|
|
9355
|
-
endMinute: number().int().min(0).max(1439)
|
|
9356
|
-
})).min(1),
|
|
9357
|
-
/** IANA timezone; default = hub host timezone. */
|
|
9358
|
-
timezone: string().optional(),
|
|
9359
|
-
/** Active OUTSIDE the windows (e.g. "only outside business hours"). */
|
|
9360
|
-
invert: boolean().optional()
|
|
9361
|
-
});
|
|
9362
|
-
/** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
|
|
9363
|
-
var NcPlateMatcherSchema = object({
|
|
9364
|
-
values: array(string().min(1)).min(1),
|
|
9365
|
-
/** Max Levenshtein distance after normalization (uppercase alphanumeric). */
|
|
9366
|
-
maxDistance: number().int().min(0).max(3).default(1)
|
|
9367
|
-
});
|
|
9368
9356
|
/**
|
|
9369
|
-
*
|
|
9370
|
-
*
|
|
9371
|
-
*
|
|
9372
|
-
*
|
|
9373
|
-
* - `became-free` — count crossed ≥ `count` → below it
|
|
9374
|
-
* - `>=` / `<=` — count is at/over or at/under `count`
|
|
9375
|
-
* `sustainSeconds` requires the condition hold continuously that long
|
|
9376
|
-
* before firing (debounces flicker; 0 = fire on the first matching edge).
|
|
9377
|
-
* Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
|
|
9378
|
-
* the condition never matches. Confirmed edge-state survives addon restarts
|
|
9379
|
-
* (declared SQLite collection, reseeded on boot).
|
|
9357
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
9358
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
9359
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
9360
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
9380
9361
|
*/
|
|
9381
|
-
var
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
sustainSeconds: number().int().min(0).max(3600).default(15)
|
|
9394
|
-
});
|
|
9362
|
+
var AttachmentSchema = object({
|
|
9363
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
9364
|
+
url: string().optional(),
|
|
9365
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
9366
|
+
mime: string().optional(),
|
|
9367
|
+
name: string().optional()
|
|
9368
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
9369
|
+
var NotificationFormatSchema = _enum([
|
|
9370
|
+
"text",
|
|
9371
|
+
"markdown",
|
|
9372
|
+
"html"
|
|
9373
|
+
]);
|
|
9395
9374
|
/**
|
|
9396
|
-
*
|
|
9375
|
+
* The CLOSED icon vocabulary an action button may use.
|
|
9397
9376
|
*
|
|
9398
|
-
*
|
|
9399
|
-
*
|
|
9400
|
-
*
|
|
9401
|
-
*
|
|
9402
|
-
*
|
|
9403
|
-
* an operator who never asked for exits must not start receiving them.
|
|
9404
|
-
* - `exit` — ONLY an exit crossing. A record that is not a crossing at all
|
|
9405
|
-
* fails closed, because "the car left the drive" is a question about a
|
|
9406
|
-
* boundary, not about a detection.
|
|
9407
|
-
* - `any` — no direction filter; entries, exits and non-crossings alike.
|
|
9377
|
+
* A closed set, not a free string, and that is the whole point: an arbitrary
|
|
9378
|
+
* icon name is one that ntfy renders, zentik silently drops, and nobody
|
|
9379
|
+
* notices — the same class of gap as a zone vocabulary nothing produced
|
|
9380
|
+
* ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
|
|
9381
|
+
* declares `actionIcons: false` and the degrade engine strips the field.
|
|
9408
9382
|
*
|
|
9409
|
-
*
|
|
9410
|
-
*
|
|
9411
|
-
*
|
|
9383
|
+
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
9384
|
+
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
9385
|
+
* differently.
|
|
9412
9386
|
*/
|
|
9413
|
-
var
|
|
9414
|
-
"
|
|
9415
|
-
"
|
|
9416
|
-
"
|
|
9387
|
+
var NotificationActionIconSchema = _enum([
|
|
9388
|
+
"acknowledge",
|
|
9389
|
+
"dismiss",
|
|
9390
|
+
"silence",
|
|
9391
|
+
"view",
|
|
9392
|
+
"play",
|
|
9393
|
+
"open",
|
|
9394
|
+
"close",
|
|
9395
|
+
"lock",
|
|
9396
|
+
"unlock",
|
|
9397
|
+
"arm",
|
|
9398
|
+
"disarm",
|
|
9399
|
+
"light",
|
|
9400
|
+
"alert"
|
|
9417
9401
|
]);
|
|
9418
|
-
/**
|
|
9419
|
-
var
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9402
|
+
/** A single tap-through action button. */
|
|
9403
|
+
var NotificationActionSchema = object({
|
|
9404
|
+
id: string(),
|
|
9405
|
+
label: string(),
|
|
9406
|
+
url: string().optional(),
|
|
9407
|
+
/** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
|
|
9408
|
+
icon: NotificationActionIconSchema.optional(),
|
|
9409
|
+
/**
|
|
9410
|
+
* Renders in a warning style where the notifier supports it.
|
|
9411
|
+
*
|
|
9412
|
+
* A HINT, never a gate. The callback's authority is its token and nothing
|
|
9413
|
+
* else — see `notification-center/action-token.ts` for what that does and
|
|
9414
|
+
* does not buy.
|
|
9415
|
+
*/
|
|
9416
|
+
destructive: boolean().optional()
|
|
9423
9417
|
});
|
|
9424
9418
|
/**
|
|
9425
|
-
* The
|
|
9426
|
-
*
|
|
9419
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
9420
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
9421
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
9422
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
9423
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
9424
|
+
* `priority` for that one target.
|
|
9427
9425
|
*/
|
|
9428
|
-
var
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9426
|
+
var NotificationSchema = object({
|
|
9427
|
+
body: string(),
|
|
9428
|
+
title: string().optional(),
|
|
9429
|
+
format: NotificationFormatSchema.default("text"),
|
|
9430
|
+
priority: number().int().min(1).max(5).default(3),
|
|
9431
|
+
level: string().optional(),
|
|
9432
|
+
attachments: array(AttachmentSchema).optional(),
|
|
9433
|
+
clickUrl: string().optional(),
|
|
9434
|
+
actions: array(NotificationActionSchema).optional(),
|
|
9435
|
+
sound: string().optional(),
|
|
9436
|
+
ttl: number().optional(),
|
|
9437
|
+
tag: string().optional(),
|
|
9438
|
+
deviceId: number().optional(),
|
|
9439
|
+
eventId: string().optional(),
|
|
9440
|
+
metadata: record(string(), unknown()).optional()
|
|
9441
|
+
});
|
|
9442
|
+
/** One declared native severity/priority level for a kind. */
|
|
9443
|
+
var TargetKindLevelSchema = object({
|
|
9444
|
+
id: string(),
|
|
9445
|
+
label: string(),
|
|
9446
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
9447
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
9448
|
+
flags: object({
|
|
9449
|
+
critical: boolean().optional(),
|
|
9450
|
+
silent: boolean().optional(),
|
|
9451
|
+
noPush: boolean().optional()
|
|
9452
|
+
}).optional(),
|
|
9453
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
9454
|
+
requires: array(string()).optional(),
|
|
9455
|
+
description: string().optional()
|
|
9456
|
+
});
|
|
9457
|
+
/** The full capability block consulted before dispatch. */
|
|
9458
|
+
var TargetKindCapsSchema = object({
|
|
9459
|
+
attachments: object({
|
|
9460
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
9461
|
+
mode: _enum([
|
|
9462
|
+
"url",
|
|
9463
|
+
"bytes",
|
|
9464
|
+
"both"
|
|
9465
|
+
]),
|
|
9466
|
+
max: number().int().nonnegative(),
|
|
9467
|
+
maxBytes: number().int().positive().optional()
|
|
9468
|
+
}),
|
|
9469
|
+
/** Max action buttons (0 = none). */
|
|
9470
|
+
actions: number().int().nonnegative(),
|
|
9448
9471
|
/**
|
|
9449
|
-
*
|
|
9450
|
-
*
|
|
9472
|
+
* Whether this kind renders a per-action ICON.
|
|
9473
|
+
*
|
|
9474
|
+
* `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
|
|
9475
|
+
* run on the addon cap path — three production failures in one day taught
|
|
9476
|
+
* this repo that once. Absent is read as false by the degrade engine, which
|
|
9477
|
+
* is the safe direction: an icon that is not rendered costs nothing, an icon
|
|
9478
|
+
* assumed and dropped costs the operator's trust in the field.
|
|
9451
9479
|
*/
|
|
9452
|
-
|
|
9480
|
+
actionIcons: boolean().optional(),
|
|
9481
|
+
levels: array(TargetKindLevelSchema),
|
|
9482
|
+
format: array(NotificationFormatSchema),
|
|
9483
|
+
clickUrl: boolean(),
|
|
9484
|
+
sound: boolean(),
|
|
9485
|
+
ttl: boolean(),
|
|
9486
|
+
bodyMaxLen: number().int().positive()
|
|
9487
|
+
});
|
|
9488
|
+
/**
|
|
9489
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
9490
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
9491
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
9492
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
9493
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
9494
|
+
*/
|
|
9495
|
+
var ConfigSchemaPassthrough$1 = unknown();
|
|
9496
|
+
var TargetKindSchema = object({
|
|
9497
|
+
kind: string(),
|
|
9498
|
+
label: string(),
|
|
9499
|
+
icon: string(),
|
|
9500
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
9501
|
+
addonId: string(),
|
|
9502
|
+
/**
|
|
9503
|
+
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
9504
|
+
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
9505
|
+
* when the addon bundles no icon for that kind — the client then falls back
|
|
9506
|
+
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
9507
|
+
*
|
|
9508
|
+
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
9509
|
+
* client, and a native client joins it onto its own hub base.
|
|
9510
|
+
*
|
|
9511
|
+
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
9512
|
+
* field that survived only because the runtime cap-router forwards provider
|
|
9513
|
+
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
9514
|
+
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
9515
|
+
* broken silently the moment output validation was tightened anywhere.
|
|
9516
|
+
*/
|
|
9517
|
+
iconUrl: string().optional(),
|
|
9518
|
+
/**
|
|
9519
|
+
* Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
|
|
9520
|
+
*
|
|
9521
|
+
* The server knows this and therefore says it, because the client cannot
|
|
9522
|
+
* safely guess: a React-Native client renders SVG and raster through two
|
|
9523
|
+
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
9524
|
+
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
9525
|
+
* placeholder glyph for every vector icon while the web build looked fine.
|
|
9526
|
+
*
|
|
9527
|
+
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
9528
|
+
* not been updated — a client that cannot determine the type should prefer
|
|
9529
|
+
* its raster path, which is the safe default for an unknown image.
|
|
9530
|
+
*/
|
|
9531
|
+
iconMediaType: string().optional(),
|
|
9532
|
+
configSchema: ConfigSchemaPassthrough$1,
|
|
9533
|
+
supportsDiscovery: boolean(),
|
|
9534
|
+
caps: TargetKindCapsSchema
|
|
9535
|
+
});
|
|
9536
|
+
/**
|
|
9537
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
9538
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
9539
|
+
* round-trip a stored secret to the UI.
|
|
9540
|
+
*/
|
|
9541
|
+
var TargetSchema = object({
|
|
9542
|
+
id: string(),
|
|
9543
|
+
name: string(),
|
|
9544
|
+
kind: string(),
|
|
9545
|
+
addonId: string(),
|
|
9546
|
+
enabled: boolean(),
|
|
9547
|
+
config: record(string(), unknown())
|
|
9548
|
+
});
|
|
9549
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
9550
|
+
var DiscoveredTargetSchema = object({
|
|
9551
|
+
kind: string(),
|
|
9552
|
+
suggestedName: string(),
|
|
9553
|
+
config: record(string(), unknown())
|
|
9554
|
+
});
|
|
9555
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
9556
|
+
var RenderedAsSchema = object({
|
|
9557
|
+
level: string(),
|
|
9558
|
+
format: NotificationFormatSchema,
|
|
9559
|
+
attachmentsSent: number().int().nonnegative(),
|
|
9560
|
+
actionsSent: number().int().nonnegative(),
|
|
9561
|
+
truncated: boolean(),
|
|
9562
|
+
dropped: array(string())
|
|
9563
|
+
});
|
|
9564
|
+
var SendResultSchema = object({
|
|
9565
|
+
success: boolean(),
|
|
9566
|
+
error: string().optional(),
|
|
9567
|
+
renderedAs: RenderedAsSchema.optional()
|
|
9568
|
+
});
|
|
9569
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
9570
|
+
var TestResultSchema = SendResultSchema;
|
|
9571
|
+
var notificationOutputCapability = {
|
|
9572
|
+
name: "notification-output",
|
|
9573
|
+
scope: "system",
|
|
9574
|
+
mode: "collection",
|
|
9575
|
+
methods: {
|
|
9576
|
+
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
9577
|
+
listTargets: method(object({}), array(TargetSchema)),
|
|
9578
|
+
discoverTargets: method(object({
|
|
9579
|
+
kind: string(),
|
|
9580
|
+
config: record(string(), unknown()).optional()
|
|
9581
|
+
}), array(DiscoveredTargetSchema)),
|
|
9582
|
+
send: method(object({
|
|
9583
|
+
targetId: string(),
|
|
9584
|
+
notification: NotificationSchema
|
|
9585
|
+
}), SendResultSchema, { kind: "mutation" }),
|
|
9586
|
+
testTarget: method(object({
|
|
9587
|
+
targetId: string(),
|
|
9588
|
+
sample: NotificationSchema.optional()
|
|
9589
|
+
}), TestResultSchema, { kind: "mutation" }),
|
|
9590
|
+
upsertTarget: method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }),
|
|
9591
|
+
deleteTarget: method(object({ targetId: string() }), _void(), { kind: "mutation" }),
|
|
9592
|
+
setTargetEnabled: method(object({
|
|
9593
|
+
targetId: string(),
|
|
9594
|
+
enabled: boolean()
|
|
9595
|
+
}), _void(), { kind: "mutation" })
|
|
9596
|
+
}
|
|
9597
|
+
};
|
|
9598
|
+
/**
|
|
9599
|
+
* notification-rules — the Notification Center rule surface (P1 core).
|
|
9600
|
+
*
|
|
9601
|
+
* Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
|
|
9602
|
+
* (operator decisions D-1/D-2/D-3 are binding):
|
|
9603
|
+
*
|
|
9604
|
+
* - D-2: rule EVALUATION lives in `addon-post-analysis` (the
|
|
9605
|
+
* `notification-center` module), hooked on the durable persistence
|
|
9606
|
+
* moments (object-event insert, TrackCloser.closeExpired) with a
|
|
9607
|
+
* persisted outbox + retry — never the lossy telemetry bus (D8).
|
|
9608
|
+
* - D-3: urgency belongs to the RULE. `delivery: 'immediate'` fires on the
|
|
9609
|
+
* FIRST persisted detection matching the conditions (per-track dedup,
|
|
9610
|
+
* `maxPerTrack` fixed at 1 — see {@link NC_MAX_PER_TRACK_IMMEDIATE});
|
|
9611
|
+
* `delivery: 'track-end'` evaluates the finalized track record at close.
|
|
9612
|
+
* - DISPATCH stays behind `notification-output` (rules reference targets
|
|
9613
|
+
* by id; per-backend params are a passthrough blob capped by the
|
|
9614
|
+
* target kind's own caps/degrade engine).
|
|
9615
|
+
*
|
|
9616
|
+
* P1 scope: admin-authored rules only (`createdBy` stamped from the
|
|
9617
|
+
* server-injected caller identity — the first `caller: 'required'`
|
|
9618
|
+
* adopter). The P1 condition subset is: devices, classes(+exclude),
|
|
9619
|
+
* minConfidence, admin zones (any/all + exclude), weekly schedule
|
|
9620
|
+
* windows, and the optional label/identity/plate matchers. User rules,
|
|
9621
|
+
* private zones, per-recipient fan-out and the wider condition table are
|
|
9622
|
+
* P2+ (see spec §7).
|
|
9623
|
+
*
|
|
9624
|
+
* All schemas here are the single source of truth — `NcRule` etc. are
|
|
9625
|
+
* `z.infer` exports; no duplicate interfaces (the advanced-notifier
|
|
9626
|
+
* schema/interface drift is explicitly not repeated).
|
|
9627
|
+
*/
|
|
9628
|
+
/**
|
|
9629
|
+
* D-3: the trigger/urgency of a rule — which persistence moment evaluates it.
|
|
9630
|
+
* The value maps 1:1 onto the evaluated record kind:
|
|
9631
|
+
* - `immediate` ↔ object-event persist (lowest-latency detection burst)
|
|
9632
|
+
* - `track-end` ↔ TrackCloser.closeExpired (finalized track record)
|
|
9633
|
+
* - `device-event` ↔ SensorEventStore insert (doorbell press / sensor state
|
|
9634
|
+
* change of a LINKED device, one row per linked camera)
|
|
9635
|
+
* - `package-event` ↔ PackageDropDetector object-event insert (a `package`
|
|
9636
|
+
* delivery / pick-up)
|
|
9637
|
+
*
|
|
9638
|
+
* `immediate`/`track-end` carry the D-3 urgency semantics; `device-event`/
|
|
9639
|
+
* `package-event` are pure trigger kinds (no urgency dimension). Extending
|
|
9640
|
+
* this one field keeps the schema additive — a rule still declares exactly
|
|
9641
|
+
* one trigger.
|
|
9642
|
+
*/
|
|
9643
|
+
var NcDeliverySchema = _enum([
|
|
9644
|
+
"immediate",
|
|
9645
|
+
"track-end",
|
|
9646
|
+
"device-event",
|
|
9647
|
+
"package-event"
|
|
9648
|
+
]);
|
|
9649
|
+
/** Weekly schedule — OR of windows; absence on the rule = always active. */
|
|
9650
|
+
var NcScheduleSchema = object({
|
|
9651
|
+
windows: array(object({
|
|
9652
|
+
/** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
|
|
9653
|
+
days: array(number().int().min(0).max(6)).min(1),
|
|
9654
|
+
startMinute: number().int().min(0).max(1439),
|
|
9655
|
+
endMinute: number().int().min(0).max(1439)
|
|
9656
|
+
})).min(1),
|
|
9657
|
+
/** IANA timezone; default = hub host timezone. */
|
|
9658
|
+
timezone: string().optional(),
|
|
9659
|
+
/** Active OUTSIDE the windows (e.g. "only outside business hours"). */
|
|
9660
|
+
invert: boolean().optional()
|
|
9661
|
+
});
|
|
9662
|
+
/** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
|
|
9663
|
+
var NcPlateMatcherSchema = object({
|
|
9664
|
+
values: array(string().min(1)).min(1),
|
|
9665
|
+
/** Max Levenshtein distance after normalization (uppercase alphanumeric). */
|
|
9666
|
+
maxDistance: number().int().min(0).max(3).default(1)
|
|
9667
|
+
});
|
|
9668
|
+
/**
|
|
9669
|
+
* Occupancy condition (DEVICE-EVENT trigger). Fires on a ZoneAnalytics
|
|
9670
|
+
* occupancy edge for a device — optionally narrowed to a single admin
|
|
9671
|
+
* `zoneId` and/or object `className`. `op` selects the edge/threshold:
|
|
9672
|
+
* - `became-occupied` (default) — count crossed 0 → ≥ `count`
|
|
9673
|
+
* - `became-free` — count crossed ≥ `count` → below it
|
|
9674
|
+
* - `>=` / `<=` — count is at/over or at/under `count`
|
|
9675
|
+
* `sustainSeconds` requires the condition hold continuously that long
|
|
9676
|
+
* before firing (debounces flicker; 0 = fire on the first matching edge).
|
|
9677
|
+
* Fail-closed: no ZoneAnalytics snapshot / missing zone / null snapshot ⇒
|
|
9678
|
+
* the condition never matches. Confirmed edge-state survives addon restarts
|
|
9679
|
+
* (declared SQLite collection, reseeded on boot).
|
|
9680
|
+
*/
|
|
9681
|
+
var NcOccupancyConditionSchema = object({
|
|
9682
|
+
/** Admin zone id to scope the count to; absent = whole-frame occupancy. */
|
|
9683
|
+
zoneId: string().optional(),
|
|
9684
|
+
/** Object class to count; absent = any class. */
|
|
9685
|
+
className: string().optional(),
|
|
9686
|
+
op: _enum([
|
|
9687
|
+
"became-occupied",
|
|
9688
|
+
"became-free",
|
|
9689
|
+
">=",
|
|
9690
|
+
"<="
|
|
9691
|
+
]).default("became-occupied"),
|
|
9692
|
+
count: number().int().min(0).default(1),
|
|
9693
|
+
sustainSeconds: number().int().min(0).max(3600).default(15)
|
|
9694
|
+
});
|
|
9695
|
+
/**
|
|
9696
|
+
* Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
|
|
9697
|
+
*
|
|
9698
|
+
* The values are not symmetric, and deliberately so — the absent value has to
|
|
9699
|
+
* mean exactly what every rule authored before this condition existed already
|
|
9700
|
+
* does:
|
|
9701
|
+
* - `enter` — entries and every NON-crossing record (movement state,
|
|
9702
|
+
* package, sensor). Exits are rejected. **This is the absent behaviour**:
|
|
9703
|
+
* an operator who never asked for exits must not start receiving them.
|
|
9704
|
+
* - `exit` — ONLY an exit crossing. A record that is not a crossing at all
|
|
9705
|
+
* fails closed, because "the car left the drive" is a question about a
|
|
9706
|
+
* boundary, not about a detection.
|
|
9707
|
+
* - `any` — no direction filter; entries, exits and non-crossings alike.
|
|
9708
|
+
*
|
|
9709
|
+
* A rule asking for a direction should normally also scope `zones`, which the
|
|
9710
|
+
* engine evaluates against the crossed zone as well as the current membership
|
|
9711
|
+
* (an exit's membership no longer contains the zone it just left).
|
|
9712
|
+
*/
|
|
9713
|
+
var NcCrossingSchema = _enum([
|
|
9714
|
+
"enter",
|
|
9715
|
+
"exit",
|
|
9716
|
+
"any"
|
|
9717
|
+
]);
|
|
9718
|
+
/** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
|
|
9719
|
+
var NcZoneConditionSchema = object({
|
|
9720
|
+
ids: array(string().min(1)).min(1),
|
|
9721
|
+
/** Quantifier over `ids` — at least one / every one visited. */
|
|
9722
|
+
match: _enum(["any", "all"]).default("any")
|
|
9723
|
+
});
|
|
9724
|
+
/**
|
|
9725
|
+
* The P1 condition set — a flat AND of groups; absent group = pass;
|
|
9726
|
+
* membership lists are OR within the list (spec §2.3).
|
|
9727
|
+
*/
|
|
9728
|
+
/**
|
|
9729
|
+
* What a rule may actuate.
|
|
9730
|
+
*
|
|
9731
|
+
* **No hand-maintained allowlist** (operator decision, and the right one — a
|
|
9732
|
+
* written list of methods is a third parallel map to keep aligned, and this
|
|
9733
|
+
* repo has paid for those). The boundary instead comes from a property the
|
|
9734
|
+
* capabilities already carry: an action may target only a **device-scoped**
|
|
9735
|
+
* capability method.
|
|
9736
|
+
*
|
|
9737
|
+
* That is not decoration. A rule can be authored by a NON-ADMIN — personal
|
|
9738
|
+
* rules are a supported flow — and the executor runs with the addon's
|
|
9739
|
+
* privileges, so an unbounded action is an arbitrary RPC channel with a
|
|
9740
|
+
* privilege escalation attached. Restricting to device scope excludes the
|
|
9741
|
+
* system caps (`device-manager.removeDevice` and friends) by construction,
|
|
9742
|
+
* costs nothing to maintain, and cannot rot: a cap that stops being
|
|
9743
|
+
* device-scoped stops being actuatable in the same change.
|
|
9744
|
+
*
|
|
9745
|
+
* The executor enforces it; {@link NcRuleActionSchema} carries the intent.
|
|
9746
|
+
*/
|
|
9747
|
+
/**
|
|
9748
|
+
* One step of a sequence.
|
|
9749
|
+
*
|
|
9750
|
+
* `wait` is a first-class step rather than a property of the next action: it is
|
|
9751
|
+
* what makes a sequence a SEQUENCE and not a list — "unlock, wait 5s, open"
|
|
9752
|
+
* cannot be expressed otherwise.
|
|
9753
|
+
*/
|
|
9754
|
+
var NcRuleActionSchema = discriminatedUnion("kind", [object({
|
|
9755
|
+
kind: literal("wait"),
|
|
9756
|
+
seconds: number().min(0).max(300)
|
|
9757
|
+
}), object({
|
|
9758
|
+
kind: literal("cap"),
|
|
9759
|
+
deviceId: number().int(),
|
|
9760
|
+
/** Capability name, e.g. `alarm-panel`. */
|
|
9761
|
+
cap: string().min(1),
|
|
9762
|
+
/** Method on it. The executor refuses a non-device-scoped cap. */
|
|
9763
|
+
method: string().min(1),
|
|
9764
|
+
/** Method arguments, minus `deviceId` (the executor injects it). */
|
|
9765
|
+
args: record(string(), unknown()).optional()
|
|
9766
|
+
})]);
|
|
9767
|
+
/**
|
|
9768
|
+
* A named, ordered run of steps with its own throttle.
|
|
9769
|
+
*
|
|
9770
|
+
* `minDelaySec` exists because a noisy rule otherwise hammers a physical
|
|
9771
|
+
* actuator — the rule's own cooldown governs NOTIFICATIONS, which is a
|
|
9772
|
+
* different budget from "how often may this gate actually open".
|
|
9773
|
+
*/
|
|
9774
|
+
var NcRuleActionSequenceSchema = object({
|
|
9775
|
+
name: string().min(1).max(120),
|
|
9776
|
+
enabled: boolean(),
|
|
9777
|
+
minDelaySec: number().int().min(0).max(86400).optional(),
|
|
9778
|
+
actions: array(NcRuleActionSchema).min(1)
|
|
9779
|
+
});
|
|
9780
|
+
/**
|
|
9781
|
+
* One button carried by the notification, running a named sequence on tap.
|
|
9782
|
+
*
|
|
9783
|
+
* **Read this before adding a button that does something physical.** The tap
|
|
9784
|
+
* arrives over a link that travelled through third-party infrastructure — ntfy,
|
|
9785
|
+
* a push relay, whatever forwarded the message — and the callback's ONLY
|
|
9786
|
+
* authority is the token in that link: single-use, short-lived, bound to this
|
|
9787
|
+
* one action of this one notification. It does not identify who tapped.
|
|
9788
|
+
* Whoever holds the notification can run the button, once, inside the window.
|
|
9789
|
+
* That is the operator's explicit choice (2026-08-05), and `destructive` is a
|
|
9790
|
+
* rendering hint, not a second gate. [D47](decisions/adr-0047.md).
|
|
9791
|
+
*/
|
|
9792
|
+
var NcRuleNotificationButtonSchema = object({
|
|
9793
|
+
/** Stable id — travels in the callback and identifies the button in logs. */
|
|
9794
|
+
id: string().min(1).max(64),
|
|
9795
|
+
label: string().min(1).max(40),
|
|
9796
|
+
/** Name of a sequence in `onTrigger`. The dispatcher drops a button whose
|
|
9797
|
+
* sequence does not exist rather than minting a token for nothing. */
|
|
9798
|
+
sequence: string().min(1).max(120),
|
|
9799
|
+
icon: NotificationActionIconSchema.optional(),
|
|
9800
|
+
destructive: boolean().optional()
|
|
9801
|
+
});
|
|
9802
|
+
/**
|
|
9803
|
+
* Sequences a rule runs, by hook point.
|
|
9804
|
+
*
|
|
9805
|
+
* ONLY `onTrigger` is here, deliberately. The reference also has activation /
|
|
9806
|
+
* deactivation / reset / post-generation hooks, and they are wanted — but this
|
|
9807
|
+
* repo's expensive failure mode is declaring a surface nothing produces, so a
|
|
9808
|
+
* hook appears here in the same change that produces its edge, never before.
|
|
9809
|
+
*/
|
|
9810
|
+
var NcRuleActionsSchema = object({
|
|
9811
|
+
/** Runs when the rule MATCHES. */
|
|
9812
|
+
onTrigger: array(NcRuleActionSequenceSchema).optional(),
|
|
9813
|
+
/**
|
|
9814
|
+
* Buttons the NOTIFICATION carries, each running one of this rule's
|
|
9815
|
+
* sequences when tapped.
|
|
9816
|
+
*
|
|
9817
|
+
* Deliberately a REFERENCE to a sequence rather than a second place to
|
|
9818
|
+
* author steps. A button that could define its own actions would be a
|
|
9819
|
+
* parallel actuation vocabulary — the executor's device-scope check, the
|
|
9820
|
+
* stop-at-first-failure rule and the per-sequence throttle all live on
|
|
9821
|
+
* sequences, and a second authoring surface would drift from every one of
|
|
9822
|
+
* them.
|
|
9823
|
+
*
|
|
9824
|
+
* A sequence reachable ONLY by a button simply appears in `onTrigger` with
|
|
9825
|
+
* `enabled: false`: it is then authored, throttled and validated like the
|
|
9826
|
+
* rest, and nothing runs it automatically.
|
|
9827
|
+
*/
|
|
9828
|
+
buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
|
|
9829
|
+
});
|
|
9830
|
+
var NcConditionsSchema = object({
|
|
9831
|
+
/** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
|
|
9832
|
+
deviceState: object({
|
|
9833
|
+
deviceId: number().int(),
|
|
9834
|
+
/** Any of these matches. */
|
|
9835
|
+
states: array(string().min(1)).min(1)
|
|
9836
|
+
}).optional(),
|
|
9837
|
+
/** Device scope — absent = all devices. */
|
|
9838
|
+
devices: array(number()).optional(),
|
|
9839
|
+
/** Detector class names (any overlap with the record's class set). */
|
|
9840
|
+
classes: array(string().min(1)).optional(),
|
|
9841
|
+
/** Veto classes — any overlap fails the rule. */
|
|
9842
|
+
classesExclude: array(string().min(1)).optional(),
|
|
9843
|
+
/** Minimum detection confidence 0–1 (fails when the record has none). */
|
|
9844
|
+
minConfidence: number().min(0).max(1).optional(),
|
|
9845
|
+
/** Admin zone membership over event `zones` / track `zonesVisited`. */
|
|
9846
|
+
zones: NcZoneConditionSchema.optional(),
|
|
9847
|
+
/** Veto zones — any hit fails the rule. */
|
|
9848
|
+
zonesExclude: array(string().min(1)).optional(),
|
|
9849
|
+
/**
|
|
9850
|
+
* Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
|
|
9851
|
+
* and a closed track carries none, so a `track-end` rule asking for one
|
|
9852
|
+
* fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
|
|
9853
|
+
* which is exactly today's behaviour. See {@link NcCrossingSchema}.
|
|
9854
|
+
*/
|
|
9855
|
+
crossing: NcCrossingSchema.optional(),
|
|
9856
|
+
/**
|
|
9857
|
+
* Exact (case-insensitive) match on the record's collapsed `label`
|
|
9858
|
+
* (identity name / plate text / subclass).
|
|
9859
|
+
*/
|
|
9860
|
+
labelEquals: array(string().min(1)).optional(),
|
|
9453
9861
|
/**
|
|
9454
9862
|
* Identity matcher. P1 boundary: matched against the record's collapsed
|
|
9455
9863
|
* `label` (the identity display name propagated by the face pipeline) —
|
|
@@ -9626,6 +10034,14 @@ var NcMediaPolicySchema = object({
|
|
|
9626
10034
|
clipPreRollSec: number().int().min(0).max(30).optional(),
|
|
9627
10035
|
clipPostRollSec: number().int().min(0).max(30).optional(),
|
|
9628
10036
|
/**
|
|
10037
|
+
* Playback rate of the attached gif / clip. Absent = 2x.
|
|
10038
|
+
*
|
|
10039
|
+
* A notification clip is GLANCED at on a lock screen, not watched: at real
|
|
10040
|
+
* time an eight-second passage is eight seconds of the recipient's attention
|
|
10041
|
+
* and twice the bytes. 1 is real time for the operator who wants it.
|
|
10042
|
+
*/
|
|
10043
|
+
clipSpeed: number().min(1).max(8).optional(),
|
|
10044
|
+
/**
|
|
9629
10045
|
* Which stream profile the footage is cut from. Absent = the CHEAPEST
|
|
9630
10046
|
* assigned profile: a notification is watched on a phone, so the 4K
|
|
9631
10047
|
* rendition would burn CPU to produce a file the client downscales anyway.
|
|
@@ -9697,7 +10113,30 @@ var NcRuleInputSchema = object({
|
|
|
9697
10113
|
* behaviour, visible to all, read-only in the viewer). Present = personal
|
|
9698
10114
|
* rule owned by this userId. Server-stamped; never trusted from a client.
|
|
9699
10115
|
*/
|
|
9700
|
-
ownerUserId: string().optional()
|
|
10116
|
+
ownerUserId: string().optional(),
|
|
10117
|
+
/**
|
|
10118
|
+
* May a non-admin snooze this rule for EVERYONE, not just themselves?
|
|
10119
|
+
*
|
|
10120
|
+
* A snooze is personal by default — it silences the person who set it. This
|
|
10121
|
+
* opts THIS rule into the "the gardener is here all afternoon" case, where
|
|
10122
|
+
* silencing the camera for the whole household is legitimate. It silences
|
|
10123
|
+
* other people, so it is off unless a rule deliberately allows it.
|
|
10124
|
+
*
|
|
10125
|
+
* `.optional()`, deliberately NOT `.default()`: a Zod default does not run on
|
|
10126
|
+
* the addon cap path (three production failures in one day), so absent is
|
|
10127
|
+
* read as `false` by {@link canSetGlobal} in the engine. Admins are not bound
|
|
10128
|
+
* by this flag — see the scope rules on that function.
|
|
10129
|
+
*/
|
|
10130
|
+
snoozeAllowGlobal: boolean().optional(),
|
|
10131
|
+
/**
|
|
10132
|
+
* Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
|
|
10133
|
+
*
|
|
10134
|
+
* This is what makes the rule set the alarm's trigger set without the alarm
|
|
10135
|
+
* being a special case: arming is
|
|
10136
|
+
* `{ cap: 'alarm-panel', method: 'arm', args: { mode: 'away' } }`, the same
|
|
10137
|
+
* shape as every other actuation.
|
|
10138
|
+
*/
|
|
10139
|
+
actions: NcRuleActionsSchema.optional()
|
|
9701
10140
|
});
|
|
9702
10141
|
/**
|
|
9703
10142
|
* Partial patch for `updateRule` — any subset of the input fields, plus the
|
|
@@ -9768,7 +10207,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
9768
10207
|
"packagePhase",
|
|
9769
10208
|
"crossingSelect",
|
|
9770
10209
|
"polygonDraw",
|
|
9771
|
-
"occupancy"
|
|
10210
|
+
"occupancy",
|
|
10211
|
+
"deviceState"
|
|
9772
10212
|
]),
|
|
9773
10213
|
operator: _enum([
|
|
9774
10214
|
"in",
|
|
@@ -9782,7 +10222,28 @@ var NcConditionDescriptorSchema = object({
|
|
|
9782
10222
|
/** Which delivery kinds the condition applies to. */
|
|
9783
10223
|
appliesTo: array(NcDeliverySchema),
|
|
9784
10224
|
phase: string(),
|
|
9785
|
-
description: string().optional()
|
|
10225
|
+
description: string().optional(),
|
|
10226
|
+
/**
|
|
10227
|
+
* The CHOICES for a single-choice widget (`sourceSelect`, `crossingSelect`,
|
|
10228
|
+
* `packagePhase`, …), served with the descriptor.
|
|
10229
|
+
*
|
|
10230
|
+
* Before this the descriptor said which widget to render and not what to put
|
|
10231
|
+
* in it, so every option list lived in three places: this file's enums, the
|
|
10232
|
+
* admin's `NC_*_OPTIONS` and the viewer's `NC_*_VALUES`. That triple mirror
|
|
10233
|
+
* is the drift that emptied the viewer's rule editor on 2026-08-04 — the app
|
|
10234
|
+
* mirrors the cap by hand, so it is only ever as current as its last build.
|
|
10235
|
+
*
|
|
10236
|
+
* With the options on the wire, a condition of an EXISTING `valueType` costs
|
|
10237
|
+
* zero client changes. Clients keep a local fallback for an older hub that
|
|
10238
|
+
* does not send them; absent here is "use your own list", not "no choices".
|
|
10239
|
+
*/
|
|
10240
|
+
options: array(object({
|
|
10241
|
+
/** Written to the rule verbatim. `''` means the ABSENT state. */
|
|
10242
|
+
value: string(),
|
|
10243
|
+
label: string(),
|
|
10244
|
+
/** What THIS choice matches — shown one at a time, under the control. */
|
|
10245
|
+
hint: string().optional()
|
|
10246
|
+
})).readonly().optional()
|
|
9786
10247
|
});
|
|
9787
10248
|
/**
|
|
9788
10249
|
* The delivery lifecycle status of a history row — a straight read of the
|
|
@@ -9867,6 +10328,74 @@ var NcHistoryFilterSchema = object({
|
|
|
9867
10328
|
until: number().optional(),
|
|
9868
10329
|
limit: number().int().min(1).max(500).default(100)
|
|
9869
10330
|
});
|
|
10331
|
+
/**
|
|
10332
|
+
* What a snooze covers. Broader scopes win when several overlap, so one window
|
|
10333
|
+
* leaves ONE digest rather than a rule snooze and a whole-feed snooze both
|
|
10334
|
+
* summarising the same silence.
|
|
10335
|
+
*/
|
|
10336
|
+
var NcSnoozeScopeSchema = _enum([
|
|
10337
|
+
"rule",
|
|
10338
|
+
"device",
|
|
10339
|
+
"all"
|
|
10340
|
+
]);
|
|
10341
|
+
/**
|
|
10342
|
+
* Client-authored snooze. The server stamps `userId`, `startedAt` and
|
|
10343
|
+
* `expiresAt` — a DURATION is sent rather than an instant so a client with a
|
|
10344
|
+
* skewed clock cannot author a window that is already over, or never ends.
|
|
10345
|
+
*/
|
|
10346
|
+
var NcSnoozeInputSchema = object({
|
|
10347
|
+
scope: NcSnoozeScopeSchema,
|
|
10348
|
+
/** Required when `scope: 'rule'` — a scoped snooze with no id matches
|
|
10349
|
+
* NOTHING rather than degrading to "everything". */
|
|
10350
|
+
ruleId: string().optional(),
|
|
10351
|
+
/** Required when `scope: 'device'`. */
|
|
10352
|
+
deviceId: number().int().optional(),
|
|
10353
|
+
durationMinutes: number().int().min(1).max(1440),
|
|
10354
|
+
/**
|
|
10355
|
+
* Silence this for EVERY recipient, not just the caller. Permission is
|
|
10356
|
+
* checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
|
|
10357
|
+
* broader scopes). Absent = personal.
|
|
10358
|
+
*/
|
|
10359
|
+
global: boolean().optional(),
|
|
10360
|
+
/**
|
|
10361
|
+
* Deliver a summary of what was suppressed when the window ends. Absent =
|
|
10362
|
+
* ON: someone silencing a nuisance camera wants it off, someone silencing a
|
|
10363
|
+
* SECURITY camera wants to know what they missed, and choosing "off" for
|
|
10364
|
+
* everybody is how a snooze becomes an outage. Resolved to a concrete
|
|
10365
|
+
* boolean by the server at create time — never left to a Zod default, which
|
|
10366
|
+
* does not run on the addon cap path.
|
|
10367
|
+
*/
|
|
10368
|
+
summary: boolean().optional()
|
|
10369
|
+
});
|
|
10370
|
+
/** A persisted snooze window. */
|
|
10371
|
+
var NcSnoozeSchema = object({
|
|
10372
|
+
id: string(),
|
|
10373
|
+
/** Who set it. Also who it silences, unless `global`. */
|
|
10374
|
+
userId: string(),
|
|
10375
|
+
scope: NcSnoozeScopeSchema,
|
|
10376
|
+
ruleId: string().optional(),
|
|
10377
|
+
deviceId: number().int().optional(),
|
|
10378
|
+
startedAt: number(),
|
|
10379
|
+
/** Exclusive: at exactly this instant the snooze is over. Expiry is a
|
|
10380
|
+
* COMPARISON, not a job — no sweeper can leave the operator silenced. */
|
|
10381
|
+
expiresAt: number(),
|
|
10382
|
+
global: boolean(),
|
|
10383
|
+
summary: boolean(),
|
|
10384
|
+
/** When the end-of-window digest went out. Absent = not sent (yet, or the
|
|
10385
|
+
* window has not closed, or `summary` is false). */
|
|
10386
|
+
digestSentAt: number().optional()
|
|
10387
|
+
});
|
|
10388
|
+
object({
|
|
10389
|
+
snoozeId: string(),
|
|
10390
|
+
targetId: string(),
|
|
10391
|
+
ruleId: string(),
|
|
10392
|
+
ruleName: string(),
|
|
10393
|
+
deviceId: number().int(),
|
|
10394
|
+
/** How many notifications this snooze hid for that pair. */
|
|
10395
|
+
count: number().int(),
|
|
10396
|
+
firstAt: number(),
|
|
10397
|
+
lastAt: number()
|
|
10398
|
+
});
|
|
9870
10399
|
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 }), {
|
|
9871
10400
|
kind: "mutation",
|
|
9872
10401
|
auth: "admin",
|
|
@@ -9896,7 +10425,13 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
9896
10425
|
}), method(object({}), object({
|
|
9897
10426
|
catalog: array(NcConditionDescriptorSchema),
|
|
9898
10427
|
taxonomy: NcTaxonomySchema.optional()
|
|
9899
|
-
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" })
|
|
10428
|
+
})), 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 }), {
|
|
10429
|
+
kind: "mutation",
|
|
10430
|
+
caller: "required"
|
|
10431
|
+
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
10432
|
+
kind: "mutation",
|
|
10433
|
+
caller: "required"
|
|
10434
|
+
});
|
|
9900
10435
|
/**
|
|
9901
10436
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
9902
10437
|
*
|
|
@@ -10644,7 +11179,15 @@ method(object({
|
|
|
10644
11179
|
format: _enum(["gif", "mp4"]).default("gif"),
|
|
10645
11180
|
maxWidth: number().int().min(120).max(1920).default(480),
|
|
10646
11181
|
/** GIF only — MP4 keeps the source cadence. */
|
|
10647
|
-
fps: number().int().min(1).max(15).default(5)
|
|
11182
|
+
fps: number().int().min(1).max(15).default(5),
|
|
11183
|
+
/**
|
|
11184
|
+
* Playback rate. A notification clip is GLANCED at on a lock screen,
|
|
11185
|
+
* not watched, so 2x is the default: the recipient sees the whole
|
|
11186
|
+
* passage in half the time and the GIF is half the bytes. `1` is real
|
|
11187
|
+
* time. Applies to MP4 as well — the operator set a speed, not a GIF
|
|
11188
|
+
* speed.
|
|
11189
|
+
*/
|
|
11190
|
+
speed: number().min(1).max(8).default(2)
|
|
10648
11191
|
}), object({
|
|
10649
11192
|
base64: string(),
|
|
10650
11193
|
mime: string(),
|
|
@@ -15430,6 +15973,210 @@ method(object({ codec: string() }), boolean()), method(_void(), object({
|
|
|
15430
15973
|
kind: "mutation",
|
|
15431
15974
|
auth: "admin"
|
|
15432
15975
|
});
|
|
15976
|
+
/**
|
|
15977
|
+
* Query filter for settings-store collections.
|
|
15978
|
+
*/
|
|
15979
|
+
var QueryFilterSchema = object({
|
|
15980
|
+
where: record(string(), unknown()).optional(),
|
|
15981
|
+
whereIn: record(string(), array(unknown())).optional(),
|
|
15982
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
15983
|
+
orderBy: object({
|
|
15984
|
+
field: string(),
|
|
15985
|
+
direction: _enum(["asc", "desc"])
|
|
15986
|
+
}).optional(),
|
|
15987
|
+
limit: number().optional(),
|
|
15988
|
+
offset: number().optional()
|
|
15989
|
+
});
|
|
15990
|
+
/**
|
|
15991
|
+
* The predicate half of a filter, for BULK MUTATIONS.
|
|
15992
|
+
*
|
|
15993
|
+
* Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
|
|
15994
|
+
* meaning for a statement that rewrites a set, and accepting them would invite
|
|
15995
|
+
* a caller to believe `limit` bounds the damage. Every field is optional here
|
|
15996
|
+
* only so the shape stays composable — the implementation REJECTS a filter
|
|
15997
|
+
* that compiles to no predicate, because that is the whole collection.
|
|
15998
|
+
*/
|
|
15999
|
+
var MutationFilterSchema = object({
|
|
16000
|
+
where: record(string(), unknown()).optional(),
|
|
16001
|
+
whereIn: record(string(), array(unknown())).optional(),
|
|
16002
|
+
whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
|
|
16003
|
+
});
|
|
16004
|
+
/** A single stored record: `{ id, data }`. */
|
|
16005
|
+
var SettingsRecordSchema = object({
|
|
16006
|
+
id: string(),
|
|
16007
|
+
data: record(string(), unknown())
|
|
16008
|
+
});
|
|
16009
|
+
/**
|
|
16010
|
+
* Column declaration for a structured (SQL-backed) collection.
|
|
16011
|
+
*
|
|
16012
|
+
* Logical types — the backend translates each to the matching SQLite
|
|
16013
|
+
* storage class and handles per-type marshaling:
|
|
16014
|
+
* - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
|
|
16015
|
+
* - `JSON` — TEXT under the hood; serialised on write, parsed on read
|
|
16016
|
+
* - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
|
|
16017
|
+
*/
|
|
16018
|
+
var CollectionColumnSchema = object({
|
|
16019
|
+
name: string(),
|
|
16020
|
+
type: _enum([
|
|
16021
|
+
"TEXT",
|
|
16022
|
+
"INTEGER",
|
|
16023
|
+
"REAL",
|
|
16024
|
+
"JSON",
|
|
16025
|
+
"BOOLEAN"
|
|
16026
|
+
]),
|
|
16027
|
+
primaryKey: boolean().optional(),
|
|
16028
|
+
notNull: boolean().optional(),
|
|
16029
|
+
unique: boolean().optional(),
|
|
16030
|
+
/**
|
|
16031
|
+
* Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
|
|
16032
|
+
* columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
|
|
16033
|
+
* collection surface simply cannot express their existing tables — which is
|
|
16034
|
+
* why they were still on the uncapped `table*` API.
|
|
16035
|
+
*/
|
|
16036
|
+
defaultValue: union([
|
|
16037
|
+
string(),
|
|
16038
|
+
number(),
|
|
16039
|
+
boolean()
|
|
16040
|
+
]).optional()
|
|
16041
|
+
});
|
|
16042
|
+
var CollectionIndexSchema = object({
|
|
16043
|
+
name: string(),
|
|
16044
|
+
columns: array(string()).readonly(),
|
|
16045
|
+
unique: boolean().optional()
|
|
16046
|
+
});
|
|
16047
|
+
method(object({
|
|
16048
|
+
namespace: string().optional(),
|
|
16049
|
+
collection: string(),
|
|
16050
|
+
key: string()
|
|
16051
|
+
}), unknown()), method(object({
|
|
16052
|
+
namespace: string().optional(),
|
|
16053
|
+
collection: string(),
|
|
16054
|
+
key: string(),
|
|
16055
|
+
value: unknown()
|
|
16056
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16057
|
+
namespace: string().optional(),
|
|
16058
|
+
collection: string(),
|
|
16059
|
+
filter: QueryFilterSchema.optional()
|
|
16060
|
+
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
16061
|
+
namespace: string().optional(),
|
|
16062
|
+
collection: string(),
|
|
16063
|
+
record: SettingsRecordSchema
|
|
16064
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16065
|
+
namespace: string().optional(),
|
|
16066
|
+
collection: string(),
|
|
16067
|
+
id: string(),
|
|
16068
|
+
data: record(string(), unknown())
|
|
16069
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16070
|
+
namespace: string().optional(),
|
|
16071
|
+
collection: string(),
|
|
16072
|
+
key: string()
|
|
16073
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16074
|
+
namespace: string().optional(),
|
|
16075
|
+
collection: string(),
|
|
16076
|
+
filter: MutationFilterSchema
|
|
16077
|
+
}), object({ deleted: number().int() }), { kind: "mutation" }), method(object({
|
|
16078
|
+
namespace: string().optional(),
|
|
16079
|
+
collection: string(),
|
|
16080
|
+
filter: MutationFilterSchema,
|
|
16081
|
+
data: record(string(), unknown())
|
|
16082
|
+
}), object({ updated: number().int() }), { kind: "mutation" }), method(object({
|
|
16083
|
+
namespace: string().optional(),
|
|
16084
|
+
collection: string(),
|
|
16085
|
+
filter: QueryFilterSchema.optional()
|
|
16086
|
+
}), number()), method(object({
|
|
16087
|
+
namespace: string().optional(),
|
|
16088
|
+
collection: string(),
|
|
16089
|
+
field: string(),
|
|
16090
|
+
bucketSize: number().int().positive(),
|
|
16091
|
+
origin: number().int(),
|
|
16092
|
+
filter: QueryFilterSchema.optional()
|
|
16093
|
+
}), array(object({
|
|
16094
|
+
bucket: number().int(),
|
|
16095
|
+
count: number().int()
|
|
16096
|
+
})).readonly()), method(object({
|
|
16097
|
+
namespace: string().optional(),
|
|
16098
|
+
collection: string()
|
|
16099
|
+
}), boolean()), method(object({
|
|
16100
|
+
namespace: string().optional(),
|
|
16101
|
+
collection: string(),
|
|
16102
|
+
columns: array(CollectionColumnSchema).readonly(),
|
|
16103
|
+
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
16104
|
+
}), _void(), { kind: "mutation" });
|
|
16105
|
+
/**
|
|
16106
|
+
* What one engine says about itself. The orchestrator uses `kind` to pick
|
|
16107
|
+
* a registrant for a collection; `engineId` is what a log line names when
|
|
16108
|
+
* a call is routed or refused.
|
|
16109
|
+
*/
|
|
16110
|
+
var EngineInfoSchema = object({
|
|
16111
|
+
engineId: string(),
|
|
16112
|
+
/**
|
|
16113
|
+
* `relational` — rows, columns, indexes, the surface `settings-store`
|
|
16114
|
+
* has always described. `vector` — an embedding store answering
|
|
16115
|
+
* similarity queries. A registrant declares exactly one; an engine that
|
|
16116
|
+
* does both registers twice, because "both" would make the routing
|
|
16117
|
+
* decision ambiguous at exactly the point it must not be.
|
|
16118
|
+
*/
|
|
16119
|
+
kind: _enum(["relational", "vector"]),
|
|
16120
|
+
displayName: string()
|
|
16121
|
+
});
|
|
16122
|
+
method(_void(), EngineInfoSchema), method(object({
|
|
16123
|
+
namespace: string().optional(),
|
|
16124
|
+
collection: string(),
|
|
16125
|
+
key: string()
|
|
16126
|
+
}), unknown()), method(object({
|
|
16127
|
+
namespace: string().optional(),
|
|
16128
|
+
collection: string(),
|
|
16129
|
+
key: string(),
|
|
16130
|
+
value: unknown()
|
|
16131
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16132
|
+
namespace: string().optional(),
|
|
16133
|
+
collection: string(),
|
|
16134
|
+
filter: QueryFilterSchema.optional()
|
|
16135
|
+
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
16136
|
+
namespace: string().optional(),
|
|
16137
|
+
collection: string(),
|
|
16138
|
+
record: SettingsRecordSchema
|
|
16139
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16140
|
+
namespace: string().optional(),
|
|
16141
|
+
collection: string(),
|
|
16142
|
+
id: string(),
|
|
16143
|
+
data: record(string(), unknown())
|
|
16144
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16145
|
+
namespace: string().optional(),
|
|
16146
|
+
collection: string(),
|
|
16147
|
+
key: string()
|
|
16148
|
+
}), _void(), { kind: "mutation" }), method(object({
|
|
16149
|
+
namespace: string().optional(),
|
|
16150
|
+
collection: string(),
|
|
16151
|
+
filter: MutationFilterSchema
|
|
16152
|
+
}), object({ deleted: number().int() }), { kind: "mutation" }), method(object({
|
|
16153
|
+
namespace: string().optional(),
|
|
16154
|
+
collection: string(),
|
|
16155
|
+
filter: MutationFilterSchema,
|
|
16156
|
+
data: record(string(), unknown())
|
|
16157
|
+
}), object({ updated: number().int() }), { kind: "mutation" }), method(object({
|
|
16158
|
+
namespace: string().optional(),
|
|
16159
|
+
collection: string(),
|
|
16160
|
+
filter: QueryFilterSchema.optional()
|
|
16161
|
+
}), number()), method(object({
|
|
16162
|
+
namespace: string().optional(),
|
|
16163
|
+
collection: string(),
|
|
16164
|
+
field: string(),
|
|
16165
|
+
bucketSize: number().int().positive(),
|
|
16166
|
+
origin: number().int(),
|
|
16167
|
+
filter: QueryFilterSchema.optional()
|
|
16168
|
+
}), array(object({
|
|
16169
|
+
bucket: number().int(),
|
|
16170
|
+
count: number().int()
|
|
16171
|
+
})).readonly()), method(object({
|
|
16172
|
+
namespace: string().optional(),
|
|
16173
|
+
collection: string()
|
|
16174
|
+
}), boolean()), method(object({
|
|
16175
|
+
namespace: string().optional(),
|
|
16176
|
+
collection: string(),
|
|
16177
|
+
columns: array(CollectionColumnSchema).readonly(),
|
|
16178
|
+
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
16179
|
+
}), _void(), { kind: "mutation" });
|
|
15433
16180
|
DeviceType.Camera;
|
|
15434
16181
|
/**
|
|
15435
16182
|
* `device-adoption` — generic discovery + adoption surface,
|
|
@@ -16108,7 +16855,20 @@ method(object({
|
|
|
16108
16855
|
}), _void(), {
|
|
16109
16856
|
kind: "mutation",
|
|
16110
16857
|
auth: "admin"
|
|
16111
|
-
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
|
|
16858
|
+
}), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
|
|
16859
|
+
addonId: string().optional(),
|
|
16860
|
+
/**
|
|
16861
|
+
* `slim` omits `config` (returned `{}`), `metadata` (null) and the
|
|
16862
|
+
* `sourceInfo` derived from config — and skips the per-device settings
|
|
16863
|
+
* read that produces them. Everything identifying a device (id, name,
|
|
16864
|
+
* type, online, features, isCamera, parent/link ids) is unchanged.
|
|
16865
|
+
* Do not use it for dispatch routing, which needs `sourceInfo`.
|
|
16866
|
+
*/
|
|
16867
|
+
projection: _enum(["full", "slim"]).optional(),
|
|
16868
|
+
/** Return only camera devices. Filtering server-side instead of
|
|
16869
|
+
* shipping 293 rows to find 12. */
|
|
16870
|
+
isCamera: boolean().optional()
|
|
16871
|
+
}), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
|
|
16112
16872
|
mode: DeviceLinkModeSchema,
|
|
16113
16873
|
devices: array(LinkedDeviceSchema)
|
|
16114
16874
|
})), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
|
|
@@ -16547,14 +17307,14 @@ var LlmProfileSchema = object({
|
|
|
16547
17307
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
16548
17308
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
16549
17309
|
* notification-output.cap.ts:151); the exported TS type re-tightens it. */
|
|
16550
|
-
var ConfigSchemaPassthrough
|
|
17310
|
+
var ConfigSchemaPassthrough = unknown();
|
|
16551
17311
|
var LlmProfileKindDescriptorSchema = object({
|
|
16552
17312
|
kind: LlmProfileKindSchema,
|
|
16553
17313
|
label: string(),
|
|
16554
17314
|
icon: string(),
|
|
16555
17315
|
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
16556
17316
|
addonId: string(),
|
|
16557
|
-
configSchema: ConfigSchemaPassthrough
|
|
17317
|
+
configSchema: ConfigSchemaPassthrough
|
|
16558
17318
|
});
|
|
16559
17319
|
var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
|
|
16560
17320
|
var LlmDefaultSchema = object({
|
|
@@ -17085,241 +17845,102 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
17085
17845
|
});
|
|
17086
17846
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
17087
17847
|
/**
|
|
17088
|
-
*
|
|
17089
|
-
*
|
|
17090
|
-
*
|
|
17091
|
-
* `docs/superpowers/specs/2026-
|
|
17092
|
-
*
|
|
17093
|
-
*
|
|
17094
|
-
*
|
|
17095
|
-
*
|
|
17096
|
-
*
|
|
17097
|
-
*
|
|
17098
|
-
*
|
|
17099
|
-
*
|
|
17100
|
-
*
|
|
17101
|
-
*
|
|
17102
|
-
*
|
|
17103
|
-
*
|
|
17104
|
-
*
|
|
17105
|
-
|
|
17106
|
-
|
|
17107
|
-
*
|
|
17108
|
-
|
|
17109
|
-
|
|
17110
|
-
*
|
|
17111
|
-
|
|
17112
|
-
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
|
|
17116
|
-
* admin "Integrations" page.
|
|
17117
|
-
*/
|
|
17118
|
-
/**
|
|
17119
|
-
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
17120
|
-
* adapter picks what it supports and the degrade engine filters the rest.
|
|
17121
|
-
*/
|
|
17122
|
-
var AttachmentMediaTypeSchema = _enum([
|
|
17123
|
-
"image",
|
|
17124
|
-
"video",
|
|
17125
|
-
"gif",
|
|
17126
|
-
"audio",
|
|
17127
|
-
"icon"
|
|
17128
|
-
]);
|
|
17129
|
-
/**
|
|
17130
|
-
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
17131
|
-
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
17132
|
-
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
17133
|
-
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
17134
|
-
*/
|
|
17135
|
-
var AttachmentSchema = object({
|
|
17136
|
-
mediaType: AttachmentMediaTypeSchema,
|
|
17137
|
-
url: string().optional(),
|
|
17138
|
-
bytes: _instanceof(Uint8Array).optional(),
|
|
17139
|
-
mime: string().optional(),
|
|
17140
|
-
name: string().optional()
|
|
17141
|
-
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
17142
|
-
var NotificationFormatSchema = _enum([
|
|
17143
|
-
"text",
|
|
17144
|
-
"markdown",
|
|
17145
|
-
"html"
|
|
17848
|
+
* core-blocks — user-authored TypeScript, stored in the kernel and executed in
|
|
17849
|
+
* its own process.
|
|
17850
|
+
*
|
|
17851
|
+
* Spec: `docs/superpowers/specs/2026-08-04-core-blocks-and-synthetic-devices-design.md`.
|
|
17852
|
+
*
|
|
17853
|
+
* The first use is **owning devices without being a device provider**: a block
|
|
17854
|
+
* declares devices under a system or custom integration and drives their state,
|
|
17855
|
+
* with the same `ctx` an addon gets. Automations come later; nothing here
|
|
17856
|
+
* models a trigger.
|
|
17857
|
+
*
|
|
17858
|
+
* **Stated plainly, because it does not change by being true:** a block has an
|
|
17859
|
+
* addon's powers — devices, storage, the event bus, `ctx.api`. It is a plugin
|
|
17860
|
+
* with no review step. What makes that survivable is not a sandbox, it is
|
|
17861
|
+
* PROCESS ISOLATION: one process per block, supervised by `CrashSupervisor`,
|
|
17862
|
+
* so a block that throws or never returns is marked `failed` and visible
|
|
17863
|
+
* instead of taking the hub with it (D6). Every method here is admin-only, and
|
|
17864
|
+
* must stay so.
|
|
17865
|
+
*/
|
|
17866
|
+
/** Where a block runs. The operator chooses — a block driving a device on an
|
|
17867
|
+
* agent is the reason placement is not fixed to the hub. */
|
|
17868
|
+
var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
|
|
17869
|
+
/** What a block's process is doing. Mirrors the addon runner's own lifecycle so
|
|
17870
|
+
* a failing block reads the same way a failing addon does. */
|
|
17871
|
+
var CoreBlockStatusSchema = _enum([
|
|
17872
|
+
"stopped",
|
|
17873
|
+
"starting",
|
|
17874
|
+
"running",
|
|
17875
|
+
"failed"
|
|
17146
17876
|
]);
|
|
17147
|
-
/**
|
|
17148
|
-
var
|
|
17149
|
-
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17156
|
-
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
17157
|
-
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
17158
|
-
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
17159
|
-
* `priority` for that one target.
|
|
17160
|
-
*/
|
|
17161
|
-
var NotificationSchema = object({
|
|
17162
|
-
body: string(),
|
|
17163
|
-
title: string().optional(),
|
|
17164
|
-
format: NotificationFormatSchema.default("text"),
|
|
17165
|
-
priority: number().int().min(1).max(5).default(3),
|
|
17166
|
-
level: string().optional(),
|
|
17167
|
-
attachments: array(AttachmentSchema).optional(),
|
|
17168
|
-
clickUrl: string().optional(),
|
|
17169
|
-
actions: array(NotificationActionSchema).optional(),
|
|
17170
|
-
sound: string().optional(),
|
|
17171
|
-
ttl: number().optional(),
|
|
17172
|
-
tag: string().optional(),
|
|
17173
|
-
deviceId: number().optional(),
|
|
17174
|
-
eventId: string().optional(),
|
|
17175
|
-
metadata: record(string(), unknown()).optional()
|
|
17176
|
-
});
|
|
17177
|
-
/** One declared native severity/priority level for a kind. */
|
|
17178
|
-
var TargetKindLevelSchema = object({
|
|
17179
|
-
id: string(),
|
|
17180
|
-
label: string(),
|
|
17181
|
-
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
17182
|
-
ordinal: number().int().min(1).max(5).nullable(),
|
|
17183
|
-
flags: object({
|
|
17184
|
-
critical: boolean().optional(),
|
|
17185
|
-
silent: boolean().optional(),
|
|
17186
|
-
noPush: boolean().optional()
|
|
17187
|
-
}).optional(),
|
|
17188
|
-
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
17189
|
-
requires: array(string()).optional(),
|
|
17190
|
-
description: string().optional()
|
|
17191
|
-
});
|
|
17192
|
-
/** The full capability block consulted before dispatch. */
|
|
17193
|
-
var TargetKindCapsSchema = object({
|
|
17194
|
-
attachments: object({
|
|
17195
|
-
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
17196
|
-
mode: _enum([
|
|
17197
|
-
"url",
|
|
17198
|
-
"bytes",
|
|
17199
|
-
"both"
|
|
17200
|
-
]),
|
|
17201
|
-
max: number().int().nonnegative(),
|
|
17202
|
-
maxBytes: number().int().positive().optional()
|
|
17203
|
-
}),
|
|
17204
|
-
/** Max action buttons (0 = none). */
|
|
17205
|
-
actions: number().int().nonnegative(),
|
|
17206
|
-
levels: array(TargetKindLevelSchema),
|
|
17207
|
-
format: array(NotificationFormatSchema),
|
|
17208
|
-
clickUrl: boolean(),
|
|
17209
|
-
sound: boolean(),
|
|
17210
|
-
ttl: boolean(),
|
|
17211
|
-
bodyMaxLen: number().int().positive()
|
|
17212
|
-
});
|
|
17213
|
-
/**
|
|
17214
|
-
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
17215
|
-
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
17216
|
-
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
17217
|
-
* the union is large and not meant for runtime validation here; the exported
|
|
17218
|
-
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
17219
|
-
*/
|
|
17220
|
-
var ConfigSchemaPassthrough = unknown();
|
|
17221
|
-
var TargetKindSchema = object({
|
|
17222
|
-
kind: string(),
|
|
17223
|
-
label: string(),
|
|
17224
|
-
icon: string(),
|
|
17225
|
-
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
17226
|
-
addonId: string(),
|
|
17227
|
-
/**
|
|
17228
|
-
* URL of the kind's bundled BRAND icon, served by the providing addon over
|
|
17229
|
-
* its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
|
|
17230
|
-
* when the addon bundles no icon for that kind — the client then falls back
|
|
17231
|
-
* to a neutral glyph rather than rendering the raw `icon` NAME as text.
|
|
17232
|
-
*
|
|
17233
|
-
* Root-relative on purpose: it resolves against whatever origin serves a web
|
|
17234
|
-
* client, and a native client joins it onto its own hub base.
|
|
17235
|
-
*
|
|
17236
|
-
* DECLARED here deliberately. It used to travel as an undeclared passthrough
|
|
17237
|
-
* field that survived only because the runtime cap-router forwards provider
|
|
17238
|
-
* output verbatim — so every consumer had to re-declare it by hand to stop
|
|
17239
|
-
* its own Zod parse from stripping it, and the whole arrangement would have
|
|
17240
|
-
* broken silently the moment output validation was tightened anywhere.
|
|
17241
|
-
*/
|
|
17242
|
-
iconUrl: string().optional(),
|
|
17877
|
+
/** Client-authored fields. */
|
|
17878
|
+
var CoreBlockInputSchema = object({
|
|
17879
|
+
name: string().min(1).max(120),
|
|
17880
|
+
/** TypeScript source. Compiled server-side before it is ever stored — a
|
|
17881
|
+
* block that does not compile is a fork failure the operator would meet
|
|
17882
|
+
* minutes later, in a log, instead of in the editor. */
|
|
17883
|
+
code: string().max(2e5),
|
|
17884
|
+
enabled: boolean(),
|
|
17885
|
+
placement: CoreBlockPlacementSchema,
|
|
17243
17886
|
/**
|
|
17244
|
-
*
|
|
17245
|
-
*
|
|
17246
|
-
* The server knows this and therefore says it, because the client cannot
|
|
17247
|
-
* safely guess: a React-Native client renders SVG and raster through two
|
|
17248
|
-
* DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
|
|
17249
|
-
* not decode SVG on iOS/Android), so without this it silently fell back to a
|
|
17250
|
-
* placeholder glyph for every vector icon while the web build looked fine.
|
|
17251
|
-
*
|
|
17252
|
-
* Absent when {@link iconUrl} is absent, or for a legacy provider that has
|
|
17253
|
-
* not been updated — a client that cannot determine the type should prefer
|
|
17254
|
-
* its raster path, which is the safe default for an unknown image.
|
|
17887
|
+
* Integration the block's devices hang from. Absent = the system integration
|
|
17888
|
+
* blocks share. A block may declare its own instead.
|
|
17255
17889
|
*/
|
|
17256
|
-
|
|
17257
|
-
configSchema: ConfigSchemaPassthrough,
|
|
17258
|
-
supportsDiscovery: boolean(),
|
|
17259
|
-
caps: TargetKindCapsSchema
|
|
17890
|
+
integrationId: string().optional()
|
|
17260
17891
|
});
|
|
17261
|
-
/**
|
|
17262
|
-
|
|
17263
|
-
* (return a presence marker only) when serving `listTargets` — never
|
|
17264
|
-
* round-trip a stored secret to the UI.
|
|
17265
|
-
*/
|
|
17266
|
-
var TargetSchema = object({
|
|
17892
|
+
/** A stored block. */
|
|
17893
|
+
var CoreBlockSchema = CoreBlockInputSchema.extend({
|
|
17267
17894
|
id: string(),
|
|
17268
|
-
|
|
17269
|
-
|
|
17270
|
-
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17283
|
-
|
|
17284
|
-
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17289
|
-
|
|
17290
|
-
|
|
17291
|
-
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
|
|
17298
|
-
|
|
17299
|
-
|
|
17300
|
-
|
|
17301
|
-
|
|
17302
|
-
|
|
17303
|
-
|
|
17304
|
-
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
17308
|
-
|
|
17309
|
-
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
setTargetEnabled: method(object({
|
|
17318
|
-
targetId: string(),
|
|
17319
|
-
enabled: boolean()
|
|
17320
|
-
}), _void(), { kind: "mutation" })
|
|
17321
|
-
}
|
|
17322
|
-
};
|
|
17895
|
+
createdAt: number(),
|
|
17896
|
+
updatedAt: number(),
|
|
17897
|
+
/** Server-stamped author. */
|
|
17898
|
+
createdBy: string(),
|
|
17899
|
+
status: CoreBlockStatusSchema,
|
|
17900
|
+
/**
|
|
17901
|
+
* Why the block is not running, when it is not. The operator's ONLY window
|
|
17902
|
+
* into a block that failed at load — a block that is silently absent is the
|
|
17903
|
+
* failure mode this whole feature has to avoid.
|
|
17904
|
+
*/
|
|
17905
|
+
lastError: string().optional(),
|
|
17906
|
+
/** Ms epoch of the last state change. */
|
|
17907
|
+
lastChangedAt: number()
|
|
17908
|
+
});
|
|
17909
|
+
/** What a compile attempt produced. */
|
|
17910
|
+
var CoreBlockCompileResultSchema = object({
|
|
17911
|
+
ok: boolean(),
|
|
17912
|
+
/** Present when `ok` is false — the first error, in the author's words. */
|
|
17913
|
+
error: string().optional(),
|
|
17914
|
+
line: number().optional(),
|
|
17915
|
+
column: number().optional()
|
|
17916
|
+
});
|
|
17917
|
+
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 }), {
|
|
17918
|
+
kind: "mutation",
|
|
17919
|
+
auth: "admin",
|
|
17920
|
+
caller: "required"
|
|
17921
|
+
}), method(object({
|
|
17922
|
+
blockId: string(),
|
|
17923
|
+
block: CoreBlockInputSchema.partial()
|
|
17924
|
+
}), object({ block: CoreBlockSchema }), {
|
|
17925
|
+
kind: "mutation",
|
|
17926
|
+
auth: "admin",
|
|
17927
|
+
caller: "required"
|
|
17928
|
+
}), method(object({ blockId: string() }), object({ success: literal(true) }), {
|
|
17929
|
+
kind: "mutation",
|
|
17930
|
+
auth: "admin"
|
|
17931
|
+
}), method(object({
|
|
17932
|
+
blockId: string(),
|
|
17933
|
+
enabled: boolean()
|
|
17934
|
+
}), object({ block: CoreBlockSchema }), {
|
|
17935
|
+
kind: "mutation",
|
|
17936
|
+
auth: "admin"
|
|
17937
|
+
}), method(object({ code: string() }), CoreBlockCompileResultSchema, {
|
|
17938
|
+
kind: "mutation",
|
|
17939
|
+
auth: "admin"
|
|
17940
|
+
}), method(object({}), object({ libs: array(object({
|
|
17941
|
+
filePath: string(),
|
|
17942
|
+
content: string()
|
|
17943
|
+
})) }), { auth: "admin" });
|
|
17323
17944
|
/**
|
|
17324
17945
|
* Zod schemas for persisted record types.
|
|
17325
17946
|
*
|
|
@@ -17567,6 +18188,11 @@ var EventKindDescriptorSchema = object({
|
|
|
17567
18188
|
deviceId: number()
|
|
17568
18189
|
})
|
|
17569
18190
|
});
|
|
18191
|
+
/** One camera's event vocabulary, as returned by `listEventKindsBatch`. */
|
|
18192
|
+
var EventKindsForDeviceSchema = object({
|
|
18193
|
+
deviceId: number(),
|
|
18194
|
+
kinds: array(EventKindDescriptorSchema).readonly()
|
|
18195
|
+
});
|
|
17570
18196
|
var SensorEventSchema = object({
|
|
17571
18197
|
id: string(),
|
|
17572
18198
|
/** The CAMERA the event is attributed to (a sensor linked to N cameras
|
|
@@ -17823,6 +18449,19 @@ var MediaFileSchema = object({
|
|
|
17823
18449
|
sizeBytes: number(),
|
|
17824
18450
|
timestamp: number()
|
|
17825
18451
|
});
|
|
18452
|
+
/**
|
|
18453
|
+
* One media row WITHOUT its bytes.
|
|
18454
|
+
*
|
|
18455
|
+
* A track's media is 5-8 MB of base64 (measured: 7.67 MB across 23 files for a
|
|
18456
|
+
* 140 s track), and a client that renders tiles from the media data plane needs
|
|
18457
|
+
* to know only WHAT EXISTS — the bytes then arrive per tile, lazily, over HTTP
|
|
18458
|
+
* with an immutable cache, instead of all at once inside a tRPC response that
|
|
18459
|
+
* blocks the whole view.
|
|
18460
|
+
*
|
|
18461
|
+
* `sizeBytes` is carried because it is what lets a client decide between the
|
|
18462
|
+
* stored blob and a `?variant=thumb` rendering without fetching either.
|
|
18463
|
+
*/
|
|
18464
|
+
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
17826
18465
|
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
17827
18466
|
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
17828
18467
|
var DeviceEventQueryInput = object({
|
|
@@ -17972,7 +18611,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
17972
18611
|
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
17973
18612
|
kind: "mutation",
|
|
17974
18613
|
auth: "admin"
|
|
17975
|
-
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
|
|
18614
|
+
}), 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({
|
|
17976
18615
|
deviceId: number(),
|
|
17977
18616
|
since: number().optional(),
|
|
17978
18617
|
until: number().optional(),
|
|
@@ -18046,7 +18685,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
18046
18685
|
}), array(MediaFileSchema).readonly()), method(object({
|
|
18047
18686
|
trackId: string(),
|
|
18048
18687
|
kinds: array(MediaFileKindEnum).optional()
|
|
18049
|
-
}), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
|
|
18688
|
+
}), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
|
|
18050
18689
|
deviceId: number(),
|
|
18051
18690
|
timestamp: number(),
|
|
18052
18691
|
frameWidth: number(),
|
|
@@ -18676,6 +19315,28 @@ var ServerUpdateStateSchema = _enum([
|
|
|
18676
19315
|
"pending-restart",
|
|
18677
19316
|
"awaiting-confirmation"
|
|
18678
19317
|
]);
|
|
19318
|
+
var ImageContractSchema = object({
|
|
19319
|
+
state: _enum([
|
|
19320
|
+
"in-sync",
|
|
19321
|
+
"behind-patch",
|
|
19322
|
+
"behind-series",
|
|
19323
|
+
"ahead",
|
|
19324
|
+
"unknown"
|
|
19325
|
+
]),
|
|
19326
|
+
/** The baked seed closure version — the image/app-bundle fingerprint. */
|
|
19327
|
+
seedVersion: string().nullable(),
|
|
19328
|
+
/** Best-known version the deployment contract delivers today. */
|
|
19329
|
+
contractVersion: string().nullable(),
|
|
19330
|
+
/**
|
|
19331
|
+
* Where `contractVersion` came from: a real registry check (`registry`), or
|
|
19332
|
+
* the node's own running version (`running` — a node can never run code
|
|
19333
|
+
* newer than the newest release, so `seed < running` proves image staleness
|
|
19334
|
+
* even before any registry check has run).
|
|
19335
|
+
*/
|
|
19336
|
+
contractSource: _enum(["registry", "running"]).nullable(),
|
|
19337
|
+
/** One operator-grade sentence: this node runs image X; the contract says Y. */
|
|
19338
|
+
message: string()
|
|
19339
|
+
});
|
|
18679
19340
|
var ServerRollbackInfoSchema = object({
|
|
18680
19341
|
/** The version that failed (or was manually rolled back). */
|
|
18681
19342
|
fromVersion: string(),
|
|
@@ -18712,7 +19373,12 @@ var ServerPackageStatusSchema = object({
|
|
|
18712
19373
|
* versions are being IGNORED. Surfaced as a warning in the UI.
|
|
18713
19374
|
*/
|
|
18714
19375
|
stateFileCorrupt: boolean(),
|
|
18715
|
-
lastCheckedAtMs: number().nullable()
|
|
19376
|
+
lastCheckedAtMs: number().nullable(),
|
|
19377
|
+
/**
|
|
19378
|
+
* Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
|
|
19379
|
+
* version skew: an older provider's payload simply omits it.
|
|
19380
|
+
*/
|
|
19381
|
+
imageContract: ImageContractSchema.optional()
|
|
18716
19382
|
});
|
|
18717
19383
|
var ServerUpdateCheckResultSchema = object({
|
|
18718
19384
|
packageName: string(),
|
|
@@ -18747,101 +19413,6 @@ version: string().optional() }), ServerUpdateActionResultSchema, {
|
|
|
18747
19413
|
auth: "admin"
|
|
18748
19414
|
});
|
|
18749
19415
|
/**
|
|
18750
|
-
* Query filter for settings-store collections.
|
|
18751
|
-
*/
|
|
18752
|
-
var QueryFilterSchema = object({
|
|
18753
|
-
where: record(string(), unknown()).optional(),
|
|
18754
|
-
whereIn: record(string(), array(unknown())).optional(),
|
|
18755
|
-
whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
|
|
18756
|
-
orderBy: object({
|
|
18757
|
-
field: string(),
|
|
18758
|
-
direction: _enum(["asc", "desc"])
|
|
18759
|
-
}).optional(),
|
|
18760
|
-
limit: number().optional(),
|
|
18761
|
-
offset: number().optional()
|
|
18762
|
-
});
|
|
18763
|
-
/** A single stored record: `{ id, data }`. */
|
|
18764
|
-
var SettingsRecordSchema = object({
|
|
18765
|
-
id: string(),
|
|
18766
|
-
data: record(string(), unknown())
|
|
18767
|
-
});
|
|
18768
|
-
/**
|
|
18769
|
-
* Column declaration for a structured (SQL-backed) collection.
|
|
18770
|
-
*
|
|
18771
|
-
* Logical types — the backend translates each to the matching SQLite
|
|
18772
|
-
* storage class and handles per-type marshaling:
|
|
18773
|
-
* - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
|
|
18774
|
-
* - `JSON` — TEXT under the hood; serialised on write, parsed on read
|
|
18775
|
-
* - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
|
|
18776
|
-
*/
|
|
18777
|
-
var CollectionColumnSchema = object({
|
|
18778
|
-
name: string(),
|
|
18779
|
-
type: _enum([
|
|
18780
|
-
"TEXT",
|
|
18781
|
-
"INTEGER",
|
|
18782
|
-
"REAL",
|
|
18783
|
-
"JSON",
|
|
18784
|
-
"BOOLEAN"
|
|
18785
|
-
]),
|
|
18786
|
-
primaryKey: boolean().optional(),
|
|
18787
|
-
notNull: boolean().optional(),
|
|
18788
|
-
unique: boolean().optional()
|
|
18789
|
-
});
|
|
18790
|
-
var CollectionIndexSchema = object({
|
|
18791
|
-
name: string(),
|
|
18792
|
-
columns: array(string()).readonly(),
|
|
18793
|
-
unique: boolean().optional()
|
|
18794
|
-
});
|
|
18795
|
-
method(object({
|
|
18796
|
-
namespace: string().optional(),
|
|
18797
|
-
collection: string(),
|
|
18798
|
-
key: string()
|
|
18799
|
-
}), unknown()), method(object({
|
|
18800
|
-
namespace: string().optional(),
|
|
18801
|
-
collection: string(),
|
|
18802
|
-
key: string(),
|
|
18803
|
-
value: unknown()
|
|
18804
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18805
|
-
namespace: string().optional(),
|
|
18806
|
-
collection: string(),
|
|
18807
|
-
filter: QueryFilterSchema.optional()
|
|
18808
|
-
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
18809
|
-
namespace: string().optional(),
|
|
18810
|
-
collection: string(),
|
|
18811
|
-
record: SettingsRecordSchema
|
|
18812
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18813
|
-
namespace: string().optional(),
|
|
18814
|
-
collection: string(),
|
|
18815
|
-
id: string(),
|
|
18816
|
-
data: record(string(), unknown())
|
|
18817
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18818
|
-
namespace: string().optional(),
|
|
18819
|
-
collection: string(),
|
|
18820
|
-
key: string()
|
|
18821
|
-
}), _void(), { kind: "mutation" }), method(object({
|
|
18822
|
-
namespace: string().optional(),
|
|
18823
|
-
collection: string(),
|
|
18824
|
-
filter: QueryFilterSchema.optional()
|
|
18825
|
-
}), number()), method(object({
|
|
18826
|
-
namespace: string().optional(),
|
|
18827
|
-
collection: string(),
|
|
18828
|
-
field: string(),
|
|
18829
|
-
bucketSize: number().int().positive(),
|
|
18830
|
-
origin: number().int(),
|
|
18831
|
-
filter: QueryFilterSchema.optional()
|
|
18832
|
-
}), array(object({
|
|
18833
|
-
bucket: number().int(),
|
|
18834
|
-
count: number().int()
|
|
18835
|
-
})).readonly()), method(object({
|
|
18836
|
-
namespace: string().optional(),
|
|
18837
|
-
collection: string()
|
|
18838
|
-
}), boolean()), method(object({
|
|
18839
|
-
namespace: string().optional(),
|
|
18840
|
-
collection: string(),
|
|
18841
|
-
columns: array(CollectionColumnSchema).readonly(),
|
|
18842
|
-
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
18843
|
-
}), _void(), { kind: "mutation" });
|
|
18844
|
-
/**
|
|
18845
19416
|
* `smtp-provider` — pluggable email delivery surface.
|
|
18846
19417
|
*
|
|
18847
19418
|
* Collection cap: a deployment may install multiple SMTP relays (e.g.
|
|
@@ -22704,6 +23275,54 @@ Object.freeze({
|
|
|
22704
23275
|
addonId: null,
|
|
22705
23276
|
access: "create"
|
|
22706
23277
|
},
|
|
23278
|
+
"coreBlocks.compile": {
|
|
23279
|
+
capName: "core-blocks",
|
|
23280
|
+
capScope: "system",
|
|
23281
|
+
addonId: null,
|
|
23282
|
+
access: "create"
|
|
23283
|
+
},
|
|
23284
|
+
"coreBlocks.create": {
|
|
23285
|
+
capName: "core-blocks",
|
|
23286
|
+
capScope: "system",
|
|
23287
|
+
addonId: null,
|
|
23288
|
+
access: "create"
|
|
23289
|
+
},
|
|
23290
|
+
"coreBlocks.delete": {
|
|
23291
|
+
capName: "core-blocks",
|
|
23292
|
+
capScope: "system",
|
|
23293
|
+
addonId: null,
|
|
23294
|
+
access: "delete"
|
|
23295
|
+
},
|
|
23296
|
+
"coreBlocks.get": {
|
|
23297
|
+
capName: "core-blocks",
|
|
23298
|
+
capScope: "system",
|
|
23299
|
+
addonId: null,
|
|
23300
|
+
access: "view"
|
|
23301
|
+
},
|
|
23302
|
+
"coreBlocks.getTypeDefs": {
|
|
23303
|
+
capName: "core-blocks",
|
|
23304
|
+
capScope: "system",
|
|
23305
|
+
addonId: null,
|
|
23306
|
+
access: "view"
|
|
23307
|
+
},
|
|
23308
|
+
"coreBlocks.list": {
|
|
23309
|
+
capName: "core-blocks",
|
|
23310
|
+
capScope: "system",
|
|
23311
|
+
addonId: null,
|
|
23312
|
+
access: "view"
|
|
23313
|
+
},
|
|
23314
|
+
"coreBlocks.setEnabled": {
|
|
23315
|
+
capName: "core-blocks",
|
|
23316
|
+
capScope: "system",
|
|
23317
|
+
addonId: null,
|
|
23318
|
+
access: "create"
|
|
23319
|
+
},
|
|
23320
|
+
"coreBlocks.update": {
|
|
23321
|
+
capName: "core-blocks",
|
|
23322
|
+
capScope: "system",
|
|
23323
|
+
addonId: null,
|
|
23324
|
+
access: "create"
|
|
23325
|
+
},
|
|
22707
23326
|
"cover.close": {
|
|
22708
23327
|
capName: "cover",
|
|
22709
23328
|
capScope: "device",
|
|
@@ -22740,6 +23359,84 @@ Object.freeze({
|
|
|
22740
23359
|
addonId: null,
|
|
22741
23360
|
access: "view"
|
|
22742
23361
|
},
|
|
23362
|
+
"dataStoreProvider.count": {
|
|
23363
|
+
capName: "data-store-provider",
|
|
23364
|
+
capScope: "system",
|
|
23365
|
+
addonId: null,
|
|
23366
|
+
access: "view"
|
|
23367
|
+
},
|
|
23368
|
+
"dataStoreProvider.declareCollection": {
|
|
23369
|
+
capName: "data-store-provider",
|
|
23370
|
+
capScope: "system",
|
|
23371
|
+
addonId: null,
|
|
23372
|
+
access: "create"
|
|
23373
|
+
},
|
|
23374
|
+
"dataStoreProvider.delete": {
|
|
23375
|
+
capName: "data-store-provider",
|
|
23376
|
+
capScope: "system",
|
|
23377
|
+
addonId: null,
|
|
23378
|
+
access: "delete"
|
|
23379
|
+
},
|
|
23380
|
+
"dataStoreProvider.deleteWhere": {
|
|
23381
|
+
capName: "data-store-provider",
|
|
23382
|
+
capScope: "system",
|
|
23383
|
+
addonId: null,
|
|
23384
|
+
access: "delete"
|
|
23385
|
+
},
|
|
23386
|
+
"dataStoreProvider.get": {
|
|
23387
|
+
capName: "data-store-provider",
|
|
23388
|
+
capScope: "system",
|
|
23389
|
+
addonId: null,
|
|
23390
|
+
access: "view"
|
|
23391
|
+
},
|
|
23392
|
+
"dataStoreProvider.getEngineInfo": {
|
|
23393
|
+
capName: "data-store-provider",
|
|
23394
|
+
capScope: "system",
|
|
23395
|
+
addonId: null,
|
|
23396
|
+
access: "view"
|
|
23397
|
+
},
|
|
23398
|
+
"dataStoreProvider.histogram": {
|
|
23399
|
+
capName: "data-store-provider",
|
|
23400
|
+
capScope: "system",
|
|
23401
|
+
addonId: null,
|
|
23402
|
+
access: "view"
|
|
23403
|
+
},
|
|
23404
|
+
"dataStoreProvider.insert": {
|
|
23405
|
+
capName: "data-store-provider",
|
|
23406
|
+
capScope: "system",
|
|
23407
|
+
addonId: null,
|
|
23408
|
+
access: "create"
|
|
23409
|
+
},
|
|
23410
|
+
"dataStoreProvider.isEmpty": {
|
|
23411
|
+
capName: "data-store-provider",
|
|
23412
|
+
capScope: "system",
|
|
23413
|
+
addonId: null,
|
|
23414
|
+
access: "view"
|
|
23415
|
+
},
|
|
23416
|
+
"dataStoreProvider.query": {
|
|
23417
|
+
capName: "data-store-provider",
|
|
23418
|
+
capScope: "system",
|
|
23419
|
+
addonId: null,
|
|
23420
|
+
access: "view"
|
|
23421
|
+
},
|
|
23422
|
+
"dataStoreProvider.set": {
|
|
23423
|
+
capName: "data-store-provider",
|
|
23424
|
+
capScope: "system",
|
|
23425
|
+
addonId: null,
|
|
23426
|
+
access: "create"
|
|
23427
|
+
},
|
|
23428
|
+
"dataStoreProvider.update": {
|
|
23429
|
+
capName: "data-store-provider",
|
|
23430
|
+
capScope: "system",
|
|
23431
|
+
addonId: null,
|
|
23432
|
+
access: "create"
|
|
23433
|
+
},
|
|
23434
|
+
"dataStoreProvider.updateWhere": {
|
|
23435
|
+
capName: "data-store-provider",
|
|
23436
|
+
capScope: "system",
|
|
23437
|
+
addonId: null,
|
|
23438
|
+
access: "create"
|
|
23439
|
+
},
|
|
22743
23440
|
"dayNight.getOptions": {
|
|
22744
23441
|
capName: "day-night",
|
|
22745
23442
|
capScope: "device",
|
|
@@ -24534,12 +25231,24 @@ Object.freeze({
|
|
|
24534
25231
|
addonId: null,
|
|
24535
25232
|
access: "create"
|
|
24536
25233
|
},
|
|
25234
|
+
"notificationRules.cancelSnooze": {
|
|
25235
|
+
capName: "notification-rules",
|
|
25236
|
+
capScope: "system",
|
|
25237
|
+
addonId: null,
|
|
25238
|
+
access: "create"
|
|
25239
|
+
},
|
|
24537
25240
|
"notificationRules.createRule": {
|
|
24538
25241
|
capName: "notification-rules",
|
|
24539
25242
|
capScope: "system",
|
|
24540
25243
|
addonId: null,
|
|
24541
25244
|
access: "create"
|
|
24542
25245
|
},
|
|
25246
|
+
"notificationRules.createSnooze": {
|
|
25247
|
+
capName: "notification-rules",
|
|
25248
|
+
capScope: "system",
|
|
25249
|
+
addonId: null,
|
|
25250
|
+
access: "create"
|
|
25251
|
+
},
|
|
24543
25252
|
"notificationRules.deleteRule": {
|
|
24544
25253
|
capName: "notification-rules",
|
|
24545
25254
|
capScope: "system",
|
|
@@ -24570,6 +25279,12 @@ Object.freeze({
|
|
|
24570
25279
|
addonId: null,
|
|
24571
25280
|
access: "view"
|
|
24572
25281
|
},
|
|
25282
|
+
"notificationRules.listSnoozes": {
|
|
25283
|
+
capName: "notification-rules",
|
|
25284
|
+
capScope: "system",
|
|
25285
|
+
addonId: null,
|
|
25286
|
+
access: "view"
|
|
25287
|
+
},
|
|
24573
25288
|
"notificationRules.setRuleEnabled": {
|
|
24574
25289
|
capName: "notification-rules",
|
|
24575
25290
|
capScope: "system",
|
|
@@ -24774,6 +25489,12 @@ Object.freeze({
|
|
|
24774
25489
|
addonId: null,
|
|
24775
25490
|
access: "view"
|
|
24776
25491
|
},
|
|
25492
|
+
"pipelineAnalytics.listEventKindsBatch": {
|
|
25493
|
+
capName: "pipeline-analytics",
|
|
25494
|
+
capScope: "device",
|
|
25495
|
+
addonId: null,
|
|
25496
|
+
access: "view"
|
|
25497
|
+
},
|
|
24777
25498
|
"pipelineAnalytics.listOpsLog": {
|
|
24778
25499
|
capName: "pipeline-analytics",
|
|
24779
25500
|
capScope: "device",
|
|
@@ -24786,6 +25507,12 @@ Object.freeze({
|
|
|
24786
25507
|
addonId: null,
|
|
24787
25508
|
access: "view"
|
|
24788
25509
|
},
|
|
25510
|
+
"pipelineAnalytics.listTrackMedia": {
|
|
25511
|
+
capName: "pipeline-analytics",
|
|
25512
|
+
capScope: "device",
|
|
25513
|
+
addonId: null,
|
|
25514
|
+
access: "view"
|
|
25515
|
+
},
|
|
24789
25516
|
"pipelineAnalytics.listTracks": {
|
|
24790
25517
|
capName: "pipeline-analytics",
|
|
24791
25518
|
capScope: "device",
|
|
@@ -25818,6 +26545,12 @@ Object.freeze({
|
|
|
25818
26545
|
addonId: null,
|
|
25819
26546
|
access: "delete"
|
|
25820
26547
|
},
|
|
26548
|
+
"settingsStore.deleteWhere": {
|
|
26549
|
+
capName: "settings-store",
|
|
26550
|
+
capScope: "system",
|
|
26551
|
+
addonId: null,
|
|
26552
|
+
access: "delete"
|
|
26553
|
+
},
|
|
25821
26554
|
"settingsStore.get": {
|
|
25822
26555
|
capName: "settings-store",
|
|
25823
26556
|
capScope: "system",
|
|
@@ -25860,6 +26593,12 @@ Object.freeze({
|
|
|
25860
26593
|
addonId: null,
|
|
25861
26594
|
access: "create"
|
|
25862
26595
|
},
|
|
26596
|
+
"settingsStore.updateWhere": {
|
|
26597
|
+
capName: "settings-store",
|
|
26598
|
+
capScope: "system",
|
|
26599
|
+
addonId: null,
|
|
26600
|
+
access: "create"
|
|
26601
|
+
},
|
|
25863
26602
|
"smtpProvider.getStatus": {
|
|
25864
26603
|
capName: "smtp-provider",
|
|
25865
26604
|
capScope: "system",
|
|
@@ -28586,6 +29325,7 @@ var WEBHOOK_CAPS = {
|
|
|
28586
29325
|
max: 99
|
|
28587
29326
|
},
|
|
28588
29327
|
actions: 99,
|
|
29328
|
+
actionIcons: true,
|
|
28589
29329
|
levels: [
|
|
28590
29330
|
{
|
|
28591
29331
|
id: "p1",
|
|
@@ -29918,7 +30658,7 @@ function createMemorySettingsStorePort() {
|
|
|
29918
30658
|
* the store persists whatever it is handed verbatim.
|
|
29919
30659
|
*/
|
|
29920
30660
|
/** The default collection name (see `settings-store` addonId-prefix note). */
|
|
29921
|
-
var TARGETS_COLLECTION = "notification-targets";
|
|
30661
|
+
var TARGETS_COLLECTION = "notifiers:notification-targets";
|
|
29922
30662
|
/** KV JSON-blob shape — a single `data` column routes the row through the
|
|
29923
30663
|
* settings backend's canonical key/value path (id TEXT PK, data TEXT). */
|
|
29924
30664
|
var KV_BLOB_COLUMNS = [{
|