@camstack/addon-mqtt-broker 1.2.9 → 1.2.10

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.
@@ -44,7 +44,7 @@ let crypto$10 = __toESM(crypto$1, 1);
44
44
  crypto$1 = __toESM(crypto$1);
45
45
  let node_fs = require("node:fs");
46
46
  let node_path = require("node:path");
47
- //#region ../types/dist/event-category-Bz24uP1U.mjs
47
+ //#region ../types/dist/event-category-41fKf-q9.mjs
48
48
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
49
49
  EventCategory["SystemBoot"] = "system.boot";
50
50
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -104,6 +104,26 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
104
104
  */
105
105
  EventCategory["AddonRetryScheduled"] = "addon.retry-scheduled";
106
106
  /**
107
+ * A liveness invariant of this node is false — it has no devices, every
108
+ * camera is offline, or a camera that was recording has stopped producing
109
+ * segments. Emitted by the `liveness-monitor` builtin, once per fault (the
110
+ * `findingId` is stable across ticks), only after its boot grace period.
111
+ * AlertCenter raises a persistent operator-visible alert.
112
+ *
113
+ * It exists because on 2026-08-03 the hub ran four hours with zero devices
114
+ * and zero recordings while every surface stayed quiet.
115
+ *
116
+ * Payload: `{ findingId, severity, title, message, deviceId? }`.
117
+ */
118
+ EventCategory["SystemLivenessFailed"] = "system.liveness-failed";
119
+ /**
120
+ * A previously reported liveness fault is true again. AlertCenter dismisses
121
+ * the matching `SystemLivenessFailed` alert.
122
+ *
123
+ * Payload: `{ findingId }`.
124
+ */
125
+ EventCategory["SystemLivenessRecovered"] = "system.liveness-recovered";
126
+ /**
107
127
  * Monitor is attempting to reload a failed addon NOW. UI uses this
108
128
  * to show a spinner during the retry attempt. Same transient nature
109
129
  * as AddonRetryScheduled.
@@ -7354,14 +7374,7 @@ var RecordingConfigSchema = object({
7354
7374
  * windows only — existing sheets are immutable, and each window's index
7355
7375
  * carries its own tile dims so mixed-preset history renders correctly.
7356
7376
  */
7357
- scrubThumbnails: ScrubThumbnailPresetSchema.optional(),
7358
- /**
7359
- * OPT-IN thumbnail-strip generation for this camera: every keyframe of the
7360
- * low recording saved as a JPEG (the fast-drag scrub depth), a derived
7361
- * cache that eviction reclaims with the footage. Absent/false = no strips
7362
- * are written and scrub reads exact keyframes at every velocity.
7363
- */
7364
- stripsEnabled: boolean().optional()
7377
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7365
7378
  }).strict();
7366
7379
  /**
7367
7380
  * Ops-log — the durable, append-only operations audit shared by the
@@ -7423,7 +7436,7 @@ var OpsLogQueryInputSchema = object({
7423
7436
  /**
7424
7437
  * Entity-relocation job state (storage entity-routing spec, Phase 4).
7425
7438
  *
7426
- * One shape shared by the recorder's `relocateFootage` (segments + strips) and
7439
+ * One shape shared by the recorder's `relocateFootage` (segments) and
7427
7440
  * pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
7428
7441
  * page renders both movers with one component. Jobs are in-RAM (a restart
7429
7442
  * forgets them — re-running is safe by construction: copy-if-absent, delete
@@ -7445,7 +7458,7 @@ var RelocateJobSchema = object({
7445
7458
  toLocationId: string(),
7446
7459
  /** Scoped device, or null = every device. */
7447
7460
  deviceId: number().nullable(),
7448
- /** What the job moves (owner-addon specific: segments/strips or media). */
7461
+ /** What the job moves (owner-addon specific: segments or media). */
7449
7462
  entities: array(string()),
7450
7463
  filesMoved: number().int(),
7451
7464
  bytesMoved: number().int(),
@@ -7459,7 +7472,7 @@ var RelocateFootageInputSchema = object({
7459
7472
  deviceId: number().optional(),
7460
7473
  fromLocationId: string(),
7461
7474
  toLocationId: string(),
7462
- entities: array(_enum(["segments", "strips"])).optional(),
7475
+ entities: array(_enum(["segments"])).optional(),
7463
7476
  /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7464
7477
  * never allowed to starve live writers. */
7465
7478
  throttleMbps: number().min(1).max(1e3).optional()
@@ -9017,6 +9030,276 @@ var MaskGridDimsSchema = object({
9017
9030
  height: number()
9018
9031
  });
9019
9032
  /**
9033
+ * notification-output — canonical, capability-gated notification delivery.
9034
+ *
9035
+ * Apprise-derived model (see
9036
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
9037
+ * callers emit ONE canonical `Notification`; each provider declares a
9038
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
9039
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
9040
+ * message to what the kind supports — callers never special-case a service.
9041
+ *
9042
+ * DESIGN DECISIONS (locked):
9043
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
9044
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
9045
+ * cap. Rationale: the admin UI needs one uniform surface across the
9046
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
9047
+ * alternative would fork the UI per addon and cannot host the
9048
+ * discovery→adopt flow.
9049
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
9050
+ * the generated cap-mount auto-`concatCollection`-fans them across every
9051
+ * registered provider (notifiers addon + HA addon) so one catalog is
9052
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
9053
+ * `addonId` the generated collection router extracts from the call input.
9054
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
9055
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
9056
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
9057
+ * base64 fallback needed.
9058
+ *
9059
+ * TODO (deferred, closed-set change — separate decision): add
9060
+ * `providerKind: 'notify'` so notification providers surface on the unified
9061
+ * admin "Integrations" page.
9062
+ */
9063
+ /**
9064
+ * Zentik-derived typed-media enum — the superset across every kind. Each
9065
+ * adapter picks what it supports and the degrade engine filters the rest.
9066
+ */
9067
+ var AttachmentMediaTypeSchema = _enum([
9068
+ "image",
9069
+ "video",
9070
+ "gif",
9071
+ "audio",
9072
+ "icon"
9073
+ ]);
9074
+ /**
9075
+ * A single attachment. Exactly one of `url` (remote source, most adapters
9076
+ * prefer this) or `bytes` (inline source; required for Pushover-style
9077
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
9078
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
9079
+ */
9080
+ var AttachmentSchema = object({
9081
+ mediaType: AttachmentMediaTypeSchema,
9082
+ url: string().optional(),
9083
+ bytes: _instanceof(Uint8Array).optional(),
9084
+ mime: string().optional(),
9085
+ name: string().optional()
9086
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
9087
+ var NotificationFormatSchema = _enum([
9088
+ "text",
9089
+ "markdown",
9090
+ "html"
9091
+ ]);
9092
+ /**
9093
+ * The CLOSED icon vocabulary an action button may use.
9094
+ *
9095
+ * A closed set, not a free string, and that is the whole point: an arbitrary
9096
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
9097
+ * notices — the same class of gap as a zone vocabulary nothing produced
9098
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
9099
+ * declares `actionIcons: false` and the degrade engine strips the field.
9100
+ *
9101
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
9102
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
9103
+ * differently.
9104
+ */
9105
+ var NotificationActionIconSchema = _enum([
9106
+ "acknowledge",
9107
+ "dismiss",
9108
+ "silence",
9109
+ "view",
9110
+ "play",
9111
+ "open",
9112
+ "close",
9113
+ "lock",
9114
+ "unlock",
9115
+ "arm",
9116
+ "disarm",
9117
+ "light",
9118
+ "alert"
9119
+ ]);
9120
+ /** A single tap-through action button. */
9121
+ var NotificationActionSchema = object({
9122
+ id: string(),
9123
+ label: string(),
9124
+ url: string().optional(),
9125
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
9126
+ icon: NotificationActionIconSchema.optional(),
9127
+ /**
9128
+ * Renders in a warning style where the notifier supports it.
9129
+ *
9130
+ * A HINT, never a gate. The callback's authority is its token and nothing
9131
+ * else — see `notification-center/action-token.ts` for what that does and
9132
+ * does not buy.
9133
+ */
9134
+ destructive: boolean().optional()
9135
+ });
9136
+ /**
9137
+ * The canonical notification. `body` is the only hard field (Apprise model).
9138
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
9139
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
9140
+ * the adapter maps this ordinal onto its native level. `level?` is an
9141
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
9142
+ * `priority` for that one target.
9143
+ */
9144
+ var NotificationSchema = object({
9145
+ body: string(),
9146
+ title: string().optional(),
9147
+ format: NotificationFormatSchema.default("text"),
9148
+ priority: number().int().min(1).max(5).default(3),
9149
+ level: string().optional(),
9150
+ attachments: array(AttachmentSchema).optional(),
9151
+ clickUrl: string().optional(),
9152
+ actions: array(NotificationActionSchema).optional(),
9153
+ sound: string().optional(),
9154
+ ttl: number().optional(),
9155
+ tag: string().optional(),
9156
+ deviceId: number().optional(),
9157
+ eventId: string().optional(),
9158
+ metadata: record(string(), unknown()).optional()
9159
+ });
9160
+ /** One declared native severity/priority level for a kind. */
9161
+ var TargetKindLevelSchema = object({
9162
+ id: string(),
9163
+ label: string(),
9164
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
9165
+ ordinal: number().int().min(1).max(5).nullable(),
9166
+ flags: object({
9167
+ critical: boolean().optional(),
9168
+ silent: boolean().optional(),
9169
+ noPush: boolean().optional()
9170
+ }).optional(),
9171
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
9172
+ requires: array(string()).optional(),
9173
+ description: string().optional()
9174
+ });
9175
+ /** The full capability block consulted before dispatch. */
9176
+ var TargetKindCapsSchema = object({
9177
+ attachments: object({
9178
+ mediaTypes: array(AttachmentMediaTypeSchema),
9179
+ mode: _enum([
9180
+ "url",
9181
+ "bytes",
9182
+ "both"
9183
+ ]),
9184
+ max: number().int().nonnegative(),
9185
+ maxBytes: number().int().positive().optional()
9186
+ }),
9187
+ /** Max action buttons (0 = none). */
9188
+ actions: number().int().nonnegative(),
9189
+ /**
9190
+ * Whether this kind renders a per-action ICON.
9191
+ *
9192
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
9193
+ * run on the addon cap path — three production failures in one day taught
9194
+ * this repo that once. Absent is read as false by the degrade engine, which
9195
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
9196
+ * assumed and dropped costs the operator's trust in the field.
9197
+ */
9198
+ actionIcons: boolean().optional(),
9199
+ levels: array(TargetKindLevelSchema),
9200
+ format: array(NotificationFormatSchema),
9201
+ clickUrl: boolean(),
9202
+ sound: boolean(),
9203
+ ttl: boolean(),
9204
+ bodyMaxLen: number().int().positive()
9205
+ });
9206
+ /**
9207
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
9208
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
9209
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
9210
+ * the union is large and not meant for runtime validation here; the exported
9211
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
9212
+ */
9213
+ var ConfigSchemaPassthrough$1 = unknown();
9214
+ var TargetKindSchema = object({
9215
+ kind: string(),
9216
+ label: string(),
9217
+ icon: string(),
9218
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
9219
+ addonId: string(),
9220
+ /**
9221
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
9222
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
9223
+ * when the addon bundles no icon for that kind — the client then falls back
9224
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
9225
+ *
9226
+ * Root-relative on purpose: it resolves against whatever origin serves a web
9227
+ * client, and a native client joins it onto its own hub base.
9228
+ *
9229
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
9230
+ * field that survived only because the runtime cap-router forwards provider
9231
+ * output verbatim — so every consumer had to re-declare it by hand to stop
9232
+ * its own Zod parse from stripping it, and the whole arrangement would have
9233
+ * broken silently the moment output validation was tightened anywhere.
9234
+ */
9235
+ iconUrl: string().optional(),
9236
+ /**
9237
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
9238
+ *
9239
+ * The server knows this and therefore says it, because the client cannot
9240
+ * safely guess: a React-Native client renders SVG and raster through two
9241
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
9242
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
9243
+ * placeholder glyph for every vector icon while the web build looked fine.
9244
+ *
9245
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
9246
+ * not been updated — a client that cannot determine the type should prefer
9247
+ * its raster path, which is the safe default for an unknown image.
9248
+ */
9249
+ iconMediaType: string().optional(),
9250
+ configSchema: ConfigSchemaPassthrough$1,
9251
+ supportsDiscovery: boolean(),
9252
+ caps: TargetKindCapsSchema
9253
+ });
9254
+ /**
9255
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
9256
+ * (return a presence marker only) when serving `listTargets` — never
9257
+ * round-trip a stored secret to the UI.
9258
+ */
9259
+ var TargetSchema = object({
9260
+ id: string(),
9261
+ name: string(),
9262
+ kind: string(),
9263
+ addonId: string(),
9264
+ enabled: boolean(),
9265
+ config: record(string(), unknown())
9266
+ });
9267
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
9268
+ var DiscoveredTargetSchema = object({
9269
+ kind: string(),
9270
+ suggestedName: string(),
9271
+ config: record(string(), unknown())
9272
+ });
9273
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
9274
+ var RenderedAsSchema = object({
9275
+ level: string(),
9276
+ format: NotificationFormatSchema,
9277
+ attachmentsSent: number().int().nonnegative(),
9278
+ actionsSent: number().int().nonnegative(),
9279
+ truncated: boolean(),
9280
+ dropped: array(string())
9281
+ });
9282
+ var SendResultSchema = object({
9283
+ success: boolean(),
9284
+ error: string().optional(),
9285
+ renderedAs: RenderedAsSchema.optional()
9286
+ });
9287
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
9288
+ var TestResultSchema = SendResultSchema;
9289
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
9290
+ kind: string(),
9291
+ config: record(string(), unknown()).optional()
9292
+ }), array(DiscoveredTargetSchema)), method(object({
9293
+ targetId: string(),
9294
+ notification: NotificationSchema
9295
+ }), SendResultSchema, { kind: "mutation" }), method(object({
9296
+ targetId: string(),
9297
+ sample: NotificationSchema.optional()
9298
+ }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
9299
+ targetId: string(),
9300
+ enabled: boolean()
9301
+ }), _void(), { kind: "mutation" });
9302
+ /**
9020
9303
  * notification-rules — the Notification Center rule surface (P1 core).
9021
9304
  *
9022
9305
  * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
@@ -9146,7 +9429,115 @@ var NcZoneConditionSchema = object({
9146
9429
  * The P1 condition set — a flat AND of groups; absent group = pass;
9147
9430
  * membership lists are OR within the list (spec §2.3).
9148
9431
  */
9432
+ /**
9433
+ * What a rule may actuate.
9434
+ *
9435
+ * **No hand-maintained allowlist** (operator decision, and the right one — a
9436
+ * written list of methods is a third parallel map to keep aligned, and this
9437
+ * repo has paid for those). The boundary instead comes from a property the
9438
+ * capabilities already carry: an action may target only a **device-scoped**
9439
+ * capability method.
9440
+ *
9441
+ * That is not decoration. A rule can be authored by a NON-ADMIN — personal
9442
+ * rules are a supported flow — and the executor runs with the addon's
9443
+ * privileges, so an unbounded action is an arbitrary RPC channel with a
9444
+ * privilege escalation attached. Restricting to device scope excludes the
9445
+ * system caps (`device-manager.removeDevice` and friends) by construction,
9446
+ * costs nothing to maintain, and cannot rot: a cap that stops being
9447
+ * device-scoped stops being actuatable in the same change.
9448
+ *
9449
+ * The executor enforces it; {@link NcRuleActionSchema} carries the intent.
9450
+ */
9451
+ /**
9452
+ * One step of a sequence.
9453
+ *
9454
+ * `wait` is a first-class step rather than a property of the next action: it is
9455
+ * what makes a sequence a SEQUENCE and not a list — "unlock, wait 5s, open"
9456
+ * cannot be expressed otherwise.
9457
+ */
9458
+ var NcRuleActionSchema = discriminatedUnion("kind", [object({
9459
+ kind: literal("wait"),
9460
+ seconds: number().min(0).max(300)
9461
+ }), object({
9462
+ kind: literal("cap"),
9463
+ deviceId: number().int(),
9464
+ /** Capability name, e.g. `alarm-panel`. */
9465
+ cap: string().min(1),
9466
+ /** Method on it. The executor refuses a non-device-scoped cap. */
9467
+ method: string().min(1),
9468
+ /** Method arguments, minus `deviceId` (the executor injects it). */
9469
+ args: record(string(), unknown()).optional()
9470
+ })]);
9471
+ /**
9472
+ * A named, ordered run of steps with its own throttle.
9473
+ *
9474
+ * `minDelaySec` exists because a noisy rule otherwise hammers a physical
9475
+ * actuator — the rule's own cooldown governs NOTIFICATIONS, which is a
9476
+ * different budget from "how often may this gate actually open".
9477
+ */
9478
+ var NcRuleActionSequenceSchema = object({
9479
+ name: string().min(1).max(120),
9480
+ enabled: boolean(),
9481
+ minDelaySec: number().int().min(0).max(86400).optional(),
9482
+ actions: array(NcRuleActionSchema).min(1)
9483
+ });
9484
+ /**
9485
+ * One button carried by the notification, running a named sequence on tap.
9486
+ *
9487
+ * **Read this before adding a button that does something physical.** The tap
9488
+ * arrives over a link that travelled through third-party infrastructure — ntfy,
9489
+ * a push relay, whatever forwarded the message — and the callback's ONLY
9490
+ * authority is the token in that link: single-use, short-lived, bound to this
9491
+ * one action of this one notification. It does not identify who tapped.
9492
+ * Whoever holds the notification can run the button, once, inside the window.
9493
+ * That is the operator's explicit choice (2026-08-05), and `destructive` is a
9494
+ * rendering hint, not a second gate. [D47](decisions/adr-0047.md).
9495
+ */
9496
+ var NcRuleNotificationButtonSchema = object({
9497
+ /** Stable id — travels in the callback and identifies the button in logs. */
9498
+ id: string().min(1).max(64),
9499
+ label: string().min(1).max(40),
9500
+ /** Name of a sequence in `onTrigger`. The dispatcher drops a button whose
9501
+ * sequence does not exist rather than minting a token for nothing. */
9502
+ sequence: string().min(1).max(120),
9503
+ icon: NotificationActionIconSchema.optional(),
9504
+ destructive: boolean().optional()
9505
+ });
9506
+ /**
9507
+ * Sequences a rule runs, by hook point.
9508
+ *
9509
+ * ONLY `onTrigger` is here, deliberately. The reference also has activation /
9510
+ * deactivation / reset / post-generation hooks, and they are wanted — but this
9511
+ * repo's expensive failure mode is declaring a surface nothing produces, so a
9512
+ * hook appears here in the same change that produces its edge, never before.
9513
+ */
9514
+ var NcRuleActionsSchema = object({
9515
+ /** Runs when the rule MATCHES. */
9516
+ onTrigger: array(NcRuleActionSequenceSchema).optional(),
9517
+ /**
9518
+ * Buttons the NOTIFICATION carries, each running one of this rule's
9519
+ * sequences when tapped.
9520
+ *
9521
+ * Deliberately a REFERENCE to a sequence rather than a second place to
9522
+ * author steps. A button that could define its own actions would be a
9523
+ * parallel actuation vocabulary — the executor's device-scope check, the
9524
+ * stop-at-first-failure rule and the per-sequence throttle all live on
9525
+ * sequences, and a second authoring surface would drift from every one of
9526
+ * them.
9527
+ *
9528
+ * A sequence reachable ONLY by a button simply appears in `onTrigger` with
9529
+ * `enabled: false`: it is then authored, throttled and validated like the
9530
+ * rest, and nothing runs it automatically.
9531
+ */
9532
+ buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
9533
+ });
9149
9534
  var NcConditionsSchema = object({
9535
+ /** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
9536
+ deviceState: object({
9537
+ deviceId: number().int(),
9538
+ /** Any of these matches. */
9539
+ states: array(string().min(1)).min(1)
9540
+ }).optional(),
9150
9541
  /** Device scope — absent = all devices. */
9151
9542
  devices: array(number()).optional(),
9152
9543
  /** Detector class names (any overlap with the record's class set). */
@@ -9347,6 +9738,14 @@ var NcMediaPolicySchema = object({
9347
9738
  clipPreRollSec: number().int().min(0).max(30).optional(),
9348
9739
  clipPostRollSec: number().int().min(0).max(30).optional(),
9349
9740
  /**
9741
+ * Playback rate of the attached gif / clip. Absent = 2x.
9742
+ *
9743
+ * A notification clip is GLANCED at on a lock screen, not watched: at real
9744
+ * time an eight-second passage is eight seconds of the recipient's attention
9745
+ * and twice the bytes. 1 is real time for the operator who wants it.
9746
+ */
9747
+ clipSpeed: number().min(1).max(8).optional(),
9748
+ /**
9350
9749
  * Which stream profile the footage is cut from. Absent = the CHEAPEST
9351
9750
  * assigned profile: a notification is watched on a phone, so the 4K
9352
9751
  * rendition would burn CPU to produce a file the client downscales anyway.
@@ -9418,7 +9817,30 @@ var NcRuleInputSchema = object({
9418
9817
  * behaviour, visible to all, read-only in the viewer). Present = personal
9419
9818
  * rule owned by this userId. Server-stamped; never trusted from a client.
9420
9819
  */
9421
- ownerUserId: string().optional()
9820
+ ownerUserId: string().optional(),
9821
+ /**
9822
+ * May a non-admin snooze this rule for EVERYONE, not just themselves?
9823
+ *
9824
+ * A snooze is personal by default — it silences the person who set it. This
9825
+ * opts THIS rule into the "the gardener is here all afternoon" case, where
9826
+ * silencing the camera for the whole household is legitimate. It silences
9827
+ * other people, so it is off unless a rule deliberately allows it.
9828
+ *
9829
+ * `.optional()`, deliberately NOT `.default()`: a Zod default does not run on
9830
+ * the addon cap path (three production failures in one day), so absent is
9831
+ * read as `false` by {@link canSetGlobal} in the engine. Admins are not bound
9832
+ * by this flag — see the scope rules on that function.
9833
+ */
9834
+ snoozeAllowGlobal: boolean().optional(),
9835
+ /**
9836
+ * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
9837
+ *
9838
+ * This is what makes the rule set the alarm's trigger set without the alarm
9839
+ * being a special case: arming is
9840
+ * `{ cap: 'alarm-panel', method: 'arm', args: { mode: 'away' } }`, the same
9841
+ * shape as every other actuation.
9842
+ */
9843
+ actions: NcRuleActionsSchema.optional()
9422
9844
  });
9423
9845
  /**
9424
9846
  * Partial patch for `updateRule` — any subset of the input fields, plus the
@@ -9489,7 +9911,8 @@ var NcConditionDescriptorSchema = object({
9489
9911
  "packagePhase",
9490
9912
  "crossingSelect",
9491
9913
  "polygonDraw",
9492
- "occupancy"
9914
+ "occupancy",
9915
+ "deviceState"
9493
9916
  ]),
9494
9917
  operator: _enum([
9495
9918
  "in",
@@ -9503,7 +9926,28 @@ var NcConditionDescriptorSchema = object({
9503
9926
  /** Which delivery kinds the condition applies to. */
9504
9927
  appliesTo: array(NcDeliverySchema),
9505
9928
  phase: string(),
9506
- description: string().optional()
9929
+ description: string().optional(),
9930
+ /**
9931
+ * The CHOICES for a single-choice widget (`sourceSelect`, `crossingSelect`,
9932
+ * `packagePhase`, …), served with the descriptor.
9933
+ *
9934
+ * Before this the descriptor said which widget to render and not what to put
9935
+ * in it, so every option list lived in three places: this file's enums, the
9936
+ * admin's `NC_*_OPTIONS` and the viewer's `NC_*_VALUES`. That triple mirror
9937
+ * is the drift that emptied the viewer's rule editor on 2026-08-04 — the app
9938
+ * mirrors the cap by hand, so it is only ever as current as its last build.
9939
+ *
9940
+ * With the options on the wire, a condition of an EXISTING `valueType` costs
9941
+ * zero client changes. Clients keep a local fallback for an older hub that
9942
+ * does not send them; absent here is "use your own list", not "no choices".
9943
+ */
9944
+ options: array(object({
9945
+ /** Written to the rule verbatim. `''` means the ABSENT state. */
9946
+ value: string(),
9947
+ label: string(),
9948
+ /** What THIS choice matches — shown one at a time, under the control. */
9949
+ hint: string().optional()
9950
+ })).readonly().optional()
9507
9951
  });
9508
9952
  /**
9509
9953
  * The delivery lifecycle status of a history row — a straight read of the
@@ -9588,6 +10032,74 @@ var NcHistoryFilterSchema = object({
9588
10032
  until: number().optional(),
9589
10033
  limit: number().int().min(1).max(500).default(100)
9590
10034
  });
10035
+ /**
10036
+ * What a snooze covers. Broader scopes win when several overlap, so one window
10037
+ * leaves ONE digest rather than a rule snooze and a whole-feed snooze both
10038
+ * summarising the same silence.
10039
+ */
10040
+ var NcSnoozeScopeSchema = _enum([
10041
+ "rule",
10042
+ "device",
10043
+ "all"
10044
+ ]);
10045
+ /**
10046
+ * Client-authored snooze. The server stamps `userId`, `startedAt` and
10047
+ * `expiresAt` — a DURATION is sent rather than an instant so a client with a
10048
+ * skewed clock cannot author a window that is already over, or never ends.
10049
+ */
10050
+ var NcSnoozeInputSchema = object({
10051
+ scope: NcSnoozeScopeSchema,
10052
+ /** Required when `scope: 'rule'` — a scoped snooze with no id matches
10053
+ * NOTHING rather than degrading to "everything". */
10054
+ ruleId: string().optional(),
10055
+ /** Required when `scope: 'device'`. */
10056
+ deviceId: number().int().optional(),
10057
+ durationMinutes: number().int().min(1).max(1440),
10058
+ /**
10059
+ * Silence this for EVERY recipient, not just the caller. Permission is
10060
+ * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
10061
+ * broader scopes). Absent = personal.
10062
+ */
10063
+ global: boolean().optional(),
10064
+ /**
10065
+ * Deliver a summary of what was suppressed when the window ends. Absent =
10066
+ * ON: someone silencing a nuisance camera wants it off, someone silencing a
10067
+ * SECURITY camera wants to know what they missed, and choosing "off" for
10068
+ * everybody is how a snooze becomes an outage. Resolved to a concrete
10069
+ * boolean by the server at create time — never left to a Zod default, which
10070
+ * does not run on the addon cap path.
10071
+ */
10072
+ summary: boolean().optional()
10073
+ });
10074
+ /** A persisted snooze window. */
10075
+ var NcSnoozeSchema = object({
10076
+ id: string(),
10077
+ /** Who set it. Also who it silences, unless `global`. */
10078
+ userId: string(),
10079
+ scope: NcSnoozeScopeSchema,
10080
+ ruleId: string().optional(),
10081
+ deviceId: number().int().optional(),
10082
+ startedAt: number(),
10083
+ /** Exclusive: at exactly this instant the snooze is over. Expiry is a
10084
+ * COMPARISON, not a job — no sweeper can leave the operator silenced. */
10085
+ expiresAt: number(),
10086
+ global: boolean(),
10087
+ summary: boolean(),
10088
+ /** When the end-of-window digest went out. Absent = not sent (yet, or the
10089
+ * window has not closed, or `summary` is false). */
10090
+ digestSentAt: number().optional()
10091
+ });
10092
+ object({
10093
+ snoozeId: string(),
10094
+ targetId: string(),
10095
+ ruleId: string(),
10096
+ ruleName: string(),
10097
+ deviceId: number().int(),
10098
+ /** How many notifications this snooze hid for that pair. */
10099
+ count: number().int(),
10100
+ firstAt: number(),
10101
+ lastAt: number()
10102
+ });
9591
10103
  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 }), {
9592
10104
  kind: "mutation",
9593
10105
  auth: "admin",
@@ -9617,7 +10129,13 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
9617
10129
  }), method(object({}), object({
9618
10130
  catalog: array(NcConditionDescriptorSchema),
9619
10131
  taxonomy: NcTaxonomySchema.optional()
9620
- })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
10132
+ })), 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 }), {
10133
+ kind: "mutation",
10134
+ caller: "required"
10135
+ }), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
10136
+ kind: "mutation",
10137
+ caller: "required"
10138
+ });
9621
10139
  /**
9622
10140
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
9623
10141
  *
@@ -10365,7 +10883,15 @@ method(object({
10365
10883
  format: _enum(["gif", "mp4"]).default("gif"),
10366
10884
  maxWidth: number().int().min(120).max(1920).default(480),
10367
10885
  /** GIF only — MP4 keeps the source cadence. */
10368
- fps: number().int().min(1).max(15).default(5)
10886
+ fps: number().int().min(1).max(15).default(5),
10887
+ /**
10888
+ * Playback rate. A notification clip is GLANCED at on a lock screen,
10889
+ * not watched, so 2x is the default: the recipient sees the whole
10890
+ * passage in half the time and the GIF is half the bytes. `1` is real
10891
+ * time. Applies to MP4 as well — the operator set a speed, not a GIF
10892
+ * speed.
10893
+ */
10894
+ speed: number().min(1).max(8).default(2)
10369
10895
  }), object({
10370
10896
  base64: string(),
10371
10897
  mime: string(),
@@ -14947,6 +15473,20 @@ var GetInputSchema = object({ id: string() });
14947
15473
  var AddInputSchema = object({
14948
15474
  kind: string().min(1),
14949
15475
  name: string().min(1),
15476
+ /**
15477
+ * ADOPT an existing id instead of minting a new one.
15478
+ *
15479
+ * Written the day this cost an outage. A broker's id is not a detail: HA
15480
+ * devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
15481
+ * lost from config and re-added as `ha_001` leaves every one of its devices
15482
+ * bound to a broker that no longer exists. Re-entering the password under the
15483
+ * ORIGINAL id turns a multi-step device migration back into re-entering a
15484
+ * password.
15485
+ *
15486
+ * A provider MUST refuse an id that is already in use — adopting a live
15487
+ * broker's id would silently take it over.
15488
+ */
15489
+ id: string().min(1).optional(),
14950
15490
  /** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
14951
15491
  * `{baseUrl,accessToken}`). Validated by the kind-specific provider
14952
15492
  * branch on receipt — invalid shape rejects the add. */
@@ -15159,6 +15699,210 @@ method(object({ codec: string() }), boolean()), method(_void(), object({
15159
15699
  kind: "mutation",
15160
15700
  auth: "admin"
15161
15701
  });
15702
+ /**
15703
+ * Query filter for settings-store collections.
15704
+ */
15705
+ var QueryFilterSchema = object({
15706
+ where: record(string(), unknown()).optional(),
15707
+ whereIn: record(string(), array(unknown())).optional(),
15708
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
15709
+ orderBy: object({
15710
+ field: string(),
15711
+ direction: _enum(["asc", "desc"])
15712
+ }).optional(),
15713
+ limit: number().optional(),
15714
+ offset: number().optional()
15715
+ });
15716
+ /**
15717
+ * The predicate half of a filter, for BULK MUTATIONS.
15718
+ *
15719
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
15720
+ * meaning for a statement that rewrites a set, and accepting them would invite
15721
+ * a caller to believe `limit` bounds the damage. Every field is optional here
15722
+ * only so the shape stays composable — the implementation REJECTS a filter
15723
+ * that compiles to no predicate, because that is the whole collection.
15724
+ */
15725
+ var MutationFilterSchema = object({
15726
+ where: record(string(), unknown()).optional(),
15727
+ whereIn: record(string(), array(unknown())).optional(),
15728
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional()
15729
+ });
15730
+ /** A single stored record: `{ id, data }`. */
15731
+ var SettingsRecordSchema = object({
15732
+ id: string(),
15733
+ data: record(string(), unknown())
15734
+ });
15735
+ /**
15736
+ * Column declaration for a structured (SQL-backed) collection.
15737
+ *
15738
+ * Logical types — the backend translates each to the matching SQLite
15739
+ * storage class and handles per-type marshaling:
15740
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
15741
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
15742
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
15743
+ */
15744
+ var CollectionColumnSchema = object({
15745
+ name: string(),
15746
+ type: _enum([
15747
+ "TEXT",
15748
+ "INTEGER",
15749
+ "REAL",
15750
+ "JSON",
15751
+ "BOOLEAN"
15752
+ ]),
15753
+ primaryKey: boolean().optional(),
15754
+ notNull: boolean().optional(),
15755
+ unique: boolean().optional(),
15756
+ /**
15757
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
15758
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
15759
+ * collection surface simply cannot express their existing tables — which is
15760
+ * why they were still on the uncapped `table*` API.
15761
+ */
15762
+ defaultValue: union([
15763
+ string(),
15764
+ number(),
15765
+ boolean()
15766
+ ]).optional()
15767
+ });
15768
+ var CollectionIndexSchema = object({
15769
+ name: string(),
15770
+ columns: array(string()).readonly(),
15771
+ unique: boolean().optional()
15772
+ });
15773
+ method(object({
15774
+ namespace: string().optional(),
15775
+ collection: string(),
15776
+ key: string()
15777
+ }), unknown()), method(object({
15778
+ namespace: string().optional(),
15779
+ collection: string(),
15780
+ key: string(),
15781
+ value: unknown()
15782
+ }), _void(), { kind: "mutation" }), method(object({
15783
+ namespace: string().optional(),
15784
+ collection: string(),
15785
+ filter: QueryFilterSchema.optional()
15786
+ }), array(SettingsRecordSchema).readonly()), method(object({
15787
+ namespace: string().optional(),
15788
+ collection: string(),
15789
+ record: SettingsRecordSchema
15790
+ }), _void(), { kind: "mutation" }), method(object({
15791
+ namespace: string().optional(),
15792
+ collection: string(),
15793
+ id: string(),
15794
+ data: record(string(), unknown())
15795
+ }), _void(), { kind: "mutation" }), method(object({
15796
+ namespace: string().optional(),
15797
+ collection: string(),
15798
+ key: string()
15799
+ }), _void(), { kind: "mutation" }), method(object({
15800
+ namespace: string().optional(),
15801
+ collection: string(),
15802
+ filter: MutationFilterSchema
15803
+ }), object({ deleted: number().int() }), { kind: "mutation" }), method(object({
15804
+ namespace: string().optional(),
15805
+ collection: string(),
15806
+ filter: MutationFilterSchema,
15807
+ data: record(string(), unknown())
15808
+ }), object({ updated: number().int() }), { kind: "mutation" }), method(object({
15809
+ namespace: string().optional(),
15810
+ collection: string(),
15811
+ filter: QueryFilterSchema.optional()
15812
+ }), number()), method(object({
15813
+ namespace: string().optional(),
15814
+ collection: string(),
15815
+ field: string(),
15816
+ bucketSize: number().int().positive(),
15817
+ origin: number().int(),
15818
+ filter: QueryFilterSchema.optional()
15819
+ }), array(object({
15820
+ bucket: number().int(),
15821
+ count: number().int()
15822
+ })).readonly()), method(object({
15823
+ namespace: string().optional(),
15824
+ collection: string()
15825
+ }), boolean()), method(object({
15826
+ namespace: string().optional(),
15827
+ collection: string(),
15828
+ columns: array(CollectionColumnSchema).readonly(),
15829
+ indexes: array(CollectionIndexSchema).readonly().optional()
15830
+ }), _void(), { kind: "mutation" });
15831
+ /**
15832
+ * What one engine says about itself. The orchestrator uses `kind` to pick
15833
+ * a registrant for a collection; `engineId` is what a log line names when
15834
+ * a call is routed or refused.
15835
+ */
15836
+ var EngineInfoSchema = object({
15837
+ engineId: string(),
15838
+ /**
15839
+ * `relational` — rows, columns, indexes, the surface `settings-store`
15840
+ * has always described. `vector` — an embedding store answering
15841
+ * similarity queries. A registrant declares exactly one; an engine that
15842
+ * does both registers twice, because "both" would make the routing
15843
+ * decision ambiguous at exactly the point it must not be.
15844
+ */
15845
+ kind: _enum(["relational", "vector"]),
15846
+ displayName: string()
15847
+ });
15848
+ method(_void(), EngineInfoSchema), method(object({
15849
+ namespace: string().optional(),
15850
+ collection: string(),
15851
+ key: string()
15852
+ }), unknown()), method(object({
15853
+ namespace: string().optional(),
15854
+ collection: string(),
15855
+ key: string(),
15856
+ value: unknown()
15857
+ }), _void(), { kind: "mutation" }), method(object({
15858
+ namespace: string().optional(),
15859
+ collection: string(),
15860
+ filter: QueryFilterSchema.optional()
15861
+ }), array(SettingsRecordSchema).readonly()), method(object({
15862
+ namespace: string().optional(),
15863
+ collection: string(),
15864
+ record: SettingsRecordSchema
15865
+ }), _void(), { kind: "mutation" }), method(object({
15866
+ namespace: string().optional(),
15867
+ collection: string(),
15868
+ id: string(),
15869
+ data: record(string(), unknown())
15870
+ }), _void(), { kind: "mutation" }), method(object({
15871
+ namespace: string().optional(),
15872
+ collection: string(),
15873
+ key: string()
15874
+ }), _void(), { kind: "mutation" }), method(object({
15875
+ namespace: string().optional(),
15876
+ collection: string(),
15877
+ filter: MutationFilterSchema
15878
+ }), object({ deleted: number().int() }), { kind: "mutation" }), method(object({
15879
+ namespace: string().optional(),
15880
+ collection: string(),
15881
+ filter: MutationFilterSchema,
15882
+ data: record(string(), unknown())
15883
+ }), object({ updated: number().int() }), { kind: "mutation" }), method(object({
15884
+ namespace: string().optional(),
15885
+ collection: string(),
15886
+ filter: QueryFilterSchema.optional()
15887
+ }), number()), method(object({
15888
+ namespace: string().optional(),
15889
+ collection: string(),
15890
+ field: string(),
15891
+ bucketSize: number().int().positive(),
15892
+ origin: number().int(),
15893
+ filter: QueryFilterSchema.optional()
15894
+ }), array(object({
15895
+ bucket: number().int(),
15896
+ count: number().int()
15897
+ })).readonly()), method(object({
15898
+ namespace: string().optional(),
15899
+ collection: string()
15900
+ }), boolean()), method(object({
15901
+ namespace: string().optional(),
15902
+ collection: string(),
15903
+ columns: array(CollectionColumnSchema).readonly(),
15904
+ indexes: array(CollectionIndexSchema).readonly().optional()
15905
+ }), _void(), { kind: "mutation" });
15162
15906
  DeviceType.Camera;
15163
15907
  /**
15164
15908
  * `device-adoption` — generic discovery + adoption surface,
@@ -15837,7 +16581,20 @@ method(object({
15837
16581
  }), _void(), {
15838
16582
  kind: "mutation",
15839
16583
  auth: "admin"
15840
- }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
16584
+ }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
16585
+ addonId: string().optional(),
16586
+ /**
16587
+ * `slim` omits `config` (returned `{}`), `metadata` (null) and the
16588
+ * `sourceInfo` derived from config — and skips the per-device settings
16589
+ * read that produces them. Everything identifying a device (id, name,
16590
+ * type, online, features, isCamera, parent/link ids) is unchanged.
16591
+ * Do not use it for dispatch routing, which needs `sourceInfo`.
16592
+ */
16593
+ projection: _enum(["full", "slim"]).optional(),
16594
+ /** Return only camera devices. Filtering server-side instead of
16595
+ * shipping 293 rows to find 12. */
16596
+ isCamera: boolean().optional()
16597
+ }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
15841
16598
  mode: DeviceLinkModeSchema,
15842
16599
  devices: array(LinkedDeviceSchema)
15843
16600
  })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
@@ -16276,14 +17033,14 @@ var LlmProfileSchema = object({
16276
17033
  /** ConfigUISchema tree passed through untyped on the wire (the
16277
17034
  * notification-output `ConfigSchemaPassthrough` precedent at
16278
17035
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
16279
- var ConfigSchemaPassthrough$1 = unknown();
17036
+ var ConfigSchemaPassthrough = unknown();
16280
17037
  var LlmProfileKindDescriptorSchema = object({
16281
17038
  kind: LlmProfileKindSchema,
16282
17039
  label: string(),
16283
17040
  icon: string(),
16284
17041
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
16285
17042
  addonId: string(),
16286
- configSchema: ConfigSchemaPassthrough$1
17043
+ configSchema: ConfigSchemaPassthrough
16287
17044
  });
16288
17045
  var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
16289
17046
  var LlmDefaultSchema = object({
@@ -16833,227 +17590,102 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
16833
17590
  });
16834
17591
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
16835
17592
  /**
16836
- * notification-outputcanonical, capability-gated notification delivery.
16837
- *
16838
- * Apprise-derived model (see
16839
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16840
- * callers emit ONE canonical `Notification`; each provider declares a
16841
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
16842
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16843
- * message to what the kind supports callers never special-case a service.
16844
- *
16845
- * DESIGN DECISIONS (locked):
16846
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16847
- * `setTargetEnabled`), each provider persisting via the `settings-store`
16848
- * cap. Rationale: the admin UI needs one uniform surface across the
16849
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16850
- * alternative would fork the UI per addon and cannot host the
16851
- * discovery→adopt flow.
16852
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16853
- * the generated cap-mount auto-`concatCollection`-fans them across every
16854
- * registered provider (notifiers addon + HA addon) so one catalog is
16855
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16856
- * `addonId` the generated collection router extracts from the call input.
16857
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16858
- * transformer) + UDS MsgPack both round-trip typed arrays already used by
16859
- * `storage` / `storage-provider` / `recording` caps over the same path. No
16860
- * base64 fallback needed.
16861
- *
16862
- * TODO (deferred, closed-set change — separate decision): add
16863
- * `providerKind: 'notify'` so notification providers surface on the unified
16864
- * admin "Integrations" page.
16865
- */
16866
- /**
16867
- * Zentik-derived typed-media enum — the superset across every kind. Each
16868
- * adapter picks what it supports and the degrade engine filters the rest.
16869
- */
16870
- var AttachmentMediaTypeSchema = _enum([
16871
- "image",
16872
- "video",
16873
- "gif",
16874
- "audio",
16875
- "icon"
16876
- ]);
16877
- /**
16878
- * A single attachment. Exactly one of `url` (remote source, most adapters
16879
- * prefer this) or `bytes` (inline source; required for Pushover-style
16880
- * bytes-only kinds) MUST be present — the degrade engine expresses a
16881
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
16882
- */
16883
- var AttachmentSchema = object({
16884
- mediaType: AttachmentMediaTypeSchema,
16885
- url: string().optional(),
16886
- bytes: _instanceof(Uint8Array).optional(),
16887
- mime: string().optional(),
16888
- name: string().optional()
16889
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16890
- var NotificationFormatSchema = _enum([
16891
- "text",
16892
- "markdown",
16893
- "html"
17593
+ * core-blocksuser-authored TypeScript, stored in the kernel and executed in
17594
+ * its own process.
17595
+ *
17596
+ * Spec: `docs/superpowers/specs/2026-08-04-core-blocks-and-synthetic-devices-design.md`.
17597
+ *
17598
+ * The first use is **owning devices without being a device provider**: a block
17599
+ * declares devices under a system or custom integration and drives their state,
17600
+ * with the same `ctx` an addon gets. Automations come later; nothing here
17601
+ * models a trigger.
17602
+ *
17603
+ * **Stated plainly, because it does not change by being true:** a block has an
17604
+ * addon's powers devices, storage, the event bus, `ctx.api`. It is a plugin
17605
+ * with no review step. What makes that survivable is not a sandbox, it is
17606
+ * PROCESS ISOLATION: one process per block, supervised by `CrashSupervisor`,
17607
+ * so a block that throws or never returns is marked `failed` and visible
17608
+ * instead of taking the hub with it (D6). Every method here is admin-only, and
17609
+ * must stay so.
17610
+ */
17611
+ /** Where a block runs. The operator chooses a block driving a device on an
17612
+ * agent is the reason placement is not fixed to the hub. */
17613
+ var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
17614
+ /** What a block's process is doing. Mirrors the addon runner's own lifecycle so
17615
+ * a failing block reads the same way a failing addon does. */
17616
+ var CoreBlockStatusSchema = _enum([
17617
+ "stopped",
17618
+ "starting",
17619
+ "running",
17620
+ "failed"
16894
17621
  ]);
16895
- /** A single tap-through action button. */
16896
- var NotificationActionSchema = object({
16897
- id: string(),
16898
- label: string(),
16899
- url: string().optional()
16900
- });
16901
- /**
16902
- * The canonical notification. `body` is the only hard field (Apprise model).
16903
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16904
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16905
- * the adapter maps this ordinal onto its native level. `level?` is an
16906
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
16907
- * `priority` for that one target.
16908
- */
16909
- var NotificationSchema = object({
16910
- body: string(),
16911
- title: string().optional(),
16912
- format: NotificationFormatSchema.default("text"),
16913
- priority: number().int().min(1).max(5).default(3),
16914
- level: string().optional(),
16915
- attachments: array(AttachmentSchema).optional(),
16916
- clickUrl: string().optional(),
16917
- actions: array(NotificationActionSchema).optional(),
16918
- sound: string().optional(),
16919
- ttl: number().optional(),
16920
- tag: string().optional(),
16921
- deviceId: number().optional(),
16922
- eventId: string().optional(),
16923
- metadata: record(string(), unknown()).optional()
16924
- });
16925
- /** One declared native severity/priority level for a kind. */
16926
- var TargetKindLevelSchema = object({
16927
- id: string(),
16928
- label: string(),
16929
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16930
- ordinal: number().int().min(1).max(5).nullable(),
16931
- flags: object({
16932
- critical: boolean().optional(),
16933
- silent: boolean().optional(),
16934
- noPush: boolean().optional()
16935
- }).optional(),
16936
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16937
- requires: array(string()).optional(),
16938
- description: string().optional()
16939
- });
16940
- /** The full capability block consulted before dispatch. */
16941
- var TargetKindCapsSchema = object({
16942
- attachments: object({
16943
- mediaTypes: array(AttachmentMediaTypeSchema),
16944
- mode: _enum([
16945
- "url",
16946
- "bytes",
16947
- "both"
16948
- ]),
16949
- max: number().int().nonnegative(),
16950
- maxBytes: number().int().positive().optional()
16951
- }),
16952
- /** Max action buttons (0 = none). */
16953
- actions: number().int().nonnegative(),
16954
- levels: array(TargetKindLevelSchema),
16955
- format: array(NotificationFormatSchema),
16956
- clickUrl: boolean(),
16957
- sound: boolean(),
16958
- ttl: boolean(),
16959
- bodyMaxLen: number().int().positive()
16960
- });
16961
- /**
16962
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16963
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16964
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16965
- * the union is large and not meant for runtime validation here; the exported
16966
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16967
- */
16968
- var ConfigSchemaPassthrough = unknown();
16969
- var TargetKindSchema = object({
16970
- kind: string(),
16971
- label: string(),
16972
- icon: string(),
16973
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
16974
- addonId: string(),
16975
- /**
16976
- * URL of the kind's bundled BRAND icon, served by the providing addon over
16977
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
16978
- * when the addon bundles no icon for that kind — the client then falls back
16979
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
16980
- *
16981
- * Root-relative on purpose: it resolves against whatever origin serves a web
16982
- * client, and a native client joins it onto its own hub base.
16983
- *
16984
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
16985
- * field that survived only because the runtime cap-router forwards provider
16986
- * output verbatim — so every consumer had to re-declare it by hand to stop
16987
- * its own Zod parse from stripping it, and the whole arrangement would have
16988
- * broken silently the moment output validation was tightened anywhere.
16989
- */
16990
- iconUrl: string().optional(),
17622
+ /** Client-authored fields. */
17623
+ var CoreBlockInputSchema = object({
17624
+ name: string().min(1).max(120),
17625
+ /** TypeScript source. Compiled server-side before it is ever stored — a
17626
+ * block that does not compile is a fork failure the operator would meet
17627
+ * minutes later, in a log, instead of in the editor. */
17628
+ code: string().max(2e5),
17629
+ enabled: boolean(),
17630
+ placement: CoreBlockPlacementSchema,
16991
17631
  /**
16992
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
16993
- *
16994
- * The server knows this and therefore says it, because the client cannot
16995
- * safely guess: a React-Native client renders SVG and raster through two
16996
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
16997
- * not decode SVG on iOS/Android), so without this it silently fell back to a
16998
- * placeholder glyph for every vector icon while the web build looked fine.
16999
- *
17000
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
17001
- * not been updated — a client that cannot determine the type should prefer
17002
- * its raster path, which is the safe default for an unknown image.
17632
+ * Integration the block's devices hang from. Absent = the system integration
17633
+ * blocks share. A block may declare its own instead.
17003
17634
  */
17004
- iconMediaType: string().optional(),
17005
- configSchema: ConfigSchemaPassthrough,
17006
- supportsDiscovery: boolean(),
17007
- caps: TargetKindCapsSchema
17635
+ integrationId: string().optional()
17008
17636
  });
17009
- /**
17010
- * A persisted target. `config` holds secrets; providers REDACT secret fields
17011
- * (return a presence marker only) when serving `listTargets` — never
17012
- * round-trip a stored secret to the UI.
17013
- */
17014
- var TargetSchema = object({
17637
+ /** A stored block. */
17638
+ var CoreBlockSchema = CoreBlockInputSchema.extend({
17015
17639
  id: string(),
17016
- name: string(),
17017
- kind: string(),
17018
- addonId: string(),
17019
- enabled: boolean(),
17020
- config: record(string(), unknown())
17021
- });
17022
- /** A discovery-surfaced candidate (config is partial + non-secret). */
17023
- var DiscoveredTargetSchema = object({
17024
- kind: string(),
17025
- suggestedName: string(),
17026
- config: record(string(), unknown())
17027
- });
17028
- /** The degrade engine's report — what was resolved / dropped / degraded. */
17029
- var RenderedAsSchema = object({
17030
- level: string(),
17031
- format: NotificationFormatSchema,
17032
- attachmentsSent: number().int().nonnegative(),
17033
- actionsSent: number().int().nonnegative(),
17034
- truncated: boolean(),
17035
- dropped: array(string())
17640
+ createdAt: number(),
17641
+ updatedAt: number(),
17642
+ /** Server-stamped author. */
17643
+ createdBy: string(),
17644
+ status: CoreBlockStatusSchema,
17645
+ /**
17646
+ * Why the block is not running, when it is not. The operator's ONLY window
17647
+ * into a block that failed at load — a block that is silently absent is the
17648
+ * failure mode this whole feature has to avoid.
17649
+ */
17650
+ lastError: string().optional(),
17651
+ /** Ms epoch of the last state change. */
17652
+ lastChangedAt: number()
17036
17653
  });
17037
- var SendResultSchema = object({
17038
- success: boolean(),
17654
+ /** What a compile attempt produced. */
17655
+ var CoreBlockCompileResultSchema = object({
17656
+ ok: boolean(),
17657
+ /** Present when `ok` is false — the first error, in the author's words. */
17039
17658
  error: string().optional(),
17040
- renderedAs: RenderedAsSchema.optional()
17659
+ line: number().optional(),
17660
+ column: number().optional()
17041
17661
  });
17042
- /** Same shape as SendResult kept as a distinct name for the test panel. */
17043
- var TestResultSchema = SendResultSchema;
17044
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
17045
- kind: string(),
17046
- config: record(string(), unknown()).optional()
17047
- }), array(DiscoveredTargetSchema)), method(object({
17048
- targetId: string(),
17049
- notification: NotificationSchema
17050
- }), SendResultSchema, { kind: "mutation" }), method(object({
17051
- targetId: string(),
17052
- sample: NotificationSchema.optional()
17053
- }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
17054
- targetId: string(),
17662
+ 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 }), {
17663
+ kind: "mutation",
17664
+ auth: "admin",
17665
+ caller: "required"
17666
+ }), method(object({
17667
+ blockId: string(),
17668
+ block: CoreBlockInputSchema.partial()
17669
+ }), object({ block: CoreBlockSchema }), {
17670
+ kind: "mutation",
17671
+ auth: "admin",
17672
+ caller: "required"
17673
+ }), method(object({ blockId: string() }), object({ success: literal(true) }), {
17674
+ kind: "mutation",
17675
+ auth: "admin"
17676
+ }), method(object({
17677
+ blockId: string(),
17055
17678
  enabled: boolean()
17056
- }), _void(), { kind: "mutation" });
17679
+ }), object({ block: CoreBlockSchema }), {
17680
+ kind: "mutation",
17681
+ auth: "admin"
17682
+ }), method(object({ code: string() }), CoreBlockCompileResultSchema, {
17683
+ kind: "mutation",
17684
+ auth: "admin"
17685
+ }), method(object({}), object({ libs: array(object({
17686
+ filePath: string(),
17687
+ content: string()
17688
+ })) }), { auth: "admin" });
17057
17689
  /**
17058
17690
  * Zod schemas for persisted record types.
17059
17691
  *
@@ -17301,6 +17933,11 @@ var EventKindDescriptorSchema = object({
17301
17933
  deviceId: number()
17302
17934
  })
17303
17935
  });
17936
+ /** One camera's event vocabulary, as returned by `listEventKindsBatch`. */
17937
+ var EventKindsForDeviceSchema = object({
17938
+ deviceId: number(),
17939
+ kinds: array(EventKindDescriptorSchema).readonly()
17940
+ });
17304
17941
  var SensorEventSchema = object({
17305
17942
  id: string(),
17306
17943
  /** The CAMERA the event is attributed to (a sensor linked to N cameras
@@ -17557,6 +18194,19 @@ var MediaFileSchema = object({
17557
18194
  sizeBytes: number(),
17558
18195
  timestamp: number()
17559
18196
  });
18197
+ /**
18198
+ * One media row WITHOUT its bytes.
18199
+ *
18200
+ * A track's media is 5-8 MB of base64 (measured: 7.67 MB across 23 files for a
18201
+ * 140 s track), and a client that renders tiles from the media data plane needs
18202
+ * to know only WHAT EXISTS — the bytes then arrive per tile, lazily, over HTTP
18203
+ * with an immutable cache, instead of all at once inside a tRPC response that
18204
+ * blocks the whole view.
18205
+ *
18206
+ * `sizeBytes` is carried because it is what lets a client decide between the
18207
+ * stored blob and a `?variant=thumb` rendering without fetching either.
18208
+ */
18209
+ var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
17560
18210
  var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
17561
18211
  var MAX_EVENT_QUERY_LIMIT = 5e3;
17562
18212
  var DeviceEventQueryInput = object({
@@ -17706,7 +18356,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17706
18356
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17707
18357
  kind: "mutation",
17708
18358
  auth: "admin"
17709
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
18359
+ }), 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({
17710
18360
  deviceId: number(),
17711
18361
  since: number().optional(),
17712
18362
  until: number().optional(),
@@ -17780,7 +18430,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17780
18430
  }), array(MediaFileSchema).readonly()), method(object({
17781
18431
  trackId: string(),
17782
18432
  kinds: array(MediaFileKindEnum).optional()
17783
- }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
18433
+ }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
17784
18434
  deviceId: number(),
17785
18435
  timestamp: number(),
17786
18436
  frameWidth: number(),
@@ -18410,6 +19060,28 @@ var ServerUpdateStateSchema = _enum([
18410
19060
  "pending-restart",
18411
19061
  "awaiting-confirmation"
18412
19062
  ]);
19063
+ var ImageContractSchema = object({
19064
+ state: _enum([
19065
+ "in-sync",
19066
+ "behind-patch",
19067
+ "behind-series",
19068
+ "ahead",
19069
+ "unknown"
19070
+ ]),
19071
+ /** The baked seed closure version — the image/app-bundle fingerprint. */
19072
+ seedVersion: string().nullable(),
19073
+ /** Best-known version the deployment contract delivers today. */
19074
+ contractVersion: string().nullable(),
19075
+ /**
19076
+ * Where `contractVersion` came from: a real registry check (`registry`), or
19077
+ * the node's own running version (`running` — a node can never run code
19078
+ * newer than the newest release, so `seed < running` proves image staleness
19079
+ * even before any registry check has run).
19080
+ */
19081
+ contractSource: _enum(["registry", "running"]).nullable(),
19082
+ /** One operator-grade sentence: this node runs image X; the contract says Y. */
19083
+ message: string()
19084
+ });
18413
19085
  var ServerRollbackInfoSchema = object({
18414
19086
  /** The version that failed (or was manually rolled back). */
18415
19087
  fromVersion: string(),
@@ -18446,7 +19118,12 @@ var ServerPackageStatusSchema = object({
18446
19118
  * versions are being IGNORED. Surfaced as a warning in the UI.
18447
19119
  */
18448
19120
  stateFileCorrupt: boolean(),
18449
- lastCheckedAtMs: number().nullable()
19121
+ lastCheckedAtMs: number().nullable(),
19122
+ /**
19123
+ * Seed-vs-contract verdict (see {@link ImageContractSchema}). Optional for
19124
+ * version skew: an older provider's payload simply omits it.
19125
+ */
19126
+ imageContract: ImageContractSchema.optional()
18450
19127
  });
18451
19128
  var ServerUpdateCheckResultSchema = object({
18452
19129
  packageName: string(),
@@ -18481,101 +19158,6 @@ version: string().optional() }), ServerUpdateActionResultSchema, {
18481
19158
  auth: "admin"
18482
19159
  });
18483
19160
  /**
18484
- * Query filter for settings-store collections.
18485
- */
18486
- var QueryFilterSchema = object({
18487
- where: record(string(), unknown()).optional(),
18488
- whereIn: record(string(), array(unknown())).optional(),
18489
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
18490
- orderBy: object({
18491
- field: string(),
18492
- direction: _enum(["asc", "desc"])
18493
- }).optional(),
18494
- limit: number().optional(),
18495
- offset: number().optional()
18496
- });
18497
- /** A single stored record: `{ id, data }`. */
18498
- var SettingsRecordSchema = object({
18499
- id: string(),
18500
- data: record(string(), unknown())
18501
- });
18502
- /**
18503
- * Column declaration for a structured (SQL-backed) collection.
18504
- *
18505
- * Logical types — the backend translates each to the matching SQLite
18506
- * storage class and handles per-type marshaling:
18507
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
18508
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
18509
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
18510
- */
18511
- var CollectionColumnSchema = object({
18512
- name: string(),
18513
- type: _enum([
18514
- "TEXT",
18515
- "INTEGER",
18516
- "REAL",
18517
- "JSON",
18518
- "BOOLEAN"
18519
- ]),
18520
- primaryKey: boolean().optional(),
18521
- notNull: boolean().optional(),
18522
- unique: boolean().optional()
18523
- });
18524
- var CollectionIndexSchema = object({
18525
- name: string(),
18526
- columns: array(string()).readonly(),
18527
- unique: boolean().optional()
18528
- });
18529
- method(object({
18530
- namespace: string().optional(),
18531
- collection: string(),
18532
- key: string()
18533
- }), unknown()), method(object({
18534
- namespace: string().optional(),
18535
- collection: string(),
18536
- key: string(),
18537
- value: unknown()
18538
- }), _void(), { kind: "mutation" }), method(object({
18539
- namespace: string().optional(),
18540
- collection: string(),
18541
- filter: QueryFilterSchema.optional()
18542
- }), array(SettingsRecordSchema).readonly()), method(object({
18543
- namespace: string().optional(),
18544
- collection: string(),
18545
- record: SettingsRecordSchema
18546
- }), _void(), { kind: "mutation" }), method(object({
18547
- namespace: string().optional(),
18548
- collection: string(),
18549
- id: string(),
18550
- data: record(string(), unknown())
18551
- }), _void(), { kind: "mutation" }), method(object({
18552
- namespace: string().optional(),
18553
- collection: string(),
18554
- key: string()
18555
- }), _void(), { kind: "mutation" }), method(object({
18556
- namespace: string().optional(),
18557
- collection: string(),
18558
- filter: QueryFilterSchema.optional()
18559
- }), number()), method(object({
18560
- namespace: string().optional(),
18561
- collection: string(),
18562
- field: string(),
18563
- bucketSize: number().int().positive(),
18564
- origin: number().int(),
18565
- filter: QueryFilterSchema.optional()
18566
- }), array(object({
18567
- bucket: number().int(),
18568
- count: number().int()
18569
- })).readonly()), method(object({
18570
- namespace: string().optional(),
18571
- collection: string()
18572
- }), boolean()), method(object({
18573
- namespace: string().optional(),
18574
- collection: string(),
18575
- columns: array(CollectionColumnSchema).readonly(),
18576
- indexes: array(CollectionIndexSchema).readonly().optional()
18577
- }), _void(), { kind: "mutation" });
18578
- /**
18579
19161
  * `smtp-provider` — pluggable email delivery surface.
18580
19162
  *
18581
19163
  * Collection cap: a deployment may install multiple SMTP relays (e.g.
@@ -22438,6 +23020,54 @@ Object.freeze({
22438
23020
  addonId: null,
22439
23021
  access: "create"
22440
23022
  },
23023
+ "coreBlocks.compile": {
23024
+ capName: "core-blocks",
23025
+ capScope: "system",
23026
+ addonId: null,
23027
+ access: "create"
23028
+ },
23029
+ "coreBlocks.create": {
23030
+ capName: "core-blocks",
23031
+ capScope: "system",
23032
+ addonId: null,
23033
+ access: "create"
23034
+ },
23035
+ "coreBlocks.delete": {
23036
+ capName: "core-blocks",
23037
+ capScope: "system",
23038
+ addonId: null,
23039
+ access: "delete"
23040
+ },
23041
+ "coreBlocks.get": {
23042
+ capName: "core-blocks",
23043
+ capScope: "system",
23044
+ addonId: null,
23045
+ access: "view"
23046
+ },
23047
+ "coreBlocks.getTypeDefs": {
23048
+ capName: "core-blocks",
23049
+ capScope: "system",
23050
+ addonId: null,
23051
+ access: "view"
23052
+ },
23053
+ "coreBlocks.list": {
23054
+ capName: "core-blocks",
23055
+ capScope: "system",
23056
+ addonId: null,
23057
+ access: "view"
23058
+ },
23059
+ "coreBlocks.setEnabled": {
23060
+ capName: "core-blocks",
23061
+ capScope: "system",
23062
+ addonId: null,
23063
+ access: "create"
23064
+ },
23065
+ "coreBlocks.update": {
23066
+ capName: "core-blocks",
23067
+ capScope: "system",
23068
+ addonId: null,
23069
+ access: "create"
23070
+ },
22441
23071
  "cover.close": {
22442
23072
  capName: "cover",
22443
23073
  capScope: "device",
@@ -22474,6 +23104,84 @@ Object.freeze({
22474
23104
  addonId: null,
22475
23105
  access: "view"
22476
23106
  },
23107
+ "dataStoreProvider.count": {
23108
+ capName: "data-store-provider",
23109
+ capScope: "system",
23110
+ addonId: null,
23111
+ access: "view"
23112
+ },
23113
+ "dataStoreProvider.declareCollection": {
23114
+ capName: "data-store-provider",
23115
+ capScope: "system",
23116
+ addonId: null,
23117
+ access: "create"
23118
+ },
23119
+ "dataStoreProvider.delete": {
23120
+ capName: "data-store-provider",
23121
+ capScope: "system",
23122
+ addonId: null,
23123
+ access: "delete"
23124
+ },
23125
+ "dataStoreProvider.deleteWhere": {
23126
+ capName: "data-store-provider",
23127
+ capScope: "system",
23128
+ addonId: null,
23129
+ access: "delete"
23130
+ },
23131
+ "dataStoreProvider.get": {
23132
+ capName: "data-store-provider",
23133
+ capScope: "system",
23134
+ addonId: null,
23135
+ access: "view"
23136
+ },
23137
+ "dataStoreProvider.getEngineInfo": {
23138
+ capName: "data-store-provider",
23139
+ capScope: "system",
23140
+ addonId: null,
23141
+ access: "view"
23142
+ },
23143
+ "dataStoreProvider.histogram": {
23144
+ capName: "data-store-provider",
23145
+ capScope: "system",
23146
+ addonId: null,
23147
+ access: "view"
23148
+ },
23149
+ "dataStoreProvider.insert": {
23150
+ capName: "data-store-provider",
23151
+ capScope: "system",
23152
+ addonId: null,
23153
+ access: "create"
23154
+ },
23155
+ "dataStoreProvider.isEmpty": {
23156
+ capName: "data-store-provider",
23157
+ capScope: "system",
23158
+ addonId: null,
23159
+ access: "view"
23160
+ },
23161
+ "dataStoreProvider.query": {
23162
+ capName: "data-store-provider",
23163
+ capScope: "system",
23164
+ addonId: null,
23165
+ access: "view"
23166
+ },
23167
+ "dataStoreProvider.set": {
23168
+ capName: "data-store-provider",
23169
+ capScope: "system",
23170
+ addonId: null,
23171
+ access: "create"
23172
+ },
23173
+ "dataStoreProvider.update": {
23174
+ capName: "data-store-provider",
23175
+ capScope: "system",
23176
+ addonId: null,
23177
+ access: "create"
23178
+ },
23179
+ "dataStoreProvider.updateWhere": {
23180
+ capName: "data-store-provider",
23181
+ capScope: "system",
23182
+ addonId: null,
23183
+ access: "create"
23184
+ },
22477
23185
  "dayNight.getOptions": {
22478
23186
  capName: "day-night",
22479
23187
  capScope: "device",
@@ -24268,12 +24976,24 @@ Object.freeze({
24268
24976
  addonId: null,
24269
24977
  access: "create"
24270
24978
  },
24979
+ "notificationRules.cancelSnooze": {
24980
+ capName: "notification-rules",
24981
+ capScope: "system",
24982
+ addonId: null,
24983
+ access: "create"
24984
+ },
24271
24985
  "notificationRules.createRule": {
24272
24986
  capName: "notification-rules",
24273
24987
  capScope: "system",
24274
24988
  addonId: null,
24275
24989
  access: "create"
24276
24990
  },
24991
+ "notificationRules.createSnooze": {
24992
+ capName: "notification-rules",
24993
+ capScope: "system",
24994
+ addonId: null,
24995
+ access: "create"
24996
+ },
24277
24997
  "notificationRules.deleteRule": {
24278
24998
  capName: "notification-rules",
24279
24999
  capScope: "system",
@@ -24304,6 +25024,12 @@ Object.freeze({
24304
25024
  addonId: null,
24305
25025
  access: "view"
24306
25026
  },
25027
+ "notificationRules.listSnoozes": {
25028
+ capName: "notification-rules",
25029
+ capScope: "system",
25030
+ addonId: null,
25031
+ access: "view"
25032
+ },
24307
25033
  "notificationRules.setRuleEnabled": {
24308
25034
  capName: "notification-rules",
24309
25035
  capScope: "system",
@@ -24508,6 +25234,12 @@ Object.freeze({
24508
25234
  addonId: null,
24509
25235
  access: "view"
24510
25236
  },
25237
+ "pipelineAnalytics.listEventKindsBatch": {
25238
+ capName: "pipeline-analytics",
25239
+ capScope: "device",
25240
+ addonId: null,
25241
+ access: "view"
25242
+ },
24511
25243
  "pipelineAnalytics.listOpsLog": {
24512
25244
  capName: "pipeline-analytics",
24513
25245
  capScope: "device",
@@ -24520,6 +25252,12 @@ Object.freeze({
24520
25252
  addonId: null,
24521
25253
  access: "view"
24522
25254
  },
25255
+ "pipelineAnalytics.listTrackMedia": {
25256
+ capName: "pipeline-analytics",
25257
+ capScope: "device",
25258
+ addonId: null,
25259
+ access: "view"
25260
+ },
24523
25261
  "pipelineAnalytics.listTracks": {
24524
25262
  capName: "pipeline-analytics",
24525
25263
  capScope: "device",
@@ -25552,6 +26290,12 @@ Object.freeze({
25552
26290
  addonId: null,
25553
26291
  access: "delete"
25554
26292
  },
26293
+ "settingsStore.deleteWhere": {
26294
+ capName: "settings-store",
26295
+ capScope: "system",
26296
+ addonId: null,
26297
+ access: "delete"
26298
+ },
25555
26299
  "settingsStore.get": {
25556
26300
  capName: "settings-store",
25557
26301
  capScope: "system",
@@ -25594,6 +26338,12 @@ Object.freeze({
25594
26338
  addonId: null,
25595
26339
  access: "create"
25596
26340
  },
26341
+ "settingsStore.updateWhere": {
26342
+ capName: "settings-store",
26343
+ capScope: "system",
26344
+ addonId: null,
26345
+ access: "create"
26346
+ },
25597
26347
  "smtpProvider.getStatus": {
25598
26348
  capName: "smtp-provider",
25599
26349
  capScope: "system",
@@ -59047,8 +59797,9 @@ var MqttBrokerAddon = class extends BaseAddon {
59047
59797
  label: "MQTT"
59048
59798
  }]
59049
59799
  }],
59050
- add: async ({ kind, name, settings }) => {
59800
+ add: async ({ kind, name, settings, id }) => {
59051
59801
  if (kind !== KIND) throw new Error(`mqtt-broker: only kind '${KIND}' is handled here (got '${kind}')`);
59802
+ if (id !== void 0) throw new Error("mqtt-broker: adopting an existing broker id is not supported by this provider");
59052
59803
  if (isOnboardRequested(settings)) return this.addOnboardBroker(name, settings);
59053
59804
  const url = typeof settings["url"] === "string" ? settings["url"] : "";
59054
59805
  if (!url) throw new Error("mqtt-broker.add: settings.url is required");