@camstack/addon-provider-bosch 0.1.40 → 0.1.41

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.
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
5981
5981
  var ZodFirstPartyTypeKind;
5982
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5983
5983
  //#endregion
5984
- //#region ../types/dist/sleep-CmjWurK5.mjs
5984
+ //#region ../types/dist/sleep-CdKka2ri.mjs
5985
5985
  /**
5986
5986
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5987
5987
  * window to float samples (D455).
@@ -9588,30 +9588,98 @@ function normalizeAddonInitResult(result) {
9588
9588
  if (Array.isArray(result)) return { providers: result };
9589
9589
  return result;
9590
9590
  }
9591
+ object({
9592
+ field: string().min(1),
9593
+ on: literal("advance")
9594
+ });
9591
9595
  /**
9592
- * The share-view scope kinds a share-link token can carry. The hub mints
9593
- * and validates them (`ShareViewGrantSchema` builds its
9594
- * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
9596
+ * Output schema shared by the contribution + live methods.
9597
+ *
9598
+ * Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
9599
+ * tabs[]) without importing from `../interfaces/config-ui.js` — a
9600
+ * concrete-but-lenient Zod object keeps tRPC output inference happy
9601
+ * (using `z.unknown()` here collapses unrelated router branches to
9602
+ * `unknown` when the generator re-inlines the huge AppRouter type).
9603
+ *
9604
+ * `.passthrough()` on sections/fields accepts whatever FormBuilder
9605
+ * extensions the caller adds (showWhen, displayScale, …) without
9606
+ * rebuilding every time a new field kind is introduced.
9595
9607
  */
9596
- var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
9597
- /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
9598
- var PeerBytesTicketSchema = object({
9599
- /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
9600
- url: string().min(1),
9601
- /**
9602
- * The HOST node this URL means something on — the hub or a named agent,
9603
- * never a runner. {@link AddonPeerBytes.open} compares it to its own and
9604
- * refuses `cross-node` by name when they differ, without dialling.
9605
- */
9606
- hostNodeId: string().min(1),
9607
- expiresAtMs: number().int().nonnegative(),
9608
- /**
9609
- * What the producer DECLARED the body to be, when it knows — `null` when it
9610
- * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
9611
- * must be able to tell "the producer did not say" from "the body is empty".
9612
- */
9613
- declaredBytes: number().int().nonnegative().nullable()
9608
+ var ContributionSectionSchema = object({
9609
+ id: string(),
9610
+ title: string(),
9611
+ description: string().optional(),
9612
+ style: _enum(["card", "accordion"]).optional(),
9613
+ defaultCollapsed: boolean().optional(),
9614
+ columns: union([
9615
+ literal(1),
9616
+ literal(2),
9617
+ literal(3),
9618
+ literal(4)
9619
+ ]).optional(),
9620
+ tab: string().optional(),
9621
+ location: _enum([
9622
+ "settings",
9623
+ "top-tab",
9624
+ "hero"
9625
+ ]).optional(),
9626
+ order: number().optional(),
9627
+ fields: array(any())
9614
9628
  });
9629
+ object({
9630
+ tabs: array(object({
9631
+ id: string(),
9632
+ label: string(),
9633
+ icon: string(),
9634
+ order: number().optional()
9635
+ })).optional(),
9636
+ sections: array(ContributionSectionSchema)
9637
+ }).nullable();
9638
+ object({ deviceId: number() }), object({ deviceId: number() }), object({
9639
+ deviceId: number(),
9640
+ patch: record(string(), unknown())
9641
+ }), object({ success: literal(true) });
9642
+ object({ deviceId: number() }), unknown().nullable();
9643
+ function method(input, output, options) {
9644
+ return {
9645
+ input,
9646
+ output,
9647
+ kind: options?.kind ?? "query",
9648
+ ...options?.auth !== void 0 ? { auth: options.auth } : {},
9649
+ ...options?.access !== void 0 ? { access: options.access } : {},
9650
+ ...options?.role !== void 0 ? { role: options.role } : {},
9651
+ ...options?.caller !== void 0 ? { caller: options.caller } : {},
9652
+ timeoutMs: options?.timeoutMs
9653
+ };
9654
+ }
9655
+ /**
9656
+ * A wrapper/system-only method: served exclusively by the cap's system-level
9657
+ * provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
9658
+ * driver natives don't stub out a wrapper concern (e.g. a cross-device cache
9659
+ * overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
9660
+ */
9661
+ function systemMethod(input, output, options) {
9662
+ return {
9663
+ ...method(input, output, options),
9664
+ systemOnly: true
9665
+ };
9666
+ }
9667
+ /**
9668
+ * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
9669
+ * on `InferProvider`. The `providerOptional: true` literal is what
9670
+ * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
9671
+ * for when this is the honest answer and when it is a soft stub.
9672
+ */
9673
+ function optionalMethod(input, output, options) {
9674
+ return {
9675
+ ...method(input, output, options),
9676
+ providerOptional: true
9677
+ };
9678
+ }
9679
+ /** Shorthand to define an event schema */
9680
+ function event(data) {
9681
+ return { data };
9682
+ }
9615
9683
  function defOf(schema) {
9616
9684
  const zodBag = Reflect.get(schema, "_zod");
9617
9685
  if (typeof zodBag !== "object" || zodBag === null) return {};
@@ -9868,98 +9936,30 @@ function defineEntryKind(name, spec) {
9868
9936
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
9869
9937
  return defined;
9870
9938
  }
9871
- object({
9872
- field: string().min(1),
9873
- on: literal("advance")
9874
- });
9875
9939
  /**
9876
- * Output schema shared by the contribution + live methods.
9877
- *
9878
- * Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
9879
- * tabs[]) without importing from `../interfaces/config-ui.js` — a
9880
- * concrete-but-lenient Zod object keeps tRPC output inference happy
9881
- * (using `z.unknown()` here collapses unrelated router branches to
9882
- * `unknown` when the generator re-inlines the huge AppRouter type).
9883
- *
9884
- * `.passthrough()` on sections/fields accepts whatever FormBuilder
9885
- * extensions the caller adds (showWhen, displayScale, …) without
9886
- * rebuilding every time a new field kind is introduced.
9940
+ * The share-view scope kinds a share-link token can carry. The hub mints
9941
+ * and validates them (`ShareViewGrantSchema` builds its
9942
+ * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
9887
9943
  */
9888
- var ContributionSectionSchema = object({
9889
- id: string(),
9890
- title: string(),
9891
- description: string().optional(),
9892
- style: _enum(["card", "accordion"]).optional(),
9893
- defaultCollapsed: boolean().optional(),
9894
- columns: union([
9895
- literal(1),
9896
- literal(2),
9897
- literal(3),
9898
- literal(4)
9899
- ]).optional(),
9900
- tab: string().optional(),
9901
- location: _enum([
9902
- "settings",
9903
- "top-tab",
9904
- "hero"
9905
- ]).optional(),
9906
- order: number().optional(),
9907
- fields: array(any())
9944
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
9945
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
9946
+ var PeerBytesTicketSchema = object({
9947
+ /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
9948
+ url: string().min(1),
9949
+ /**
9950
+ * The HOST node this URL means something on — the hub or a named agent,
9951
+ * never a runner. {@link AddonPeerBytes.open} compares it to its own and
9952
+ * refuses `cross-node` by name when they differ, without dialling.
9953
+ */
9954
+ hostNodeId: string().min(1),
9955
+ expiresAtMs: number().int().nonnegative(),
9956
+ /**
9957
+ * What the producer DECLARED the body to be, when it knows — `null` when it
9958
+ * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
9959
+ * must be able to tell "the producer did not say" from "the body is empty".
9960
+ */
9961
+ declaredBytes: number().int().nonnegative().nullable()
9908
9962
  });
9909
- object({
9910
- tabs: array(object({
9911
- id: string(),
9912
- label: string(),
9913
- icon: string(),
9914
- order: number().optional()
9915
- })).optional(),
9916
- sections: array(ContributionSectionSchema)
9917
- }).nullable();
9918
- object({ deviceId: number() }), object({ deviceId: number() }), object({
9919
- deviceId: number(),
9920
- patch: record(string(), unknown())
9921
- }), object({ success: literal(true) });
9922
- object({ deviceId: number() }), unknown().nullable();
9923
- function method(input, output, options) {
9924
- return {
9925
- input,
9926
- output,
9927
- kind: options?.kind ?? "query",
9928
- ...options?.auth !== void 0 ? { auth: options.auth } : {},
9929
- ...options?.access !== void 0 ? { access: options.access } : {},
9930
- ...options?.role !== void 0 ? { role: options.role } : {},
9931
- ...options?.caller !== void 0 ? { caller: options.caller } : {},
9932
- timeoutMs: options?.timeoutMs
9933
- };
9934
- }
9935
- /**
9936
- * A wrapper/system-only method: served exclusively by the cap's system-level
9937
- * provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
9938
- * driver natives don't stub out a wrapper concern (e.g. a cross-device cache
9939
- * overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
9940
- */
9941
- function systemMethod(input, output, options) {
9942
- return {
9943
- ...method(input, output, options),
9944
- systemOnly: true
9945
- };
9946
- }
9947
- /**
9948
- * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
9949
- * on `InferProvider`. The `providerOptional: true` literal is what
9950
- * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
9951
- * for when this is the honest answer and when it is a soft stub.
9952
- */
9953
- function optionalMethod(input, output, options) {
9954
- return {
9955
- ...method(input, output, options),
9956
- providerOptional: true
9957
- };
9958
- }
9959
- /** Shorthand to define an event schema */
9960
- function event(data) {
9961
- return { data };
9962
- }
9963
9963
  var READING_QUANTITIES = [
9964
9964
  "temperature",
9965
9965
  "pressure",
@@ -10560,7 +10560,6 @@ var CAP_NAMES = Object.freeze([
10560
10560
  "climate-control",
10561
10561
  "color",
10562
10562
  "composer",
10563
- "connection-test",
10564
10563
  "connectivity",
10565
10564
  "consumables",
10566
10565
  "contact",
@@ -12580,8 +12579,7 @@ Object.freeze({
12580
12579
  "offerArtifact": 3e5,
12581
12580
  "readResult": 3e5,
12582
12581
  "submitJob": 18e5,
12583
- "uploadDataset": 72e5,
12584
- "validateCredentials": 3e4
12582
+ "uploadDataset": 72e5
12585
12583
  }
12586
12584
  });
12587
12585
  object({
@@ -13936,6 +13934,432 @@ var IntegrationInstanceRefSchema = object({
13936
13934
  ]),
13937
13935
  candidateStableId: string().optional()
13938
13936
  });
13937
+ /**
13938
+ * notification-output — canonical, capability-gated notification delivery.
13939
+ *
13940
+ * Apprise-derived model (see
13941
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
13942
+ * callers emit ONE canonical `Notification`; each provider declares a
13943
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
13944
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
13945
+ * message to what the kind supports — callers never special-case a service.
13946
+ *
13947
+ * DESIGN DECISIONS (locked):
13948
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
13949
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
13950
+ * cap. Rationale: the admin UI needs one uniform surface across the
13951
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
13952
+ * alternative would fork the UI per addon and cannot host the
13953
+ * discovery→adopt flow.
13954
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
13955
+ * the generated cap-mount auto-`concatCollection`-fans them across every
13956
+ * registered provider (notifiers addon + HA addon) so one catalog is
13957
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
13958
+ * `addonId` the generated collection router extracts from the call input.
13959
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
13960
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
13961
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
13962
+ * base64 fallback needed.
13963
+ *
13964
+ * TODO (deferred, closed-set change — separate decision): add
13965
+ * `providerKind: 'notify'` so notification providers surface on the unified
13966
+ * admin "Integrations" page.
13967
+ */
13968
+ /**
13969
+ * Zentik-derived typed-media enum — the superset across every kind. Each
13970
+ * adapter picks what it supports and the degrade engine filters the rest.
13971
+ */
13972
+ var AttachmentMediaTypeSchema = _enum([
13973
+ "image",
13974
+ "video",
13975
+ "gif",
13976
+ "audio",
13977
+ "icon"
13978
+ ]);
13979
+ /**
13980
+ * A single attachment. Exactly one of `url` (remote source, most adapters
13981
+ * prefer this) or `bytes` (inline source; required for Pushover-style
13982
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
13983
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
13984
+ */
13985
+ var AttachmentSchema = object({
13986
+ mediaType: AttachmentMediaTypeSchema,
13987
+ url: string().optional(),
13988
+ bytes: _instanceof(Uint8Array).optional(),
13989
+ mime: string().optional(),
13990
+ name: string().optional()
13991
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
13992
+ var NotificationFormatSchema = _enum([
13993
+ "text",
13994
+ "markdown",
13995
+ "html"
13996
+ ]);
13997
+ /**
13998
+ * The CLOSED icon vocabulary an action button may use.
13999
+ *
14000
+ * A closed set, not a free string, and that is the whole point: an arbitrary
14001
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
14002
+ * notices — the same class of gap as a zone vocabulary nothing produced
14003
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
14004
+ * declares `actionIcons: false` and the degrade engine strips the field.
14005
+ *
14006
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
14007
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
14008
+ * differently.
14009
+ *
14010
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
14011
+ *
14012
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
14013
+ * the whole set onto its own renderer's vocabulary through a
14014
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
14015
+ * fallback, so adding a member here fails every adapter's build until someone
14016
+ * decides its glyph, which is the only place that decision can be made
14017
+ * honestly.
14018
+ *
14019
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
14020
+ * `disarm` straight through; iOS feeds that string to
14021
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
14022
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
14023
+ * mapping, and "the field is documented" is not "the value renders".
14024
+ *
14025
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
14026
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
14027
+ * so an addon that emits a token the running hub does not know does not lose an
14028
+ * icon — its whole `send` fails Zod validation and the notification never
14029
+ * arrives. Never emit a new token from an addon before the train carrying it.
14030
+ */
14031
+ var NotificationActionIconSchema = _enum([
14032
+ "acknowledge",
14033
+ "dismiss",
14034
+ "silence",
14035
+ "snooze",
14036
+ "view",
14037
+ "play",
14038
+ "open",
14039
+ "close",
14040
+ "lock",
14041
+ "unlock",
14042
+ "arm",
14043
+ "arm-home",
14044
+ "arm-away",
14045
+ "arm-night",
14046
+ "disarm",
14047
+ "light",
14048
+ "alert",
14049
+ "camera"
14050
+ ]);
14051
+ /** A single tap-through action button. */
14052
+ var NotificationActionSchema = object({
14053
+ id: string(),
14054
+ label: string(),
14055
+ url: string().optional(),
14056
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
14057
+ icon: NotificationActionIconSchema.optional(),
14058
+ /**
14059
+ * Renders in a warning style where the notifier supports it.
14060
+ *
14061
+ * A HINT, never a gate. The callback's authority is its token and nothing
14062
+ * else — see `notification-center/action-token.ts` for what that does and
14063
+ * does not buy.
14064
+ */
14065
+ destructive: boolean().optional(),
14066
+ /**
14067
+ * How the tap should REACH the url.
14068
+ *
14069
+ * `navigate` (absent, and every button authored before this field) opens it:
14070
+ * the phone leaves the notification and shows whatever the callback returns.
14071
+ * That is right for a button whose answer the operator wants to read.
14072
+ *
14073
+ * `background` fires it as a POST and stays put. It exists for the buttons
14074
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
14075
+ * an answer to the notification, and being thrown into a browser tab to
14076
+ * confirm it costs more attention than the notification did. A backend that
14077
+ * cannot do a background call renders it as an ordinary link (the adapters
14078
+ * fall back rather than dropping the button), so this is a preference, never
14079
+ * a requirement.
14080
+ */
14081
+ mode: _enum(["navigate", "background"]).optional()
14082
+ });
14083
+ /** A delivery parameter value: what a notifier declared it takes. */
14084
+ var DeliveryParamValueSchema = union([
14085
+ string(),
14086
+ number(),
14087
+ boolean()
14088
+ ]);
14089
+ /**
14090
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
14091
+ * addresses a target of that kind may set it; a kind that declares nothing is
14092
+ * never handed one. The admin UI derives its control from this descriptor —
14093
+ * no editor knows a service by name.
14094
+ *
14095
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
14096
+ * run on the addon cap path).
14097
+ */
14098
+ var DeliveryParamSchema = object({
14099
+ key: string().min(1),
14100
+ label: string(),
14101
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
14102
+ labelKey: string().optional(),
14103
+ type: _enum([
14104
+ "enum",
14105
+ "number",
14106
+ "boolean",
14107
+ "string"
14108
+ ]),
14109
+ /** `enum` only: the closed set of accepted values. */
14110
+ options: array(object({
14111
+ value: string(),
14112
+ label: string()
14113
+ })).optional(),
14114
+ /** `number` only. */
14115
+ min: number().optional(),
14116
+ max: number().optional(),
14117
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
14118
+ default: DeliveryParamValueSchema.optional()
14119
+ });
14120
+ /**
14121
+ * The canonical notification. `body` is the only hard field (Apprise model).
14122
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
14123
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
14124
+ * the adapter maps this ordinal onto its native level. `level?` is an
14125
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
14126
+ * `priority` for that one target.
14127
+ */
14128
+ var NotificationSchema = object({
14129
+ body: string(),
14130
+ title: string().optional(),
14131
+ format: NotificationFormatSchema.default("text"),
14132
+ priority: number().int().min(1).max(5).default(3),
14133
+ level: string().optional(),
14134
+ attachments: array(AttachmentSchema).optional(),
14135
+ clickUrl: string().optional(),
14136
+ actions: array(NotificationActionSchema).optional(),
14137
+ sound: string().optional(),
14138
+ ttl: number().optional(),
14139
+ tag: string().optional(),
14140
+ deviceId: number().optional(),
14141
+ eventId: string().optional(),
14142
+ metadata: record(string(), unknown()).optional(),
14143
+ /**
14144
+ * Values for the parameters the target's kind DECLARES in
14145
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
14146
+ * declaration by the sender; a notifier reads only the keys it declared.
14147
+ */
14148
+ delivery: record(string(), DeliveryParamValueSchema).optional()
14149
+ });
14150
+ /** One declared native severity/priority level for a kind. */
14151
+ var TargetKindLevelSchema = object({
14152
+ id: string(),
14153
+ label: string(),
14154
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
14155
+ ordinal: number().int().min(1).max(5).nullable(),
14156
+ flags: object({
14157
+ critical: boolean().optional(),
14158
+ silent: boolean().optional(),
14159
+ noPush: boolean().optional()
14160
+ }).optional(),
14161
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
14162
+ requires: array(string()).optional(),
14163
+ description: string().optional()
14164
+ });
14165
+ /** The full capability block consulted before dispatch. */
14166
+ var TargetKindCapsSchema = object({
14167
+ attachments: object({
14168
+ mediaTypes: array(AttachmentMediaTypeSchema),
14169
+ mode: _enum([
14170
+ "url",
14171
+ "bytes",
14172
+ "both"
14173
+ ]),
14174
+ max: number().int().nonnegative(),
14175
+ maxBytes: number().int().positive().optional()
14176
+ }),
14177
+ /** Max action buttons (0 = none). */
14178
+ actions: number().int().nonnegative(),
14179
+ /**
14180
+ * Whether this kind renders a per-action ICON.
14181
+ *
14182
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
14183
+ * run on the addon cap path — three production failures in one day taught
14184
+ * this repo that once. Absent is read as false by the degrade engine, which
14185
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
14186
+ * assumed and dropped costs the operator's trust in the field.
14187
+ */
14188
+ actionIcons: boolean().optional(),
14189
+ levels: array(TargetKindLevelSchema),
14190
+ format: array(NotificationFormatSchema),
14191
+ clickUrl: boolean(),
14192
+ sound: boolean(),
14193
+ ttl: boolean(),
14194
+ bodyMaxLen: number().int().positive()
14195
+ });
14196
+ /**
14197
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
14198
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
14199
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
14200
+ * the union is large and not meant for runtime validation here; the exported
14201
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14202
+ */
14203
+ var ConfigSchemaPassthrough$4 = unknown();
14204
+ var TargetKindSchema = object({
14205
+ kind: string(),
14206
+ label: string(),
14207
+ icon: string(),
14208
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
14209
+ addonId: string(),
14210
+ /**
14211
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
14212
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
14213
+ * when the addon bundles no icon for that kind — the client then falls back
14214
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
14215
+ *
14216
+ * Root-relative on purpose: it resolves against whatever origin serves a web
14217
+ * client, and a native client joins it onto its own hub base.
14218
+ *
14219
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
14220
+ * field that survived only because the runtime cap-router forwards provider
14221
+ * output verbatim — so every consumer had to re-declare it by hand to stop
14222
+ * its own Zod parse from stripping it, and the whole arrangement would have
14223
+ * broken silently the moment output validation was tightened anywhere.
14224
+ */
14225
+ iconUrl: string().optional(),
14226
+ /**
14227
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
14228
+ *
14229
+ * The server knows this and therefore says it, because the client cannot
14230
+ * safely guess: a React-Native client renders SVG and raster through two
14231
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
14232
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
14233
+ * placeholder glyph for every vector icon while the web build looked fine.
14234
+ *
14235
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
14236
+ * not been updated — a client that cannot determine the type should prefer
14237
+ * its raster path, which is the safe default for an unknown image.
14238
+ */
14239
+ iconMediaType: string().optional(),
14240
+ configSchema: ConfigSchemaPassthrough$4,
14241
+ /**
14242
+ * The config keys that decide WHERE this kind's secrets are sent (server,
14243
+ * base URL, instance, chat, recipient). A save that changes any of them
14244
+ * while leaving a secret redacted is refused by the provider (D634), so an
14245
+ * editor uses this to ask for the secret on exactly those edits instead of
14246
+ * on every edit.
14247
+ *
14248
+ * Optional: absent from a provider that holds no secret (the Home Assistant
14249
+ * kinds) and from a provider older than this field — an editor that finds
14250
+ * it absent must assume ANY edit may need the secret.
14251
+ */
14252
+ destinationKeys: array(string()).optional(),
14253
+ supportsDiscovery: boolean(),
14254
+ /**
14255
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
14256
+ * Absent = none; the rule editor then offers nothing.
14257
+ */
14258
+ deliveryParams: array(DeliveryParamSchema).optional(),
14259
+ caps: TargetKindCapsSchema
14260
+ });
14261
+ /**
14262
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
14263
+ * (return a presence marker only) when serving `listTargets` — never
14264
+ * round-trip a stored secret to the UI.
14265
+ */
14266
+ var TargetSchema = object({
14267
+ id: string(),
14268
+ name: string(),
14269
+ kind: string(),
14270
+ addonId: string(),
14271
+ enabled: boolean(),
14272
+ config: record(string(), unknown())
14273
+ });
14274
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
14275
+ var DiscoveredTargetSchema = object({
14276
+ kind: string(),
14277
+ suggestedName: string(),
14278
+ config: record(string(), unknown())
14279
+ });
14280
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
14281
+ var RenderedAsSchema = object({
14282
+ level: string(),
14283
+ format: NotificationFormatSchema,
14284
+ attachmentsSent: number().int().nonnegative(),
14285
+ actionsSent: number().int().nonnegative(),
14286
+ truncated: boolean(),
14287
+ dropped: array(string())
14288
+ });
14289
+ var SendResultSchema = object({
14290
+ success: boolean(),
14291
+ error: string().optional(),
14292
+ renderedAs: RenderedAsSchema.optional()
14293
+ });
14294
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
14295
+ var TestResultSchema = SendResultSchema;
14296
+ /**
14297
+ * Pre-save credential probe against a partial (unsaved) target config.
14298
+ * `resolvedUrl` never includes secrets (token stripped from path).
14299
+ */
14300
+ var ProbeTargetConfigResultSchema = object({
14301
+ ok: boolean(),
14302
+ resolvedUrl: string(),
14303
+ httpStatus: number().int().optional(),
14304
+ stateInstance: string().optional(),
14305
+ error: string().optional()
14306
+ });
14307
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
14308
+ kind: string(),
14309
+ config: record(string(), unknown()).optional(),
14310
+ /**
14311
+ * When editing / duplicating, the blank password field never round-trips
14312
+ * the stored token. Pass the source target id so the provider can restore
14313
+ * secrets server-side before dialling (same merge as upsert).
14314
+ */
14315
+ targetId: string().optional()
14316
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
14317
+ kind: string(),
14318
+ config: record(string(), unknown()),
14319
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
14320
+ targetId: string().optional()
14321
+ }), ProbeTargetConfigResultSchema, {
14322
+ kind: "mutation",
14323
+ auth: "admin"
14324
+ }), method(object({
14325
+ targetId: string(),
14326
+ notification: NotificationSchema
14327
+ }), SendResultSchema, {
14328
+ kind: "mutation",
14329
+ auth: "admin"
14330
+ }), method(object({
14331
+ targetId: string(),
14332
+ sample: NotificationSchema.optional()
14333
+ }), TestResultSchema, {
14334
+ kind: "mutation",
14335
+ auth: "admin"
14336
+ }), method(object({
14337
+ target: TargetSchema,
14338
+ /**
14339
+ * Duplicate flow: create a NEW target id while copying secrets from an
14340
+ * existing row. The UI never holds the stored token; blank / redacted
14341
+ * password fields are restored from this source (D634 still applies if
14342
+ * destinations diverge without a re-typed secret).
14343
+ */
14344
+ cloneSecretsFrom: string().optional()
14345
+ }), TargetSchema, {
14346
+ kind: "mutation",
14347
+ auth: "admin"
14348
+ }), method(object({ targetId: string() }), _void(), {
14349
+ kind: "mutation",
14350
+ auth: "admin"
14351
+ }), method(object({
14352
+ targetId: string(),
14353
+ enabled: boolean()
14354
+ }), _void(), {
14355
+ kind: "mutation",
14356
+ auth: "admin"
14357
+ });
14358
+ looseObject({
14359
+ name: string().min(1),
14360
+ enabled: boolean().optional()
14361
+ });
14362
+ TargetSchema.omit({ id: true });
13939
14363
  var EncodeProfileSchema = object({
13940
14364
  video: object({
13941
14365
  codec: _enum([
@@ -22277,7 +22701,6 @@ var SYSTEM_CAP_VOCABULARY = {
22277
22701
  "calendar-source": system(INTERNAL),
22278
22702
  calendars: system("calendars feed schedules and conditions, not device actions"),
22279
22703
  composer: system("device composition — an authoring tool, not an action"),
22280
- "connection-test": system(INTERNAL),
22281
22704
  "core-blocks": system(INTERNAL),
22282
22705
  "custom-model-registry": system(ML),
22283
22706
  "data-store-provider": system(INTERNAL),
@@ -44101,658 +44524,237 @@ var AutomationRunVerdictSchema = object({
44101
44524
  "cooldown",
44102
44525
  "bypass"
44103
44526
  ]),
44104
- reason: string()
44105
- });
44106
- /** One device of a multi-device step: did the call land. */
44107
- var StepDeviceTraceSchema = object({
44108
- deviceId: number().int(),
44109
- outcome: _enum(["ok", "error"]),
44110
- message: string().optional()
44111
- });
44112
- var AutomationStepTraceSchema = object({
44113
- /** `actions.<i>` */
44114
- path: string().min(1),
44115
- kind: string().min(1),
44116
- cap: string().optional(),
44117
- method: string().optional(),
44118
- /** The vocabulary action the step performs (`cover.open`), when it names one. */
44119
- actionId: string().optional(),
44120
- deviceId: number().int().optional(),
44121
- /** What happened per device, when the step drove several (or a domain). */
44122
- devices: array(StepDeviceTraceSchema).optional(),
44123
- startedAt: number().optional(),
44124
- endedAt: number().optional(),
44125
- /**
44126
- * The parameters the step SENT, after `{{vars.x}}` substitution — what the
44127
- * device was actually asked. Bounded like trigger `vars`; a secret parameter
44128
- * is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
44129
- */
44130
- args: record(string(), TraceValueSchema).optional(),
44131
- /** `empty`: the target resolved to no device, so nothing was done — never silently `ok`. */
44132
- outcome: _enum([
44133
- "ok",
44134
- "error",
44135
- "not-run",
44136
- "empty"
44137
- ]),
44138
- message: string().optional()
44139
- });
44140
- var AutomationRunTraceSchema = object({
44141
- runId: string().min(1),
44142
- startedAt: number(),
44143
- endedAt: number(),
44144
- trigger: AutomationTriggerTraceSchema,
44145
- conditions: array(AutomationConditionTraceNodeSchema),
44146
- verdict: AutomationRunVerdictSchema.optional(),
44147
- steps: array(AutomationStepTraceSchema),
44148
- outcome: AutomationRunOutcomeSchema,
44149
- /** How many identical consecutive skips this trace stands for (>= 1). */
44150
- repeat: number().int().min(1),
44151
- /** When the latest of those repeats happened. */
44152
- lastAt: number(),
44153
- /**
44154
- * A scheduler point's own outcome (D983). `outcome` carries the closest
44155
- * automation outcome so a reader that does not know schedulers still reads
44156
- * the trace; this field says exactly what happened.
44157
- */
44158
- scheduler: SchedulerOutcomeSchema.optional()
44159
- });
44160
- /**
44161
- * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
44162
- * `action:<actionId>`): the cooldown stamp and the trace ring.
44163
- */
44164
- var AutomationStateDocSchema = object({
44165
- version: literal(1),
44166
- lastRunStartedAt: number().nullable(),
44167
- traces: array(AutomationRunTraceSchema)
44168
- });
44169
- /**
44170
- * Actions — named, runnable things an operator (or an automation) can start,
44171
- * declared as LIST ENTRIES, never as devices.
44172
- *
44173
- * An entry is either **internal** (a short list of cap/wait steps kept in the
44174
- * hub's `actions` record kind) or declared by an addon through the
44175
- * `action-source` collection (a Home Assistant script, a scene …). The one
44176
- * surface the UI and automations talk to is the `actions` cap; it keeps no
44177
- * catalog of its own (D3) — every `list` asks the providers live.
44178
- *
44179
- * This file is the data half: the entry schema, the variables an entry may
44180
- * declare, and the pure resolution of the values a run was given. It holds no
44181
- * runtime.
44182
- */
44183
- /** What a variable's value is. `select` takes one of `options`. */
44184
- var ActionVariableTypeSchema = _enum([
44185
- "string",
44186
- "number",
44187
- "boolean",
44188
- "select"
44189
- ]);
44190
- var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
44191
- var ActionVariableValueSchema = union([
44192
- string(),
44193
- number(),
44194
- boolean()
44195
- ]);
44196
- /**
44197
- * One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
44198
- * value) or `{{vars.name}}` (inside a string) in their args.
44199
- */
44200
- var ActionVariableSchema = object({
44201
- name: string().regex(ACTION_VARIABLE_NAME_RE),
44202
- label: string().min(1).max(120).optional(),
44203
- type: ActionVariableTypeSchema,
44204
- required: boolean().optional(),
44205
- default: ActionVariableValueSchema.optional(),
44206
- /** The allowed values of a `select`. */
44207
- options: array(string().min(1)).min(1).optional()
44208
- }).superRefine((v, ctx) => {
44209
- if (v.type === "select" && v.options === void 0) ctx.addIssue({
44210
- code: "custom",
44211
- message: "a select variable needs options",
44212
- path: ["options"]
44213
- });
44214
- if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
44215
- code: "custom",
44216
- message: variableValueError(v, v.default) ?? "invalid default",
44217
- path: ["default"]
44218
- });
44219
- });
44220
- /** Why `value` is not a legal value for the variable, or null when it is. */
44221
- function variableValueError(variable, value) {
44222
- switch (variable.type) {
44223
- case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
44224
- case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
44225
- case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
44226
- case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
44227
- }
44228
- }
44229
- var ActionSourceKindSchema = _enum(["internal", "addon"]);
44230
- function isInternalActionId(actionId) {
44231
- return actionId.startsWith("internal:") && actionId.length > 9;
44232
- }
44233
- /** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
44234
- function addonIdOfActionId(actionId) {
44235
- if (isInternalActionId(actionId)) return null;
44236
- const at = actionId.indexOf(":");
44237
- return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
44238
- }
44239
- var ActionStatusSchema = object({
44240
- isRunning: boolean(),
44241
- /** Ms epoch the latest run STARTED; null when it never ran. */
44242
- lastRunAt: number().nullable(),
44243
- /** The latest run's failure; null on success or when it never ran. */
44244
- lastError: string().nullable()
44245
- });
44246
- var ActionEntrySchema = object({
44247
- /** `internal:<uuid>` or `<addonId>:<localId>`. */
44248
- id: string().min(3),
44249
- name: string().min(1).max(120),
44250
- /** The addon that declared it; `core-blocks` for an internal one. */
44251
- addonId: string().min(1),
44252
- source: ActionSourceKindSchema,
44253
- /** An addon-declared entry is edited where it was declared. */
44254
- readOnly: boolean(),
44255
- /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
44256
- steps: array(ActionStepSchema).min(1).max(50).optional(),
44257
- variables: array(ActionVariableSchema).max(20).optional(),
44258
- status: ActionStatusSchema
44259
- }).superRefine((entry, ctx) => {
44260
- if (entry.source === "internal") {
44261
- if (!isInternalActionId(entry.id)) ctx.addIssue({
44262
- code: "custom",
44263
- message: "an internal action id is internal:<uuid>",
44264
- path: ["id"]
44265
- });
44266
- return;
44267
- }
44268
- if (entry.steps !== void 0) ctx.addIssue({
44269
- code: "custom",
44270
- message: "only an internal action carries steps",
44271
- path: ["steps"]
44272
- });
44273
- if (!entry.readOnly) ctx.addIssue({
44274
- code: "custom",
44275
- message: "an addon action is read-only here",
44276
- path: ["readOnly"]
44277
- });
44278
- if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
44279
- code: "custom",
44280
- message: "an addon action id is <addonId>:<localId>",
44281
- path: ["id"]
44282
- });
44283
- });
44284
- /** What an operator writes for an internal action. */
44285
- var ActionInputSchema = object({
44286
- name: string().min(1).max(120),
44287
- steps: array(ActionStepSchema).min(1).max(50),
44288
- variables: array(ActionVariableSchema).max(20).optional()
44289
- });
44290
- /** Who started a run. Absent = the operator pressed it. */
44291
- var ActionRunOriginSchema = object({
44292
- /** The automation block whose step started it. */
44293
- blockId: string().min(1).optional() });
44294
- /**
44295
- * `started` = the caller did not ask to wait. `timed-out` = the run reached the
44296
- * ceiling (`ACTION_RUN_MAX_MS`) and was stopped; it is never a `ran`.
44297
- */
44298
- var ActionRunOutcomeSchema = union([
44299
- AutomationRunOutcomeSchema,
44300
- literal("started"),
44301
- literal("timed-out")
44302
- ]);
44303
- var ActionRunResultSchema = object({
44304
- runId: string().min(1),
44305
- outcome: ActionRunOutcomeSchema,
44306
- /** Why it did not run, or what failed. */
44307
- message: string().optional()
44308
- });
44309
- _enum([
44310
- "not-found",
44311
- "read-only",
44312
- "invalid-variables",
44313
- "invalid-steps",
44314
- "not-linked",
44315
- "busy"
44316
- ]);
44317
- object({
44318
- actionId: string(),
44319
- runId: string(),
44320
- isRunning: boolean(),
44321
- /** Set when the run ended. */
44322
- outcome: AutomationRunOutcomeSchema.optional(),
44323
- lastRunAt: number().nullable(),
44324
- lastError: string().nullable()
44325
- });
44326
- /** A bare number accepted as a base unit — the save succeeded, say so. */
44327
- var UnitWarningSchema = object({
44328
- /** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
44329
- path: string(),
44330
- deviceId: number().int().optional(),
44331
- field: string(),
44332
- value: number(),
44333
- assumedUnit: string(),
44334
- message: string()
44335
- });
44336
- /**
44337
- * notification-output — canonical, capability-gated notification delivery.
44338
- *
44339
- * Apprise-derived model (see
44340
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
44341
- * callers emit ONE canonical `Notification`; each provider declares a
44342
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
44343
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
44344
- * message to what the kind supports — callers never special-case a service.
44345
- *
44346
- * DESIGN DECISIONS (locked):
44347
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
44348
- * `setTargetEnabled`), each provider persisting via the `settings-store`
44349
- * cap. Rationale: the admin UI needs one uniform surface across the
44350
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
44351
- * alternative would fork the UI per addon and cannot host the
44352
- * discovery→adopt flow.
44353
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
44354
- * the generated cap-mount auto-`concatCollection`-fans them across every
44355
- * registered provider (notifiers addon + HA addon) so one catalog is
44356
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
44357
- * `addonId` the generated collection router extracts from the call input.
44358
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
44359
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
44360
- * `storage` / `storage-provider` / `recording` caps over the same path. No
44361
- * base64 fallback needed.
44362
- *
44363
- * TODO (deferred, closed-set change — separate decision): add
44364
- * `providerKind: 'notify'` so notification providers surface on the unified
44365
- * admin "Integrations" page.
44366
- */
44367
- /**
44368
- * Zentik-derived typed-media enum — the superset across every kind. Each
44369
- * adapter picks what it supports and the degrade engine filters the rest.
44370
- */
44371
- var AttachmentMediaTypeSchema = _enum([
44372
- "image",
44373
- "video",
44374
- "gif",
44375
- "audio",
44376
- "icon"
44377
- ]);
44378
- /**
44379
- * A single attachment. Exactly one of `url` (remote source, most adapters
44380
- * prefer this) or `bytes` (inline source; required for Pushover-style
44381
- * bytes-only kinds) MUST be present — the degrade engine expresses a
44382
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
44383
- */
44384
- var AttachmentSchema = object({
44385
- mediaType: AttachmentMediaTypeSchema,
44386
- url: string().optional(),
44387
- bytes: _instanceof(Uint8Array).optional(),
44388
- mime: string().optional(),
44389
- name: string().optional()
44390
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
44391
- var NotificationFormatSchema = _enum([
44392
- "text",
44393
- "markdown",
44394
- "html"
44395
- ]);
44396
- /**
44397
- * The CLOSED icon vocabulary an action button may use.
44398
- *
44399
- * A closed set, not a free string, and that is the whole point: an arbitrary
44400
- * icon name is one that ntfy renders, zentik silently drops, and nobody
44401
- * notices — the same class of gap as a zone vocabulary nothing produced
44402
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
44403
- * declares `actionIcons: false` and the degrade engine strips the field.
44404
- *
44405
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
44406
- * renderer's icon set; "acknowledge" survives an adapter that draws it
44407
- * differently.
44408
- *
44409
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
44410
- *
44411
- * These names are for US. **No adapter may forward one verbatim.** Each maps
44412
- * the whole set onto its own renderer's vocabulary through a
44413
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
44414
- * fallback, so adding a member here fails every adapter's build until someone
44415
- * decides its glyph, which is the only place that decision can be made
44416
- * honestly.
44417
- *
44418
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
44419
- * `disarm` straight through; iOS feeds that string to
44420
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
44421
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
44422
- * mapping, and "the field is documented" is not "the value renders".
44423
- *
44424
- * Adding a member is TRAIN-BOUND. The enum lives in the published
44425
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
44426
- * so an addon that emits a token the running hub does not know does not lose an
44427
- * icon — its whole `send` fails Zod validation and the notification never
44428
- * arrives. Never emit a new token from an addon before the train carrying it.
44429
- */
44430
- var NotificationActionIconSchema = _enum([
44431
- "acknowledge",
44432
- "dismiss",
44433
- "silence",
44434
- "snooze",
44435
- "view",
44436
- "play",
44437
- "open",
44438
- "close",
44439
- "lock",
44440
- "unlock",
44441
- "arm",
44442
- "arm-home",
44443
- "arm-away",
44444
- "arm-night",
44445
- "disarm",
44446
- "light",
44447
- "alert",
44448
- "camera"
44449
- ]);
44450
- /** A single tap-through action button. */
44451
- var NotificationActionSchema = object({
44452
- id: string(),
44453
- label: string(),
44454
- url: string().optional(),
44455
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
44456
- icon: NotificationActionIconSchema.optional(),
44457
- /**
44458
- * Renders in a warning style where the notifier supports it.
44459
- *
44460
- * A HINT, never a gate. The callback's authority is its token and nothing
44461
- * else — see `notification-center/action-token.ts` for what that does and
44462
- * does not buy.
44463
- */
44464
- destructive: boolean().optional(),
44465
- /**
44466
- * How the tap should REACH the url.
44467
- *
44468
- * `navigate` (absent, and every button authored before this field) opens it:
44469
- * the phone leaves the notification and shows whatever the callback returns.
44470
- * That is right for a button whose answer the operator wants to read.
44471
- *
44472
- * `background` fires it as a POST and stays put. It exists for the buttons
44473
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
44474
- * an answer to the notification, and being thrown into a browser tab to
44475
- * confirm it costs more attention than the notification did. A backend that
44476
- * cannot do a background call renders it as an ordinary link (the adapters
44477
- * fall back rather than dropping the button), so this is a preference, never
44478
- * a requirement.
44479
- */
44480
- mode: _enum(["navigate", "background"]).optional()
44481
- });
44482
- /** A delivery parameter value: what a notifier declared it takes. */
44483
- var DeliveryParamValueSchema = union([
44484
- string(),
44485
- number(),
44486
- boolean()
44487
- ]);
44488
- /**
44489
- * One extra delivery parameter a notifier KIND declares (D938). A rule that
44490
- * addresses a target of that kind may set it; a kind that declares nothing is
44491
- * never handed one. The admin UI derives its control from this descriptor —
44492
- * no editor knows a service by name.
44493
- *
44494
- * Optional fields are `.optional()`, never `.default()` (a Zod default does not
44495
- * run on the addon cap path).
44496
- */
44497
- var DeliveryParamSchema = object({
44498
- key: string().min(1),
44499
- label: string(),
44500
- /** i18n key of the label, resolved by the declaring addon's namespace. */
44501
- labelKey: string().optional(),
44502
- type: _enum([
44503
- "enum",
44504
- "number",
44505
- "boolean",
44506
- "string"
44507
- ]),
44508
- /** `enum` only: the closed set of accepted values. */
44509
- options: array(object({
44510
- value: string(),
44511
- label: string()
44512
- })).optional(),
44513
- /** `number` only. */
44514
- min: number().optional(),
44515
- max: number().optional(),
44516
- /** What the notifier does when the rule sets nothing; shown as the placeholder. */
44517
- default: DeliveryParamValueSchema.optional()
44518
- });
44519
- /**
44520
- * The canonical notification. `body` is the only hard field (Apprise model).
44521
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
44522
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
44523
- * the adapter maps this ordinal onto its native level. `level?` is an
44524
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
44525
- * `priority` for that one target.
44526
- */
44527
- var NotificationSchema = object({
44528
- body: string(),
44529
- title: string().optional(),
44530
- format: NotificationFormatSchema.default("text"),
44531
- priority: number().int().min(1).max(5).default(3),
44532
- level: string().optional(),
44533
- attachments: array(AttachmentSchema).optional(),
44534
- clickUrl: string().optional(),
44535
- actions: array(NotificationActionSchema).optional(),
44536
- sound: string().optional(),
44537
- ttl: number().optional(),
44538
- tag: string().optional(),
44539
- deviceId: number().optional(),
44540
- eventId: string().optional(),
44541
- metadata: record(string(), unknown()).optional(),
44527
+ reason: string()
44528
+ });
44529
+ /** One device of a multi-device step: did the call land. */
44530
+ var StepDeviceTraceSchema = object({
44531
+ deviceId: number().int(),
44532
+ outcome: _enum(["ok", "error"]),
44533
+ message: string().optional()
44534
+ });
44535
+ var AutomationStepTraceSchema = object({
44536
+ /** `actions.<i>` */
44537
+ path: string().min(1),
44538
+ kind: string().min(1),
44539
+ cap: string().optional(),
44540
+ method: string().optional(),
44541
+ /** The vocabulary action the step performs (`cover.open`), when it names one. */
44542
+ actionId: string().optional(),
44543
+ deviceId: number().int().optional(),
44544
+ /** What happened per device, when the step drove several (or a domain). */
44545
+ devices: array(StepDeviceTraceSchema).optional(),
44546
+ startedAt: number().optional(),
44547
+ endedAt: number().optional(),
44542
44548
  /**
44543
- * Values for the parameters the target's kind DECLARES in
44544
- * {@link TargetKindSchema} `deliveryParams`. Already validated against the
44545
- * declaration by the sender; a notifier reads only the keys it declared.
44549
+ * The parameters the step SENT, after `{{vars.x}}` substitution — what the
44550
+ * device was actually asked. Bounded like trigger `vars`; a secret parameter
44551
+ * is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
44546
44552
  */
44547
- delivery: record(string(), DeliveryParamValueSchema).optional()
44548
- });
44549
- /** One declared native severity/priority level for a kind. */
44550
- var TargetKindLevelSchema = object({
44551
- id: string(),
44552
- label: string(),
44553
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
44554
- ordinal: number().int().min(1).max(5).nullable(),
44555
- flags: object({
44556
- critical: boolean().optional(),
44557
- silent: boolean().optional(),
44558
- noPush: boolean().optional()
44559
- }).optional(),
44560
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
44561
- requires: array(string()).optional(),
44562
- description: string().optional()
44553
+ args: record(string(), TraceValueSchema).optional(),
44554
+ /** `empty`: the target resolved to no device, so nothing was done — never silently `ok`. */
44555
+ outcome: _enum([
44556
+ "ok",
44557
+ "error",
44558
+ "not-run",
44559
+ "empty"
44560
+ ]),
44561
+ message: string().optional()
44563
44562
  });
44564
- /** The full capability block consulted before dispatch. */
44565
- var TargetKindCapsSchema = object({
44566
- attachments: object({
44567
- mediaTypes: array(AttachmentMediaTypeSchema),
44568
- mode: _enum([
44569
- "url",
44570
- "bytes",
44571
- "both"
44572
- ]),
44573
- max: number().int().nonnegative(),
44574
- maxBytes: number().int().positive().optional()
44575
- }),
44576
- /** Max action buttons (0 = none). */
44577
- actions: number().int().nonnegative(),
44563
+ var AutomationRunTraceSchema = object({
44564
+ runId: string().min(1),
44565
+ startedAt: number(),
44566
+ endedAt: number(),
44567
+ trigger: AutomationTriggerTraceSchema,
44568
+ conditions: array(AutomationConditionTraceNodeSchema),
44569
+ verdict: AutomationRunVerdictSchema.optional(),
44570
+ steps: array(AutomationStepTraceSchema),
44571
+ outcome: AutomationRunOutcomeSchema,
44572
+ /** How many identical consecutive skips this trace stands for (>= 1). */
44573
+ repeat: number().int().min(1),
44574
+ /** When the latest of those repeats happened. */
44575
+ lastAt: number(),
44578
44576
  /**
44579
- * Whether this kind renders a per-action ICON.
44580
- *
44581
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
44582
- * run on the addon cap path — three production failures in one day taught
44583
- * this repo that once. Absent is read as false by the degrade engine, which
44584
- * is the safe direction: an icon that is not rendered costs nothing, an icon
44585
- * assumed and dropped costs the operator's trust in the field.
44577
+ * A scheduler point's own outcome (D983). `outcome` carries the closest
44578
+ * automation outcome so a reader that does not know schedulers still reads
44579
+ * the trace; this field says exactly what happened.
44586
44580
  */
44587
- actionIcons: boolean().optional(),
44588
- levels: array(TargetKindLevelSchema),
44589
- format: array(NotificationFormatSchema),
44590
- clickUrl: boolean(),
44591
- sound: boolean(),
44592
- ttl: boolean(),
44593
- bodyMaxLen: number().int().positive()
44581
+ scheduler: SchedulerOutcomeSchema.optional()
44594
44582
  });
44595
44583
  /**
44596
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
44597
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
44598
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
44599
- * the union is large and not meant for runtime validation here; the exported
44600
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
44584
+ * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
44585
+ * `action:<actionId>`): the cooldown stamp and the trace ring.
44601
44586
  */
44602
- var ConfigSchemaPassthrough$4 = unknown();
44603
- var TargetKindSchema = object({
44604
- kind: string(),
44605
- label: string(),
44606
- icon: string(),
44607
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
44608
- addonId: string(),
44609
- /**
44610
- * URL of the kind's bundled BRAND icon, served by the providing addon over
44611
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
44612
- * when the addon bundles no icon for that kind — the client then falls back
44613
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
44614
- *
44615
- * Root-relative on purpose: it resolves against whatever origin serves a web
44616
- * client, and a native client joins it onto its own hub base.
44617
- *
44618
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
44619
- * field that survived only because the runtime cap-router forwards provider
44620
- * output verbatim — so every consumer had to re-declare it by hand to stop
44621
- * its own Zod parse from stripping it, and the whole arrangement would have
44622
- * broken silently the moment output validation was tightened anywhere.
44623
- */
44624
- iconUrl: string().optional(),
44625
- /**
44626
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
44627
- *
44628
- * The server knows this and therefore says it, because the client cannot
44629
- * safely guess: a React-Native client renders SVG and raster through two
44630
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
44631
- * not decode SVG on iOS/Android), so without this it silently fell back to a
44632
- * placeholder glyph for every vector icon while the web build looked fine.
44633
- *
44634
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
44635
- * not been updated — a client that cannot determine the type should prefer
44636
- * its raster path, which is the safe default for an unknown image.
44637
- */
44638
- iconMediaType: string().optional(),
44639
- configSchema: ConfigSchemaPassthrough$4,
44640
- /**
44641
- * The config keys that decide WHERE this kind's secrets are sent (server,
44642
- * base URL, instance, chat, recipient). A save that changes any of them
44643
- * while leaving a secret redacted is refused by the provider (D634), so an
44644
- * editor uses this to ask for the secret on exactly those edits instead of
44645
- * on every edit.
44646
- *
44647
- * Optional: absent from a provider that holds no secret (the Home Assistant
44648
- * kinds) and from a provider older than this field — an editor that finds
44649
- * it absent must assume ANY edit may need the secret.
44650
- */
44651
- destinationKeys: array(string()).optional(),
44652
- supportsDiscovery: boolean(),
44653
- /**
44654
- * Extra delivery parameters a rule may set for a target of this kind (D938).
44655
- * Absent = none; the rule editor then offers nothing.
44656
- */
44657
- deliveryParams: array(DeliveryParamSchema).optional(),
44658
- caps: TargetKindCapsSchema
44587
+ var AutomationStateDocSchema = object({
44588
+ version: literal(1),
44589
+ lastRunStartedAt: number().nullable(),
44590
+ traces: array(AutomationRunTraceSchema)
44659
44591
  });
44660
44592
  /**
44661
- * A persisted target. `config` holds secrets; providers REDACT secret fields
44662
- * (return a presence marker only) when serving `listTargets` — never
44663
- * round-trip a stored secret to the UI.
44593
+ * Actions — named, runnable things an operator (or an automation) can start,
44594
+ * declared as LIST ENTRIES, never as devices.
44595
+ *
44596
+ * An entry is either **internal** (a short list of cap/wait steps kept in the
44597
+ * hub's `actions` record kind) or declared by an addon through the
44598
+ * `action-source` collection (a Home Assistant script, a scene …). The one
44599
+ * surface the UI and automations talk to is the `actions` cap; it keeps no
44600
+ * catalog of its own (D3) — every `list` asks the providers live.
44601
+ *
44602
+ * This file is the data half: the entry schema, the variables an entry may
44603
+ * declare, and the pure resolution of the values a run was given. It holds no
44604
+ * runtime.
44664
44605
  */
44665
- var TargetSchema = object({
44666
- id: string(),
44667
- name: string(),
44668
- kind: string(),
44669
- addonId: string(),
44670
- enabled: boolean(),
44671
- config: record(string(), unknown())
44606
+ /** What a variable's value is. `select` takes one of `options`. */
44607
+ var ActionVariableTypeSchema = _enum([
44608
+ "string",
44609
+ "number",
44610
+ "boolean",
44611
+ "select"
44612
+ ]);
44613
+ var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
44614
+ var ActionVariableValueSchema = union([
44615
+ string(),
44616
+ number(),
44617
+ boolean()
44618
+ ]);
44619
+ /**
44620
+ * One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
44621
+ * value) or `{{vars.name}}` (inside a string) in their args.
44622
+ */
44623
+ var ActionVariableSchema = object({
44624
+ name: string().regex(ACTION_VARIABLE_NAME_RE),
44625
+ label: string().min(1).max(120).optional(),
44626
+ type: ActionVariableTypeSchema,
44627
+ required: boolean().optional(),
44628
+ default: ActionVariableValueSchema.optional(),
44629
+ /** The allowed values of a `select`. */
44630
+ options: array(string().min(1)).min(1).optional()
44631
+ }).superRefine((v, ctx) => {
44632
+ if (v.type === "select" && v.options === void 0) ctx.addIssue({
44633
+ code: "custom",
44634
+ message: "a select variable needs options",
44635
+ path: ["options"]
44636
+ });
44637
+ if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
44638
+ code: "custom",
44639
+ message: variableValueError(v, v.default) ?? "invalid default",
44640
+ path: ["default"]
44641
+ });
44672
44642
  });
44673
- /** A discovery-surfaced candidate (config is partial + non-secret). */
44674
- var DiscoveredTargetSchema = object({
44675
- kind: string(),
44676
- suggestedName: string(),
44677
- config: record(string(), unknown())
44643
+ /** Why `value` is not a legal value for the variable, or null when it is. */
44644
+ function variableValueError(variable, value) {
44645
+ switch (variable.type) {
44646
+ case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
44647
+ case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
44648
+ case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
44649
+ case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
44650
+ }
44651
+ }
44652
+ var ActionSourceKindSchema = _enum(["internal", "addon"]);
44653
+ function isInternalActionId(actionId) {
44654
+ return actionId.startsWith("internal:") && actionId.length > 9;
44655
+ }
44656
+ /** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
44657
+ function addonIdOfActionId(actionId) {
44658
+ if (isInternalActionId(actionId)) return null;
44659
+ const at = actionId.indexOf(":");
44660
+ return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
44661
+ }
44662
+ var ActionStatusSchema = object({
44663
+ isRunning: boolean(),
44664
+ /** Ms epoch the latest run STARTED; null when it never ran. */
44665
+ lastRunAt: number().nullable(),
44666
+ /** The latest run's failure; null on success or when it never ran. */
44667
+ lastError: string().nullable()
44678
44668
  });
44679
- /** The degrade engine's report — what was resolved / dropped / degraded. */
44680
- var RenderedAsSchema = object({
44681
- level: string(),
44682
- format: NotificationFormatSchema,
44683
- attachmentsSent: number().int().nonnegative(),
44684
- actionsSent: number().int().nonnegative(),
44685
- truncated: boolean(),
44686
- dropped: array(string())
44669
+ var ActionEntrySchema = object({
44670
+ /** `internal:<uuid>` or `<addonId>:<localId>`. */
44671
+ id: string().min(3),
44672
+ name: string().min(1).max(120),
44673
+ /** The addon that declared it; `core-blocks` for an internal one. */
44674
+ addonId: string().min(1),
44675
+ source: ActionSourceKindSchema,
44676
+ /** An addon-declared entry is edited where it was declared. */
44677
+ readOnly: boolean(),
44678
+ /** Internal only: cap and wait steps. No action-in-action, so no cycles. */
44679
+ steps: array(ActionStepSchema).min(1).max(50).optional(),
44680
+ variables: array(ActionVariableSchema).max(20).optional(),
44681
+ status: ActionStatusSchema
44682
+ }).superRefine((entry, ctx) => {
44683
+ if (entry.source === "internal") {
44684
+ if (!isInternalActionId(entry.id)) ctx.addIssue({
44685
+ code: "custom",
44686
+ message: "an internal action id is internal:<uuid>",
44687
+ path: ["id"]
44688
+ });
44689
+ return;
44690
+ }
44691
+ if (entry.steps !== void 0) ctx.addIssue({
44692
+ code: "custom",
44693
+ message: "only an internal action carries steps",
44694
+ path: ["steps"]
44695
+ });
44696
+ if (!entry.readOnly) ctx.addIssue({
44697
+ code: "custom",
44698
+ message: "an addon action is read-only here",
44699
+ path: ["readOnly"]
44700
+ });
44701
+ if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
44702
+ code: "custom",
44703
+ message: "an addon action id is <addonId>:<localId>",
44704
+ path: ["id"]
44705
+ });
44687
44706
  });
44688
- var SendResultSchema = object({
44689
- success: boolean(),
44690
- error: string().optional(),
44691
- renderedAs: RenderedAsSchema.optional()
44707
+ /** What an operator writes for an internal action. */
44708
+ var ActionInputSchema = object({
44709
+ name: string().min(1).max(120),
44710
+ steps: array(ActionStepSchema).min(1).max(50),
44711
+ variables: array(ActionVariableSchema).max(20).optional()
44692
44712
  });
44693
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44694
- var TestResultSchema = SendResultSchema;
44713
+ /** Who started a run. Absent = the operator pressed it. */
44714
+ var ActionRunOriginSchema = object({
44715
+ /** The automation block whose step started it. */
44716
+ blockId: string().min(1).optional() });
44695
44717
  /**
44696
- * Pre-save credential probe against a partial (unsaved) target config.
44697
- * `resolvedUrl` never includes secrets (token stripped from path).
44718
+ * `started` = the caller did not ask to wait. `timed-out` = the run reached the
44719
+ * ceiling (`ACTION_RUN_MAX_MS`) and was stopped; it is never a `ran`.
44698
44720
  */
44699
- var ProbeTargetConfigResultSchema = object({
44700
- ok: boolean(),
44701
- resolvedUrl: string(),
44702
- httpStatus: number().int().optional(),
44703
- stateInstance: string().optional(),
44704
- error: string().optional()
44721
+ var ActionRunOutcomeSchema = union([
44722
+ AutomationRunOutcomeSchema,
44723
+ literal("started"),
44724
+ literal("timed-out")
44725
+ ]);
44726
+ var ActionRunResultSchema = object({
44727
+ runId: string().min(1),
44728
+ outcome: ActionRunOutcomeSchema,
44729
+ /** Why it did not run, or what failed. */
44730
+ message: string().optional()
44705
44731
  });
44706
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
44707
- kind: string(),
44708
- config: record(string(), unknown()).optional(),
44709
- /**
44710
- * When editing / duplicating, the blank password field never round-trips
44711
- * the stored token. Pass the source target id so the provider can restore
44712
- * secrets server-side before dialling (same merge as upsert).
44713
- */
44714
- targetId: string().optional()
44715
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
44716
- kind: string(),
44717
- config: record(string(), unknown()),
44718
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
44719
- targetId: string().optional()
44720
- }), ProbeTargetConfigResultSchema, {
44721
- kind: "mutation",
44722
- auth: "admin"
44723
- }), method(object({
44724
- targetId: string(),
44725
- notification: NotificationSchema
44726
- }), SendResultSchema, {
44727
- kind: "mutation",
44728
- auth: "admin"
44729
- }), method(object({
44730
- targetId: string(),
44731
- sample: NotificationSchema.optional()
44732
- }), TestResultSchema, {
44733
- kind: "mutation",
44734
- auth: "admin"
44735
- }), method(object({
44736
- target: TargetSchema,
44737
- /**
44738
- * Duplicate flow: create a NEW target id while copying secrets from an
44739
- * existing row. The UI never holds the stored token; blank / redacted
44740
- * password fields are restored from this source (D634 still applies if
44741
- * destinations diverge without a re-typed secret).
44742
- */
44743
- cloneSecretsFrom: string().optional()
44744
- }), TargetSchema, {
44745
- kind: "mutation",
44746
- auth: "admin"
44747
- }), method(object({ targetId: string() }), _void(), {
44748
- kind: "mutation",
44749
- auth: "admin"
44750
- }), method(object({
44751
- targetId: string(),
44752
- enabled: boolean()
44753
- }), _void(), {
44754
- kind: "mutation",
44755
- auth: "admin"
44732
+ _enum([
44733
+ "not-found",
44734
+ "read-only",
44735
+ "invalid-variables",
44736
+ "invalid-steps",
44737
+ "not-linked",
44738
+ "busy"
44739
+ ]);
44740
+ object({
44741
+ actionId: string(),
44742
+ runId: string(),
44743
+ isRunning: boolean(),
44744
+ /** Set when the run ended. */
44745
+ outcome: AutomationRunOutcomeSchema.optional(),
44746
+ lastRunAt: number().nullable(),
44747
+ lastError: string().nullable()
44748
+ });
44749
+ /** A bare number accepted as a base unit — the save succeeded, say so. */
44750
+ var UnitWarningSchema = object({
44751
+ /** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
44752
+ path: string(),
44753
+ deviceId: number().int().optional(),
44754
+ field: string(),
44755
+ value: number(),
44756
+ assumedUnit: string(),
44757
+ message: string()
44756
44758
  });
44757
44759
  _enum([
44758
44760
  "automation",
@@ -49293,45 +49295,6 @@ method(object({
49293
49295
  kind: "mutation",
49294
49296
  auth: "admin"
49295
49297
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
49296
- var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
49297
- object({
49298
- outcome: literal("validated"),
49299
- /** Round-trip of the sign-in, when the provider measured it. */
49300
- latencyMs: number().nonnegative().optional(),
49301
- /** Optional human detail worth showing next to the tick
49302
- * ("3 devices visible on this account"). */
49303
- detail: string().optional()
49304
- }).strict(),
49305
- object({
49306
- outcome: literal("rejected"),
49307
- error: string()
49308
- }).strict(),
49309
- object({
49310
- outcome: literal("inconclusive"),
49311
- error: string()
49312
- }).strict()
49313
- ]);
49314
- var ConnectionTestInputSchema = object({
49315
- /** Candidate integration settings, exactly as the create form collected them. */
49316
- settings: record(string(), unknown()) });
49317
- /**
49318
- * What the provider's test actually DOES, so the UI can say it in words before
49319
- * the operator presses the button ("Signs in to the Dreo cloud"). Purely
49320
- * descriptive — it never changes routing.
49321
- */
49322
- var ConnectionTestDescriptorSchema = object({ label: string() });
49323
- var connectionTestCapability = {
49324
- name: "connection-test",
49325
- scope: "system",
49326
- mode: "collection",
49327
- methods: {
49328
- testSettings: method(ConnectionTestInputSchema, ConnectionTestOutcomeSchema, {
49329
- kind: "mutation",
49330
- auth: "admin"
49331
- }),
49332
- describeTest: method(_void(), ConnectionTestDescriptorSchema, { auth: "admin" })
49333
- }
49334
- };
49335
49298
  /**
49336
49299
  * Upstream-system connectivity sensor — distinct from `device-status`,
49337
49300
  * which is the kernel-managed online/offline flag for the device's
@@ -56485,10 +56448,9 @@ var AvailableIntegrationTypeSchema = object({
56485
56448
  * locations" checkbox. Provider-declared in the addon manifest. */
56486
56449
  supportsLocationImport: boolean(),
56487
56450
  /**
56488
- * True when this integration DECLARES a pre-creation test (the
56489
- * `connection-test` cap, or a broker whose settings it stores). Drives the
56490
- * Test button: an integration that cannot be tested must say so up front
56491
- * rather than offering a button that always answers the same nonsense.
56451
+ * True when this integration's instances declare a Test (`integration-instances.describe().canTest`,
56452
+ * D1016). An integration that cannot be tested must say so up front rather than offering a
56453
+ * button that always answers the same nonsense.
56492
56454
  */
56493
56455
  canTest: boolean(),
56494
56456
  existingInstances: array(object({
@@ -56542,7 +56504,9 @@ var CreateIntegrationInputSchema = object({
56542
56504
  info: record(string(), unknown()).optional(),
56543
56505
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56544
56506
  originStableId: string().optional(),
56545
- settings: record(string(), unknown()).optional()
56507
+ settings: record(string(), unknown()).optional(),
56508
+ /** The instance kind being created, for a provider with several; omitted = its only testable kind. */
56509
+ kind: string().min(1).optional()
56546
56510
  });
56547
56511
  var UpdateIntegrationInputSchema = object({
56548
56512
  id: string(),
@@ -56568,13 +56532,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56568
56532
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56569
56533
  kind: "mutation",
56570
56534
  auth: "admin"
56571
- }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56572
- addonId: string(),
56573
- settings: record(string(), unknown())
56574
- }), TestConnectionResultSchema, {
56575
- kind: "mutation",
56576
- auth: "admin"
56577
- });
56535
+ }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" });
56578
56536
  /**
56579
56537
  * `integration-instances` - the connection of an addon that holds ONE or SEVERAL
56580
56538
  * (an account, a relay, a destination) as typed records (D985, D986). The addon's
@@ -65317,13 +65275,6 @@ var AddBrokerInputSchema = object({
65317
65275
  });
65318
65276
  var AddBrokerResultSchema = object({ id: string() });
65319
65277
  var IdInputSchema = object({ id: string() });
65320
- var TestResultSchema$1 = discriminatedUnion("ok", [object({
65321
- ok: literal(true),
65322
- latencyMs: number()
65323
- }), object({
65324
- ok: literal(false),
65325
- error: string()
65326
- })]);
65327
65278
  var StartEmbeddedInputSchema = object({
65328
65279
  port: number().int().min(1).max(65535).default(1883),
65329
65280
  /** Allow anonymous connect (no username/password). Default: false. */
@@ -65346,9 +65297,6 @@ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnection
65346
65297
  }), method(IdInputSchema, _void(), {
65347
65298
  kind: "mutation",
65348
65299
  auth: "admin"
65349
- }), method(IdInputSchema, TestResultSchema$1, {
65350
- kind: "mutation",
65351
- auth: "admin"
65352
65300
  }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
65353
65301
  kind: "mutation",
65354
65302
  auth: "admin"
@@ -78463,7 +78411,7 @@ var TrainingProviderTypeSchema = object({
78463
78411
  label: string(),
78464
78412
  /** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
78465
78413
  iconAsset: string(),
78466
- /** Owning `training-provider` addon — collection selector for createEntry. */
78414
+ /** Owning `training-provider` addon. */
78467
78415
  addonId: string(),
78468
78416
  /**
78469
78417
  * Fields for one entry of this type (credentials + defaults). Absent on a
@@ -78508,36 +78456,6 @@ var TrainingProviderDescriptorSchema = object({
78508
78456
  */
78509
78457
  formValues: record(string(), unknown()).optional()
78510
78458
  });
78511
- /**
78512
- * Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
78513
- * An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
78514
- * `{ $secretClear: true }` clears it.
78515
- */
78516
- var TrainingProviderUpdateEntryInputSchema = object({
78517
- entryId: string().min(1),
78518
- name: string().min(1),
78519
- tokenId: string().optional(),
78520
- tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
78521
- environment: string().optional(),
78522
- gpuType: string().optional(),
78523
- region: string().optional(),
78524
- costCapUsd: number().nonnegative().optional(),
78525
- maxRuntimeSec: number().int().positive().optional(),
78526
- failedDatasetGraceHours: number().nonnegative().optional()
78527
- });
78528
- var TrainingCredentialCheckSchema = object({
78529
- ok: boolean(),
78530
- /** Workspace / account name, when the provider says. */
78531
- account: string().optional(),
78532
- reason: _enum([
78533
- "missing",
78534
- "invalid",
78535
- "forbidden",
78536
- "network",
78537
- "quota"
78538
- ]).optional(),
78539
- detail: string().optional()
78540
- });
78541
78459
  var ProviderJobStateSchema = _enum([
78542
78460
  "pending",
78543
78461
  "starting",
@@ -78585,22 +78503,6 @@ var TrainingLogLineSchema = object({
78585
78503
  text: string()
78586
78504
  });
78587
78505
  method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderTypeSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderEntrySchema).readonly(), { auth: "admin" }), method(object({
78588
- typeId: string().min(1),
78589
- name: string().min(1)
78590
- }), object({ entryId: string() }), {
78591
- kind: "mutation",
78592
- auth: "admin"
78593
- }), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
78594
- kind: "mutation",
78595
- auth: "admin"
78596
- }), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
78597
- kind: "mutation",
78598
- auth: "admin"
78599
- }), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
78600
- kind: "mutation",
78601
- auth: "admin",
78602
- timeoutMs: 3e4
78603
- }), method(object({
78604
78506
  entryId: string().min(1),
78605
78507
  gpuType: string(),
78606
78508
  region: string(),
@@ -83992,20 +83894,6 @@ Object.freeze({
83992
83894
  access: "view",
83993
83895
  role: "admin"
83994
83896
  },
83995
- "connectionTest.describeTest": {
83996
- capName: "connection-test",
83997
- capScope: "system",
83998
- addonId: null,
83999
- access: "view",
84000
- role: "admin"
84001
- },
84002
- "connectionTest.testSettings": {
84003
- capName: "connection-test",
84004
- capScope: "system",
84005
- addonId: null,
84006
- access: "create",
84007
- role: "admin"
84008
- },
84009
83897
  "connectivity.getStatus": {
84010
83898
  capName: "connectivity",
84011
83899
  capScope: "device",
@@ -86372,13 +86260,6 @@ Object.freeze({
86372
86260
  access: "view",
86373
86261
  role: "admin"
86374
86262
  },
86375
- "integrations.testConnection": {
86376
- capName: "integrations",
86377
- capScope: "system",
86378
- addonId: null,
86379
- access: "create",
86380
- role: "admin"
86381
- },
86382
86263
  "integrations.update": {
86383
86264
  capName: "integrations",
86384
86265
  capScope: "system",
@@ -87611,13 +87492,6 @@ Object.freeze({
87611
87492
  access: "create",
87612
87493
  role: "admin"
87613
87494
  },
87614
- "mqttBroker.testConnection": {
87615
- capName: "mqtt-broker",
87616
- capScope: "system",
87617
- addonId: null,
87618
- access: "create",
87619
- role: "admin"
87620
- },
87621
87495
  "nativeObjectDetection.getOptions": {
87622
87496
  capName: "native-object-detection",
87623
87497
  capScope: "device",
@@ -92448,20 +92322,6 @@ Object.freeze({
92448
92322
  access: "create",
92449
92323
  role: "admin"
92450
92324
  },
92451
- "trainingProvider.createEntry": {
92452
- capName: "training-provider",
92453
- capScope: "system",
92454
- addonId: null,
92455
- access: "create",
92456
- role: "admin"
92457
- },
92458
- "trainingProvider.deleteEntry": {
92459
- capName: "training-provider",
92460
- capScope: "system",
92461
- addonId: null,
92462
- access: "delete",
92463
- role: "admin"
92464
- },
92465
92325
  "trainingProvider.describe": {
92466
92326
  capName: "training-provider",
92467
92327
  capScope: "system",
@@ -92532,13 +92392,6 @@ Object.freeze({
92532
92392
  access: "create",
92533
92393
  role: "admin"
92534
92394
  },
92535
- "trainingProvider.updateEntry": {
92536
- capName: "training-provider",
92537
- capScope: "system",
92538
- addonId: null,
92539
- access: "create",
92540
- role: "admin"
92541
- },
92542
92395
  "trainingProvider.uploadDataset": {
92543
92396
  capName: "training-provider",
92544
92397
  capScope: "system",
@@ -92546,13 +92399,6 @@ Object.freeze({
92546
92399
  access: "create",
92547
92400
  role: "admin"
92548
92401
  },
92549
- "trainingProvider.validateCredentials": {
92550
- capName: "training-provider",
92551
- capScope: "system",
92552
- addonId: null,
92553
- access: "create",
92554
- role: "admin"
92555
- },
92556
92402
  "turnProvider.getTurnServers": {
92557
92403
  capName: "turn-provider",
92558
92404
  capScope: "system",
@@ -98570,4 +98416,4 @@ function keepBodyKeys(provider, keys) {
98570
98416
  };
98571
98417
  }
98572
98418
  //#endregion
98573
- export { EventCategory as $, waterHeaterCapability as A, _enum as B, numericSensorCapability as C, runningStateCapability as D, runningFromDishwasherOperation as E, defineEntryKind as F, object as G, boolean as H, entryBody as I, union as J, record as K, isSecretMarker as L, createEvent as M, customAction as N, switchCapability as O, defineCustomActions as P, errMsg as Q, isSecretMarkerLike as R, integrationInstancesCapability as S, quantityFromUnit as T, literal as U, array as V, number as W, DeviceRole as X, unknown as Y, DeviceType as Z, dishwasherCapability as _, BaseDevice as a, heatingValveCapability as b, RUNNING_STATE_COLD as c, batteryCapability as d, binaryCapability as f, deviceAdoptionCapability as g, consumablesCapability as h, testBoschInstance as i, RecordIntegrationInstances as j, temperatureSensorCapability as k, VENDOR_LOGIN_PROXY_PREFIX as l, connectionTestCapability as m, boschRowsPorts as n, BaseDeviceProvider as o, climateControlCapability as p, string as q, keepBodyKeys as r, DishwasherOperationStateSchema as s, boschRowsMigration as t, VendorLoginProxy as u, enumSensorCapability as v, powerMeterCapability as w, ingestReading as x, eventEmitterCapability as y, rowsDepsOver as z };
98419
+ export { RecordIntegrationInstances as A, array as B, powerMeterCapability as C, switchCapability as D, runningStateCapability as E, entryBody as F, record as G, literal as H, isSecretMarker as I, unknown as J, string as K, isSecretMarkerLike as L, customAction as M, defineCustomActions as N, temperatureSensorCapability as O, defineEntryKind as P, EventCategory as Q, rowsDepsOver as R, numericSensorCapability as S, runningFromDishwasherOperation as T, number as U, boolean as V, object as W, DeviceType as X, DeviceRole as Y, errMsg as Z, enumSensorCapability as _, BaseDevice as a, ingestReading as b, RUNNING_STATE_COLD as c, batteryCapability as d, binaryCapability as f, dishwasherCapability as g, deviceAdoptionCapability as h, testBoschInstance as i, createEvent as j, waterHeaterCapability as k, VENDOR_LOGIN_PROXY_PREFIX as l, consumablesCapability as m, boschRowsPorts as n, BaseDeviceProvider as o, climateControlCapability as p, union as q, keepBodyKeys as r, DishwasherOperationStateSchema as s, boschRowsMigration as t, VendorLoginProxy as u, eventEmitterCapability as v, quantityFromUnit as w, integrationInstancesCapability as x, heatingValveCapability as y, _enum as z };