@camstack/addon-provider-bosch 0.1.40 → 0.1.42

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-D7ZKm4nb.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).
@@ -6750,10 +6750,33 @@ function rowsDepsOver(api) {
6750
6750
  };
6751
6751
  }
6752
6752
  var PREFIX = "records/";
6753
+ /**
6754
+ * Cross-realm brand. An addon bundles its own copy of this module (D15) while
6755
+ * `ctx.records` is the HOST's door, so an error thrown by the host is an
6756
+ * instance of the host's classes, not of the addon's. The brand plus the code
6757
+ * make `instanceof` answer by identity of the failure, not of the copy.
6758
+ */
6759
+ var RECORDS_ERROR_BRAND = Symbol.for("camstack.records-error");
6760
+ function carriesBrand(value) {
6761
+ return typeof value === "object" && value !== null && Reflect.get(value, RECORDS_ERROR_BRAND) === true;
6762
+ }
6753
6763
  /** The base of every records failure. `detail` is the human part of the message. */
6754
6764
  var RecordsError = class extends Error {
6765
+ /** The code a subclass stands for; the base stands for every code. */
6766
+ static code = void 0;
6767
+ /**
6768
+ * `instanceof` across copies of this module: any branded error is a
6769
+ * `RecordsError`, and is an instance of a subclass only when its code is the
6770
+ * subclass's code. Same-copy answers are unchanged.
6771
+ */
6772
+ static [Symbol.hasInstance](value) {
6773
+ if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
6774
+ if (!carriesBrand(value)) return false;
6775
+ return this.code === void 0 || value.code === this.code;
6776
+ }
6755
6777
  code;
6756
6778
  detail;
6779
+ [RECORDS_ERROR_BRAND] = true;
6757
6780
  constructor(code, detail) {
6758
6781
  super(`${PREFIX}${code}: ${detail}`);
6759
6782
  this.name = "RecordsError";
@@ -6762,29 +6785,33 @@ var RecordsError = class extends Error {
6762
6785
  }
6763
6786
  };
6764
6787
  /** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
6765
- var RevConflictError = class extends RecordsError {
6788
+ var RevConflictError = class RevConflictError extends RecordsError {
6789
+ static code = "rev-conflict";
6766
6790
  constructor(detail) {
6767
- super("rev-conflict", detail);
6791
+ super(RevConflictError.code, detail);
6768
6792
  this.name = "RevConflictError";
6769
6793
  }
6770
6794
  };
6771
6795
  /** The keychain could not store or serve a value: explicit, never an empty string. */
6772
- var RecordSecretUnavailableError = class extends RecordsError {
6796
+ var RecordSecretUnavailableError = class RecordSecretUnavailableError extends RecordsError {
6797
+ static code = "secret-unavailable";
6773
6798
  constructor(detail) {
6774
- super("secret-unavailable", detail);
6799
+ super(RecordSecretUnavailableError.code, detail);
6775
6800
  this.name = "RecordSecretUnavailableError";
6776
6801
  }
6777
6802
  };
6778
- var InvalidRecordError = class extends RecordsError {
6803
+ var InvalidRecordError = class InvalidRecordError extends RecordsError {
6804
+ static code = "invalid-record";
6779
6805
  constructor(detail) {
6780
- super("invalid-record", detail);
6806
+ super(InvalidRecordError.code, detail);
6781
6807
  this.name = "InvalidRecordError";
6782
6808
  }
6783
6809
  };
6784
6810
  /** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
6785
- var DeclarationRefusedError = class extends RecordsError {
6811
+ var DeclarationRefusedError = class DeclarationRefusedError extends RecordsError {
6812
+ static code = "declaration-refused";
6786
6813
  constructor(detail) {
6787
- super("declaration-refused", detail);
6814
+ super(DeclarationRefusedError.code, detail);
6788
6815
  this.name = "DeclarationRefusedError";
6789
6816
  }
6790
6817
  };
@@ -9588,286 +9615,6 @@ function normalizeAddonInitResult(result) {
9588
9615
  if (Array.isArray(result)) return { providers: result };
9589
9616
  return result;
9590
9617
  }
9591
- /**
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`.
9595
- */
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()
9614
- });
9615
- function defOf(schema) {
9616
- const zodBag = Reflect.get(schema, "_zod");
9617
- if (typeof zodBag !== "object" || zodBag === null) return {};
9618
- const def = Reflect.get(zodBag, "def");
9619
- if (typeof def !== "object" || def === null) return {};
9620
- return def;
9621
- }
9622
- function declaredHere(schema) {
9623
- return globalRegistry.get(schema)?.secret === true;
9624
- }
9625
- /** Wrapper kinds whose `innerType` is the field itself. */
9626
- var WRAPPERS = new Set([
9627
- "optional",
9628
- "nullable",
9629
- "default",
9630
- "prefault",
9631
- "catch",
9632
- "readonly",
9633
- "nonoptional"
9634
- ]);
9635
- /**
9636
- * The schema layers that make up ONE field: the schema itself and every
9637
- * wrapper below it (optional / default / nullable / readonly / catch), plus
9638
- * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
9639
- * `.transform` on `in`).
9640
- */
9641
- function layersOf(schema) {
9642
- const out = [];
9643
- const queue = [schema];
9644
- while (queue.length > 0) {
9645
- const s = queue.shift();
9646
- if (!s || out.includes(s)) continue;
9647
- out.push(s);
9648
- const def = defOf(s);
9649
- if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
9650
- if (def.type === "pipe") {
9651
- if (def.in) queue.push(def.in);
9652
- if (def.out) queue.push(def.out);
9653
- }
9654
- }
9655
- return out;
9656
- }
9657
- /** True when any layer of this field declares `.meta({ secret: true })`. */
9658
- function isZodSecret(schema) {
9659
- return layersOf(schema).some(declaredHere);
9660
- }
9661
- /**
9662
- * Every declared secret path in a config schema: dotted keys, `[]` for "each
9663
- * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9664
- * map" (`applianceSecrets{}`). A declared
9665
- * object or array is reported as one path and not descended into — everything
9666
- * under it is secret.
9667
- */
9668
- function collectZodSecretPaths(schema, prefix = "") {
9669
- if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
9670
- const paths = [];
9671
- for (const layer of layersOf(schema)) {
9672
- const def = defOf(layer);
9673
- if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9674
- else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9675
- else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9676
- else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9677
- else if (def.type === "intersection") {
9678
- if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
9679
- if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
9680
- }
9681
- }
9682
- return [...new Set(paths)];
9683
- }
9684
- /**
9685
- * Record kinds (D881): the declaration an addon makes of one kind of fact, the
9686
- * Zod row schema that describes it, and the helper that turns the two into the
9687
- * JSON the engine stores.
9688
- *
9689
- * A kind is declared ONCE, next to the code that owns it, and the engine's
9690
- * reconciler creates its indexes - nothing creates DDL at runtime from a write.
9691
- * Everything the generic machinery needs (secret fields, the entry descriptor)
9692
- * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
9693
- * never a field name (D879).
9694
- */
9695
- /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
9696
- var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
9697
- var RecordIndexSchema = object({
9698
- name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
9699
- fields: array(RecordFieldPathSchema).min(1).max(4),
9700
- unique: boolean().optional()
9701
- }).strict();
9702
- var RecordRetentionSchema = union([
9703
- object({
9704
- ttlMs: number().int().positive(),
9705
- from: _enum(["at", "write"])
9706
- }).strict(),
9707
- object({
9708
- maxRows: number().int().positive().optional(),
9709
- maxRowsPerDevice: number().int().positive().optional()
9710
- }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
9711
- object({
9712
- owner: string().min(1),
9713
- reason: string().min(1)
9714
- }).strict(),
9715
- object({ none: string().min(1) }).strict()
9716
- ]);
9717
- /** The descriptor a generic entries UI reads (D881 "generic entries"). */
9718
- var EntryDescriptorSchema = object({
9719
- title: string().min(1),
9720
- /** The record body field shown as the entry's name. */
9721
- nameField: string().min(1),
9722
- /** JSON Schema of the entry body; `secret: true` marks a password field. */
9723
- fields: record(string(), unknown())
9724
- }).strict();
9725
- /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
9726
- var RecordKindDeclarationSchema = object({
9727
- database: _enum(RECORD_DATABASES).default("settings"),
9728
- class: _enum(RECORD_CLASSES),
9729
- writeTrigger: string().min(1),
9730
- device: _enum([
9731
- "required",
9732
- "optional",
9733
- "none"
9734
- ]).default("none"),
9735
- at: _enum(["required", "none"]).default("none"),
9736
- indexes: array(RecordIndexSchema).max(8).default([]),
9737
- retention: RecordRetentionSchema,
9738
- deviceRemoval: _enum(["delete", "none"]).default("none"),
9739
- maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
9740
- /** Derived from the Zod schema; never written by hand. */
9741
- secretPaths: array(string().min(1)).default([]),
9742
- /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
9743
- secretArrayKeys: record(string(), string()).default({}),
9744
- /** Kinds that must agree through `transact` sit in one file under one family name. */
9745
- transactFamily: string().min(1).optional(),
9746
- entry: EntryDescriptorSchema.optional()
9747
- }).strict();
9748
- /** The kind names: lower-case, dash separated, short enough for an index name. */
9749
- var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
9750
- /** Cross-field rules a declaration must satisfy. Throws {@link DeclarationRefusedError}. */
9751
- function assertValidDeclaration(name, decl) {
9752
- const refuse = (why) => {
9753
- throw new DeclarationRefusedError(`kind "${name}": ${why}`);
9754
- };
9755
- if (!RecordKindNameSchema.safeParse(name).success) refuse("the name must be lower-case-dashed");
9756
- const seen = /* @__PURE__ */ new Set();
9757
- for (const index of decl.indexes) {
9758
- if (seen.has(index.name)) refuse(`index name "${index.name}" is declared twice`);
9759
- seen.add(index.name);
9760
- }
9761
- const { retention } = decl;
9762
- const hasSecrets = decl.secretPaths.length > 0;
9763
- if ("ttlMs" in retention) {
9764
- if (retention.from === "at" && decl.at !== "required") refuse("a ttl from \"at\" needs at: \"required\"");
9765
- if (hasSecrets) refuse("a kind with secret fields cannot use engine TTL (its keychain values would be orphaned)");
9766
- }
9767
- if ("maxRows" in retention || "maxRowsPerDevice" in retention) {
9768
- if (retention.maxRowsPerDevice !== void 0 && decl.device !== "required") refuse("maxRowsPerDevice needs device: \"required\"");
9769
- if (decl.at !== "required") refuse("a row bound trims the oldest \"at\": it needs at: \"required\"");
9770
- if (hasSecrets) refuse("a kind with secret fields cannot use an engine row bound");
9771
- const bound = Math.max(retention.maxRows ?? 0, retention.maxRowsPerDevice ?? 0);
9772
- if (decl.database === "settings" && bound > 5e4) refuse(`a row bound above ${RECORD_SETTINGS_MAX_ROWS} belongs in database: "events" (a big delete on the settings thread stalls every configuration read)`);
9773
- }
9774
- if (decl.deviceRemoval === "delete" && decl.device === "none") refuse("deviceRemoval: \"delete\" needs a device: \"required\" or \"optional\"");
9775
- for (const path of decl.secretPaths) for (const prefix of secretArrayPrefixes(path)) if (decl.secretArrayKeys[prefix] === void 0) refuse(`secret path "${path}" crosses the array "${prefix}": declare its id key in secretArrayKeys`);
9776
- }
9777
- function bodyFieldsOf(schema) {
9778
- return { ...toJSONSchema(schema, {
9779
- unrepresentable: "any",
9780
- io: "input"
9781
- }) };
9782
- }
9783
- function isRecordLike(value) {
9784
- return typeof value === "object" && value !== null && !Array.isArray(value);
9785
- }
9786
- /** Does every branch of the body schema carry `field`? (A discriminated union has several.) */
9787
- function hasNameField(json, field) {
9788
- const branches = json["oneOf"] ?? json["anyOf"];
9789
- if (Array.isArray(branches)) return branches.length > 0 && branches.every((b) => isRecordLike(b) && hasNameField(b, field));
9790
- const properties = json["properties"];
9791
- return isRecordLike(properties) && Object.prototype.hasOwnProperty.call(properties, field);
9792
- }
9793
- /**
9794
- * Declare the kinds of a package. The keys are the kind names. The result is
9795
- * what `ctx.records.declare` sends to the engine and what `ctx.records.kind`
9796
- * types its handle from.
9797
- */
9798
- function defineRecordKinds(specs) {
9799
- const out = {};
9800
- for (const [name, spec] of Object.entries(specs)) {
9801
- const secretPaths = collectZodSecretPaths(spec.schema);
9802
- const nameField = spec.entry?.nameField ?? "name";
9803
- const fields = bodyFieldsOf(spec.schema);
9804
- if (spec.entry !== void 0 && !hasNameField(fields, nameField)) throw new DeclarationRefusedError(`kind "${name}": an entry kind's schema needs a "${nameField}" field`);
9805
- const parsed = RecordKindDeclarationSchema.parse({
9806
- database: spec.database,
9807
- class: spec.class,
9808
- writeTrigger: spec.writeTrigger,
9809
- device: spec.device,
9810
- at: spec.at,
9811
- indexes: spec.indexes === void 0 ? void 0 : [...spec.indexes],
9812
- retention: spec.retention,
9813
- deviceRemoval: spec.deviceRemoval,
9814
- maxDataBytes: spec.maxDataBytes,
9815
- secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9816
- secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9817
- transactFamily: spec.transactFamily,
9818
- entry: spec.entry === void 0 ? void 0 : {
9819
- title: spec.entry.title,
9820
- nameField,
9821
- fields
9822
- }
9823
- });
9824
- assertValidDeclaration(name, parsed);
9825
- out[name] = {
9826
- name,
9827
- schema: spec.schema,
9828
- declaration: parsed
9829
- };
9830
- }
9831
- return out;
9832
- }
9833
- /**
9834
- * Entry kinds (D881): every list of user-created entries (notification
9835
- * targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
9836
- * kind-specific body, and the admin UI lists/adds/edits/deletes all of them
9837
- * through the `record-entries` capability with password fields derived from
9838
- * the schema (`.meta({ secret: true })`).
9839
- */
9840
- /** The body of a plain entry: a name and the fields the kind adds. */
9841
- function entryBody(shape) {
9842
- return object({
9843
- name: string().min(1),
9844
- enabled: boolean().optional(),
9845
- ...shape
9846
- });
9847
- }
9848
- /**
9849
- * Declare one entry kind. An entry is configuration: never trimmed by the
9850
- * engine, never aged, belonging to no device.
9851
- */
9852
- function defineEntryKind(name, spec) {
9853
- const defined = defineRecordKinds({ [name]: {
9854
- schema: spec.schema,
9855
- class: "config",
9856
- writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9857
- retention: { none: "an entry goes only when the operator deletes it" },
9858
- ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9859
- ...spec.database === void 0 ? {} : { database: spec.database },
9860
- ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9861
- ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9862
- ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9863
- entry: {
9864
- title: spec.title,
9865
- ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
9866
- }
9867
- } })[name];
9868
- if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
9869
- return defined;
9870
- }
9871
9618
  object({
9872
9619
  field: string().min(1),
9873
9620
  on: literal("advance")
@@ -9960,6 +9707,286 @@ function optionalMethod(input, output, options) {
9960
9707
  function event(data) {
9961
9708
  return { data };
9962
9709
  }
9710
+ function defOf(schema) {
9711
+ const zodBag = Reflect.get(schema, "_zod");
9712
+ if (typeof zodBag !== "object" || zodBag === null) return {};
9713
+ const def = Reflect.get(zodBag, "def");
9714
+ if (typeof def !== "object" || def === null) return {};
9715
+ return def;
9716
+ }
9717
+ function declaredHere(schema) {
9718
+ return globalRegistry.get(schema)?.secret === true;
9719
+ }
9720
+ /** Wrapper kinds whose `innerType` is the field itself. */
9721
+ var WRAPPERS = new Set([
9722
+ "optional",
9723
+ "nullable",
9724
+ "default",
9725
+ "prefault",
9726
+ "catch",
9727
+ "readonly",
9728
+ "nonoptional"
9729
+ ]);
9730
+ /**
9731
+ * The schema layers that make up ONE field: the schema itself and every
9732
+ * wrapper below it (optional / default / nullable / readonly / catch), plus
9733
+ * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
9734
+ * `.transform` on `in`).
9735
+ */
9736
+ function layersOf(schema) {
9737
+ const out = [];
9738
+ const queue = [schema];
9739
+ while (queue.length > 0) {
9740
+ const s = queue.shift();
9741
+ if (!s || out.includes(s)) continue;
9742
+ out.push(s);
9743
+ const def = defOf(s);
9744
+ if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
9745
+ if (def.type === "pipe") {
9746
+ if (def.in) queue.push(def.in);
9747
+ if (def.out) queue.push(def.out);
9748
+ }
9749
+ }
9750
+ return out;
9751
+ }
9752
+ /** True when any layer of this field declares `.meta({ secret: true })`. */
9753
+ function isZodSecret(schema) {
9754
+ return layersOf(schema).some(declaredHere);
9755
+ }
9756
+ /**
9757
+ * Every declared secret path in a config schema: dotted keys, `[]` for "each
9758
+ * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9759
+ * map" (`applianceSecrets{}`). A declared
9760
+ * object or array is reported as one path and not descended into — everything
9761
+ * under it is secret.
9762
+ */
9763
+ function collectZodSecretPaths(schema, prefix = "") {
9764
+ if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
9765
+ const paths = [];
9766
+ for (const layer of layersOf(schema)) {
9767
+ const def = defOf(layer);
9768
+ if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9769
+ else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9770
+ else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9771
+ else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9772
+ else if (def.type === "intersection") {
9773
+ if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
9774
+ if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
9775
+ }
9776
+ }
9777
+ return [...new Set(paths)];
9778
+ }
9779
+ /**
9780
+ * Record kinds (D881): the declaration an addon makes of one kind of fact, the
9781
+ * Zod row schema that describes it, and the helper that turns the two into the
9782
+ * JSON the engine stores.
9783
+ *
9784
+ * A kind is declared ONCE, next to the code that owns it, and the engine's
9785
+ * reconciler creates its indexes - nothing creates DDL at runtime from a write.
9786
+ * Everything the generic machinery needs (secret fields, the entry descriptor)
9787
+ * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
9788
+ * never a field name (D879).
9789
+ */
9790
+ /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
9791
+ var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
9792
+ var RecordIndexSchema = object({
9793
+ name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
9794
+ fields: array(RecordFieldPathSchema).min(1).max(4),
9795
+ unique: boolean().optional()
9796
+ }).strict();
9797
+ var RecordRetentionSchema = union([
9798
+ object({
9799
+ ttlMs: number().int().positive(),
9800
+ from: _enum(["at", "write"])
9801
+ }).strict(),
9802
+ object({
9803
+ maxRows: number().int().positive().optional(),
9804
+ maxRowsPerDevice: number().int().positive().optional()
9805
+ }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
9806
+ object({
9807
+ owner: string().min(1),
9808
+ reason: string().min(1)
9809
+ }).strict(),
9810
+ object({ none: string().min(1) }).strict()
9811
+ ]);
9812
+ /** The descriptor a generic entries UI reads (D881 "generic entries"). */
9813
+ var EntryDescriptorSchema = object({
9814
+ title: string().min(1),
9815
+ /** The record body field shown as the entry's name. */
9816
+ nameField: string().min(1),
9817
+ /** JSON Schema of the entry body; `secret: true` marks a password field. */
9818
+ fields: record(string(), unknown())
9819
+ }).strict();
9820
+ /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
9821
+ var RecordKindDeclarationSchema = object({
9822
+ database: _enum(RECORD_DATABASES).default("settings"),
9823
+ class: _enum(RECORD_CLASSES),
9824
+ writeTrigger: string().min(1),
9825
+ device: _enum([
9826
+ "required",
9827
+ "optional",
9828
+ "none"
9829
+ ]).default("none"),
9830
+ at: _enum(["required", "none"]).default("none"),
9831
+ indexes: array(RecordIndexSchema).max(8).default([]),
9832
+ retention: RecordRetentionSchema,
9833
+ deviceRemoval: _enum(["delete", "none"]).default("none"),
9834
+ maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
9835
+ /** Derived from the Zod schema; never written by hand. */
9836
+ secretPaths: array(string().min(1)).default([]),
9837
+ /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
9838
+ secretArrayKeys: record(string(), string()).default({}),
9839
+ /** Kinds that must agree through `transact` sit in one file under one family name. */
9840
+ transactFamily: string().min(1).optional(),
9841
+ entry: EntryDescriptorSchema.optional()
9842
+ }).strict();
9843
+ /** The kind names: lower-case, dash separated, short enough for an index name. */
9844
+ var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
9845
+ /** Cross-field rules a declaration must satisfy. Throws {@link DeclarationRefusedError}. */
9846
+ function assertValidDeclaration(name, decl) {
9847
+ const refuse = (why) => {
9848
+ throw new DeclarationRefusedError(`kind "${name}": ${why}`);
9849
+ };
9850
+ if (!RecordKindNameSchema.safeParse(name).success) refuse("the name must be lower-case-dashed");
9851
+ const seen = /* @__PURE__ */ new Set();
9852
+ for (const index of decl.indexes) {
9853
+ if (seen.has(index.name)) refuse(`index name "${index.name}" is declared twice`);
9854
+ seen.add(index.name);
9855
+ }
9856
+ const { retention } = decl;
9857
+ const hasSecrets = decl.secretPaths.length > 0;
9858
+ if ("ttlMs" in retention) {
9859
+ if (retention.from === "at" && decl.at !== "required") refuse("a ttl from \"at\" needs at: \"required\"");
9860
+ if (hasSecrets) refuse("a kind with secret fields cannot use engine TTL (its keychain values would be orphaned)");
9861
+ }
9862
+ if ("maxRows" in retention || "maxRowsPerDevice" in retention) {
9863
+ if (retention.maxRowsPerDevice !== void 0 && decl.device !== "required") refuse("maxRowsPerDevice needs device: \"required\"");
9864
+ if (decl.at !== "required") refuse("a row bound trims the oldest \"at\": it needs at: \"required\"");
9865
+ if (hasSecrets) refuse("a kind with secret fields cannot use an engine row bound");
9866
+ const bound = Math.max(retention.maxRows ?? 0, retention.maxRowsPerDevice ?? 0);
9867
+ if (decl.database === "settings" && bound > 5e4) refuse(`a row bound above ${RECORD_SETTINGS_MAX_ROWS} belongs in database: "events" (a big delete on the settings thread stalls every configuration read)`);
9868
+ }
9869
+ if (decl.deviceRemoval === "delete" && decl.device === "none") refuse("deviceRemoval: \"delete\" needs a device: \"required\" or \"optional\"");
9870
+ for (const path of decl.secretPaths) for (const prefix of secretArrayPrefixes(path)) if (decl.secretArrayKeys[prefix] === void 0) refuse(`secret path "${path}" crosses the array "${prefix}": declare its id key in secretArrayKeys`);
9871
+ }
9872
+ function bodyFieldsOf(schema) {
9873
+ return { ...toJSONSchema(schema, {
9874
+ unrepresentable: "any",
9875
+ io: "input"
9876
+ }) };
9877
+ }
9878
+ function isRecordLike(value) {
9879
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9880
+ }
9881
+ /** Does every branch of the body schema carry `field`? (A discriminated union has several.) */
9882
+ function hasNameField(json, field) {
9883
+ const branches = json["oneOf"] ?? json["anyOf"];
9884
+ if (Array.isArray(branches)) return branches.length > 0 && branches.every((b) => isRecordLike(b) && hasNameField(b, field));
9885
+ const properties = json["properties"];
9886
+ return isRecordLike(properties) && Object.prototype.hasOwnProperty.call(properties, field);
9887
+ }
9888
+ /**
9889
+ * Declare the kinds of a package. The keys are the kind names. The result is
9890
+ * what `ctx.records.declare` sends to the engine and what `ctx.records.kind`
9891
+ * types its handle from.
9892
+ */
9893
+ function defineRecordKinds(specs) {
9894
+ const out = {};
9895
+ for (const [name, spec] of Object.entries(specs)) {
9896
+ const secretPaths = collectZodSecretPaths(spec.schema);
9897
+ const nameField = spec.entry?.nameField ?? "name";
9898
+ const fields = bodyFieldsOf(spec.schema);
9899
+ if (spec.entry !== void 0 && !hasNameField(fields, nameField)) throw new DeclarationRefusedError(`kind "${name}": an entry kind's schema needs a "${nameField}" field`);
9900
+ const parsed = RecordKindDeclarationSchema.parse({
9901
+ database: spec.database,
9902
+ class: spec.class,
9903
+ writeTrigger: spec.writeTrigger,
9904
+ device: spec.device,
9905
+ at: spec.at,
9906
+ indexes: spec.indexes === void 0 ? void 0 : [...spec.indexes],
9907
+ retention: spec.retention,
9908
+ deviceRemoval: spec.deviceRemoval,
9909
+ maxDataBytes: spec.maxDataBytes,
9910
+ secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9911
+ secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9912
+ transactFamily: spec.transactFamily,
9913
+ entry: spec.entry === void 0 ? void 0 : {
9914
+ title: spec.entry.title,
9915
+ nameField,
9916
+ fields
9917
+ }
9918
+ });
9919
+ assertValidDeclaration(name, parsed);
9920
+ out[name] = {
9921
+ name,
9922
+ schema: spec.schema,
9923
+ declaration: parsed
9924
+ };
9925
+ }
9926
+ return out;
9927
+ }
9928
+ /**
9929
+ * Entry kinds (D881): every list of user-created entries (notification
9930
+ * targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
9931
+ * kind-specific body, and the admin UI lists/adds/edits/deletes all of them
9932
+ * through the `record-entries` capability with password fields derived from
9933
+ * the schema (`.meta({ secret: true })`).
9934
+ */
9935
+ /** The body of a plain entry: a name and the fields the kind adds. */
9936
+ function entryBody(shape) {
9937
+ return object({
9938
+ name: string().min(1),
9939
+ enabled: boolean().optional(),
9940
+ ...shape
9941
+ });
9942
+ }
9943
+ /**
9944
+ * Declare one entry kind. An entry is configuration: never trimmed by the
9945
+ * engine, never aged, belonging to no device.
9946
+ */
9947
+ function defineEntryKind(name, spec) {
9948
+ const defined = defineRecordKinds({ [name]: {
9949
+ schema: spec.schema,
9950
+ class: "config",
9951
+ writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9952
+ retention: { none: "an entry goes only when the operator deletes it" },
9953
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9954
+ ...spec.database === void 0 ? {} : { database: spec.database },
9955
+ ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9956
+ ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9957
+ ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9958
+ entry: {
9959
+ title: spec.title,
9960
+ ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
9961
+ }
9962
+ } })[name];
9963
+ if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
9964
+ return defined;
9965
+ }
9966
+ /**
9967
+ * The share-view scope kinds a share-link token can carry. The hub mints
9968
+ * and validates them (`ShareViewGrantSchema` builds its
9969
+ * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
9970
+ */
9971
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
9972
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
9973
+ var PeerBytesTicketSchema = object({
9974
+ /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
9975
+ url: string().min(1),
9976
+ /**
9977
+ * The HOST node this URL means something on — the hub or a named agent,
9978
+ * never a runner. {@link AddonPeerBytes.open} compares it to its own and
9979
+ * refuses `cross-node` by name when they differ, without dialling.
9980
+ */
9981
+ hostNodeId: string().min(1),
9982
+ expiresAtMs: number().int().nonnegative(),
9983
+ /**
9984
+ * What the producer DECLARED the body to be, when it knows — `null` when it
9985
+ * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
9986
+ * must be able to tell "the producer did not say" from "the body is empty".
9987
+ */
9988
+ declaredBytes: number().int().nonnegative().nullable()
9989
+ });
9963
9990
  var READING_QUANTITIES = [
9964
9991
  "temperature",
9965
9992
  "pressure",
@@ -10560,7 +10587,6 @@ var CAP_NAMES = Object.freeze([
10560
10587
  "climate-control",
10561
10588
  "color",
10562
10589
  "composer",
10563
- "connection-test",
10564
10590
  "connectivity",
10565
10591
  "consumables",
10566
10592
  "contact",
@@ -12580,8 +12606,7 @@ Object.freeze({
12580
12606
  "offerArtifact": 3e5,
12581
12607
  "readResult": 3e5,
12582
12608
  "submitJob": 18e5,
12583
- "uploadDataset": 72e5,
12584
- "validateCredentials": 3e4
12609
+ "uploadDataset": 72e5
12585
12610
  }
12586
12611
  });
12587
12612
  object({
@@ -13936,6 +13961,432 @@ var IntegrationInstanceRefSchema = object({
13936
13961
  ]),
13937
13962
  candidateStableId: string().optional()
13938
13963
  });
13964
+ /**
13965
+ * notification-output — canonical, capability-gated notification delivery.
13966
+ *
13967
+ * Apprise-derived model (see
13968
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
13969
+ * callers emit ONE canonical `Notification`; each provider declares a
13970
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
13971
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
13972
+ * message to what the kind supports — callers never special-case a service.
13973
+ *
13974
+ * DESIGN DECISIONS (locked):
13975
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
13976
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
13977
+ * cap. Rationale: the admin UI needs one uniform surface across the
13978
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
13979
+ * alternative would fork the UI per addon and cannot host the
13980
+ * discovery→adopt flow.
13981
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
13982
+ * the generated cap-mount auto-`concatCollection`-fans them across every
13983
+ * registered provider (notifiers addon + HA addon) so one catalog is
13984
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
13985
+ * `addonId` the generated collection router extracts from the call input.
13986
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
13987
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
13988
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
13989
+ * base64 fallback needed.
13990
+ *
13991
+ * TODO (deferred, closed-set change — separate decision): add
13992
+ * `providerKind: 'notify'` so notification providers surface on the unified
13993
+ * admin "Integrations" page.
13994
+ */
13995
+ /**
13996
+ * Zentik-derived typed-media enum — the superset across every kind. Each
13997
+ * adapter picks what it supports and the degrade engine filters the rest.
13998
+ */
13999
+ var AttachmentMediaTypeSchema = _enum([
14000
+ "image",
14001
+ "video",
14002
+ "gif",
14003
+ "audio",
14004
+ "icon"
14005
+ ]);
14006
+ /**
14007
+ * A single attachment. Exactly one of `url` (remote source, most adapters
14008
+ * prefer this) or `bytes` (inline source; required for Pushover-style
14009
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
14010
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
14011
+ */
14012
+ var AttachmentSchema = object({
14013
+ mediaType: AttachmentMediaTypeSchema,
14014
+ url: string().optional(),
14015
+ bytes: _instanceof(Uint8Array).optional(),
14016
+ mime: string().optional(),
14017
+ name: string().optional()
14018
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
14019
+ var NotificationFormatSchema = _enum([
14020
+ "text",
14021
+ "markdown",
14022
+ "html"
14023
+ ]);
14024
+ /**
14025
+ * The CLOSED icon vocabulary an action button may use.
14026
+ *
14027
+ * A closed set, not a free string, and that is the whole point: an arbitrary
14028
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
14029
+ * notices — the same class of gap as a zone vocabulary nothing produced
14030
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
14031
+ * declares `actionIcons: false` and the degrade engine strips the field.
14032
+ *
14033
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
14034
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
14035
+ * differently.
14036
+ *
14037
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
14038
+ *
14039
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
14040
+ * the whole set onto its own renderer's vocabulary through a
14041
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
14042
+ * fallback, so adding a member here fails every adapter's build until someone
14043
+ * decides its glyph, which is the only place that decision can be made
14044
+ * honestly.
14045
+ *
14046
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
14047
+ * `disarm` straight through; iOS feeds that string to
14048
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
14049
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
14050
+ * mapping, and "the field is documented" is not "the value renders".
14051
+ *
14052
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
14053
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
14054
+ * so an addon that emits a token the running hub does not know does not lose an
14055
+ * icon — its whole `send` fails Zod validation and the notification never
14056
+ * arrives. Never emit a new token from an addon before the train carrying it.
14057
+ */
14058
+ var NotificationActionIconSchema = _enum([
14059
+ "acknowledge",
14060
+ "dismiss",
14061
+ "silence",
14062
+ "snooze",
14063
+ "view",
14064
+ "play",
14065
+ "open",
14066
+ "close",
14067
+ "lock",
14068
+ "unlock",
14069
+ "arm",
14070
+ "arm-home",
14071
+ "arm-away",
14072
+ "arm-night",
14073
+ "disarm",
14074
+ "light",
14075
+ "alert",
14076
+ "camera"
14077
+ ]);
14078
+ /** A single tap-through action button. */
14079
+ var NotificationActionSchema = object({
14080
+ id: string(),
14081
+ label: string(),
14082
+ url: string().optional(),
14083
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
14084
+ icon: NotificationActionIconSchema.optional(),
14085
+ /**
14086
+ * Renders in a warning style where the notifier supports it.
14087
+ *
14088
+ * A HINT, never a gate. The callback's authority is its token and nothing
14089
+ * else — see `notification-center/action-token.ts` for what that does and
14090
+ * does not buy.
14091
+ */
14092
+ destructive: boolean().optional(),
14093
+ /**
14094
+ * How the tap should REACH the url.
14095
+ *
14096
+ * `navigate` (absent, and every button authored before this field) opens it:
14097
+ * the phone leaves the notification and shows whatever the callback returns.
14098
+ * That is right for a button whose answer the operator wants to read.
14099
+ *
14100
+ * `background` fires it as a POST and stays put. It exists for the buttons
14101
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
14102
+ * an answer to the notification, and being thrown into a browser tab to
14103
+ * confirm it costs more attention than the notification did. A backend that
14104
+ * cannot do a background call renders it as an ordinary link (the adapters
14105
+ * fall back rather than dropping the button), so this is a preference, never
14106
+ * a requirement.
14107
+ */
14108
+ mode: _enum(["navigate", "background"]).optional()
14109
+ });
14110
+ /** A delivery parameter value: what a notifier declared it takes. */
14111
+ var DeliveryParamValueSchema = union([
14112
+ string(),
14113
+ number(),
14114
+ boolean()
14115
+ ]);
14116
+ /**
14117
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
14118
+ * addresses a target of that kind may set it; a kind that declares nothing is
14119
+ * never handed one. The admin UI derives its control from this descriptor —
14120
+ * no editor knows a service by name.
14121
+ *
14122
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
14123
+ * run on the addon cap path).
14124
+ */
14125
+ var DeliveryParamSchema = object({
14126
+ key: string().min(1),
14127
+ label: string(),
14128
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
14129
+ labelKey: string().optional(),
14130
+ type: _enum([
14131
+ "enum",
14132
+ "number",
14133
+ "boolean",
14134
+ "string"
14135
+ ]),
14136
+ /** `enum` only: the closed set of accepted values. */
14137
+ options: array(object({
14138
+ value: string(),
14139
+ label: string()
14140
+ })).optional(),
14141
+ /** `number` only. */
14142
+ min: number().optional(),
14143
+ max: number().optional(),
14144
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
14145
+ default: DeliveryParamValueSchema.optional()
14146
+ });
14147
+ /**
14148
+ * The canonical notification. `body` is the only hard field (Apprise model).
14149
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
14150
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
14151
+ * the adapter maps this ordinal onto its native level. `level?` is an
14152
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
14153
+ * `priority` for that one target.
14154
+ */
14155
+ var NotificationSchema = object({
14156
+ body: string(),
14157
+ title: string().optional(),
14158
+ format: NotificationFormatSchema.default("text"),
14159
+ priority: number().int().min(1).max(5).default(3),
14160
+ level: string().optional(),
14161
+ attachments: array(AttachmentSchema).optional(),
14162
+ clickUrl: string().optional(),
14163
+ actions: array(NotificationActionSchema).optional(),
14164
+ sound: string().optional(),
14165
+ ttl: number().optional(),
14166
+ tag: string().optional(),
14167
+ deviceId: number().optional(),
14168
+ eventId: string().optional(),
14169
+ metadata: record(string(), unknown()).optional(),
14170
+ /**
14171
+ * Values for the parameters the target's kind DECLARES in
14172
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
14173
+ * declaration by the sender; a notifier reads only the keys it declared.
14174
+ */
14175
+ delivery: record(string(), DeliveryParamValueSchema).optional()
14176
+ });
14177
+ /** One declared native severity/priority level for a kind. */
14178
+ var TargetKindLevelSchema = object({
14179
+ id: string(),
14180
+ label: string(),
14181
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
14182
+ ordinal: number().int().min(1).max(5).nullable(),
14183
+ flags: object({
14184
+ critical: boolean().optional(),
14185
+ silent: boolean().optional(),
14186
+ noPush: boolean().optional()
14187
+ }).optional(),
14188
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
14189
+ requires: array(string()).optional(),
14190
+ description: string().optional()
14191
+ });
14192
+ /** The full capability block consulted before dispatch. */
14193
+ var TargetKindCapsSchema = object({
14194
+ attachments: object({
14195
+ mediaTypes: array(AttachmentMediaTypeSchema),
14196
+ mode: _enum([
14197
+ "url",
14198
+ "bytes",
14199
+ "both"
14200
+ ]),
14201
+ max: number().int().nonnegative(),
14202
+ maxBytes: number().int().positive().optional()
14203
+ }),
14204
+ /** Max action buttons (0 = none). */
14205
+ actions: number().int().nonnegative(),
14206
+ /**
14207
+ * Whether this kind renders a per-action ICON.
14208
+ *
14209
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
14210
+ * run on the addon cap path — three production failures in one day taught
14211
+ * this repo that once. Absent is read as false by the degrade engine, which
14212
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
14213
+ * assumed and dropped costs the operator's trust in the field.
14214
+ */
14215
+ actionIcons: boolean().optional(),
14216
+ levels: array(TargetKindLevelSchema),
14217
+ format: array(NotificationFormatSchema),
14218
+ clickUrl: boolean(),
14219
+ sound: boolean(),
14220
+ ttl: boolean(),
14221
+ bodyMaxLen: number().int().positive()
14222
+ });
14223
+ /**
14224
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
14225
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
14226
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
14227
+ * the union is large and not meant for runtime validation here; the exported
14228
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14229
+ */
14230
+ var ConfigSchemaPassthrough$4 = unknown();
14231
+ var TargetKindSchema = object({
14232
+ kind: string(),
14233
+ label: string(),
14234
+ icon: string(),
14235
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
14236
+ addonId: string(),
14237
+ /**
14238
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
14239
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
14240
+ * when the addon bundles no icon for that kind — the client then falls back
14241
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
14242
+ *
14243
+ * Root-relative on purpose: it resolves against whatever origin serves a web
14244
+ * client, and a native client joins it onto its own hub base.
14245
+ *
14246
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
14247
+ * field that survived only because the runtime cap-router forwards provider
14248
+ * output verbatim — so every consumer had to re-declare it by hand to stop
14249
+ * its own Zod parse from stripping it, and the whole arrangement would have
14250
+ * broken silently the moment output validation was tightened anywhere.
14251
+ */
14252
+ iconUrl: string().optional(),
14253
+ /**
14254
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
14255
+ *
14256
+ * The server knows this and therefore says it, because the client cannot
14257
+ * safely guess: a React-Native client renders SVG and raster through two
14258
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
14259
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
14260
+ * placeholder glyph for every vector icon while the web build looked fine.
14261
+ *
14262
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
14263
+ * not been updated — a client that cannot determine the type should prefer
14264
+ * its raster path, which is the safe default for an unknown image.
14265
+ */
14266
+ iconMediaType: string().optional(),
14267
+ configSchema: ConfigSchemaPassthrough$4,
14268
+ /**
14269
+ * The config keys that decide WHERE this kind's secrets are sent (server,
14270
+ * base URL, instance, chat, recipient). A save that changes any of them
14271
+ * while leaving a secret redacted is refused by the provider (D634), so an
14272
+ * editor uses this to ask for the secret on exactly those edits instead of
14273
+ * on every edit.
14274
+ *
14275
+ * Optional: absent from a provider that holds no secret (the Home Assistant
14276
+ * kinds) and from a provider older than this field — an editor that finds
14277
+ * it absent must assume ANY edit may need the secret.
14278
+ */
14279
+ destinationKeys: array(string()).optional(),
14280
+ supportsDiscovery: boolean(),
14281
+ /**
14282
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
14283
+ * Absent = none; the rule editor then offers nothing.
14284
+ */
14285
+ deliveryParams: array(DeliveryParamSchema).optional(),
14286
+ caps: TargetKindCapsSchema
14287
+ });
14288
+ /**
14289
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
14290
+ * (return a presence marker only) when serving `listTargets` — never
14291
+ * round-trip a stored secret to the UI.
14292
+ */
14293
+ var TargetSchema = object({
14294
+ id: string(),
14295
+ name: string(),
14296
+ kind: string(),
14297
+ addonId: string(),
14298
+ enabled: boolean(),
14299
+ config: record(string(), unknown())
14300
+ });
14301
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
14302
+ var DiscoveredTargetSchema = object({
14303
+ kind: string(),
14304
+ suggestedName: string(),
14305
+ config: record(string(), unknown())
14306
+ });
14307
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
14308
+ var RenderedAsSchema = object({
14309
+ level: string(),
14310
+ format: NotificationFormatSchema,
14311
+ attachmentsSent: number().int().nonnegative(),
14312
+ actionsSent: number().int().nonnegative(),
14313
+ truncated: boolean(),
14314
+ dropped: array(string())
14315
+ });
14316
+ var SendResultSchema = object({
14317
+ success: boolean(),
14318
+ error: string().optional(),
14319
+ renderedAs: RenderedAsSchema.optional()
14320
+ });
14321
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
14322
+ var TestResultSchema = SendResultSchema;
14323
+ /**
14324
+ * Pre-save credential probe against a partial (unsaved) target config.
14325
+ * `resolvedUrl` never includes secrets (token stripped from path).
14326
+ */
14327
+ var ProbeTargetConfigResultSchema = object({
14328
+ ok: boolean(),
14329
+ resolvedUrl: string(),
14330
+ httpStatus: number().int().optional(),
14331
+ stateInstance: string().optional(),
14332
+ error: string().optional()
14333
+ });
14334
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
14335
+ kind: string(),
14336
+ config: record(string(), unknown()).optional(),
14337
+ /**
14338
+ * When editing / duplicating, the blank password field never round-trips
14339
+ * the stored token. Pass the source target id so the provider can restore
14340
+ * secrets server-side before dialling (same merge as upsert).
14341
+ */
14342
+ targetId: string().optional()
14343
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
14344
+ kind: string(),
14345
+ config: record(string(), unknown()),
14346
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
14347
+ targetId: string().optional()
14348
+ }), ProbeTargetConfigResultSchema, {
14349
+ kind: "mutation",
14350
+ auth: "admin"
14351
+ }), method(object({
14352
+ targetId: string(),
14353
+ notification: NotificationSchema
14354
+ }), SendResultSchema, {
14355
+ kind: "mutation",
14356
+ auth: "admin"
14357
+ }), method(object({
14358
+ targetId: string(),
14359
+ sample: NotificationSchema.optional()
14360
+ }), TestResultSchema, {
14361
+ kind: "mutation",
14362
+ auth: "admin"
14363
+ }), method(object({
14364
+ target: TargetSchema,
14365
+ /**
14366
+ * Duplicate flow: create a NEW target id while copying secrets from an
14367
+ * existing row. The UI never holds the stored token; blank / redacted
14368
+ * password fields are restored from this source (D634 still applies if
14369
+ * destinations diverge without a re-typed secret).
14370
+ */
14371
+ cloneSecretsFrom: string().optional()
14372
+ }), TargetSchema, {
14373
+ kind: "mutation",
14374
+ auth: "admin"
14375
+ }), method(object({ targetId: string() }), _void(), {
14376
+ kind: "mutation",
14377
+ auth: "admin"
14378
+ }), method(object({
14379
+ targetId: string(),
14380
+ enabled: boolean()
14381
+ }), _void(), {
14382
+ kind: "mutation",
14383
+ auth: "admin"
14384
+ });
14385
+ looseObject({
14386
+ name: string().min(1),
14387
+ enabled: boolean().optional()
14388
+ });
14389
+ TargetSchema.omit({ id: true });
13939
14390
  var EncodeProfileSchema = object({
13940
14391
  video: object({
13941
14392
  codec: _enum([
@@ -22277,7 +22728,6 @@ var SYSTEM_CAP_VOCABULARY = {
22277
22728
  "calendar-source": system(INTERNAL),
22278
22729
  calendars: system("calendars feed schedules and conditions, not device actions"),
22279
22730
  composer: system("device composition — an authoring tool, not an action"),
22280
- "connection-test": system(INTERNAL),
22281
22731
  "core-blocks": system(INTERNAL),
22282
22732
  "custom-model-registry": system(ML),
22283
22733
  "data-store-provider": system(INTERNAL),
@@ -44333,427 +44783,6 @@ var UnitWarningSchema = object({
44333
44783
  assumedUnit: string(),
44334
44784
  message: string()
44335
44785
  });
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(),
44542
- /**
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.
44546
- */
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()
44563
- });
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(),
44578
- /**
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.
44586
- */
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()
44594
- });
44595
- /**
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`.
44601
- */
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
44659
- });
44660
- /**
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.
44664
- */
44665
- var TargetSchema = object({
44666
- id: string(),
44667
- name: string(),
44668
- kind: string(),
44669
- addonId: string(),
44670
- enabled: boolean(),
44671
- config: record(string(), unknown())
44672
- });
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())
44678
- });
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())
44687
- });
44688
- var SendResultSchema = object({
44689
- success: boolean(),
44690
- error: string().optional(),
44691
- renderedAs: RenderedAsSchema.optional()
44692
- });
44693
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44694
- var TestResultSchema = SendResultSchema;
44695
- /**
44696
- * Pre-save credential probe against a partial (unsaved) target config.
44697
- * `resolvedUrl` never includes secrets (token stripped from path).
44698
- */
44699
- var ProbeTargetConfigResultSchema = object({
44700
- ok: boolean(),
44701
- resolvedUrl: string(),
44702
- httpStatus: number().int().optional(),
44703
- stateInstance: string().optional(),
44704
- error: string().optional()
44705
- });
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"
44756
- });
44757
44786
  _enum([
44758
44787
  "automation",
44759
44788
  "action",
@@ -49293,45 +49322,6 @@ method(object({
49293
49322
  kind: "mutation",
49294
49323
  auth: "admin"
49295
49324
  }), 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
49325
  /**
49336
49326
  * Upstream-system connectivity sensor — distinct from `device-status`,
49337
49327
  * which is the kernel-managed online/offline flag for the device's
@@ -56485,10 +56475,9 @@ var AvailableIntegrationTypeSchema = object({
56485
56475
  * locations" checkbox. Provider-declared in the addon manifest. */
56486
56476
  supportsLocationImport: boolean(),
56487
56477
  /**
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.
56478
+ * True when this integration's instances declare a Test (`integration-instances.describe().canTest`,
56479
+ * D1016). An integration that cannot be tested must say so up front rather than offering a
56480
+ * button that always answers the same nonsense.
56492
56481
  */
56493
56482
  canTest: boolean(),
56494
56483
  existingInstances: array(object({
@@ -56542,7 +56531,9 @@ var CreateIntegrationInputSchema = object({
56542
56531
  info: record(string(), unknown()).optional(),
56543
56532
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56544
56533
  originStableId: string().optional(),
56545
- settings: record(string(), unknown()).optional()
56534
+ settings: record(string(), unknown()).optional(),
56535
+ /** The instance kind being created, for a provider with several; omitted = its only testable kind. */
56536
+ kind: string().min(1).optional()
56546
56537
  });
56547
56538
  var UpdateIntegrationInputSchema = object({
56548
56539
  id: string(),
@@ -56568,13 +56559,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56568
56559
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56569
56560
  kind: "mutation",
56570
56561
  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
- });
56562
+ }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" });
56578
56563
  /**
56579
56564
  * `integration-instances` - the connection of an addon that holds ONE or SEVERAL
56580
56565
  * (an account, a relay, a destination) as typed records (D985, D986). The addon's
@@ -65317,13 +65302,6 @@ var AddBrokerInputSchema = object({
65317
65302
  });
65318
65303
  var AddBrokerResultSchema = object({ id: string() });
65319
65304
  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
65305
  var StartEmbeddedInputSchema = object({
65328
65306
  port: number().int().min(1).max(65535).default(1883),
65329
65307
  /** Allow anonymous connect (no username/password). Default: false. */
@@ -65346,9 +65324,6 @@ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnection
65346
65324
  }), method(IdInputSchema, _void(), {
65347
65325
  kind: "mutation",
65348
65326
  auth: "admin"
65349
- }), method(IdInputSchema, TestResultSchema$1, {
65350
- kind: "mutation",
65351
- auth: "admin"
65352
65327
  }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
65353
65328
  kind: "mutation",
65354
65329
  auth: "admin"
@@ -78463,7 +78438,7 @@ var TrainingProviderTypeSchema = object({
78463
78438
  label: string(),
78464
78439
  /** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
78465
78440
  iconAsset: string(),
78466
- /** Owning `training-provider` addon — collection selector for createEntry. */
78441
+ /** Owning `training-provider` addon. */
78467
78442
  addonId: string(),
78468
78443
  /**
78469
78444
  * Fields for one entry of this type (credentials + defaults). Absent on a
@@ -78508,36 +78483,6 @@ var TrainingProviderDescriptorSchema = object({
78508
78483
  */
78509
78484
  formValues: record(string(), unknown()).optional()
78510
78485
  });
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
78486
  var ProviderJobStateSchema = _enum([
78542
78487
  "pending",
78543
78488
  "starting",
@@ -78585,22 +78530,6 @@ var TrainingLogLineSchema = object({
78585
78530
  text: string()
78586
78531
  });
78587
78532
  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
78533
  entryId: string().min(1),
78605
78534
  gpuType: string(),
78606
78535
  region: string(),
@@ -83992,20 +83921,6 @@ Object.freeze({
83992
83921
  access: "view",
83993
83922
  role: "admin"
83994
83923
  },
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
83924
  "connectivity.getStatus": {
84010
83925
  capName: "connectivity",
84011
83926
  capScope: "device",
@@ -86372,13 +86287,6 @@ Object.freeze({
86372
86287
  access: "view",
86373
86288
  role: "admin"
86374
86289
  },
86375
- "integrations.testConnection": {
86376
- capName: "integrations",
86377
- capScope: "system",
86378
- addonId: null,
86379
- access: "create",
86380
- role: "admin"
86381
- },
86382
86290
  "integrations.update": {
86383
86291
  capName: "integrations",
86384
86292
  capScope: "system",
@@ -87611,13 +87519,6 @@ Object.freeze({
87611
87519
  access: "create",
87612
87520
  role: "admin"
87613
87521
  },
87614
- "mqttBroker.testConnection": {
87615
- capName: "mqtt-broker",
87616
- capScope: "system",
87617
- addonId: null,
87618
- access: "create",
87619
- role: "admin"
87620
- },
87621
87522
  "nativeObjectDetection.getOptions": {
87622
87523
  capName: "native-object-detection",
87623
87524
  capScope: "device",
@@ -92448,20 +92349,6 @@ Object.freeze({
92448
92349
  access: "create",
92449
92350
  role: "admin"
92450
92351
  },
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
92352
  "trainingProvider.describe": {
92466
92353
  capName: "training-provider",
92467
92354
  capScope: "system",
@@ -92532,13 +92419,6 @@ Object.freeze({
92532
92419
  access: "create",
92533
92420
  role: "admin"
92534
92421
  },
92535
- "trainingProvider.updateEntry": {
92536
- capName: "training-provider",
92537
- capScope: "system",
92538
- addonId: null,
92539
- access: "create",
92540
- role: "admin"
92541
- },
92542
92422
  "trainingProvider.uploadDataset": {
92543
92423
  capName: "training-provider",
92544
92424
  capScope: "system",
@@ -92546,13 +92426,6 @@ Object.freeze({
92546
92426
  access: "create",
92547
92427
  role: "admin"
92548
92428
  },
92549
- "trainingProvider.validateCredentials": {
92550
- capName: "training-provider",
92551
- capScope: "system",
92552
- addonId: null,
92553
- access: "create",
92554
- role: "admin"
92555
- },
92556
92429
  "turnProvider.getTurnServers": {
92557
92430
  capName: "turn-provider",
92558
92431
  capScope: "system",
@@ -98678,12 +98551,6 @@ Object.defineProperty(exports, "climateControlCapability", {
98678
98551
  return climateControlCapability;
98679
98552
  }
98680
98553
  });
98681
- Object.defineProperty(exports, "connectionTestCapability", {
98682
- enumerable: true,
98683
- get: function() {
98684
- return connectionTestCapability;
98685
- }
98686
- });
98687
98554
  Object.defineProperty(exports, "consumablesCapability", {
98688
98555
  enumerable: true,
98689
98556
  get: function() {