@camstack/addon-provider-enpal 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.mjs CHANGED
@@ -5980,7 +5980,7 @@ var ZodIssueCode = {
5980
5980
  var ZodFirstPartyTypeKind;
5981
5981
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5982
5982
  //#endregion
5983
- //#region ../types/dist/sleep-CmjWurK5.mjs
5983
+ //#region ../types/dist/sleep-D7ZKm4nb.mjs
5984
5984
  /**
5985
5985
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5986
5986
  * window to float samples (D455).
@@ -6749,10 +6749,33 @@ function rowsDepsOver(api) {
6749
6749
  };
6750
6750
  }
6751
6751
  var PREFIX = "records/";
6752
+ /**
6753
+ * Cross-realm brand. An addon bundles its own copy of this module (D15) while
6754
+ * `ctx.records` is the HOST's door, so an error thrown by the host is an
6755
+ * instance of the host's classes, not of the addon's. The brand plus the code
6756
+ * make `instanceof` answer by identity of the failure, not of the copy.
6757
+ */
6758
+ var RECORDS_ERROR_BRAND = Symbol.for("camstack.records-error");
6759
+ function carriesBrand(value) {
6760
+ return typeof value === "object" && value !== null && Reflect.get(value, RECORDS_ERROR_BRAND) === true;
6761
+ }
6752
6762
  /** The base of every records failure. `detail` is the human part of the message. */
6753
6763
  var RecordsError = class extends Error {
6764
+ /** The code a subclass stands for; the base stands for every code. */
6765
+ static code = void 0;
6766
+ /**
6767
+ * `instanceof` across copies of this module: any branded error is a
6768
+ * `RecordsError`, and is an instance of a subclass only when its code is the
6769
+ * subclass's code. Same-copy answers are unchanged.
6770
+ */
6771
+ static [Symbol.hasInstance](value) {
6772
+ if (Function.prototype[Symbol.hasInstance].call(this, value)) return true;
6773
+ if (!carriesBrand(value)) return false;
6774
+ return this.code === void 0 || value.code === this.code;
6775
+ }
6754
6776
  code;
6755
6777
  detail;
6778
+ [RECORDS_ERROR_BRAND] = true;
6756
6779
  constructor(code, detail) {
6757
6780
  super(`${PREFIX}${code}: ${detail}`);
6758
6781
  this.name = "RecordsError";
@@ -6761,29 +6784,33 @@ var RecordsError = class extends Error {
6761
6784
  }
6762
6785
  };
6763
6786
  /** A write guarded by `ifRev` found another revision (or an existing row for `ifRev: 0`). */
6764
- var RevConflictError = class extends RecordsError {
6787
+ var RevConflictError = class RevConflictError extends RecordsError {
6788
+ static code = "rev-conflict";
6765
6789
  constructor(detail) {
6766
- super("rev-conflict", detail);
6790
+ super(RevConflictError.code, detail);
6767
6791
  this.name = "RevConflictError";
6768
6792
  }
6769
6793
  };
6770
6794
  /** The keychain could not store or serve a value: explicit, never an empty string. */
6771
- var RecordSecretUnavailableError = class extends RecordsError {
6795
+ var RecordSecretUnavailableError = class RecordSecretUnavailableError extends RecordsError {
6796
+ static code = "secret-unavailable";
6772
6797
  constructor(detail) {
6773
- super("secret-unavailable", detail);
6798
+ super(RecordSecretUnavailableError.code, detail);
6774
6799
  this.name = "RecordSecretUnavailableError";
6775
6800
  }
6776
6801
  };
6777
- var InvalidRecordError = class extends RecordsError {
6802
+ var InvalidRecordError = class InvalidRecordError extends RecordsError {
6803
+ static code = "invalid-record";
6778
6804
  constructor(detail) {
6779
- super("invalid-record", detail);
6805
+ super(InvalidRecordError.code, detail);
6780
6806
  this.name = "InvalidRecordError";
6781
6807
  }
6782
6808
  };
6783
6809
  /** A kind declaration the engine refuses (non-additive change, bad shape, wrong file). */
6784
- var DeclarationRefusedError = class extends RecordsError {
6810
+ var DeclarationRefusedError = class DeclarationRefusedError extends RecordsError {
6811
+ static code = "declaration-refused";
6785
6812
  constructor(detail) {
6786
- super("declaration-refused", detail);
6813
+ super(DeclarationRefusedError.code, detail);
6787
6814
  this.name = "DeclarationRefusedError";
6788
6815
  }
6789
6816
  };
@@ -9587,286 +9614,6 @@ function normalizeAddonInitResult(result) {
9587
9614
  if (Array.isArray(result)) return { providers: result };
9588
9615
  return result;
9589
9616
  }
9590
- /**
9591
- * The share-view scope kinds a share-link token can carry. The hub mints
9592
- * and validates them (`ShareViewGrantSchema` builds its
9593
- * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
9594
- */
9595
- var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
9596
- /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
9597
- var PeerBytesTicketSchema = object({
9598
- /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
9599
- url: string().min(1),
9600
- /**
9601
- * The HOST node this URL means something on — the hub or a named agent,
9602
- * never a runner. {@link AddonPeerBytes.open} compares it to its own and
9603
- * refuses `cross-node` by name when they differ, without dialling.
9604
- */
9605
- hostNodeId: string().min(1),
9606
- expiresAtMs: number().int().nonnegative(),
9607
- /**
9608
- * What the producer DECLARED the body to be, when it knows — `null` when it
9609
- * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
9610
- * must be able to tell "the producer did not say" from "the body is empty".
9611
- */
9612
- declaredBytes: number().int().nonnegative().nullable()
9613
- });
9614
- function defOf(schema) {
9615
- const zodBag = Reflect.get(schema, "_zod");
9616
- if (typeof zodBag !== "object" || zodBag === null) return {};
9617
- const def = Reflect.get(zodBag, "def");
9618
- if (typeof def !== "object" || def === null) return {};
9619
- return def;
9620
- }
9621
- function declaredHere(schema) {
9622
- return globalRegistry.get(schema)?.secret === true;
9623
- }
9624
- /** Wrapper kinds whose `innerType` is the field itself. */
9625
- var WRAPPERS = new Set([
9626
- "optional",
9627
- "nullable",
9628
- "default",
9629
- "prefault",
9630
- "catch",
9631
- "readonly",
9632
- "nonoptional"
9633
- ]);
9634
- /**
9635
- * The schema layers that make up ONE field: the schema itself and every
9636
- * wrapper below it (optional / default / nullable / readonly / catch), plus
9637
- * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
9638
- * `.transform` on `in`).
9639
- */
9640
- function layersOf(schema) {
9641
- const out = [];
9642
- const queue = [schema];
9643
- while (queue.length > 0) {
9644
- const s = queue.shift();
9645
- if (!s || out.includes(s)) continue;
9646
- out.push(s);
9647
- const def = defOf(s);
9648
- if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
9649
- if (def.type === "pipe") {
9650
- if (def.in) queue.push(def.in);
9651
- if (def.out) queue.push(def.out);
9652
- }
9653
- }
9654
- return out;
9655
- }
9656
- /** True when any layer of this field declares `.meta({ secret: true })`. */
9657
- function isZodSecret(schema) {
9658
- return layersOf(schema).some(declaredHere);
9659
- }
9660
- /**
9661
- * Every declared secret path in a config schema: dotted keys, `[]` for "each
9662
- * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9663
- * map" (`applianceSecrets{}`). A declared
9664
- * object or array is reported as one path and not descended into — everything
9665
- * under it is secret.
9666
- */
9667
- function collectZodSecretPaths(schema, prefix = "") {
9668
- if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
9669
- const paths = [];
9670
- for (const layer of layersOf(schema)) {
9671
- const def = defOf(layer);
9672
- if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9673
- else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9674
- else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9675
- else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9676
- else if (def.type === "intersection") {
9677
- if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
9678
- if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
9679
- }
9680
- }
9681
- return [...new Set(paths)];
9682
- }
9683
- /**
9684
- * Record kinds (D881): the declaration an addon makes of one kind of fact, the
9685
- * Zod row schema that describes it, and the helper that turns the two into the
9686
- * JSON the engine stores.
9687
- *
9688
- * A kind is declared ONCE, next to the code that owns it, and the engine's
9689
- * reconciler creates its indexes - nothing creates DDL at runtime from a write.
9690
- * Everything the generic machinery needs (secret fields, the entry descriptor)
9691
- * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
9692
- * never a field name (D879).
9693
- */
9694
- /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
9695
- var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
9696
- var RecordIndexSchema = object({
9697
- name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
9698
- fields: array(RecordFieldPathSchema).min(1).max(4),
9699
- unique: boolean().optional()
9700
- }).strict();
9701
- var RecordRetentionSchema = union([
9702
- object({
9703
- ttlMs: number().int().positive(),
9704
- from: _enum(["at", "write"])
9705
- }).strict(),
9706
- object({
9707
- maxRows: number().int().positive().optional(),
9708
- maxRowsPerDevice: number().int().positive().optional()
9709
- }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
9710
- object({
9711
- owner: string().min(1),
9712
- reason: string().min(1)
9713
- }).strict(),
9714
- object({ none: string().min(1) }).strict()
9715
- ]);
9716
- /** The descriptor a generic entries UI reads (D881 "generic entries"). */
9717
- var EntryDescriptorSchema = object({
9718
- title: string().min(1),
9719
- /** The record body field shown as the entry's name. */
9720
- nameField: string().min(1),
9721
- /** JSON Schema of the entry body; `secret: true` marks a password field. */
9722
- fields: record(string(), unknown())
9723
- }).strict();
9724
- /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
9725
- var RecordKindDeclarationSchema = object({
9726
- database: _enum(RECORD_DATABASES).default("settings"),
9727
- class: _enum(RECORD_CLASSES),
9728
- writeTrigger: string().min(1),
9729
- device: _enum([
9730
- "required",
9731
- "optional",
9732
- "none"
9733
- ]).default("none"),
9734
- at: _enum(["required", "none"]).default("none"),
9735
- indexes: array(RecordIndexSchema).max(8).default([]),
9736
- retention: RecordRetentionSchema,
9737
- deviceRemoval: _enum(["delete", "none"]).default("none"),
9738
- maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
9739
- /** Derived from the Zod schema; never written by hand. */
9740
- secretPaths: array(string().min(1)).default([]),
9741
- /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
9742
- secretArrayKeys: record(string(), string()).default({}),
9743
- /** Kinds that must agree through `transact` sit in one file under one family name. */
9744
- transactFamily: string().min(1).optional(),
9745
- entry: EntryDescriptorSchema.optional()
9746
- }).strict();
9747
- /** The kind names: lower-case, dash separated, short enough for an index name. */
9748
- var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
9749
- /** Cross-field rules a declaration must satisfy. Throws {@link DeclarationRefusedError}. */
9750
- function assertValidDeclaration(name, decl) {
9751
- const refuse = (why) => {
9752
- throw new DeclarationRefusedError(`kind "${name}": ${why}`);
9753
- };
9754
- if (!RecordKindNameSchema.safeParse(name).success) refuse("the name must be lower-case-dashed");
9755
- const seen = /* @__PURE__ */ new Set();
9756
- for (const index of decl.indexes) {
9757
- if (seen.has(index.name)) refuse(`index name "${index.name}" is declared twice`);
9758
- seen.add(index.name);
9759
- }
9760
- const { retention } = decl;
9761
- const hasSecrets = decl.secretPaths.length > 0;
9762
- if ("ttlMs" in retention) {
9763
- if (retention.from === "at" && decl.at !== "required") refuse("a ttl from \"at\" needs at: \"required\"");
9764
- if (hasSecrets) refuse("a kind with secret fields cannot use engine TTL (its keychain values would be orphaned)");
9765
- }
9766
- if ("maxRows" in retention || "maxRowsPerDevice" in retention) {
9767
- if (retention.maxRowsPerDevice !== void 0 && decl.device !== "required") refuse("maxRowsPerDevice needs device: \"required\"");
9768
- if (decl.at !== "required") refuse("a row bound trims the oldest \"at\": it needs at: \"required\"");
9769
- if (hasSecrets) refuse("a kind with secret fields cannot use an engine row bound");
9770
- const bound = Math.max(retention.maxRows ?? 0, retention.maxRowsPerDevice ?? 0);
9771
- 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)`);
9772
- }
9773
- if (decl.deviceRemoval === "delete" && decl.device === "none") refuse("deviceRemoval: \"delete\" needs a device: \"required\" or \"optional\"");
9774
- 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`);
9775
- }
9776
- function bodyFieldsOf(schema) {
9777
- return { ...toJSONSchema(schema, {
9778
- unrepresentable: "any",
9779
- io: "input"
9780
- }) };
9781
- }
9782
- function isRecordLike(value) {
9783
- return typeof value === "object" && value !== null && !Array.isArray(value);
9784
- }
9785
- /** Does every branch of the body schema carry `field`? (A discriminated union has several.) */
9786
- function hasNameField(json, field) {
9787
- const branches = json["oneOf"] ?? json["anyOf"];
9788
- if (Array.isArray(branches)) return branches.length > 0 && branches.every((b) => isRecordLike(b) && hasNameField(b, field));
9789
- const properties = json["properties"];
9790
- return isRecordLike(properties) && Object.prototype.hasOwnProperty.call(properties, field);
9791
- }
9792
- /**
9793
- * Declare the kinds of a package. The keys are the kind names. The result is
9794
- * what `ctx.records.declare` sends to the engine and what `ctx.records.kind`
9795
- * types its handle from.
9796
- */
9797
- function defineRecordKinds(specs) {
9798
- const out = {};
9799
- for (const [name, spec] of Object.entries(specs)) {
9800
- const secretPaths = collectZodSecretPaths(spec.schema);
9801
- const nameField = spec.entry?.nameField ?? "name";
9802
- const fields = bodyFieldsOf(spec.schema);
9803
- if (spec.entry !== void 0 && !hasNameField(fields, nameField)) throw new DeclarationRefusedError(`kind "${name}": an entry kind's schema needs a "${nameField}" field`);
9804
- const parsed = RecordKindDeclarationSchema.parse({
9805
- database: spec.database,
9806
- class: spec.class,
9807
- writeTrigger: spec.writeTrigger,
9808
- device: spec.device,
9809
- at: spec.at,
9810
- indexes: spec.indexes === void 0 ? void 0 : [...spec.indexes],
9811
- retention: spec.retention,
9812
- deviceRemoval: spec.deviceRemoval,
9813
- maxDataBytes: spec.maxDataBytes,
9814
- secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9815
- secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9816
- transactFamily: spec.transactFamily,
9817
- entry: spec.entry === void 0 ? void 0 : {
9818
- title: spec.entry.title,
9819
- nameField,
9820
- fields
9821
- }
9822
- });
9823
- assertValidDeclaration(name, parsed);
9824
- out[name] = {
9825
- name,
9826
- schema: spec.schema,
9827
- declaration: parsed
9828
- };
9829
- }
9830
- return out;
9831
- }
9832
- /**
9833
- * Entry kinds (D881): every list of user-created entries (notification
9834
- * targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
9835
- * kind-specific body, and the admin UI lists/adds/edits/deletes all of them
9836
- * through the `record-entries` capability with password fields derived from
9837
- * the schema (`.meta({ secret: true })`).
9838
- */
9839
- /** The body of a plain entry: a name and the fields the kind adds. */
9840
- function entryBody(shape) {
9841
- return object({
9842
- name: string().min(1),
9843
- enabled: boolean().optional(),
9844
- ...shape
9845
- });
9846
- }
9847
- /**
9848
- * Declare one entry kind. An entry is configuration: never trimmed by the
9849
- * engine, never aged, belonging to no device.
9850
- */
9851
- function defineEntryKind(name, spec) {
9852
- const defined = defineRecordKinds({ [name]: {
9853
- schema: spec.schema,
9854
- class: "config",
9855
- writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9856
- retention: { none: "an entry goes only when the operator deletes it" },
9857
- ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9858
- ...spec.database === void 0 ? {} : { database: spec.database },
9859
- ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9860
- ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9861
- ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9862
- entry: {
9863
- title: spec.title,
9864
- ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
9865
- }
9866
- } })[name];
9867
- if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
9868
- return defined;
9869
- }
9870
9617
  object({
9871
9618
  field: string().min(1),
9872
9619
  on: literal("advance")
@@ -9959,6 +9706,286 @@ function optionalMethod(input, output, options) {
9959
9706
  function event(data) {
9960
9707
  return { data };
9961
9708
  }
9709
+ function defOf(schema) {
9710
+ const zodBag = Reflect.get(schema, "_zod");
9711
+ if (typeof zodBag !== "object" || zodBag === null) return {};
9712
+ const def = Reflect.get(zodBag, "def");
9713
+ if (typeof def !== "object" || def === null) return {};
9714
+ return def;
9715
+ }
9716
+ function declaredHere(schema) {
9717
+ return globalRegistry.get(schema)?.secret === true;
9718
+ }
9719
+ /** Wrapper kinds whose `innerType` is the field itself. */
9720
+ var WRAPPERS = new Set([
9721
+ "optional",
9722
+ "nullable",
9723
+ "default",
9724
+ "prefault",
9725
+ "catch",
9726
+ "readonly",
9727
+ "nonoptional"
9728
+ ]);
9729
+ /**
9730
+ * The schema layers that make up ONE field: the schema itself and every
9731
+ * wrapper below it (optional / default / nullable / readonly / catch), plus
9732
+ * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
9733
+ * `.transform` on `in`).
9734
+ */
9735
+ function layersOf(schema) {
9736
+ const out = [];
9737
+ const queue = [schema];
9738
+ while (queue.length > 0) {
9739
+ const s = queue.shift();
9740
+ if (!s || out.includes(s)) continue;
9741
+ out.push(s);
9742
+ const def = defOf(s);
9743
+ if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
9744
+ if (def.type === "pipe") {
9745
+ if (def.in) queue.push(def.in);
9746
+ if (def.out) queue.push(def.out);
9747
+ }
9748
+ }
9749
+ return out;
9750
+ }
9751
+ /** True when any layer of this field declares `.meta({ secret: true })`. */
9752
+ function isZodSecret(schema) {
9753
+ return layersOf(schema).some(declaredHere);
9754
+ }
9755
+ /**
9756
+ * Every declared secret path in a config schema: dotted keys, `[]` for "each
9757
+ * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9758
+ * map" (`applianceSecrets{}`). A declared
9759
+ * object or array is reported as one path and not descended into — everything
9760
+ * under it is secret.
9761
+ */
9762
+ function collectZodSecretPaths(schema, prefix = "") {
9763
+ if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
9764
+ const paths = [];
9765
+ for (const layer of layersOf(schema)) {
9766
+ const def = defOf(layer);
9767
+ if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9768
+ else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9769
+ else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9770
+ else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9771
+ else if (def.type === "intersection") {
9772
+ if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
9773
+ if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
9774
+ }
9775
+ }
9776
+ return [...new Set(paths)];
9777
+ }
9778
+ /**
9779
+ * Record kinds (D881): the declaration an addon makes of one kind of fact, the
9780
+ * Zod row schema that describes it, and the helper that turns the two into the
9781
+ * JSON the engine stores.
9782
+ *
9783
+ * A kind is declared ONCE, next to the code that owns it, and the engine's
9784
+ * reconciler creates its indexes - nothing creates DDL at runtime from a write.
9785
+ * Everything the generic machinery needs (secret fields, the entry descriptor)
9786
+ * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
9787
+ * never a field name (D879).
9788
+ */
9789
+ /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
9790
+ var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
9791
+ var RecordIndexSchema = object({
9792
+ name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
9793
+ fields: array(RecordFieldPathSchema).min(1).max(4),
9794
+ unique: boolean().optional()
9795
+ }).strict();
9796
+ var RecordRetentionSchema = union([
9797
+ object({
9798
+ ttlMs: number().int().positive(),
9799
+ from: _enum(["at", "write"])
9800
+ }).strict(),
9801
+ object({
9802
+ maxRows: number().int().positive().optional(),
9803
+ maxRowsPerDevice: number().int().positive().optional()
9804
+ }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
9805
+ object({
9806
+ owner: string().min(1),
9807
+ reason: string().min(1)
9808
+ }).strict(),
9809
+ object({ none: string().min(1) }).strict()
9810
+ ]);
9811
+ /** The descriptor a generic entries UI reads (D881 "generic entries"). */
9812
+ var EntryDescriptorSchema = object({
9813
+ title: string().min(1),
9814
+ /** The record body field shown as the entry's name. */
9815
+ nameField: string().min(1),
9816
+ /** JSON Schema of the entry body; `secret: true` marks a password field. */
9817
+ fields: record(string(), unknown())
9818
+ }).strict();
9819
+ /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
9820
+ var RecordKindDeclarationSchema = object({
9821
+ database: _enum(RECORD_DATABASES).default("settings"),
9822
+ class: _enum(RECORD_CLASSES),
9823
+ writeTrigger: string().min(1),
9824
+ device: _enum([
9825
+ "required",
9826
+ "optional",
9827
+ "none"
9828
+ ]).default("none"),
9829
+ at: _enum(["required", "none"]).default("none"),
9830
+ indexes: array(RecordIndexSchema).max(8).default([]),
9831
+ retention: RecordRetentionSchema,
9832
+ deviceRemoval: _enum(["delete", "none"]).default("none"),
9833
+ maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
9834
+ /** Derived from the Zod schema; never written by hand. */
9835
+ secretPaths: array(string().min(1)).default([]),
9836
+ /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
9837
+ secretArrayKeys: record(string(), string()).default({}),
9838
+ /** Kinds that must agree through `transact` sit in one file under one family name. */
9839
+ transactFamily: string().min(1).optional(),
9840
+ entry: EntryDescriptorSchema.optional()
9841
+ }).strict();
9842
+ /** The kind names: lower-case, dash separated, short enough for an index name. */
9843
+ var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
9844
+ /** Cross-field rules a declaration must satisfy. Throws {@link DeclarationRefusedError}. */
9845
+ function assertValidDeclaration(name, decl) {
9846
+ const refuse = (why) => {
9847
+ throw new DeclarationRefusedError(`kind "${name}": ${why}`);
9848
+ };
9849
+ if (!RecordKindNameSchema.safeParse(name).success) refuse("the name must be lower-case-dashed");
9850
+ const seen = /* @__PURE__ */ new Set();
9851
+ for (const index of decl.indexes) {
9852
+ if (seen.has(index.name)) refuse(`index name "${index.name}" is declared twice`);
9853
+ seen.add(index.name);
9854
+ }
9855
+ const { retention } = decl;
9856
+ const hasSecrets = decl.secretPaths.length > 0;
9857
+ if ("ttlMs" in retention) {
9858
+ if (retention.from === "at" && decl.at !== "required") refuse("a ttl from \"at\" needs at: \"required\"");
9859
+ if (hasSecrets) refuse("a kind with secret fields cannot use engine TTL (its keychain values would be orphaned)");
9860
+ }
9861
+ if ("maxRows" in retention || "maxRowsPerDevice" in retention) {
9862
+ if (retention.maxRowsPerDevice !== void 0 && decl.device !== "required") refuse("maxRowsPerDevice needs device: \"required\"");
9863
+ if (decl.at !== "required") refuse("a row bound trims the oldest \"at\": it needs at: \"required\"");
9864
+ if (hasSecrets) refuse("a kind with secret fields cannot use an engine row bound");
9865
+ const bound = Math.max(retention.maxRows ?? 0, retention.maxRowsPerDevice ?? 0);
9866
+ 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)`);
9867
+ }
9868
+ if (decl.deviceRemoval === "delete" && decl.device === "none") refuse("deviceRemoval: \"delete\" needs a device: \"required\" or \"optional\"");
9869
+ 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`);
9870
+ }
9871
+ function bodyFieldsOf(schema) {
9872
+ return { ...toJSONSchema(schema, {
9873
+ unrepresentable: "any",
9874
+ io: "input"
9875
+ }) };
9876
+ }
9877
+ function isRecordLike(value) {
9878
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9879
+ }
9880
+ /** Does every branch of the body schema carry `field`? (A discriminated union has several.) */
9881
+ function hasNameField(json, field) {
9882
+ const branches = json["oneOf"] ?? json["anyOf"];
9883
+ if (Array.isArray(branches)) return branches.length > 0 && branches.every((b) => isRecordLike(b) && hasNameField(b, field));
9884
+ const properties = json["properties"];
9885
+ return isRecordLike(properties) && Object.prototype.hasOwnProperty.call(properties, field);
9886
+ }
9887
+ /**
9888
+ * Declare the kinds of a package. The keys are the kind names. The result is
9889
+ * what `ctx.records.declare` sends to the engine and what `ctx.records.kind`
9890
+ * types its handle from.
9891
+ */
9892
+ function defineRecordKinds(specs) {
9893
+ const out = {};
9894
+ for (const [name, spec] of Object.entries(specs)) {
9895
+ const secretPaths = collectZodSecretPaths(spec.schema);
9896
+ const nameField = spec.entry?.nameField ?? "name";
9897
+ const fields = bodyFieldsOf(spec.schema);
9898
+ if (spec.entry !== void 0 && !hasNameField(fields, nameField)) throw new DeclarationRefusedError(`kind "${name}": an entry kind's schema needs a "${nameField}" field`);
9899
+ const parsed = RecordKindDeclarationSchema.parse({
9900
+ database: spec.database,
9901
+ class: spec.class,
9902
+ writeTrigger: spec.writeTrigger,
9903
+ device: spec.device,
9904
+ at: spec.at,
9905
+ indexes: spec.indexes === void 0 ? void 0 : [...spec.indexes],
9906
+ retention: spec.retention,
9907
+ deviceRemoval: spec.deviceRemoval,
9908
+ maxDataBytes: spec.maxDataBytes,
9909
+ secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9910
+ secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9911
+ transactFamily: spec.transactFamily,
9912
+ entry: spec.entry === void 0 ? void 0 : {
9913
+ title: spec.entry.title,
9914
+ nameField,
9915
+ fields
9916
+ }
9917
+ });
9918
+ assertValidDeclaration(name, parsed);
9919
+ out[name] = {
9920
+ name,
9921
+ schema: spec.schema,
9922
+ declaration: parsed
9923
+ };
9924
+ }
9925
+ return out;
9926
+ }
9927
+ /**
9928
+ * Entry kinds (D881): every list of user-created entries (notification
9929
+ * targets, brokers, LLM profiles) is ONE record kind with an id, a name and a
9930
+ * kind-specific body, and the admin UI lists/adds/edits/deletes all of them
9931
+ * through the `record-entries` capability with password fields derived from
9932
+ * the schema (`.meta({ secret: true })`).
9933
+ */
9934
+ /** The body of a plain entry: a name and the fields the kind adds. */
9935
+ function entryBody(shape) {
9936
+ return object({
9937
+ name: string().min(1),
9938
+ enabled: boolean().optional(),
9939
+ ...shape
9940
+ });
9941
+ }
9942
+ /**
9943
+ * Declare one entry kind. An entry is configuration: never trimmed by the
9944
+ * engine, never aged, belonging to no device.
9945
+ */
9946
+ function defineEntryKind(name, spec) {
9947
+ const defined = defineRecordKinds({ [name]: {
9948
+ schema: spec.schema,
9949
+ class: "config",
9950
+ writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9951
+ retention: { none: "an entry goes only when the operator deletes it" },
9952
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9953
+ ...spec.database === void 0 ? {} : { database: spec.database },
9954
+ ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9955
+ ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9956
+ ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9957
+ entry: {
9958
+ title: spec.title,
9959
+ ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
9960
+ }
9961
+ } })[name];
9962
+ if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
9963
+ return defined;
9964
+ }
9965
+ /**
9966
+ * The share-view scope kinds a share-link token can carry. The hub mints
9967
+ * and validates them (`ShareViewGrantSchema` builds its
9968
+ * enum from this list); a data plane names the kinds it ACCEPTS in `share`.
9969
+ */
9970
+ var SHARE_VIEW_KINDS = ["grid-view", "events-view"];
9971
+ /** The wire shape of {@link PeerBytesTicket} — see the type for what it is. */
9972
+ var PeerBytesTicketSchema = object({
9973
+ /** `http://127.0.0.1:<port>/<token>`. One `GET` takes it. */
9974
+ url: string().min(1),
9975
+ /**
9976
+ * The HOST node this URL means something on — the hub or a named agent,
9977
+ * never a runner. {@link AddonPeerBytes.open} compares it to its own and
9978
+ * refuses `cross-node` by name when they differ, without dialling.
9979
+ */
9980
+ hostNodeId: string().min(1),
9981
+ expiresAtMs: number().int().nonnegative(),
9982
+ /**
9983
+ * What the producer DECLARED the body to be, when it knows — `null` when it
9984
+ * does not. Never `0` for unknown (D393): a consumer sizing a bound off this
9985
+ * must be able to tell "the producer did not say" from "the body is empty".
9986
+ */
9987
+ declaredBytes: number().int().nonnegative().nullable()
9988
+ });
9962
9989
  var READING_QUANTITIES = [
9963
9990
  "temperature",
9964
9991
  "pressure",
@@ -10537,7 +10564,6 @@ var CAP_NAMES = Object.freeze([
10537
10564
  "climate-control",
10538
10565
  "color",
10539
10566
  "composer",
10540
- "connection-test",
10541
10567
  "connectivity",
10542
10568
  "consumables",
10543
10569
  "contact",
@@ -12557,8 +12583,7 @@ Object.freeze({
12557
12583
  "offerArtifact": 3e5,
12558
12584
  "readResult": 3e5,
12559
12585
  "submitJob": 18e5,
12560
- "uploadDataset": 72e5,
12561
- "validateCredentials": 3e4
12586
+ "uploadDataset": 72e5
12562
12587
  }
12563
12588
  });
12564
12589
  object({
@@ -13913,6 +13938,432 @@ var IntegrationInstanceRefSchema = object({
13913
13938
  ]),
13914
13939
  candidateStableId: string().optional()
13915
13940
  });
13941
+ /**
13942
+ * notification-output — canonical, capability-gated notification delivery.
13943
+ *
13944
+ * Apprise-derived model (see
13945
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
13946
+ * callers emit ONE canonical `Notification`; each provider declares a
13947
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
13948
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
13949
+ * message to what the kind supports — callers never special-case a service.
13950
+ *
13951
+ * DESIGN DECISIONS (locked):
13952
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
13953
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
13954
+ * cap. Rationale: the admin UI needs one uniform surface across the
13955
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
13956
+ * alternative would fork the UI per addon and cannot host the
13957
+ * discovery→adopt flow.
13958
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
13959
+ * the generated cap-mount auto-`concatCollection`-fans them across every
13960
+ * registered provider (notifiers addon + HA addon) so one catalog is
13961
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
13962
+ * `addonId` the generated collection router extracts from the call input.
13963
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
13964
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
13965
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
13966
+ * base64 fallback needed.
13967
+ *
13968
+ * TODO (deferred, closed-set change — separate decision): add
13969
+ * `providerKind: 'notify'` so notification providers surface on the unified
13970
+ * admin "Integrations" page.
13971
+ */
13972
+ /**
13973
+ * Zentik-derived typed-media enum — the superset across every kind. Each
13974
+ * adapter picks what it supports and the degrade engine filters the rest.
13975
+ */
13976
+ var AttachmentMediaTypeSchema = _enum([
13977
+ "image",
13978
+ "video",
13979
+ "gif",
13980
+ "audio",
13981
+ "icon"
13982
+ ]);
13983
+ /**
13984
+ * A single attachment. Exactly one of `url` (remote source, most adapters
13985
+ * prefer this) or `bytes` (inline source; required for Pushover-style
13986
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
13987
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
13988
+ */
13989
+ var AttachmentSchema = object({
13990
+ mediaType: AttachmentMediaTypeSchema,
13991
+ url: string().optional(),
13992
+ bytes: _instanceof(Uint8Array).optional(),
13993
+ mime: string().optional(),
13994
+ name: string().optional()
13995
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
13996
+ var NotificationFormatSchema = _enum([
13997
+ "text",
13998
+ "markdown",
13999
+ "html"
14000
+ ]);
14001
+ /**
14002
+ * The CLOSED icon vocabulary an action button may use.
14003
+ *
14004
+ * A closed set, not a free string, and that is the whole point: an arbitrary
14005
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
14006
+ * notices — the same class of gap as a zone vocabulary nothing produced
14007
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
14008
+ * declares `actionIcons: false` and the degrade engine strips the field.
14009
+ *
14010
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
14011
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
14012
+ * differently.
14013
+ *
14014
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
14015
+ *
14016
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
14017
+ * the whole set onto its own renderer's vocabulary through a
14018
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
14019
+ * fallback, so adding a member here fails every adapter's build until someone
14020
+ * decides its glyph, which is the only place that decision can be made
14021
+ * honestly.
14022
+ *
14023
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
14024
+ * `disarm` straight through; iOS feeds that string to
14025
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
14026
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
14027
+ * mapping, and "the field is documented" is not "the value renders".
14028
+ *
14029
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
14030
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
14031
+ * so an addon that emits a token the running hub does not know does not lose an
14032
+ * icon — its whole `send` fails Zod validation and the notification never
14033
+ * arrives. Never emit a new token from an addon before the train carrying it.
14034
+ */
14035
+ var NotificationActionIconSchema = _enum([
14036
+ "acknowledge",
14037
+ "dismiss",
14038
+ "silence",
14039
+ "snooze",
14040
+ "view",
14041
+ "play",
14042
+ "open",
14043
+ "close",
14044
+ "lock",
14045
+ "unlock",
14046
+ "arm",
14047
+ "arm-home",
14048
+ "arm-away",
14049
+ "arm-night",
14050
+ "disarm",
14051
+ "light",
14052
+ "alert",
14053
+ "camera"
14054
+ ]);
14055
+ /** A single tap-through action button. */
14056
+ var NotificationActionSchema = object({
14057
+ id: string(),
14058
+ label: string(),
14059
+ url: string().optional(),
14060
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
14061
+ icon: NotificationActionIconSchema.optional(),
14062
+ /**
14063
+ * Renders in a warning style where the notifier supports it.
14064
+ *
14065
+ * A HINT, never a gate. The callback's authority is its token and nothing
14066
+ * else — see `notification-center/action-token.ts` for what that does and
14067
+ * does not buy.
14068
+ */
14069
+ destructive: boolean().optional(),
14070
+ /**
14071
+ * How the tap should REACH the url.
14072
+ *
14073
+ * `navigate` (absent, and every button authored before this field) opens it:
14074
+ * the phone leaves the notification and shows whatever the callback returns.
14075
+ * That is right for a button whose answer the operator wants to read.
14076
+ *
14077
+ * `background` fires it as a POST and stays put. It exists for the buttons
14078
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
14079
+ * an answer to the notification, and being thrown into a browser tab to
14080
+ * confirm it costs more attention than the notification did. A backend that
14081
+ * cannot do a background call renders it as an ordinary link (the adapters
14082
+ * fall back rather than dropping the button), so this is a preference, never
14083
+ * a requirement.
14084
+ */
14085
+ mode: _enum(["navigate", "background"]).optional()
14086
+ });
14087
+ /** A delivery parameter value: what a notifier declared it takes. */
14088
+ var DeliveryParamValueSchema = union([
14089
+ string(),
14090
+ number(),
14091
+ boolean()
14092
+ ]);
14093
+ /**
14094
+ * One extra delivery parameter a notifier KIND declares (D938). A rule that
14095
+ * addresses a target of that kind may set it; a kind that declares nothing is
14096
+ * never handed one. The admin UI derives its control from this descriptor —
14097
+ * no editor knows a service by name.
14098
+ *
14099
+ * Optional fields are `.optional()`, never `.default()` (a Zod default does not
14100
+ * run on the addon cap path).
14101
+ */
14102
+ var DeliveryParamSchema = object({
14103
+ key: string().min(1),
14104
+ label: string(),
14105
+ /** i18n key of the label, resolved by the declaring addon's namespace. */
14106
+ labelKey: string().optional(),
14107
+ type: _enum([
14108
+ "enum",
14109
+ "number",
14110
+ "boolean",
14111
+ "string"
14112
+ ]),
14113
+ /** `enum` only: the closed set of accepted values. */
14114
+ options: array(object({
14115
+ value: string(),
14116
+ label: string()
14117
+ })).optional(),
14118
+ /** `number` only. */
14119
+ min: number().optional(),
14120
+ max: number().optional(),
14121
+ /** What the notifier does when the rule sets nothing; shown as the placeholder. */
14122
+ default: DeliveryParamValueSchema.optional()
14123
+ });
14124
+ /**
14125
+ * The canonical notification. `body` is the only hard field (Apprise model).
14126
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
14127
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
14128
+ * the adapter maps this ordinal onto its native level. `level?` is an
14129
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
14130
+ * `priority` for that one target.
14131
+ */
14132
+ var NotificationSchema = object({
14133
+ body: string(),
14134
+ title: string().optional(),
14135
+ format: NotificationFormatSchema.default("text"),
14136
+ priority: number().int().min(1).max(5).default(3),
14137
+ level: string().optional(),
14138
+ attachments: array(AttachmentSchema).optional(),
14139
+ clickUrl: string().optional(),
14140
+ actions: array(NotificationActionSchema).optional(),
14141
+ sound: string().optional(),
14142
+ ttl: number().optional(),
14143
+ tag: string().optional(),
14144
+ deviceId: number().optional(),
14145
+ eventId: string().optional(),
14146
+ metadata: record(string(), unknown()).optional(),
14147
+ /**
14148
+ * Values for the parameters the target's kind DECLARES in
14149
+ * {@link TargetKindSchema} `deliveryParams`. Already validated against the
14150
+ * declaration by the sender; a notifier reads only the keys it declared.
14151
+ */
14152
+ delivery: record(string(), DeliveryParamValueSchema).optional()
14153
+ });
14154
+ /** One declared native severity/priority level for a kind. */
14155
+ var TargetKindLevelSchema = object({
14156
+ id: string(),
14157
+ label: string(),
14158
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
14159
+ ordinal: number().int().min(1).max(5).nullable(),
14160
+ flags: object({
14161
+ critical: boolean().optional(),
14162
+ silent: boolean().optional(),
14163
+ noPush: boolean().optional()
14164
+ }).optional(),
14165
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
14166
+ requires: array(string()).optional(),
14167
+ description: string().optional()
14168
+ });
14169
+ /** The full capability block consulted before dispatch. */
14170
+ var TargetKindCapsSchema = object({
14171
+ attachments: object({
14172
+ mediaTypes: array(AttachmentMediaTypeSchema),
14173
+ mode: _enum([
14174
+ "url",
14175
+ "bytes",
14176
+ "both"
14177
+ ]),
14178
+ max: number().int().nonnegative(),
14179
+ maxBytes: number().int().positive().optional()
14180
+ }),
14181
+ /** Max action buttons (0 = none). */
14182
+ actions: number().int().nonnegative(),
14183
+ /**
14184
+ * Whether this kind renders a per-action ICON.
14185
+ *
14186
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
14187
+ * run on the addon cap path — three production failures in one day taught
14188
+ * this repo that once. Absent is read as false by the degrade engine, which
14189
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
14190
+ * assumed and dropped costs the operator's trust in the field.
14191
+ */
14192
+ actionIcons: boolean().optional(),
14193
+ levels: array(TargetKindLevelSchema),
14194
+ format: array(NotificationFormatSchema),
14195
+ clickUrl: boolean(),
14196
+ sound: boolean(),
14197
+ ttl: boolean(),
14198
+ bodyMaxLen: number().int().positive()
14199
+ });
14200
+ /**
14201
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
14202
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
14203
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
14204
+ * the union is large and not meant for runtime validation here; the exported
14205
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
14206
+ */
14207
+ var ConfigSchemaPassthrough$4 = unknown();
14208
+ var TargetKindSchema = object({
14209
+ kind: string(),
14210
+ label: string(),
14211
+ icon: string(),
14212
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
14213
+ addonId: string(),
14214
+ /**
14215
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
14216
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
14217
+ * when the addon bundles no icon for that kind — the client then falls back
14218
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
14219
+ *
14220
+ * Root-relative on purpose: it resolves against whatever origin serves a web
14221
+ * client, and a native client joins it onto its own hub base.
14222
+ *
14223
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
14224
+ * field that survived only because the runtime cap-router forwards provider
14225
+ * output verbatim — so every consumer had to re-declare it by hand to stop
14226
+ * its own Zod parse from stripping it, and the whole arrangement would have
14227
+ * broken silently the moment output validation was tightened anywhere.
14228
+ */
14229
+ iconUrl: string().optional(),
14230
+ /**
14231
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
14232
+ *
14233
+ * The server knows this and therefore says it, because the client cannot
14234
+ * safely guess: a React-Native client renders SVG and raster through two
14235
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
14236
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
14237
+ * placeholder glyph for every vector icon while the web build looked fine.
14238
+ *
14239
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
14240
+ * not been updated — a client that cannot determine the type should prefer
14241
+ * its raster path, which is the safe default for an unknown image.
14242
+ */
14243
+ iconMediaType: string().optional(),
14244
+ configSchema: ConfigSchemaPassthrough$4,
14245
+ /**
14246
+ * The config keys that decide WHERE this kind's secrets are sent (server,
14247
+ * base URL, instance, chat, recipient). A save that changes any of them
14248
+ * while leaving a secret redacted is refused by the provider (D634), so an
14249
+ * editor uses this to ask for the secret on exactly those edits instead of
14250
+ * on every edit.
14251
+ *
14252
+ * Optional: absent from a provider that holds no secret (the Home Assistant
14253
+ * kinds) and from a provider older than this field — an editor that finds
14254
+ * it absent must assume ANY edit may need the secret.
14255
+ */
14256
+ destinationKeys: array(string()).optional(),
14257
+ supportsDiscovery: boolean(),
14258
+ /**
14259
+ * Extra delivery parameters a rule may set for a target of this kind (D938).
14260
+ * Absent = none; the rule editor then offers nothing.
14261
+ */
14262
+ deliveryParams: array(DeliveryParamSchema).optional(),
14263
+ caps: TargetKindCapsSchema
14264
+ });
14265
+ /**
14266
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
14267
+ * (return a presence marker only) when serving `listTargets` — never
14268
+ * round-trip a stored secret to the UI.
14269
+ */
14270
+ var TargetSchema = object({
14271
+ id: string(),
14272
+ name: string(),
14273
+ kind: string(),
14274
+ addonId: string(),
14275
+ enabled: boolean(),
14276
+ config: record(string(), unknown())
14277
+ });
14278
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
14279
+ var DiscoveredTargetSchema = object({
14280
+ kind: string(),
14281
+ suggestedName: string(),
14282
+ config: record(string(), unknown())
14283
+ });
14284
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
14285
+ var RenderedAsSchema = object({
14286
+ level: string(),
14287
+ format: NotificationFormatSchema,
14288
+ attachmentsSent: number().int().nonnegative(),
14289
+ actionsSent: number().int().nonnegative(),
14290
+ truncated: boolean(),
14291
+ dropped: array(string())
14292
+ });
14293
+ var SendResultSchema = object({
14294
+ success: boolean(),
14295
+ error: string().optional(),
14296
+ renderedAs: RenderedAsSchema.optional()
14297
+ });
14298
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
14299
+ var TestResultSchema = SendResultSchema;
14300
+ /**
14301
+ * Pre-save credential probe against a partial (unsaved) target config.
14302
+ * `resolvedUrl` never includes secrets (token stripped from path).
14303
+ */
14304
+ var ProbeTargetConfigResultSchema = object({
14305
+ ok: boolean(),
14306
+ resolvedUrl: string(),
14307
+ httpStatus: number().int().optional(),
14308
+ stateInstance: string().optional(),
14309
+ error: string().optional()
14310
+ });
14311
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
14312
+ kind: string(),
14313
+ config: record(string(), unknown()).optional(),
14314
+ /**
14315
+ * When editing / duplicating, the blank password field never round-trips
14316
+ * the stored token. Pass the source target id so the provider can restore
14317
+ * secrets server-side before dialling (same merge as upsert).
14318
+ */
14319
+ targetId: string().optional()
14320
+ }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
14321
+ kind: string(),
14322
+ config: record(string(), unknown()),
14323
+ /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
14324
+ targetId: string().optional()
14325
+ }), ProbeTargetConfigResultSchema, {
14326
+ kind: "mutation",
14327
+ auth: "admin"
14328
+ }), method(object({
14329
+ targetId: string(),
14330
+ notification: NotificationSchema
14331
+ }), SendResultSchema, {
14332
+ kind: "mutation",
14333
+ auth: "admin"
14334
+ }), method(object({
14335
+ targetId: string(),
14336
+ sample: NotificationSchema.optional()
14337
+ }), TestResultSchema, {
14338
+ kind: "mutation",
14339
+ auth: "admin"
14340
+ }), method(object({
14341
+ target: TargetSchema,
14342
+ /**
14343
+ * Duplicate flow: create a NEW target id while copying secrets from an
14344
+ * existing row. The UI never holds the stored token; blank / redacted
14345
+ * password fields are restored from this source (D634 still applies if
14346
+ * destinations diverge without a re-typed secret).
14347
+ */
14348
+ cloneSecretsFrom: string().optional()
14349
+ }), TargetSchema, {
14350
+ kind: "mutation",
14351
+ auth: "admin"
14352
+ }), method(object({ targetId: string() }), _void(), {
14353
+ kind: "mutation",
14354
+ auth: "admin"
14355
+ }), method(object({
14356
+ targetId: string(),
14357
+ enabled: boolean()
14358
+ }), _void(), {
14359
+ kind: "mutation",
14360
+ auth: "admin"
14361
+ });
14362
+ looseObject({
14363
+ name: string().min(1),
14364
+ enabled: boolean().optional()
14365
+ });
14366
+ TargetSchema.omit({ id: true });
13916
14367
  var EncodeProfileSchema = object({
13917
14368
  video: object({
13918
14369
  codec: _enum([
@@ -22327,7 +22778,6 @@ var SYSTEM_CAP_VOCABULARY = {
22327
22778
  "calendar-source": system(INTERNAL),
22328
22779
  calendars: system("calendars feed schedules and conditions, not device actions"),
22329
22780
  composer: system("device composition — an authoring tool, not an action"),
22330
- "connection-test": system(INTERNAL),
22331
22781
  "core-blocks": system(INTERNAL),
22332
22782
  "custom-model-registry": system(ML),
22333
22783
  "data-store-provider": system(INTERNAL),
@@ -44383,427 +44833,6 @@ var UnitWarningSchema = object({
44383
44833
  assumedUnit: string(),
44384
44834
  message: string()
44385
44835
  });
44386
- /**
44387
- * notification-output — canonical, capability-gated notification delivery.
44388
- *
44389
- * Apprise-derived model (see
44390
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
44391
- * callers emit ONE canonical `Notification`; each provider declares a
44392
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
44393
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
44394
- * message to what the kind supports — callers never special-case a service.
44395
- *
44396
- * DESIGN DECISIONS (locked):
44397
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
44398
- * `setTargetEnabled`), each provider persisting via the `settings-store`
44399
- * cap. Rationale: the admin UI needs one uniform surface across the
44400
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
44401
- * alternative would fork the UI per addon and cannot host the
44402
- * discovery→adopt flow.
44403
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
44404
- * the generated cap-mount auto-`concatCollection`-fans them across every
44405
- * registered provider (notifiers addon + HA addon) so one catalog is
44406
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
44407
- * `addonId` the generated collection router extracts from the call input.
44408
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
44409
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
44410
- * `storage` / `storage-provider` / `recording` caps over the same path. No
44411
- * base64 fallback needed.
44412
- *
44413
- * TODO (deferred, closed-set change — separate decision): add
44414
- * `providerKind: 'notify'` so notification providers surface on the unified
44415
- * admin "Integrations" page.
44416
- */
44417
- /**
44418
- * Zentik-derived typed-media enum — the superset across every kind. Each
44419
- * adapter picks what it supports and the degrade engine filters the rest.
44420
- */
44421
- var AttachmentMediaTypeSchema = _enum([
44422
- "image",
44423
- "video",
44424
- "gif",
44425
- "audio",
44426
- "icon"
44427
- ]);
44428
- /**
44429
- * A single attachment. Exactly one of `url` (remote source, most adapters
44430
- * prefer this) or `bytes` (inline source; required for Pushover-style
44431
- * bytes-only kinds) MUST be present — the degrade engine expresses a
44432
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
44433
- */
44434
- var AttachmentSchema = object({
44435
- mediaType: AttachmentMediaTypeSchema,
44436
- url: string().optional(),
44437
- bytes: _instanceof(Uint8Array).optional(),
44438
- mime: string().optional(),
44439
- name: string().optional()
44440
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
44441
- var NotificationFormatSchema = _enum([
44442
- "text",
44443
- "markdown",
44444
- "html"
44445
- ]);
44446
- /**
44447
- * The CLOSED icon vocabulary an action button may use.
44448
- *
44449
- * A closed set, not a free string, and that is the whole point: an arbitrary
44450
- * icon name is one that ntfy renders, zentik silently drops, and nobody
44451
- * notices — the same class of gap as a zone vocabulary nothing produced
44452
- * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
44453
- * declares `actionIcons: false` and the degrade engine strips the field.
44454
- *
44455
- * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
44456
- * renderer's icon set; "acknowledge" survives an adapter that draws it
44457
- * differently.
44458
- *
44459
- * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
44460
- *
44461
- * These names are for US. **No adapter may forward one verbatim.** Each maps
44462
- * the whole set onto its own renderer's vocabulary through a
44463
- * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
44464
- * fallback, so adding a member here fails every adapter's build until someone
44465
- * decides its glyph, which is the only place that decision can be made
44466
- * honestly.
44467
- *
44468
- * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
44469
- * `disarm` straight through; iOS feeds that string to
44470
- * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
44471
- * and every snooze and alarm button arrived BLANK. A pass-through is not a
44472
- * mapping, and "the field is documented" is not "the value renders".
44473
- *
44474
- * Adding a member is TRAIN-BOUND. The enum lives in the published
44475
- * `@camstack/server` closure and the cap seam validates against the HUB's copy,
44476
- * so an addon that emits a token the running hub does not know does not lose an
44477
- * icon — its whole `send` fails Zod validation and the notification never
44478
- * arrives. Never emit a new token from an addon before the train carrying it.
44479
- */
44480
- var NotificationActionIconSchema = _enum([
44481
- "acknowledge",
44482
- "dismiss",
44483
- "silence",
44484
- "snooze",
44485
- "view",
44486
- "play",
44487
- "open",
44488
- "close",
44489
- "lock",
44490
- "unlock",
44491
- "arm",
44492
- "arm-home",
44493
- "arm-away",
44494
- "arm-night",
44495
- "disarm",
44496
- "light",
44497
- "alert",
44498
- "camera"
44499
- ]);
44500
- /** A single tap-through action button. */
44501
- var NotificationActionSchema = object({
44502
- id: string(),
44503
- label: string(),
44504
- url: string().optional(),
44505
- /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
44506
- icon: NotificationActionIconSchema.optional(),
44507
- /**
44508
- * Renders in a warning style where the notifier supports it.
44509
- *
44510
- * A HINT, never a gate. The callback's authority is its token and nothing
44511
- * else — see `notification-center/action-token.ts` for what that does and
44512
- * does not buy.
44513
- */
44514
- destructive: boolean().optional(),
44515
- /**
44516
- * How the tap should REACH the url.
44517
- *
44518
- * `navigate` (absent, and every button authored before this field) opens it:
44519
- * the phone leaves the notification and shows whatever the callback returns.
44520
- * That is right for a button whose answer the operator wants to read.
44521
- *
44522
- * `background` fires it as a POST and stays put. It exists for the buttons
44523
- * whose whole point is not to interrupt — "silence this for 30 minutes" is
44524
- * an answer to the notification, and being thrown into a browser tab to
44525
- * confirm it costs more attention than the notification did. A backend that
44526
- * cannot do a background call renders it as an ordinary link (the adapters
44527
- * fall back rather than dropping the button), so this is a preference, never
44528
- * a requirement.
44529
- */
44530
- mode: _enum(["navigate", "background"]).optional()
44531
- });
44532
- /** A delivery parameter value: what a notifier declared it takes. */
44533
- var DeliveryParamValueSchema = union([
44534
- string(),
44535
- number(),
44536
- boolean()
44537
- ]);
44538
- /**
44539
- * One extra delivery parameter a notifier KIND declares (D938). A rule that
44540
- * addresses a target of that kind may set it; a kind that declares nothing is
44541
- * never handed one. The admin UI derives its control from this descriptor —
44542
- * no editor knows a service by name.
44543
- *
44544
- * Optional fields are `.optional()`, never `.default()` (a Zod default does not
44545
- * run on the addon cap path).
44546
- */
44547
- var DeliveryParamSchema = object({
44548
- key: string().min(1),
44549
- label: string(),
44550
- /** i18n key of the label, resolved by the declaring addon's namespace. */
44551
- labelKey: string().optional(),
44552
- type: _enum([
44553
- "enum",
44554
- "number",
44555
- "boolean",
44556
- "string"
44557
- ]),
44558
- /** `enum` only: the closed set of accepted values. */
44559
- options: array(object({
44560
- value: string(),
44561
- label: string()
44562
- })).optional(),
44563
- /** `number` only. */
44564
- min: number().optional(),
44565
- max: number().optional(),
44566
- /** What the notifier does when the rule sets nothing; shown as the placeholder. */
44567
- default: DeliveryParamValueSchema.optional()
44568
- });
44569
- /**
44570
- * The canonical notification. `body` is the only hard field (Apprise model).
44571
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
44572
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
44573
- * the adapter maps this ordinal onto its native level. `level?` is an
44574
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
44575
- * `priority` for that one target.
44576
- */
44577
- var NotificationSchema = object({
44578
- body: string(),
44579
- title: string().optional(),
44580
- format: NotificationFormatSchema.default("text"),
44581
- priority: number().int().min(1).max(5).default(3),
44582
- level: string().optional(),
44583
- attachments: array(AttachmentSchema).optional(),
44584
- clickUrl: string().optional(),
44585
- actions: array(NotificationActionSchema).optional(),
44586
- sound: string().optional(),
44587
- ttl: number().optional(),
44588
- tag: string().optional(),
44589
- deviceId: number().optional(),
44590
- eventId: string().optional(),
44591
- metadata: record(string(), unknown()).optional(),
44592
- /**
44593
- * Values for the parameters the target's kind DECLARES in
44594
- * {@link TargetKindSchema} `deliveryParams`. Already validated against the
44595
- * declaration by the sender; a notifier reads only the keys it declared.
44596
- */
44597
- delivery: record(string(), DeliveryParamValueSchema).optional()
44598
- });
44599
- /** One declared native severity/priority level for a kind. */
44600
- var TargetKindLevelSchema = object({
44601
- id: string(),
44602
- label: string(),
44603
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
44604
- ordinal: number().int().min(1).max(5).nullable(),
44605
- flags: object({
44606
- critical: boolean().optional(),
44607
- silent: boolean().optional(),
44608
- noPush: boolean().optional()
44609
- }).optional(),
44610
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
44611
- requires: array(string()).optional(),
44612
- description: string().optional()
44613
- });
44614
- /** The full capability block consulted before dispatch. */
44615
- var TargetKindCapsSchema = object({
44616
- attachments: object({
44617
- mediaTypes: array(AttachmentMediaTypeSchema),
44618
- mode: _enum([
44619
- "url",
44620
- "bytes",
44621
- "both"
44622
- ]),
44623
- max: number().int().nonnegative(),
44624
- maxBytes: number().int().positive().optional()
44625
- }),
44626
- /** Max action buttons (0 = none). */
44627
- actions: number().int().nonnegative(),
44628
- /**
44629
- * Whether this kind renders a per-action ICON.
44630
- *
44631
- * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
44632
- * run on the addon cap path — three production failures in one day taught
44633
- * this repo that once. Absent is read as false by the degrade engine, which
44634
- * is the safe direction: an icon that is not rendered costs nothing, an icon
44635
- * assumed and dropped costs the operator's trust in the field.
44636
- */
44637
- actionIcons: boolean().optional(),
44638
- levels: array(TargetKindLevelSchema),
44639
- format: array(NotificationFormatSchema),
44640
- clickUrl: boolean(),
44641
- sound: boolean(),
44642
- ttl: boolean(),
44643
- bodyMaxLen: number().int().positive()
44644
- });
44645
- /**
44646
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
44647
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
44648
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
44649
- * the union is large and not meant for runtime validation here; the exported
44650
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
44651
- */
44652
- var ConfigSchemaPassthrough$4 = unknown();
44653
- var TargetKindSchema = object({
44654
- kind: string(),
44655
- label: string(),
44656
- icon: string(),
44657
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
44658
- addonId: string(),
44659
- /**
44660
- * URL of the kind's bundled BRAND icon, served by the providing addon over
44661
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
44662
- * when the addon bundles no icon for that kind — the client then falls back
44663
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
44664
- *
44665
- * Root-relative on purpose: it resolves against whatever origin serves a web
44666
- * client, and a native client joins it onto its own hub base.
44667
- *
44668
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
44669
- * field that survived only because the runtime cap-router forwards provider
44670
- * output verbatim — so every consumer had to re-declare it by hand to stop
44671
- * its own Zod parse from stripping it, and the whole arrangement would have
44672
- * broken silently the moment output validation was tightened anywhere.
44673
- */
44674
- iconUrl: string().optional(),
44675
- /**
44676
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
44677
- *
44678
- * The server knows this and therefore says it, because the client cannot
44679
- * safely guess: a React-Native client renders SVG and raster through two
44680
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
44681
- * not decode SVG on iOS/Android), so without this it silently fell back to a
44682
- * placeholder glyph for every vector icon while the web build looked fine.
44683
- *
44684
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
44685
- * not been updated — a client that cannot determine the type should prefer
44686
- * its raster path, which is the safe default for an unknown image.
44687
- */
44688
- iconMediaType: string().optional(),
44689
- configSchema: ConfigSchemaPassthrough$4,
44690
- /**
44691
- * The config keys that decide WHERE this kind's secrets are sent (server,
44692
- * base URL, instance, chat, recipient). A save that changes any of them
44693
- * while leaving a secret redacted is refused by the provider (D634), so an
44694
- * editor uses this to ask for the secret on exactly those edits instead of
44695
- * on every edit.
44696
- *
44697
- * Optional: absent from a provider that holds no secret (the Home Assistant
44698
- * kinds) and from a provider older than this field — an editor that finds
44699
- * it absent must assume ANY edit may need the secret.
44700
- */
44701
- destinationKeys: array(string()).optional(),
44702
- supportsDiscovery: boolean(),
44703
- /**
44704
- * Extra delivery parameters a rule may set for a target of this kind (D938).
44705
- * Absent = none; the rule editor then offers nothing.
44706
- */
44707
- deliveryParams: array(DeliveryParamSchema).optional(),
44708
- caps: TargetKindCapsSchema
44709
- });
44710
- /**
44711
- * A persisted target. `config` holds secrets; providers REDACT secret fields
44712
- * (return a presence marker only) when serving `listTargets` — never
44713
- * round-trip a stored secret to the UI.
44714
- */
44715
- var TargetSchema = object({
44716
- id: string(),
44717
- name: string(),
44718
- kind: string(),
44719
- addonId: string(),
44720
- enabled: boolean(),
44721
- config: record(string(), unknown())
44722
- });
44723
- /** A discovery-surfaced candidate (config is partial + non-secret). */
44724
- var DiscoveredTargetSchema = object({
44725
- kind: string(),
44726
- suggestedName: string(),
44727
- config: record(string(), unknown())
44728
- });
44729
- /** The degrade engine's report — what was resolved / dropped / degraded. */
44730
- var RenderedAsSchema = object({
44731
- level: string(),
44732
- format: NotificationFormatSchema,
44733
- attachmentsSent: number().int().nonnegative(),
44734
- actionsSent: number().int().nonnegative(),
44735
- truncated: boolean(),
44736
- dropped: array(string())
44737
- });
44738
- var SendResultSchema = object({
44739
- success: boolean(),
44740
- error: string().optional(),
44741
- renderedAs: RenderedAsSchema.optional()
44742
- });
44743
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
44744
- var TestResultSchema = SendResultSchema;
44745
- /**
44746
- * Pre-save credential probe against a partial (unsaved) target config.
44747
- * `resolvedUrl` never includes secrets (token stripped from path).
44748
- */
44749
- var ProbeTargetConfigResultSchema = object({
44750
- ok: boolean(),
44751
- resolvedUrl: string(),
44752
- httpStatus: number().int().optional(),
44753
- stateInstance: string().optional(),
44754
- error: string().optional()
44755
- });
44756
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({ targetId: string() }), TargetSchema, { auth: "admin" }), method(object({
44757
- kind: string(),
44758
- config: record(string(), unknown()).optional(),
44759
- /**
44760
- * When editing / duplicating, the blank password field never round-trips
44761
- * the stored token. Pass the source target id so the provider can restore
44762
- * secrets server-side before dialling (same merge as upsert).
44763
- */
44764
- targetId: string().optional()
44765
- }), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
44766
- kind: string(),
44767
- config: record(string(), unknown()),
44768
- /** See `discoverTargets.targetId` — restore stored secrets for a blank password. */
44769
- targetId: string().optional()
44770
- }), ProbeTargetConfigResultSchema, {
44771
- kind: "mutation",
44772
- auth: "admin"
44773
- }), method(object({
44774
- targetId: string(),
44775
- notification: NotificationSchema
44776
- }), SendResultSchema, {
44777
- kind: "mutation",
44778
- auth: "admin"
44779
- }), method(object({
44780
- targetId: string(),
44781
- sample: NotificationSchema.optional()
44782
- }), TestResultSchema, {
44783
- kind: "mutation",
44784
- auth: "admin"
44785
- }), method(object({
44786
- target: TargetSchema,
44787
- /**
44788
- * Duplicate flow: create a NEW target id while copying secrets from an
44789
- * existing row. The UI never holds the stored token; blank / redacted
44790
- * password fields are restored from this source (D634 still applies if
44791
- * destinations diverge without a re-typed secret).
44792
- */
44793
- cloneSecretsFrom: string().optional()
44794
- }), TargetSchema, {
44795
- kind: "mutation",
44796
- auth: "admin"
44797
- }), method(object({ targetId: string() }), _void(), {
44798
- kind: "mutation",
44799
- auth: "admin"
44800
- }), method(object({
44801
- targetId: string(),
44802
- enabled: boolean()
44803
- }), _void(), {
44804
- kind: "mutation",
44805
- auth: "admin"
44806
- });
44807
44836
  _enum([
44808
44837
  "automation",
44809
44838
  "action",
@@ -49343,37 +49372,6 @@ method(object({
49343
49372
  kind: "mutation",
49344
49373
  auth: "admin"
49345
49374
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
49346
- var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
49347
- object({
49348
- outcome: literal("validated"),
49349
- /** Round-trip of the sign-in, when the provider measured it. */
49350
- latencyMs: number().nonnegative().optional(),
49351
- /** Optional human detail worth showing next to the tick
49352
- * ("3 devices visible on this account"). */
49353
- detail: string().optional()
49354
- }).strict(),
49355
- object({
49356
- outcome: literal("rejected"),
49357
- error: string()
49358
- }).strict(),
49359
- object({
49360
- outcome: literal("inconclusive"),
49361
- error: string()
49362
- }).strict()
49363
- ]);
49364
- var ConnectionTestInputSchema = object({
49365
- /** Candidate integration settings, exactly as the create form collected them. */
49366
- settings: record(string(), unknown()) });
49367
- /**
49368
- * What the provider's test actually DOES, so the UI can say it in words before
49369
- * the operator presses the button ("Signs in to the Dreo cloud"). Purely
49370
- * descriptive — it never changes routing.
49371
- */
49372
- var ConnectionTestDescriptorSchema = object({ label: string() });
49373
- method(ConnectionTestInputSchema, ConnectionTestOutcomeSchema, {
49374
- kind: "mutation",
49375
- auth: "admin"
49376
- }), method(_void(), ConnectionTestDescriptorSchema, { auth: "admin" });
49377
49375
  /**
49378
49376
  * Upstream-system connectivity sensor — distinct from `device-status`,
49379
49377
  * which is the kernel-managed online/offline flag for the device's
@@ -56548,10 +56546,9 @@ var AvailableIntegrationTypeSchema = object({
56548
56546
  * locations" checkbox. Provider-declared in the addon manifest. */
56549
56547
  supportsLocationImport: boolean(),
56550
56548
  /**
56551
- * True when this integration DECLARES a pre-creation test (the
56552
- * `connection-test` cap, or a broker whose settings it stores). Drives the
56553
- * Test button: an integration that cannot be tested must say so up front
56554
- * rather than offering a button that always answers the same nonsense.
56549
+ * True when this integration's instances declare a Test (`integration-instances.describe().canTest`,
56550
+ * D1016). An integration that cannot be tested must say so up front rather than offering a
56551
+ * button that always answers the same nonsense.
56555
56552
  */
56556
56553
  canTest: boolean(),
56557
56554
  existingInstances: array(object({
@@ -56605,7 +56602,9 @@ var CreateIntegrationInputSchema = object({
56605
56602
  info: record(string(), unknown()).optional(),
56606
56603
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56607
56604
  originStableId: string().optional(),
56608
- settings: record(string(), unknown()).optional()
56605
+ settings: record(string(), unknown()).optional(),
56606
+ /** The instance kind being created, for a provider with several; omitted = its only testable kind. */
56607
+ kind: string().min(1).optional()
56609
56608
  });
56610
56609
  var UpdateIntegrationInputSchema = object({
56611
56610
  id: string(),
@@ -56631,13 +56630,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56631
56630
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56632
56631
  kind: "mutation",
56633
56632
  auth: "admin"
56634
- }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56635
- addonId: string(),
56636
- settings: record(string(), unknown())
56637
- }), TestConnectionResultSchema, {
56638
- kind: "mutation",
56639
- auth: "admin"
56640
- });
56633
+ }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" });
56641
56634
  /**
56642
56635
  * `integration-instances` - the connection of an addon that holds ONE or SEVERAL
56643
56636
  * (an account, a relay, a destination) as typed records (D985, D986). The addon's
@@ -65380,13 +65373,6 @@ var AddBrokerInputSchema = object({
65380
65373
  });
65381
65374
  var AddBrokerResultSchema = object({ id: string() });
65382
65375
  var IdInputSchema = object({ id: string() });
65383
- var TestResultSchema$1 = discriminatedUnion("ok", [object({
65384
- ok: literal(true),
65385
- latencyMs: number()
65386
- }), object({
65387
- ok: literal(false),
65388
- error: string()
65389
- })]);
65390
65376
  var StartEmbeddedInputSchema = object({
65391
65377
  port: number().int().min(1).max(65535).default(1883),
65392
65378
  /** Allow anonymous connect (no username/password). Default: false. */
@@ -65409,9 +65395,6 @@ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnection
65409
65395
  }), method(IdInputSchema, _void(), {
65410
65396
  kind: "mutation",
65411
65397
  auth: "admin"
65412
- }), method(IdInputSchema, TestResultSchema$1, {
65413
- kind: "mutation",
65414
- auth: "admin"
65415
65398
  }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
65416
65399
  kind: "mutation",
65417
65400
  auth: "admin"
@@ -78518,7 +78501,7 @@ var TrainingProviderTypeSchema = object({
78518
78501
  label: string(),
78519
78502
  /** Path under the addon package (e.g. `assets/training-modal/icon.svg`). */
78520
78503
  iconAsset: string(),
78521
- /** Owning `training-provider` addon — collection selector for createEntry. */
78504
+ /** Owning `training-provider` addon. */
78522
78505
  addonId: string(),
78523
78506
  /**
78524
78507
  * Fields for one entry of this type (credentials + defaults). Absent on a
@@ -78563,36 +78546,6 @@ var TrainingProviderDescriptorSchema = object({
78563
78546
  */
78564
78547
  formValues: record(string(), unknown()).optional()
78565
78548
  });
78566
- /**
78567
- * Patch one entry's name, credentials and defaults. Pin with owning `{ addonId }`.
78568
- * An omitted or empty `tokenSecret` means KEEP the stored secret (D787).
78569
- * `{ $secretClear: true }` clears it.
78570
- */
78571
- var TrainingProviderUpdateEntryInputSchema = object({
78572
- entryId: string().min(1),
78573
- name: string().min(1),
78574
- tokenId: string().optional(),
78575
- tokenSecret: union([string(), object({ $secretClear: literal(true) })]).optional(),
78576
- environment: string().optional(),
78577
- gpuType: string().optional(),
78578
- region: string().optional(),
78579
- costCapUsd: number().nonnegative().optional(),
78580
- maxRuntimeSec: number().int().positive().optional(),
78581
- failedDatasetGraceHours: number().nonnegative().optional()
78582
- });
78583
- var TrainingCredentialCheckSchema = object({
78584
- ok: boolean(),
78585
- /** Workspace / account name, when the provider says. */
78586
- account: string().optional(),
78587
- reason: _enum([
78588
- "missing",
78589
- "invalid",
78590
- "forbidden",
78591
- "network",
78592
- "quota"
78593
- ]).optional(),
78594
- detail: string().optional()
78595
- });
78596
78549
  var ProviderJobStateSchema = _enum([
78597
78550
  "pending",
78598
78551
  "starting",
@@ -78640,22 +78593,6 @@ var TrainingLogLineSchema = object({
78640
78593
  text: string()
78641
78594
  });
78642
78595
  method(object({}), array(TrainingProviderDescriptorSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderTypeSchema).readonly(), { auth: "admin" }), method(object({}), array(TrainingProviderEntrySchema).readonly(), { auth: "admin" }), method(object({
78643
- typeId: string().min(1),
78644
- name: string().min(1)
78645
- }), object({ entryId: string() }), {
78646
- kind: "mutation",
78647
- auth: "admin"
78648
- }), method(object({ entryId: string().min(1) }), object({ ok: literal(true) }), {
78649
- kind: "mutation",
78650
- auth: "admin"
78651
- }), method(TrainingProviderUpdateEntryInputSchema, object({ ok: literal(true) }), {
78652
- kind: "mutation",
78653
- auth: "admin"
78654
- }), method(object({ entryId: string().min(1) }), TrainingCredentialCheckSchema, {
78655
- kind: "mutation",
78656
- auth: "admin",
78657
- timeoutMs: 3e4
78658
- }), method(object({
78659
78596
  entryId: string().min(1),
78660
78597
  gpuType: string(),
78661
78598
  region: string(),
@@ -84108,20 +84045,6 @@ Object.freeze({
84108
84045
  access: "view",
84109
84046
  role: "admin"
84110
84047
  },
84111
- "connectionTest.describeTest": {
84112
- capName: "connection-test",
84113
- capScope: "system",
84114
- addonId: null,
84115
- access: "view",
84116
- role: "admin"
84117
- },
84118
- "connectionTest.testSettings": {
84119
- capName: "connection-test",
84120
- capScope: "system",
84121
- addonId: null,
84122
- access: "create",
84123
- role: "admin"
84124
- },
84125
84048
  "connectivity.getStatus": {
84126
84049
  capName: "connectivity",
84127
84050
  capScope: "device",
@@ -86488,13 +86411,6 @@ Object.freeze({
86488
86411
  access: "view",
86489
86412
  role: "admin"
86490
86413
  },
86491
- "integrations.testConnection": {
86492
- capName: "integrations",
86493
- capScope: "system",
86494
- addonId: null,
86495
- access: "create",
86496
- role: "admin"
86497
- },
86498
86414
  "integrations.update": {
86499
86415
  capName: "integrations",
86500
86416
  capScope: "system",
@@ -87727,13 +87643,6 @@ Object.freeze({
87727
87643
  access: "create",
87728
87644
  role: "admin"
87729
87645
  },
87730
- "mqttBroker.testConnection": {
87731
- capName: "mqtt-broker",
87732
- capScope: "system",
87733
- addonId: null,
87734
- access: "create",
87735
- role: "admin"
87736
- },
87737
87646
  "nativeObjectDetection.getOptions": {
87738
87647
  capName: "native-object-detection",
87739
87648
  capScope: "device",
@@ -92564,20 +92473,6 @@ Object.freeze({
92564
92473
  access: "create",
92565
92474
  role: "admin"
92566
92475
  },
92567
- "trainingProvider.createEntry": {
92568
- capName: "training-provider",
92569
- capScope: "system",
92570
- addonId: null,
92571
- access: "create",
92572
- role: "admin"
92573
- },
92574
- "trainingProvider.deleteEntry": {
92575
- capName: "training-provider",
92576
- capScope: "system",
92577
- addonId: null,
92578
- access: "delete",
92579
- role: "admin"
92580
- },
92581
92476
  "trainingProvider.describe": {
92582
92477
  capName: "training-provider",
92583
92478
  capScope: "system",
@@ -92648,13 +92543,6 @@ Object.freeze({
92648
92543
  access: "create",
92649
92544
  role: "admin"
92650
92545
  },
92651
- "trainingProvider.updateEntry": {
92652
- capName: "training-provider",
92653
- capScope: "system",
92654
- addonId: null,
92655
- access: "create",
92656
- role: "admin"
92657
- },
92658
92546
  "trainingProvider.uploadDataset": {
92659
92547
  capName: "training-provider",
92660
92548
  capScope: "system",
@@ -92662,13 +92550,6 @@ Object.freeze({
92662
92550
  access: "create",
92663
92551
  role: "admin"
92664
92552
  },
92665
- "trainingProvider.validateCredentials": {
92666
- capName: "training-provider",
92667
- capScope: "system",
92668
- addonId: null,
92669
- access: "create",
92670
- role: "admin"
92671
- },
92672
92553
  "turnProvider.getTurnServers": {
92673
92554
  capName: "turn-provider",
92674
92555
  capScope: "system",