@camstack/addon-provider-rtsp 1.2.150 → 1.2.152

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -6005,7 +6005,7 @@ var ZodIssueCode = {
6005
6005
  var ZodFirstPartyTypeKind;
6006
6006
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6007
6007
  //#endregion
6008
- //#region ../types/dist/sleep-Cv4Zmie9.mjs
6008
+ //#region ../types/dist/sleep-BX_MqzUe.mjs
6009
6009
  /**
6010
6010
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6011
6011
  * window to float samples (D455).
@@ -6814,6 +6814,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6814
6814
  }
6815
6815
  return out;
6816
6816
  }
6817
+ /** Every scope, in the order the guard and the docs list them. */
6818
+ var SCOPED_SETTINGS_SCOPES = [
6819
+ "cluster",
6820
+ "device-defaults",
6821
+ "node"
6822
+ ];
6817
6823
  /**
6818
6824
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6819
6825
  *
@@ -7628,6 +7634,28 @@ var SECRET_RETRY_MAX_MS = 5 * 6e4;
7628
7634
  /** Coalesces a burst of `keychain.changed` events into one re-resolve. */
7629
7635
  var SECRET_CHANGE_DEBOUNCE_MS = 250;
7630
7636
  /**
7637
+ * Pair a catalogue with the function that builds its ports FROM THE MIGRATION API. The one
7638
+ * place a catalogue is bound: a migration reaches nothing a ports binder cannot reach
7639
+ * through `api` (guard `check-migration-ports-surface`).
7640
+ */
7641
+ function migrationSet(catalogue, bindPorts) {
7642
+ return { bind: (api) => defineAddonMigrations(catalogue, bindPorts(api)) };
7643
+ }
7644
+ /** A mixed array is a mistake: the other kind would be dropped without a word. */
7645
+ function refuseMixed(form, kept, dropped) {
7646
+ if (dropped > 0) throw new Error(`runMigrations(${form}): ${dropped} of ${kept + dropped} entries are of the other kind and would be dropped; pass one kind per call`);
7647
+ }
7648
+ function catalogueOf(list) {
7649
+ const kept = list.filter((entry) => !("execute" in entry));
7650
+ refuseMixed("catalogue, (api) => ports", kept.length, list.length - kept.length);
7651
+ return kept;
7652
+ }
7653
+ function boundOf(list) {
7654
+ const kept = list.filter((entry) => "execute" in entry);
7655
+ refuseMixed("bound migrations", kept.length, list.length - kept.length);
7656
+ return kept;
7657
+ }
7658
+ /**
7631
7659
  * Base class for CamStack addons. Eliminates settings boilerplate:
7632
7660
  *
7633
7661
  * - Typed `config` property with automatic resolution from store + defaults
@@ -7705,6 +7733,13 @@ var BaseAddon = class {
7705
7733
  */
7706
7734
  secretStatePaths = [];
7707
7735
  _addonSecretTree = null;
7736
+ /**
7737
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7738
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7739
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7740
+ */
7741
+ _scopedByCap = /* @__PURE__ */ new Map();
7742
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7708
7743
  _deviceSecretTree = null;
7709
7744
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7710
7745
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7774,6 +7809,7 @@ var BaseAddon = class {
7774
7809
  this._ctx = context;
7775
7810
  this._secretSettings = null;
7776
7811
  }
7812
+ await this.loadScopedSchemas();
7777
7813
  await this.resolveConfig();
7778
7814
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7779
7815
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7783,25 +7819,24 @@ var BaseAddon = class {
7783
7819
  const normalized = normalizeAddonInitResult(result);
7784
7820
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7785
7821
  this._registeredCapNames = providers.map((p) => p.capability.name);
7822
+ await this.loadScopedSchemas();
7786
7823
  return normalized;
7787
7824
  }
7825
+ async runMigrations(first, second, third) {
7826
+ if (typeof second === "function") return this.runMigrationSets([migrationSet(catalogueOf(first), second)], third);
7827
+ return this.runBound(boundOf(first), second);
7828
+ }
7788
7829
  /**
7789
- * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
7790
- * engine. Call it at the point of the boot where the data must already have the new
7791
- * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
7792
- * restored devices — with the catalogue bound to the addon's ports
7793
- * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
7794
- *
7795
- * An engine that cannot be reached or refuses the catalogue is logged and runs
7796
- * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7797
- * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7798
- * serve data whose shape it cannot trust; `tolerate` migrations never do.
7799
- *
7800
- * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7801
- * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7802
- * omitted applied ids "newer").
7830
+ * Several catalogues of ONE owner in one declaration (the engine replaces an owner's
7831
+ * catalogue on every declare, so two calls would each judge the other's ids "newer").
7832
+ * Build each entry with `migrationSet(catalogue, (api) => ports)`.
7803
7833
  */
7804
- async runMigrations(migrations, options) {
7834
+ async runMigrationSets(sets, options) {
7835
+ const api = this.ctx.migrationApi;
7836
+ if (api === void 0) throw new Error(`${this.ctx.id}: the migration API is not available on this context`);
7837
+ return this.runBound(sets.flatMap((set) => set.bind(api)), options);
7838
+ }
7839
+ async runBound(migrations, options) {
7805
7840
  const api = this.ctx.api.migrations;
7806
7841
  const report = await runAddonMigrations({
7807
7842
  declareOwner: (input) => api.declareOwner.mutate(input),
@@ -7871,6 +7906,16 @@ var BaseAddon = class {
7871
7906
  schema(schema) {
7872
7907
  return schema;
7873
7908
  }
7909
+ /**
7910
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7911
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7912
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7913
+ * must already know which keys are per node. Names the providers register at init are added
7914
+ * once known.
7915
+ */
7916
+ scopedSettingsCapabilities() {
7917
+ return [];
7918
+ }
7874
7919
  /** Override to provide global-level settings UI schema. */
7875
7920
  globalSettingsSchema(_cap) {
7876
7921
  return null;
@@ -7914,7 +7959,8 @@ var BaseAddon = class {
7914
7959
  return [];
7915
7960
  }
7916
7961
  async getGlobalSettings(overlay, cap, nodeId) {
7917
- const schema = this.globalSettingsSchema(cap);
7962
+ await this.loadScopedSchemas(cap);
7963
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7918
7964
  if (!schema) return { sections: [] };
7919
7965
  const projected = await this.readRawProjectedStore(nodeId, cap);
7920
7966
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -7949,10 +7995,9 @@ var BaseAddon = class {
7949
7995
  *
7950
7996
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7951
7997
  * needs its connection fields, the default integration the addon's own sections. It is the
7952
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7953
- * unchanged (`integrations.setSettings` for an account integration).
7998
+ * SHAPE of the form only — the values live on the instance record.
7954
7999
  */
7955
- async integrationSettingsSchema(_instance) {
8000
+ async integrationFormSchema(_instance) {
7956
8001
  return null;
7957
8002
  }
7958
8003
  /**
@@ -7998,7 +8043,7 @@ var BaseAddon = class {
7998
8043
  * per-instance schema when it declares one, else its global schema (what they always showed).
7999
8044
  */
8000
8045
  async getIntegrationFormSchema(instance, nodeId) {
8001
- const scoped = await this.integrationSettingsSchema(instance);
8046
+ const scoped = await this.integrationFormSchema(instance);
8002
8047
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8003
8048
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8004
8049
  }
@@ -8046,6 +8091,7 @@ var BaseAddon = class {
8046
8091
  * resolved blob back is a no-op for every unchanged secret.
8047
8092
  */
8048
8093
  async resolveGlobalStore(nodeId, cap) {
8094
+ await this.loadScopedSchemas(cap);
8049
8095
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8050
8096
  return this.projectForNode(raw, nodeId, cap);
8051
8097
  }
@@ -8056,6 +8102,7 @@ var BaseAddon = class {
8056
8102
  }
8057
8103
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8058
8104
  async readRawProjectedStore(nodeId, cap) {
8105
+ await this.loadScopedSchemas(cap);
8059
8106
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8060
8107
  return this.projectForNode(raw, nodeId, cap);
8061
8108
  }
@@ -8158,6 +8205,7 @@ var BaseAddon = class {
8158
8205
  await this.applyGlobalPatch(patch, nodeId);
8159
8206
  }
8160
8207
  async applyGlobalPatch(barePatch, nodeId) {
8208
+ await this.loadScopedSchemas();
8161
8209
  const keys = this.perNodeKeys();
8162
8210
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8163
8211
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8171,7 +8219,8 @@ var BaseAddon = class {
8171
8219
  await this.resolveConfig();
8172
8220
  await this.onConfigChanged();
8173
8221
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8174
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8222
+ const legacy = this.globalSettingsSchema();
8223
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8175
8224
  }
8176
8225
  /**
8177
8226
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8179,14 +8228,67 @@ var BaseAddon = class {
8179
8228
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8180
8229
  * settings API behaves exactly like the legacy node-agnostic one.
8181
8230
  */
8182
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8231
+ /**
8232
+ * Load the scoped schemas of every capability this addon declares (the override and the
8233
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8234
+ * derived from the previous set. Idempotent.
8235
+ */
8236
+ async loadScopedSchemas(extraCap) {
8237
+ const caps = new Set([
8238
+ ...this.scopedSettingsCapabilities(),
8239
+ ...this._registeredCapNames,
8240
+ ...extraCap === void 0 ? [] : [extraCap]
8241
+ ]);
8242
+ let added = false;
8243
+ for (const cap of caps) {
8244
+ if (this._scopedByCap.has(cap)) continue;
8245
+ const loaded = [];
8246
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8247
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8248
+ if (schema !== null) loaded.push({
8249
+ scope,
8250
+ schema
8251
+ });
8252
+ }
8253
+ this._scopedByCap.set(cap, loaded);
8254
+ added = true;
8255
+ }
8256
+ if (!added) return;
8257
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8258
+ this._addonSecretTree = null;
8259
+ }
8260
+ /**
8261
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8262
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8263
+ */
8264
+ scopedFormSchema(cap) {
8265
+ const byId = /* @__PURE__ */ new Map();
8266
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8267
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8268
+ }
8269
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8270
+ loadedScopedSchemas(cap) {
8271
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8272
+ return [...this._scopedByCap.values()].flat();
8273
+ }
8274
+ /**
8275
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8276
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8277
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8278
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8279
+ * Call {@link loadScopedSchemas} first (the async seams do).
8280
+ */
8183
8281
  perNodeKeys(cap) {
8184
8282
  const cacheKey = cap ?? "";
8185
- const cached = this._perNodeKeysCache.get(cacheKey);
8283
+ const cached = this._nodeKeysByCap.get(cacheKey);
8186
8284
  if (cached) return cached;
8187
- const schema = this.globalSettingsSchema(cap);
8188
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8189
- this._perNodeKeysCache.set(cacheKey, keys);
8285
+ const legacy = this.globalSettingsSchema(cap);
8286
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8287
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8288
+ if (scope !== "node") continue;
8289
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8290
+ }
8291
+ this._nodeKeysByCap.set(cacheKey, keys);
8190
8292
  return keys;
8191
8293
  }
8192
8294
  /**
@@ -8196,10 +8298,9 @@ var BaseAddon = class {
8196
8298
  * time to return its response before the addon is torn down and
8197
8299
  * re-initialised by `AddonRegistryService.restartAddon`.
8198
8300
  */
8199
- maybeAutoRestart(patchKeys, schema) {
8200
- if (!schema) return;
8301
+ maybeAutoRestart(patchKeys, schemas) {
8201
8302
  const restartKeys = /* @__PURE__ */ new Set();
8202
- for (const section of schema.sections) for (const field of section.fields) {
8303
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8203
8304
  if (field.type === "separator" || field.type === "info") continue;
8204
8305
  if (field.requiresRestart) restartKeys.add(field.key);
8205
8306
  }
@@ -8395,12 +8496,14 @@ var BaseAddon = class {
8395
8496
  * here, in the addon's own process, so the hub keeps no copy of it.
8396
8497
  */
8397
8498
  async getDeclaredSettingsKeys() {
8499
+ await this.loadScopedSchemas();
8398
8500
  return { keys: [...this.declaredKeySet()].toSorted() };
8399
8501
  }
8400
8502
  declaredKeySet() {
8401
8503
  const keys = new Set(Object.keys(this.defaults));
8402
8504
  const schema = this.globalSettingsSchema();
8403
8505
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8506
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8404
8507
  for (const key of this._declaredStateKeys) keys.add(key);
8405
8508
  for (const path of this.secretStatePaths) {
8406
8509
  const root = path.split(/[.[]/, 1)[0];
@@ -8496,7 +8599,11 @@ var BaseAddon = class {
8496
8599
  }
8497
8600
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8498
8601
  addonSecretTree() {
8499
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8602
+ if (this._addonSecretTree === null) {
8603
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8604
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8605
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8606
+ }
8500
8607
  return this._addonSecretTree;
8501
8608
  }
8502
8609
  deviceSecretTree() {
@@ -8746,6 +8853,23 @@ function dropPerNodeFields(fields) {
8746
8853
  }
8747
8854
  return kept;
8748
8855
  }
8856
+ /** The keys of the value-holding fields, recursing into layout containers. */
8857
+ function collectValueFieldKeys(fields) {
8858
+ const collected = [];
8859
+ for (const field of fields) {
8860
+ if (field.type === "group") {
8861
+ collected.push(...collectValueFieldKeys(field.fields));
8862
+ continue;
8863
+ }
8864
+ if (field.type === "sub-tabs") {
8865
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8866
+ continue;
8867
+ }
8868
+ if (field.type === "separator" || field.type === "info") continue;
8869
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8870
+ }
8871
+ return collected;
8872
+ }
8749
8873
  function collectFieldKeys(fields) {
8750
8874
  const collected = [];
8751
8875
  for (const field of fields) {
@@ -9288,6 +9412,7 @@ function defineEntryKind(name, spec) {
9288
9412
  class: "config",
9289
9413
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9290
9414
  retention: { none: "an entry goes only when the operator deletes it" },
9415
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9291
9416
  ...spec.database === void 0 ? {} : { database: spec.database },
9292
9417
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9293
9418
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -12046,7 +12171,7 @@ function maskUrlCredentials(rawUrl) {
12046
12171
  }
12047
12172
  }
12048
12173
  //#endregion
12049
- //#region ../types/dist/composition-CRNwcFYY.mjs
12174
+ //#region ../types/dist/composition-pOHbTzRB.mjs
12050
12175
  /**
12051
12176
  * The ffmpeg build every CamStack node downloads, per platform and arch.
12052
12177
  *
@@ -13527,7 +13652,7 @@ var RecordIntegrationInstances = class {
13527
13652
  return record === null ? null : this.viewOf(record);
13528
13653
  };
13529
13654
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
13530
- createInstance = async ({ id, name, kind }) => {
13655
+ createInstance = async ({ id, name, kind, values }) => {
13531
13656
  const minted = id ?? this.hooks.mintId?.({
13532
13657
  name,
13533
13658
  ...kind !== void 0 ? { kind } : {}
@@ -13537,10 +13662,20 @@ var RecordIntegrationInstances = class {
13537
13662
  id: minted,
13538
13663
  data: this.hooks.initialBody(name, kind)
13539
13664
  }, { ifRev: 0 });
13665
+ const patch = this.patchOf(values ?? {});
13666
+ if (Object.keys(patch).length > 0) await this.handle.patch(minted, patch);
13540
13667
  await this.hooks.onChanged?.(minted, "created");
13541
13668
  return { id: minted };
13542
13669
  };
13543
13670
  updateInstance = async ({ id, name, values }) => {
13671
+ const patch = this.patchOf(values);
13672
+ if (name !== void 0) patch[this.nameField] = name;
13673
+ await this.handle.patch(id, patch);
13674
+ await this.hooks.onChanged?.(id, "updated");
13675
+ return { ok: true };
13676
+ };
13677
+ /** The record patch a form `values` object means: secrets kept, cleared or replaced. */
13678
+ patchOf(values) {
13544
13679
  const patch = {};
13545
13680
  for (const [key, value] of Object.entries(values)) {
13546
13681
  if (value === void 0) continue;
@@ -13551,11 +13686,8 @@ var RecordIntegrationInstances = class {
13551
13686
  else if (isSecretClear(value)) patch[key] = null;
13552
13687
  else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
13553
13688
  }
13554
- if (name !== void 0) patch[this.nameField] = name;
13555
- await this.handle.patch(id, patch);
13556
- await this.hooks.onChanged?.(id, "updated");
13557
- return { ok: true };
13558
- };
13689
+ return patch;
13690
+ }
13559
13691
  /** The switch lives in the record body; the addon is told so it applies it (D996). */
13560
13692
  setInstanceEnabled = async ({ id, enabled }) => {
13561
13693
  if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
@@ -14754,20 +14886,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14754
14886
  };
14755
14887
  }
14756
14888
  /**
14757
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14758
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14759
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14760
- * the new record through the addon's own records handle. The hub deletes the rows and their
14761
- * keychain values; the addon never touches the registry.
14762
- */
14889
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14890
+ * from its own process, so the leftover rows are reached through the migration-only door
14891
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14892
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14893
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14894
+ * door to delete the rows. The addon's own record kind is its records handle.
14895
+ */
14896
+ async function resolvedRows(stored, secrets) {
14897
+ if (secrets === null) return stored;
14898
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14899
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14900
+ return value;
14901
+ }
14763
14902
  function integrationRowsPortsOverApi(deps) {
14903
+ const secrets = deps.secrets ?? null;
14764
14904
  return {
14765
14905
  warn: (message) => deps.log.warn(message),
14766
14906
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14767
14907
  id: i.id,
14768
14908
  name: i.name
14769
14909
  })),
14770
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14910
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14771
14911
  putRecord: async (id, body) => {
14772
14912
  if (await deps.records.get(id) !== null) return "exists";
14773
14913
  try {
@@ -14786,7 +14926,9 @@ function integrationRowsPortsOverApi(deps) {
14786
14926
  await deps.records.delete(id);
14787
14927
  },
14788
14928
  clearRows: async (integrationId) => {
14789
- await deps.api.clearSettings({ id: integrationId });
14929
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14930
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14931
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14790
14932
  }
14791
14933
  };
14792
14934
  }
@@ -14848,13 +14990,13 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14848
14990
  };
14849
14991
  if (orphans.some((d) => (d.parentDeviceId ?? null) === null) && fallback === null) throw new Error(`${orphans.length} device(s) of '${spec.addonId}' carry no integration and the addon has no integration to claim them to`);
14850
14992
  for (const device of orphans) if (resolve(device, new Set([device.id])) === null) unresolved.add(device.id);
14851
- for (const id of unresolved) ports.warnDevice(`device ${id} of '${spec.addonId}' carries no integration and its owner chain does not resolve to one: left unstamped`, id);
14993
+ for (const id of unresolved) await ports.warnDevice(`device ${id} of '${spec.addonId}' carries no integration and its owner chain does not resolve to one: left unstamped`, id);
14852
14994
  for (const [deviceId, integrationId] of plan) await ports.setIntegrationId(deviceId, integrationId);
14853
14995
  const wrong = [];
14854
14996
  for (const [deviceId, integrationId] of plan) if (await ports.readIntegrationId(deviceId) !== integrationId) wrong.push(deviceId);
14855
14997
  if (wrong.length > 0) throw new Error(`device(s) ${wrong.join(", ")} did not read back their integration after the claim`);
14856
14998
  const claimed = plan.size;
14857
- ports.info(`${claimed} device(s) of '${spec.addonId}' claimed to their owner's integration or the default; ${unresolved.size} unresolved, ${reservations.size} reservation(s) skipped`);
14999
+ await ports.info(`${claimed} device(s) of '${spec.addonId}' claimed to their owner's integration or the default; ${unresolved.size} unresolved, ${reservations.size} reservation(s) skipped`);
14858
15000
  return {
14859
15001
  outcome: claimed > 0 ? "done" : "already-done",
14860
15002
  summary: {
@@ -14876,14 +15018,14 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14876
15018
  * 2. `claim-devices` (tolerate): `claimDevicesToDefaultIntegration`; with no integration to claim
14877
15019
  * to it fails and is retried on the next boot.
14878
15020
  *
14879
- * Both run in the addon process through the hub API (`ctx.api`), BEFORE the addon registers its
14880
- * `integration-instances` provider: while a provider is registered the hub answers `getSettings`
14881
- * from the record, not from the rows.
15021
+ * Both run in the addon process through the hub API (`ctx.api`). The leftover rows are read
15022
+ * through the migration-only door, which answers empty once the store is gone.
14882
15023
  */
14883
15024
  function defaultIntegrationPortsOver(deps) {
14884
15025
  return {
14885
15026
  ...integrationRowsPortsOverApi({
14886
15027
  api: deps.api,
15028
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14887
15029
  records: deps.records.kind({
14888
15030
  name: deps.kind.name,
14889
15031
  declaration: deps.kind.declaration,
@@ -14980,25 +15122,6 @@ function defaultIntegrationMigrations(spec) {
14980
15122
  ...spec.eligible !== void 0 ? { eligible: spec.eligible } : {}
14981
15123
  })];
14982
15124
  }
14983
- function defaultIntegrationApiOver(api) {
14984
- return {
14985
- list: () => api.integrations.list.query(),
14986
- getSettings: (input) => api.integrations.getSettings.query(input),
14987
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14988
- listDevices: (addonId) => api.deviceManager.listAll.query({
14989
- addonId,
14990
- projection: "slim"
14991
- }),
14992
- setDeviceIntegrationId: async (deviceId, integrationId) => {
14993
- await api.deviceManager.setIntegrationId.mutate({
14994
- deviceId,
14995
- integrationId
14996
- });
14997
- },
14998
- readDeviceHasConfig: async (deviceId) => Object.keys((await api.deviceManager.getDevice.query({ deviceId }))?.config ?? {}).length > 0,
14999
- readDeviceIntegrationId: async (deviceId) => (await api.deviceManager.getDevice.query({ deviceId }))?.integrationId ?? null
15000
- };
15001
- }
15002
15125
  /**
15003
15126
  * The trainer contract — what the hub hands the cloud trainer and what it
15004
15127
  * gets back (cloud training, D805+).
@@ -16119,6 +16242,39 @@ var UnstampedEventMediaCountSchema = object({
16119
16242
  /** Sum across both, or `null` when either lane could not be counted. */
16120
16243
  total: number().int().nonnegative().nullable()
16121
16244
  }).nullable();
16245
+ /**
16246
+ * Where the stored event media sits, per storage location: how many media rows
16247
+ * and how many bytes carry each location id, how many carry none (legacy,
16248
+ * resolved to the default `eventMedia` location), and how many name a location
16249
+ * that was not asked about (a deleted or never-listed one).
16250
+ *
16251
+ * One covering-index aggregate per asked location plus the NULL one, and one
16252
+ * unfiltered total that scans the covering index: O(rows), not a walk of the
16253
+ * rows themselves. `unlisted` is total minus the ASKED locations minus NULL, so
16254
+ * it is a deleted location only when every location id was asked.
16255
+ * `null` is "could not be measured", never zero.
16256
+ */
16257
+ var MediaLocationCensusInputSchema = object({ locationIds: array(string().min(1)).max(50).readonly() });
16258
+ var MediaLocationCensusSchema = object({
16259
+ locations: array(object({
16260
+ locationId: string(),
16261
+ rows: number().int().nonnegative(),
16262
+ bytes: number().nonnegative()
16263
+ })).readonly(),
16264
+ unstamped: object({
16265
+ rows: number().int().nonnegative(),
16266
+ bytes: number().nonnegative()
16267
+ }),
16268
+ /** Rows stamped with a location id that was not in the asked list. */
16269
+ unlisted: object({
16270
+ rows: number().int().nonnegative(),
16271
+ bytes: number().nonnegative()
16272
+ }),
16273
+ total: object({
16274
+ rows: number().int().nonnegative(),
16275
+ bytes: number().nonnegative()
16276
+ })
16277
+ });
16122
16278
  var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
16123
16279
  /** The independently selectable logical storage classes — every class
16124
16280
  * `storage.listLocationDeclarations` reports, so an operator never meets a
@@ -47155,52 +47311,7 @@ onBrightnessChanged: { data: object({
47155
47311
  minIntervalMs: 0
47156
47312
  } } }
47157
47313
  };
47158
- /**
47159
- * `broker` — unified pub/sub broker registry, system-scoped collection.
47160
- *
47161
- * The cap models any kind-tagged message broker the user wants to
47162
- * register and that other addons might consume. The first two kinds
47163
- * are MQTT (mosquitto / aedes embedded / cloud bridge) and
47164
- * Home Assistant (HA WebSocket — subscribe_entities + call_service).
47165
- * Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
47166
- * the same surface.
47167
- *
47168
- * Why one cap, not one cap per kind:
47169
- * - The integrations page wants a single table of "every broker"
47170
- * across kinds; a unified cap drives it without join logic.
47171
- * - Generic admin operations (add / remove / test / status) are
47172
- * identical across kinds — duplicating them per cap was busywork.
47173
- * - The `network-access` cap precedent: same interface, many
47174
- * `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
47175
- *
47176
- * What stays kind-specific:
47177
- * - `getBrokerConfig` payload (kind decides whether it returns
47178
- * mqtt URL+creds, HA baseUrl+token, …) — typed as
47179
- * `Record<string, unknown>` at the wire; consumers narrow.
47180
- * - `publish` / `subscribe` / `getState` arguments (target / filter /
47181
- * key) — also typed as `Record<string, unknown>` so each kind can
47182
- * evolve its surface (MQTT topic+qos vs HA entity_id+domain)
47183
- * without rebreaking the cap signature.
47184
- * - `add` `settings` payload (different fields per kind, validated by
47185
- * the kind-specific provider).
47186
- *
47187
- * Bidirectionality:
47188
- * - `publish` is RPC — synchronous result (provider-defined).
47189
- * - `subscribe` returns a subscription handle; the actual message
47190
- * stream flows over the typed event-bus as `broker.message` events
47191
- * keyed by `(brokerId, subscriptionId)`. Consumers filter in their
47192
- * event handler. Subscriptions persist across reconnects — the
47193
- * provider re-subscribes upstream after a transport drop.
47194
- *
47195
- * Why event-bus push (not a subscription RPC stream):
47196
- * - Matches the rest of CamStack's D8 contract — events for telemetry,
47197
- * RPC for loss-is-a-bug. Broker messages are telemetry (a single
47198
- * drop is recoverable via `getState({key})`).
47199
- * - Frees `broker.subscribe` from holding a long-lived RPC channel.
47200
- * - Cross-process: events already route through `$event-bus`; no
47201
- * per-broker wire-shaping needed.
47202
- */
47203
- var BrokerStatusEnum = _enum([
47314
+ _enum([
47204
47315
  "connected",
47205
47316
  "disconnected",
47206
47317
  "connecting",
@@ -47208,141 +47319,12 @@ var BrokerStatusEnum = _enum([
47208
47319
  "unreachable",
47209
47320
  "error"
47210
47321
  ]);
47211
- var BrokerInfoSchema = object({
47212
- /** Stable broker id. Persisted; survives addon restarts. */
47213
- id: string(),
47214
- /** Addon id of the provider that OWNS this broker.
47215
- *
47216
- * The `broker` cap is a system-scoped collection: several addons
47217
- * register a `broker` provider, each owning a DISJOINT set of
47218
- * brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
47219
- * `ha_*`). A broker therefore belongs to exactly one addon — like a
47220
- * device belongs to one integration. The admin UI threads this id
47221
- * back as the `{ addonId }` system-collection selector on every
47222
- * id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
47223
- * `testConnection`), so the call routes to the OWNING provider
47224
- * instead of defaulting to the first-registered one. */
47225
- addonId: string(),
47226
- /** Human-readable name (operator-chosen at add-time). */
47227
- name: string(),
47228
- /** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
47229
- kind: string(),
47230
- status: BrokerStatusEnum,
47231
- /** Free-form provider-specific info (HA version, MQTT broker
47232
- * flavour, latency, mTLS active, …). */
47233
- info: record(string(), unknown()),
47234
- /** Ms epoch of the last connection probe / status update. */
47235
- lastCheckedAt: number().nullable(),
47236
- /** Last error message, when `status` indicates failure. */
47237
- error: string().nullable()
47238
- });
47239
47322
  var RegistryStatusSchema = object({
47240
47323
  brokerCount: number().int().nonnegative(),
47241
47324
  connectedCount: number().int().nonnegative()
47242
47325
  });
47243
- /**
47244
- * One entry per `broker` provider — which addon provides which broker
47245
- * kind(s). The unified create picker reads this to know, for each
47246
- * (addonId, kind) pair the operator can pick, where to route the
47247
- * follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
47248
- * `{ addonId }` selector). A provider only advertises a kind here when
47249
- * it implements `getSettingsSchema` for that kind (so the picker never
47250
- * offers a kind whose creation form can't be rendered).
47251
- */
47252
- var BrokerProviderInfoSchema = object({
47253
- /** Addon id of the `broker` provider this entry describes. */
47254
- addonId: string(),
47255
- /** Broker kinds this provider can create, with a display label. */
47256
- kinds: array(object({
47257
- kind: string(),
47258
- label: string(),
47259
- /**
47260
- * Relative hub path to start an interactive account link (OAuth popup).
47261
- * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
47262
- */
47263
- interactiveConnectPath: string().min(1).optional(),
47264
- /** Relative hub path to disconnect the linked account (same popup pattern). */
47265
- interactiveDisconnectPath: string().min(1).optional(),
47266
- /**
47267
- * The kind signs in through a server-held interactive session instead of
47268
- * (or before) typed credentials. `basePath` is a hub-relative route set
47269
- * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
47270
- * contract: `POST {basePath}/start` (body: the form's settings) answers
47271
- * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
47272
- * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
47273
- * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
47274
- * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
47275
- * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
47276
- * (`{ sessionId }`) drops the session. The dialog that edits a broker
47277
- * offers it as the default path; the fields the session fills are
47278
- * marked `filledBySignIn` in the kind's settings schema.
47279
- *
47280
- * `methods` lists the ways the session can sign in, recommended first:
47281
- * `'device-code'` adds `POST {basePath}/code/start` (body: the form's
47282
- * settings) answering `{ sessionId, userCode, verificationUrl,
47283
- * expiresAt }` — the operator types `userCode` at `verificationUrl` on
47284
- * any device, and the same `status` / `commit` / `cancel` routes follow
47285
- * (status then carries `method`, `userCode`, `verificationUrl`,
47286
- * `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
47287
- * `'proxy'` is the `{basePath}/start` flow above. Absent means
47288
- * `['proxy']`.
47289
- */
47290
- signIn: object({
47291
- basePath: string().min(1),
47292
- methods: array(_enum(["device-code", "proxy"])).optional()
47293
- }).optional()
47294
- }))
47295
- });
47296
- var ListInputSchema = object({
47297
- /** Optional kind filter — `list({kind:'home-assistant'})` returns
47298
- * only HA brokers. Omit to list every broker. */
47299
- kind: string().optional() });
47300
47326
  var GetInputSchema = object({ id: string() });
47301
- var AddInputSchema = object({
47302
- kind: string().min(1),
47303
- name: string().min(1),
47304
- /**
47305
- * ADOPT an existing id instead of minting a new one.
47306
- *
47307
- * Written the day this cost an outage. A broker's id is not a detail: HA
47308
- * devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
47309
- * lost from config and re-added as `ha_001` leaves every one of its devices
47310
- * bound to a broker that no longer exists. Re-entering the password under the
47311
- * ORIGINAL id turns a multi-step device migration back into re-entering a
47312
- * password.
47313
- *
47314
- * A provider MUST refuse an id that is already in use — adopting a live
47315
- * broker's id would silently take it over.
47316
- */
47317
- id: string().min(1).optional(),
47318
- /** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
47319
- * `{baseUrl,accessToken}`). Validated by the kind-specific provider
47320
- * branch on receipt — invalid shape rejects the add. */
47321
- settings: record(string(), unknown())
47322
- });
47323
- var AddResultSchema = object({ id: string() });
47324
- var RemoveInputSchema = object({ id: string() });
47325
- var TestConnectionResultSchema = discriminatedUnion("ok", [object({
47326
- ok: literal(true),
47327
- latencyMs: number().nonnegative()
47328
- }), object({
47329
- ok: literal(false),
47330
- error: string()
47331
- })]);
47332
47327
  var SettingsRecordSchema$1 = record(string(), unknown());
47333
- var SettingsSchemaInputSchema = object({ kind: string() });
47334
- var TestSettingsInputSchema = object({
47335
- kind: string(),
47336
- settings: SettingsRecordSchema$1
47337
- });
47338
- var TestSettingsResultSchema = discriminatedUnion("ok", [object({
47339
- ok: literal(true),
47340
- latencyMs: number().nonnegative().optional()
47341
- }).strict(), object({
47342
- ok: literal(false),
47343
- error: string()
47344
- })]);
47345
- var SettingsSchemaResultSchema = unknown().nullable();
47346
47328
  var PublishInputSchema = object({
47347
47329
  brokerId: string(),
47348
47330
  /** Kind-specific routing target.
@@ -47376,25 +47358,7 @@ var GetStateInputSchema = object({
47376
47358
  * HA: entity_id (returns the cached entity state). */
47377
47359
  key: string()
47378
47360
  });
47379
- method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
47380
- kind: "mutation",
47381
- auth: "admin"
47382
- }), method(RemoveInputSchema, _void(), {
47383
- kind: "mutation",
47384
- auth: "admin"
47385
- }), method(GetInputSchema, TestConnectionResultSchema, {
47386
- kind: "mutation",
47387
- auth: "admin"
47388
- }), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(object({
47389
- id: string(),
47390
- settings: SettingsRecordSchema$1
47391
- }), _void(), {
47392
- kind: "mutation",
47393
- auth: "admin"
47394
- }), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }), method(TestSettingsInputSchema, TestSettingsResultSchema, {
47395
- kind: "mutation",
47396
- auth: "admin"
47397
- }), method(PublishInputSchema, unknown(), {
47361
+ method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(PublishInputSchema, unknown(), {
47398
47362
  kind: "mutation",
47399
47363
  auth: "admin"
47400
47364
  }), method(SubscribeInputSchema, SubscribeResultSchema, {
@@ -50623,6 +50587,9 @@ var RunningProposalEvidenceSchema = object({
50623
50587
  runsFound: number().int().nonnegative(),
50624
50588
  pausesFound: number().int().nonnegative(),
50625
50589
  longestPauseMs: number().nonnegative(),
50590
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50591
+ pulsesFound: number().int().nonnegative(),
50592
+ longestPulseMs: number().nonnegative(),
50626
50593
  idleFloorW: number(),
50627
50594
  noiseCeilingW: number(),
50628
50595
  plateauW: number(),
@@ -52199,7 +52166,14 @@ var DiscoveryCandidateSchema = object({
52199
52166
  /** `stableId` of the gateway candidate this device was found BEHIND. */
52200
52167
  parentStableId: string().optional(),
52201
52168
  /** `false` = shown for context but not pickable. Default true. */
52202
- selectable: boolean().optional()
52169
+ selectable: boolean().optional(),
52170
+ /**
52171
+ * Keys of the provider's creation form (`getChildCreationSchema`) this candidate
52172
+ * cannot be adopted without — a key for an encrypted board, a pairing code. The
52173
+ * admin asks for exactly these, on the creation form prefilled with
52174
+ * `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
52175
+ */
52176
+ needsParams: array(string()).optional()
52203
52177
  });
52204
52178
  /**
52205
52179
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -52326,8 +52300,14 @@ var deviceProviderCapability = {
52326
52300
  * provider does not support manual creation. Lets the Add-Device dialog
52327
52301
  * pick the right type instead of assuming Camera.
52328
52302
  */
52329
- getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
52330
- adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
52303
+ getManualCreationType: method(object({
52304
+ /** The integration asked about; a provider whose integrations differ answers per one. */
52305
+ integrationId: string().optional() }), object({ deviceType: _enum(DeviceType).nullable() })),
52306
+ adoptDiscoveredDevice: method(object({
52307
+ candidate: DiscoveryCandidateSchema,
52308
+ /** The integration the operator adopts into; a provider with several instances needs it. */
52309
+ integrationId: string().optional()
52310
+ }), DeviceSummarySchema, {
52331
52311
  kind: "mutation",
52332
52312
  auth: "admin"
52333
52313
  }),
@@ -52338,10 +52318,16 @@ var deviceProviderCapability = {
52338
52318
  * devices of that type. The output is a `ConfigUISchema` — the
52339
52319
  * router type-asserts it at the boundary.
52340
52320
  */
52341
- getChildCreationSchema: method(object({ type: _enum(DeviceType) }), CreationSchemaOutputSchema),
52321
+ getChildCreationSchema: method(object({
52322
+ type: _enum(DeviceType),
52323
+ /** The integration asked about; a provider whose integrations differ answers per one. */
52324
+ integrationId: string().optional()
52325
+ }), CreationSchemaOutputSchema),
52342
52326
  createDevice: method(object({
52343
52327
  type: _enum(DeviceType),
52344
- config: record(string(), unknown())
52328
+ config: record(string(), unknown()),
52329
+ /** The integration the operator adds the device to; a provider with several instances needs it. */
52330
+ integrationId: string().optional()
52345
52331
  }), DeviceSummarySchema, {
52346
52332
  kind: "mutation",
52347
52333
  auth: "admin"
@@ -53066,7 +53052,10 @@ method(object({
53066
53052
  auth: "admin"
53067
53053
  }), method(object({
53068
53054
  addonId: string(),
53069
- type: _enum(DeviceType)
53055
+ type: _enum(DeviceType),
53056
+ /** The integration the form is for; a provider whose integrations differ (a hub
53057
+ * integration takes no manual add) answers per integration. Absent = the addon's own. */
53058
+ integrationId: string().optional()
53070
53059
  }), unknown().nullable()), method(object({
53071
53060
  addonId: string(),
53072
53061
  type: _enum(DeviceType),
@@ -53150,7 +53139,11 @@ method(object({
53150
53139
  kind: "mutation",
53151
53140
  auth: "admin",
53152
53141
  timeoutMs: 12e4
53153
- }), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
53142
+ }), method(object({
53143
+ addonId: string(),
53144
+ /** The integration the Add-Device dialog is open on; absent = the addon's own. */
53145
+ integrationId: string().optional()
53146
+ }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
53154
53147
  deviceId: number(),
53155
53148
  key: string(),
53156
53149
  value: unknown()
@@ -56430,7 +56423,7 @@ var TestConnectionStatusEnum = _enum([
56430
56423
  "inconclusive",
56431
56424
  "unsupported"
56432
56425
  ]);
56433
- var TestConnectionResultSchema$1 = object({
56426
+ var TestConnectionResultSchema = object({
56434
56427
  /** True ONLY for `validated`. Never true for a test that did not run. */
56435
56428
  success: boolean(),
56436
56429
  error: string().optional(),
@@ -56451,11 +56444,7 @@ var CreateIntegrationInputSchema = object({
56451
56444
  info: record(string(), unknown()).optional(),
56452
56445
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56453
56446
  originStableId: string().optional(),
56454
- settings: record(string(), unknown()).optional(),
56455
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56456
- * the keychain, not the table (D787). */
56457
- secretKeys: array(string()).optional(),
56458
- skipRestart: boolean().optional()
56447
+ settings: record(string(), unknown()).optional()
56459
56448
  });
56460
56449
  var UpdateIntegrationInputSchema = object({
56461
56450
  id: string(),
@@ -56476,26 +56465,10 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56476
56465
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56477
56466
  kind: "mutation",
56478
56467
  auth: "admin"
56479
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56480
- id: string(),
56481
- settings: record(string(), unknown()),
56482
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56483
- secretKeys: array(string()).optional(),
56484
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56485
- skipRestart: boolean().optional()
56486
- }), object({ success: boolean() }), {
56487
- kind: "mutation",
56488
- auth: "admin"
56489
- }), method(object({ id: string() }), object({
56490
- rows: number().int().nonnegative(),
56491
- secrets: number().int().nonnegative()
56492
- }), {
56493
- kind: "mutation",
56494
- auth: "admin"
56495
56468
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56496
56469
  addonId: string(),
56497
56470
  settings: record(string(), unknown())
56498
- }), TestConnectionResultSchema$1, {
56471
+ }), TestConnectionResultSchema, {
56499
56472
  kind: "mutation",
56500
56473
  auth: "admin"
56501
56474
  });
@@ -56522,6 +56495,14 @@ var ConfigSchemaPassthrough$3 = custom();
56522
56495
  var IntegrationInstanceIdSchema = string().min(1);
56523
56496
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
56524
56497
  /**
56498
+ * What the addon's instances ARE for the Integrations page (planning#416). `devices`: each
56499
+ * instance is an integration that brings devices (the registry owns its on/off switch);
56500
+ * `services`: a connection configured from the addon's own function page, never listed on
56501
+ * Integrations.
56502
+ * It is the same vocabulary as the manifest's `integrationRole`, which stays as an override.
56503
+ */
56504
+ var IntegrationInstanceRoleSchema = _enum(["devices", "services"]);
56505
+ /**
56525
56506
  * One kind of instance a provider can create (an adapter, an account type). The sign-in and
56526
56507
  * connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
56527
56508
  * hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
@@ -56532,6 +56513,22 @@ var IntegrationInstanceKindSchema = object({
56532
56513
  /** Stable id of the kind, sent back as `createInstance.kind`. */
56533
56514
  id: string().min(1),
56534
56515
  label: string().min(1),
56516
+ /**
56517
+ * The function capability this kind is configured for (`broker`, `smtp-provider`,
56518
+ * `device-provider`, ...). A page section is the kinds whose `functionCap` it names (D1013); a
56519
+ * provider with more than one section must name it on every kind.
56520
+ */
56521
+ functionCap: string().min(1),
56522
+ /**
56523
+ * For `functionCap: 'broker'`: the registry kind of the broker an instance of this kind is
56524
+ * (`matter`, `home-assistant`, ...). A caller that needs a broker of one kind (a link field)
56525
+ * finds the instances whose kind names it; absent takes the kind `id`.
56526
+ */
56527
+ brokerKind: string().min(1).optional(),
56528
+ /** What instances of this kind are for the Integrations page; absent takes the provider's `role`. */
56529
+ role: IntegrationInstanceRoleSchema.optional(),
56530
+ /** `testInstance` really probes the remote for this kind; absent takes the provider's `canTest`. */
56531
+ canTest: boolean().optional(),
56535
56532
  /** The kind signs in through a server-held interactive session. */
56536
56533
  signIn: object({
56537
56534
  basePath: string().min(1),
@@ -56544,11 +56541,7 @@ var IntegrationInstanceKindSchema = object({
56544
56541
  });
56545
56542
  var IntegrationInstanceDescriptionSchema = object({
56546
56543
  /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
56547
- role: _enum([
56548
- "devices",
56549
- "services",
56550
- "both"
56551
- ]),
56544
+ role: IntegrationInstanceRoleSchema,
56552
56545
  /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
56553
56546
  discovery: object({
56554
56547
  mode: _enum([
@@ -56623,7 +56616,11 @@ var integrationInstancesCapability = {
56623
56616
  methods: {
56624
56617
  describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
56625
56618
  /** The form; `id: null` is the create form. */
56626
- instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
56619
+ instanceFormSchema: method(object({
56620
+ id: IntegrationInstanceIdSchema.nullable(),
56621
+ /** The kind of the create form (`id: null`) of a provider with several. */
56622
+ kind: string().min(1).optional()
56623
+ }), ConfigSchemaPassthrough$3, { auth: "admin" }),
56627
56624
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
56628
56625
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
56629
56626
  /**
@@ -56633,7 +56630,9 @@ var integrationInstancesCapability = {
56633
56630
  createInstance: method(object({
56634
56631
  id: IntegrationInstanceIdSchema.optional(),
56635
56632
  name: string().min(1),
56636
- kind: string().min(1).optional()
56633
+ kind: string().min(1).optional(),
56634
+ /** Form values written with the record (a link picked in the create dialog). */
56635
+ values: record(string(), unknown()).optional()
56637
56636
  }), object({ id: string() }), {
56638
56637
  kind: "mutation",
56639
56638
  auth: "admin"
@@ -56660,8 +56659,10 @@ var integrationInstancesCapability = {
56660
56659
  }),
56661
56660
  testInstance: optionalMethod(object({
56662
56661
  id: IntegrationInstanceIdSchema.nullable(),
56663
- values: record(string(), unknown())
56664
- }), TestConnectionResultSchema$1, {
56662
+ values: record(string(), unknown()),
56663
+ /** The kind to test when `id` is null and the provider has several. */
56664
+ kind: string().min(1).optional()
56665
+ }), TestConnectionResultSchema, {
56665
56666
  kind: "mutation",
56666
56667
  auth: "admin"
56667
56668
  })
@@ -59418,6 +59419,380 @@ var SystemMetricsSchema = object({
59418
59419
  gpuMemoryPercent: number().optional()
59419
59420
  });
59420
59421
  method(_void(), SystemResourceSnapshotSchema), method(_void(), SystemResourceSnapshotSchema.nullable()), method(_void(), SystemMetricsSchema), method(object({ dirPath: string() }), DiskSpaceInfoSchema), method(_void(), MetricsGpuInfoSchema.nullable()), method(_void(), number().nullable()), method(object({ pids: array(number()) }), array(PidResourceStatsSchema)), method(_void(), array(AddonInstanceSchema).readonly()), method(object({ addonId: string() }), PidResourceStatsSchema.nullable()), method(_void(), array(NodeProcessSchema).readonly()), method(GetLoadSeriesInputSchema, NodeLoadSeriesSchema);
59422
+ /**
59423
+ * records - an addon's own facts and documents (D881): ledgers, audit rows,
59424
+ * config documents, mirrors. The third storage API next to `ctx.settings`
59425
+ * (operator intent, D870) and device-history (numbers over time, D850).
59426
+ *
59427
+ * Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
59428
+ * crossed a process and stated by a hub in-process caller (`ctx.records` does
59429
+ * that with the addon id closed over). It is deliberately NOT in any input
59430
+ * schema, so a session can never supply it, and a call with no stamp is refused
59431
+ * `records/no-caller`. A kind is readable and writable only by its owner; other
59432
+ * addons go through the owner's capability.
59433
+ *
59434
+ * Every read is three-way: a row, `null`, or the call THROWS. A failed read is
59435
+ * never `[]`. Secret fields are never on the wire: a read shows the
59436
+ * redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
59437
+ * resolves itself, and a write may carry only a keychain marker bound to the
59438
+ * row's own ref.
59439
+ *
59440
+ * Internal: addons reach it through `ctx.records`, never directly.
59441
+ */
59442
+ var RecordIdSchema = string().min(1).max(256);
59443
+ /** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
59444
+ var RecordBodySchema = record(string(), unknown());
59445
+ var RecordInputSchema = object({
59446
+ id: RecordIdSchema,
59447
+ /** The device this row belongs to (D865 removal, per-device reads). */
59448
+ deviceId: number().int().optional(),
59449
+ /** The domain clock, ms: observed / created / opened. */
59450
+ at: number().int().optional(),
59451
+ /** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
59452
+ expiresAt: number().int().optional(),
59453
+ data: RecordBodySchema
59454
+ });
59455
+ var RecordViewSchema = object({
59456
+ id: string(),
59457
+ deviceId: number().int().nullable(),
59458
+ at: number().nullable(),
59459
+ expiresAt: number().nullable(),
59460
+ rev: number().int(),
59461
+ updatedAt: number(),
59462
+ data: RecordBodySchema
59463
+ });
59464
+ /** Declared index fields only. Anything else does not compile, by design. */
59465
+ var RecordWhereSchema = record(RecordFieldPathSchema, union([
59466
+ string(),
59467
+ number(),
59468
+ boolean(),
59469
+ object({ in: array(union([
59470
+ string(),
59471
+ number(),
59472
+ boolean()
59473
+ ])).min(1).max(RECORD_BATCH_MAX) }),
59474
+ object({
59475
+ gt: union([string(), number()]).optional(),
59476
+ gte: union([string(), number()]).optional(),
59477
+ lt: union([string(), number()]).optional(),
59478
+ lte: union([string(), number()]).optional()
59479
+ }).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
59480
+ ]));
59481
+ var KindShape = { kind: RecordKindNameSchema };
59482
+ var TimeWindowShape = {
59483
+ deviceId: number().int().optional(),
59484
+ atFrom: number().optional(),
59485
+ atTo: number().optional()
59486
+ };
59487
+ var RecordQueryInputSchema = object({
59488
+ ...KindShape,
59489
+ ...TimeWindowShape,
59490
+ where: RecordWhereSchema.optional(),
59491
+ /** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
59492
+ order: _enum(["at", "id"]).optional(),
59493
+ desc: boolean().optional(),
59494
+ limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
59495
+ /** Opaque keyset cursor from a previous answer. There is no offset. */
59496
+ cursor: string().min(1).optional()
59497
+ });
59498
+ var RecordQueryResultSchema = object({
59499
+ records: array(RecordViewSchema),
59500
+ nextCursor: string().nullable()
59501
+ });
59502
+ var RecordKindStateSchema = _enum([
59503
+ "created",
59504
+ "unchanged",
59505
+ "updated"
59506
+ ]);
59507
+ var DeclareKindResultSchema = object({
59508
+ kindId: number().int(),
59509
+ state: RecordKindStateSchema,
59510
+ createdIndexes: array(string())
59511
+ });
59512
+ /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
59513
+ var SecretRefsList = array(string());
59514
+ /**
59515
+ * One op of a `transact`: the single-row writes, each on a kind of the caller.
59516
+ * All ops of one call share ONE database file and ONE `transactFamily`.
59517
+ */
59518
+ var RecordTransactOpSchema = discriminatedUnion("op", [
59519
+ object({
59520
+ op: literal("put"),
59521
+ kind: RecordKindNameSchema,
59522
+ record: RecordInputSchema,
59523
+ ifRev: number().int().min(0).optional()
59524
+ }),
59525
+ object({
59526
+ op: literal("patch"),
59527
+ kind: RecordKindNameSchema,
59528
+ id: RecordIdSchema,
59529
+ data: RecordBodySchema,
59530
+ at: number().int().nullable().optional(),
59531
+ expiresAt: number().int().nullable().optional(),
59532
+ ifRev: number().int().min(1).optional()
59533
+ }),
59534
+ object({
59535
+ op: literal("delete"),
59536
+ kind: RecordKindNameSchema,
59537
+ id: RecordIdSchema
59538
+ })
59539
+ ]);
59540
+ var RecordSecretRefsSchema = record(string(), record(string(), string()));
59541
+ method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
59542
+ kind: "mutation",
59543
+ auth: "admin"
59544
+ }), method(object({
59545
+ ...KindShape,
59546
+ id: RecordIdSchema
59547
+ }), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
59548
+ ...KindShape,
59549
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
59550
+ }), array(RecordViewSchema), { auth: "admin" }), method(object({
59551
+ ...KindShape,
59552
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
59553
+ }), RecordSecretRefsSchema, { auth: "admin" }), method(object({
59554
+ ...KindShape,
59555
+ record: RecordInputSchema,
59556
+ /** Compare-and-set. `0` means "the row must not exist". */
59557
+ ifRev: number().int().min(0).optional()
59558
+ }), object({
59559
+ rev: number().int(),
59560
+ releasedRefs: SecretRefsList
59561
+ }), {
59562
+ kind: "mutation",
59563
+ auth: "admin"
59564
+ }), method(object({
59565
+ ...KindShape,
59566
+ records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
59567
+ }), object({
59568
+ count: number().int(),
59569
+ releasedRefs: SecretRefsList
59570
+ }), {
59571
+ kind: "mutation",
59572
+ auth: "admin"
59573
+ }), method(object({
59574
+ ...KindShape,
59575
+ id: RecordIdSchema,
59576
+ data: RecordBodySchema,
59577
+ at: number().int().nullable().optional(),
59578
+ expiresAt: number().int().nullable().optional(),
59579
+ ifRev: number().int().min(1).optional()
59580
+ }), object({
59581
+ rev: number().int(),
59582
+ releasedRefs: SecretRefsList
59583
+ }), {
59584
+ kind: "mutation",
59585
+ auth: "admin"
59586
+ }), method(object({
59587
+ ...KindShape,
59588
+ id: RecordIdSchema
59589
+ }), object({
59590
+ deleted: boolean(),
59591
+ secretRefs: SecretRefsList
59592
+ }), {
59593
+ kind: "mutation",
59594
+ auth: "admin"
59595
+ }), method(object({
59596
+ ...KindShape,
59597
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
59598
+ }), object({
59599
+ deleted: number().int(),
59600
+ secretRefs: SecretRefsList
59601
+ }), {
59602
+ kind: "mutation",
59603
+ auth: "admin"
59604
+ }), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
59605
+ ...KindShape,
59606
+ ...TimeWindowShape,
59607
+ where: RecordWhereSchema.optional()
59608
+ }), object({ count: number().int() }), { auth: "admin" }), method(object({
59609
+ ...KindShape,
59610
+ deviceId: number().int().optional(),
59611
+ from: number(),
59612
+ to: number(),
59613
+ bucketMs: number().int().positive()
59614
+ }), object({ buckets: array(object({
59615
+ bucketStart: number(),
59616
+ count: number().int()
59617
+ })) }), { auth: "admin" }), method(object({
59618
+ ...KindShape,
59619
+ op: _enum([
59620
+ "sum",
59621
+ "min",
59622
+ "max",
59623
+ "count"
59624
+ ]),
59625
+ field: RecordFieldPathSchema.optional(),
59626
+ groupBy: RecordFieldPathSchema.optional(),
59627
+ where: RecordWhereSchema.optional()
59628
+ }), object({ rows: array(object({
59629
+ group: union([
59630
+ string(),
59631
+ number(),
59632
+ _null()
59633
+ ]),
59634
+ value: number().nullable()
59635
+ })) }), { auth: "admin" }), method(object({
59636
+ ...KindShape,
59637
+ deviceId: number().int().optional(),
59638
+ atBefore: number().optional(),
59639
+ where: RecordWhereSchema.optional()
59640
+ }), object({
59641
+ deleted: number().int(),
59642
+ secretRefs: SecretRefsList
59643
+ }), {
59644
+ kind: "mutation",
59645
+ auth: "admin"
59646
+ }), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
59647
+ results: array(object({
59648
+ rev: number().int().nullable(),
59649
+ deleted: boolean()
59650
+ })),
59651
+ releasedRefs: SecretRefsList,
59652
+ secretRefs: SecretRefsList
59653
+ }), {
59654
+ kind: "mutation",
59655
+ auth: "admin"
59656
+ }), method(object({
59657
+ deviceId: number().int(),
59658
+ kinds: array(RecordKindNameSchema).optional()
59659
+ }), object({
59660
+ deleted: record(string(), number().int()),
59661
+ secretRefs: SecretRefsList
59662
+ }), {
59663
+ kind: "mutation",
59664
+ auth: "admin"
59665
+ }), method(object(KindShape), object({ id: number().int() }), {
59666
+ kind: "mutation",
59667
+ auth: "admin"
59668
+ });
59669
+ /**
59670
+ * The wire shapes of the migration API, version 1 (planning#447).
59671
+ *
59672
+ * A migration is code that outlives the server it was written against: an addon at
59673
+ * version 1.4 that is brought to 1.9 in one jump still runs its 1.4 catalogue, against
59674
+ * the 1.9 kernel. The only thing that code may call is {@link MigrationApiV1}; this file
59675
+ * is the contract of every method of it, one Zod pair per method.
59676
+ *
59677
+ * APPEND-ONLY. A released method is never removed and never changes meaning: an input
59678
+ * field is never removed or made required, an enum never narrows, an output field is
59679
+ * never removed. A change that cannot be expressed that way is `migration-api.v2`, next
59680
+ * to this file. `scripts/check-migration-api-append-only.ts` holds the line against
59681
+ * `scripts/migration-api-contract.json`, and every `behaviours` id below must have an
59682
+ * `it('<id>'` in `migration-api.contract.spec.ts`.
59683
+ */
59684
+ var Rows = record(string(), unknown());
59685
+ var IntegrationId = object({ integrationId: string().min(1) });
59686
+ var RecordId = object({ id: string().min(1) });
59687
+ var RecordRowSchema = object({
59688
+ id: string(),
59689
+ deviceId: number().int().nullable(),
59690
+ at: number().nullable(),
59691
+ expiresAt: number().nullable(),
59692
+ rev: number().int(),
59693
+ updatedAt: number(),
59694
+ data: Rows
59695
+ });
59696
+ var RecordPutShape = {
59697
+ id: string().min(1),
59698
+ deviceId: number().int().optional(),
59699
+ at: number().int().optional(),
59700
+ expiresAt: number().int().optional(),
59701
+ data: Rows
59702
+ };
59703
+ var MigrationIntegrationSchema = object({
59704
+ id: string(),
59705
+ addonId: string(),
59706
+ name: string(),
59707
+ info: Rows,
59708
+ originStableId: string().optional()
59709
+ });
59710
+ var MigrationDeviceSchema = object({
59711
+ id: number().int(),
59712
+ integrationId: string().nullable().optional(),
59713
+ /** The owning device; null or absent for a root. */
59714
+ parentDeviceId: number().int().nullable().optional(),
59715
+ type: string().optional(),
59716
+ name: string().optional(),
59717
+ stableId: string().optional()
59718
+ });
59719
+ /** What the engine says about the migration API it serves: lets an addon notice an older server. */
59720
+ var MigrationApiInfoSchema = object({
59721
+ version: number().int(),
59722
+ methods: array(string())
59723
+ });
59724
+ object({
59725
+ message: string(),
59726
+ meta: Rows.optional(),
59727
+ /** Grouping tags of the line; a line about a device carries `{ deviceId }`. */
59728
+ tags: object({ deviceId: number().int().optional() }).optional()
59729
+ });
59730
+ object({
59731
+ owner: string().min(1),
59732
+ metas: array(MigrationMetaSchema)
59733
+ }), object({
59734
+ bootId: string(),
59735
+ snapshot: _enum([
59736
+ "none",
59737
+ "written",
59738
+ "refused"
59739
+ ]),
59740
+ steps: array(MigrationOwnerStepSchema),
59741
+ /** The migration API the engine serves; absent from a server that predates it. */
59742
+ migrationApi: MigrationApiInfoSchema.optional()
59743
+ }), object({ id: string().min(1) }), object({
59744
+ proceed: boolean(),
59745
+ state: union([literal("pending"), MigrationStatusSchema]),
59746
+ reason: string().nullable()
59747
+ }), object({
59748
+ id: string().min(1),
59749
+ result: MigrationOwnerResultSchema
59750
+ }), object({
59751
+ status: MigrationStatusSchema,
59752
+ error: string().nullable(),
59753
+ rowsChanged: number().int().nonnegative()
59754
+ }), object({
59755
+ rows: number().int().nonnegative(),
59756
+ secrets: number().int().nonnegative()
59757
+ }), IntegrationId.extend({ patch: Rows }), object({ written: boolean() }), IntegrationId.extend({ keys: array(string().min(1)) }), object({
59758
+ present: boolean(),
59759
+ removed: Rows
59760
+ }), RecordRowSchema.nullable(), RecordRowSchema.nullable(), object({
59761
+ ...RecordPutShape,
59762
+ ifRev: number().int().optional()
59763
+ }), object({ rev: number().int() }), object({ records: array(object(RecordPutShape)) }), object({ count: number().int().nonnegative() }), RecordId.extend({
59764
+ partial: Rows,
59765
+ ifRev: number().int().optional()
59766
+ }), object({ rev: number().int() }), object({ deleted: boolean() }), object({
59767
+ deviceId: number().int().optional(),
59768
+ atFrom: number().optional(),
59769
+ atTo: number().optional(),
59770
+ where: RecordWhereSchema.optional(),
59771
+ limit: number().int().min(1).optional(),
59772
+ cursor: string().min(1).optional(),
59773
+ resolveSecrets: boolean().optional()
59774
+ }), object({
59775
+ records: array(RecordRowSchema),
59776
+ nextCursor: string().nullable()
59777
+ }), object({
59778
+ deviceId: number().int().optional(),
59779
+ atBefore: number().optional(),
59780
+ where: RecordWhereSchema.optional()
59781
+ }), object({ count: number().int().nonnegative() }), object({}), object({ store: Rows }), object({ patch: Rows }), object({}), object({ keys: array(string().min(1)) }), object({ removed: array(string()) }), object({ refs: array(string()) }), object({ values: record(string(), string().nullable()) }), object({
59782
+ ref: string().min(1),
59783
+ value: string()
59784
+ }), object({}), object({ refs: array(string()) }), object({ deleted: number().int().nonnegative() }), object({}), object({ integrations: array(MigrationIntegrationSchema) }), object({ id: string().min(1) }), MigrationIntegrationSchema.nullable(), object({
59785
+ addonId: string().min(1),
59786
+ integrationId: string().min(1).optional()
59787
+ }), object({ devices: array(MigrationDeviceSchema) }), object({ deviceId: number().int() }), object({
59788
+ id: number().int(),
59789
+ integrationId: string().nullable(),
59790
+ /** Whether the device carries any stored config; its content is not offered. */
59791
+ hasConfig: boolean()
59792
+ }).nullable(), object({
59793
+ deviceId: number().int(),
59794
+ integrationId: string().min(1)
59795
+ }), object({}), object({}), object({ nodeId: string() }), object({}), object({});
59421
59796
  method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
59422
59797
  id: string().min(1),
59423
59798
  error: string().min(1)
@@ -59447,7 +59822,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59447
59822
  "written",
59448
59823
  "refused"
59449
59824
  ]),
59450
- steps: array(MigrationOwnerStepSchema)
59825
+ steps: array(MigrationOwnerStepSchema),
59826
+ /** The migration API this engine serves (additive: a server that predates it omits it). */
59827
+ migrationApi: MigrationApiInfoSchema.optional()
59451
59828
  }), {
59452
59829
  kind: "mutation",
59453
59830
  auth: "admin"
@@ -59468,6 +59845,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59468
59845
  }), {
59469
59846
  kind: "mutation",
59470
59847
  auth: "admin"
59848
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59849
+ rows: number().int().nonnegative(),
59850
+ secrets: number().int().nonnegative()
59851
+ }), {
59852
+ kind: "mutation",
59853
+ auth: "admin"
59854
+ }), method(object({
59855
+ integrationId: string().min(1),
59856
+ patch: record(string(), unknown())
59857
+ }), object({ written: boolean() }), {
59858
+ kind: "mutation",
59859
+ auth: "admin"
59860
+ }), method(object({
59861
+ integrationId: string().min(1),
59862
+ keys: array(string().min(1))
59863
+ }), object({
59864
+ present: boolean(),
59865
+ removed: record(string(), unknown())
59866
+ }), {
59867
+ kind: "mutation",
59868
+ auth: "admin"
59471
59869
  });
59472
59870
  method(object({
59473
59871
  sourceUrl: string(),
@@ -62093,7 +62491,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
62093
62491
  }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
62094
62492
  kind: "mutation",
62095
62493
  auth: "admin"
62096
- }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
62494
+ }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(MediaLocationCensusInputSchema, MediaLocationCensusSchema, {
62495
+ kind: "query",
62496
+ auth: "admin"
62497
+ }), method(RelocatableMediaCountInputSchema, RelocatableMediaCountSchema, {
62097
62498
  kind: "query",
62098
62499
  auth: "admin"
62099
62500
  }), method(object({}), array(RelocateJobSchema).readonly(), {
@@ -64748,7 +65149,7 @@ var BrokerStatusSchema$1 = _enum([
64748
65149
  "unreachable",
64749
65150
  "tls-error"
64750
65151
  ]);
64751
- var BrokerInfoSchema$1 = object({
65152
+ var BrokerInfoSchema = object({
64752
65153
  id: string(),
64753
65154
  name: string(),
64754
65155
  url: string(),
@@ -64811,7 +65212,7 @@ var StatusSchema = object({
64811
65212
  brokerCount: number(),
64812
65213
  embeddedRunning: boolean()
64813
65214
  });
64814
- method(_void(), array(BrokerInfoSchema$1)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
65215
+ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
64815
65216
  kind: "mutation",
64816
65217
  auth: "admin"
64817
65218
  }), method(IdInputSchema, _void(), {
@@ -71993,253 +72394,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
71993
72394
  auth: "admin"
71994
72395
  });
71995
72396
  /**
71996
- * records - an addon's own facts and documents (D881): ledgers, audit rows,
71997
- * config documents, mirrors. The third storage API next to `ctx.settings`
71998
- * (operator intent, D870) and device-history (numbers over time, D850).
71999
- *
72000
- * Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
72001
- * crossed a process and stated by a hub in-process caller (`ctx.records` does
72002
- * that with the addon id closed over). It is deliberately NOT in any input
72003
- * schema, so a session can never supply it, and a call with no stamp is refused
72004
- * `records/no-caller`. A kind is readable and writable only by its owner; other
72005
- * addons go through the owner's capability.
72006
- *
72007
- * Every read is three-way: a row, `null`, or the call THROWS. A failed read is
72008
- * never `[]`. Secret fields are never on the wire: a read shows the
72009
- * redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
72010
- * resolves itself, and a write may carry only a keychain marker bound to the
72011
- * row's own ref.
72012
- *
72013
- * Internal: addons reach it through `ctx.records`, never directly.
72014
- */
72015
- var RecordIdSchema = string().min(1).max(256);
72016
- /** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
72017
- var RecordBodySchema = record(string(), unknown());
72018
- var RecordInputSchema = object({
72019
- id: RecordIdSchema,
72020
- /** The device this row belongs to (D865 removal, per-device reads). */
72021
- deviceId: number().int().optional(),
72022
- /** The domain clock, ms: observed / created / opened. */
72023
- at: number().int().optional(),
72024
- /** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
72025
- expiresAt: number().int().optional(),
72026
- data: RecordBodySchema
72027
- });
72028
- var RecordViewSchema = object({
72029
- id: string(),
72030
- deviceId: number().int().nullable(),
72031
- at: number().nullable(),
72032
- expiresAt: number().nullable(),
72033
- rev: number().int(),
72034
- updatedAt: number(),
72035
- data: RecordBodySchema
72036
- });
72037
- /** Declared index fields only. Anything else does not compile, by design. */
72038
- var RecordWhereSchema = record(RecordFieldPathSchema, union([
72039
- string(),
72040
- number(),
72041
- boolean(),
72042
- object({ in: array(union([
72043
- string(),
72044
- number(),
72045
- boolean()
72046
- ])).min(1).max(RECORD_BATCH_MAX) }),
72047
- object({
72048
- gt: union([string(), number()]).optional(),
72049
- gte: union([string(), number()]).optional(),
72050
- lt: union([string(), number()]).optional(),
72051
- lte: union([string(), number()]).optional()
72052
- }).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
72053
- ]));
72054
- var KindShape = { kind: RecordKindNameSchema };
72055
- var TimeWindowShape = {
72056
- deviceId: number().int().optional(),
72057
- atFrom: number().optional(),
72058
- atTo: number().optional()
72059
- };
72060
- var RecordQueryInputSchema = object({
72061
- ...KindShape,
72062
- ...TimeWindowShape,
72063
- where: RecordWhereSchema.optional(),
72064
- /** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
72065
- order: _enum(["at", "id"]).optional(),
72066
- desc: boolean().optional(),
72067
- limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
72068
- /** Opaque keyset cursor from a previous answer. There is no offset. */
72069
- cursor: string().min(1).optional()
72070
- });
72071
- var RecordQueryResultSchema = object({
72072
- records: array(RecordViewSchema),
72073
- nextCursor: string().nullable()
72074
- });
72075
- var RecordKindStateSchema = _enum([
72076
- "created",
72077
- "unchanged",
72078
- "updated"
72079
- ]);
72080
- var DeclareKindResultSchema = object({
72081
- kindId: number().int(),
72082
- state: RecordKindStateSchema,
72083
- createdIndexes: array(string())
72084
- });
72085
- /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
72086
- var SecretRefsList = array(string());
72087
- /**
72088
- * One op of a `transact`: the single-row writes, each on a kind of the caller.
72089
- * All ops of one call share ONE database file and ONE `transactFamily`.
72090
- */
72091
- var RecordTransactOpSchema = discriminatedUnion("op", [
72092
- object({
72093
- op: literal("put"),
72094
- kind: RecordKindNameSchema,
72095
- record: RecordInputSchema,
72096
- ifRev: number().int().min(0).optional()
72097
- }),
72098
- object({
72099
- op: literal("patch"),
72100
- kind: RecordKindNameSchema,
72101
- id: RecordIdSchema,
72102
- data: RecordBodySchema,
72103
- at: number().int().nullable().optional(),
72104
- expiresAt: number().int().nullable().optional(),
72105
- ifRev: number().int().min(1).optional()
72106
- }),
72107
- object({
72108
- op: literal("delete"),
72109
- kind: RecordKindNameSchema,
72110
- id: RecordIdSchema
72111
- })
72112
- ]);
72113
- var RecordSecretRefsSchema = record(string(), record(string(), string()));
72114
- method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
72115
- kind: "mutation",
72116
- auth: "admin"
72117
- }), method(object({
72118
- ...KindShape,
72119
- id: RecordIdSchema
72120
- }), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
72121
- ...KindShape,
72122
- ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
72123
- }), array(RecordViewSchema), { auth: "admin" }), method(object({
72124
- ...KindShape,
72125
- ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
72126
- }), RecordSecretRefsSchema, { auth: "admin" }), method(object({
72127
- ...KindShape,
72128
- record: RecordInputSchema,
72129
- /** Compare-and-set. `0` means "the row must not exist". */
72130
- ifRev: number().int().min(0).optional()
72131
- }), object({
72132
- rev: number().int(),
72133
- releasedRefs: SecretRefsList
72134
- }), {
72135
- kind: "mutation",
72136
- auth: "admin"
72137
- }), method(object({
72138
- ...KindShape,
72139
- records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
72140
- }), object({
72141
- count: number().int(),
72142
- releasedRefs: SecretRefsList
72143
- }), {
72144
- kind: "mutation",
72145
- auth: "admin"
72146
- }), method(object({
72147
- ...KindShape,
72148
- id: RecordIdSchema,
72149
- data: RecordBodySchema,
72150
- at: number().int().nullable().optional(),
72151
- expiresAt: number().int().nullable().optional(),
72152
- ifRev: number().int().min(1).optional()
72153
- }), object({
72154
- rev: number().int(),
72155
- releasedRefs: SecretRefsList
72156
- }), {
72157
- kind: "mutation",
72158
- auth: "admin"
72159
- }), method(object({
72160
- ...KindShape,
72161
- id: RecordIdSchema
72162
- }), object({
72163
- deleted: boolean(),
72164
- secretRefs: SecretRefsList
72165
- }), {
72166
- kind: "mutation",
72167
- auth: "admin"
72168
- }), method(object({
72169
- ...KindShape,
72170
- ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
72171
- }), object({
72172
- deleted: number().int(),
72173
- secretRefs: SecretRefsList
72174
- }), {
72175
- kind: "mutation",
72176
- auth: "admin"
72177
- }), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
72178
- ...KindShape,
72179
- ...TimeWindowShape,
72180
- where: RecordWhereSchema.optional()
72181
- }), object({ count: number().int() }), { auth: "admin" }), method(object({
72182
- ...KindShape,
72183
- deviceId: number().int().optional(),
72184
- from: number(),
72185
- to: number(),
72186
- bucketMs: number().int().positive()
72187
- }), object({ buckets: array(object({
72188
- bucketStart: number(),
72189
- count: number().int()
72190
- })) }), { auth: "admin" }), method(object({
72191
- ...KindShape,
72192
- op: _enum([
72193
- "sum",
72194
- "min",
72195
- "max",
72196
- "count"
72197
- ]),
72198
- field: RecordFieldPathSchema.optional(),
72199
- groupBy: RecordFieldPathSchema.optional(),
72200
- where: RecordWhereSchema.optional()
72201
- }), object({ rows: array(object({
72202
- group: union([
72203
- string(),
72204
- number(),
72205
- _null()
72206
- ]),
72207
- value: number().nullable()
72208
- })) }), { auth: "admin" }), method(object({
72209
- ...KindShape,
72210
- deviceId: number().int().optional(),
72211
- atBefore: number().optional(),
72212
- where: RecordWhereSchema.optional()
72213
- }), object({
72214
- deleted: number().int(),
72215
- secretRefs: SecretRefsList
72216
- }), {
72217
- kind: "mutation",
72218
- auth: "admin"
72219
- }), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
72220
- results: array(object({
72221
- rev: number().int().nullable(),
72222
- deleted: boolean()
72223
- })),
72224
- releasedRefs: SecretRefsList,
72225
- secretRefs: SecretRefsList
72226
- }), {
72227
- kind: "mutation",
72228
- auth: "admin"
72229
- }), method(object({
72230
- deviceId: number().int(),
72231
- kinds: array(RecordKindNameSchema).optional()
72232
- }), object({
72233
- deleted: record(string(), number().int()),
72234
- secretRefs: SecretRefsList
72235
- }), {
72236
- kind: "mutation",
72237
- auth: "admin"
72238
- }), method(object(KindShape), object({ id: number().int() }), {
72239
- kind: "mutation",
72240
- auth: "admin"
72241
- });
72242
- /**
72243
72397
  * record-entries - the ONE admin surface for every list of user-created entries
72244
72398
  * (D881): notification targets, brokers, LLM profiles, anything an addon
72245
72399
  * declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
@@ -80898,11 +81052,15 @@ discriminatedUnion("kind", [object({
80898
81052
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80899
81053
  * near F. A device that is not near one level most of the time has no floor.
80900
81054
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80901
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80902
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
81055
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
81056
+ * display waking): it is neither a run nor a pause. Among the remaining
81057
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
81058
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
81059
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
81060
+ * is no run.
80903
81061
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80904
- * grid a person would type; stop dwell covers the longest pause; start dwell
80905
- * covers the longest standby blip above T.
81062
+ * grid a person would type; stop dwell covers the longest pause and the
81063
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80906
81064
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80907
81065
  * run must hold exactly one true interval and nothing may flap outside.
80908
81066
  */
@@ -80913,6 +81071,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80913
81071
  30 * SECOND_MS;
80914
81072
  60 * SECOND_MS;
80915
81073
  20 * MINUTE_MS$1;
81074
+ 90 * SECOND_MS;
81075
+ 30 * MINUTE_MS$1;
81076
+ 10 * MINUTE_MS$1;
81077
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80916
81078
  10 * SECOND_MS;
80917
81079
  10 * SECOND_MS;
80918
81080
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -82063,7 +82225,7 @@ var BaseDeviceProvider = class extends BaseAddon {
82063
82225
  * `deviceClasses` map (first registered type). `null` when manual creation is
82064
82226
  * unsupported. Lets the Add-Device dialog pick the right type per provider.
82065
82227
  */
82066
- async getManualCreationType() {
82228
+ async getManualCreationType(_input) {
82067
82229
  if (!await this.supportsManualCreation()) return { deviceType: null };
82068
82230
  return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
82069
82231
  }
@@ -82074,7 +82236,7 @@ var BaseDeviceProvider = class extends BaseAddon {
82074
82236
  return true;
82075
82237
  }
82076
82238
  async getChildCreationSchema(input) {
82077
- return this.onGetCreationSchema(input.type);
82239
+ return this.onGetCreationSchema(input.type, { integrationId: input.integrationId });
82078
82240
  }
82079
82241
  /**
82080
82242
  * Default kernel-orchestrated `createDevice` implementation. The
@@ -82086,7 +82248,7 @@ var BaseDeviceProvider = class extends BaseAddon {
82086
82248
  * `deviceClasses` instead.
82087
82249
  */
82088
82250
  async createDevice(input) {
82089
- const spec = await this.onCreateDevice(input.type, input.config);
82251
+ const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
82090
82252
  const Class = this.deviceClasses[spec.meta.type];
82091
82253
  if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
82092
82254
  const stableId = this.generateStableId(spec.meta.type, spec.config);
@@ -83495,20 +83657,6 @@ Object.freeze({
83495
83657
  access: "create",
83496
83658
  role: "viewer"
83497
83659
  },
83498
- "broker.add": {
83499
- capName: "broker",
83500
- capScope: "system",
83501
- addonId: null,
83502
- access: "create",
83503
- role: "admin"
83504
- },
83505
- "broker.get": {
83506
- capName: "broker",
83507
- capScope: "system",
83508
- addonId: null,
83509
- access: "view",
83510
- role: "admin"
83511
- },
83512
83660
  "broker.getBrokerConfig": {
83513
83661
  capName: "broker",
83514
83662
  capScope: "system",
@@ -83516,20 +83664,6 @@ Object.freeze({
83516
83664
  access: "view",
83517
83665
  role: "admin"
83518
83666
  },
83519
- "broker.getSettings": {
83520
- capName: "broker",
83521
- capScope: "system",
83522
- addonId: null,
83523
- access: "view",
83524
- role: "admin"
83525
- },
83526
- "broker.getSettingsSchema": {
83527
- capName: "broker",
83528
- capScope: "system",
83529
- addonId: null,
83530
- access: "view",
83531
- role: "admin"
83532
- },
83533
83667
  "broker.getState": {
83534
83668
  capName: "broker",
83535
83669
  capScope: "system",
@@ -83544,20 +83678,6 @@ Object.freeze({
83544
83678
  access: "view",
83545
83679
  role: "admin"
83546
83680
  },
83547
- "broker.list": {
83548
- capName: "broker",
83549
- capScope: "system",
83550
- addonId: null,
83551
- access: "view",
83552
- role: "admin"
83553
- },
83554
- "broker.listProviders": {
83555
- capName: "broker",
83556
- capScope: "system",
83557
- addonId: null,
83558
- access: "view",
83559
- role: "admin"
83560
- },
83561
83681
  "broker.publish": {
83562
83682
  capName: "broker",
83563
83683
  capScope: "system",
@@ -83565,20 +83685,6 @@ Object.freeze({
83565
83685
  access: "create",
83566
83686
  role: "admin"
83567
83687
  },
83568
- "broker.remove": {
83569
- capName: "broker",
83570
- capScope: "system",
83571
- addonId: null,
83572
- access: "delete",
83573
- role: "admin"
83574
- },
83575
- "broker.setSettings": {
83576
- capName: "broker",
83577
- capScope: "system",
83578
- addonId: null,
83579
- access: "create",
83580
- role: "admin"
83581
- },
83582
83688
  "broker.subscribe": {
83583
83689
  capName: "broker",
83584
83690
  capScope: "system",
@@ -83586,20 +83692,6 @@ Object.freeze({
83586
83692
  access: "create",
83587
83693
  role: "admin"
83588
83694
  },
83589
- "broker.testConnection": {
83590
- capName: "broker",
83591
- capScope: "system",
83592
- addonId: null,
83593
- access: "create",
83594
- role: "admin"
83595
- },
83596
- "broker.testSettings": {
83597
- capName: "broker",
83598
- capScope: "system",
83599
- addonId: null,
83600
- access: "create",
83601
- role: "admin"
83602
- },
83603
83695
  "broker.unsubscribe": {
83604
83696
  capName: "broker",
83605
83697
  capScope: "system",
@@ -86197,13 +86289,6 @@ Object.freeze({
86197
86289
  access: "create",
86198
86290
  role: "admin"
86199
86291
  },
86200
- "integrations.clearSettings": {
86201
- capName: "integrations",
86202
- capScope: "system",
86203
- addonId: null,
86204
- access: "delete",
86205
- role: "admin"
86206
- },
86207
86292
  "integrations.create": {
86208
86293
  capName: "integrations",
86209
86294
  capScope: "system",
@@ -86239,13 +86324,6 @@ Object.freeze({
86239
86324
  access: "view",
86240
86325
  role: "admin"
86241
86326
  },
86242
- "integrations.getSettings": {
86243
- capName: "integrations",
86244
- capScope: "system",
86245
- addonId: null,
86246
- access: "view",
86247
- role: "admin"
86248
- },
86249
86327
  "integrations.list": {
86250
86328
  capName: "integrations",
86251
86329
  capScope: "system",
@@ -86253,13 +86331,6 @@ Object.freeze({
86253
86331
  access: "view",
86254
86332
  role: "admin"
86255
86333
  },
86256
- "integrations.setSettings": {
86257
- capName: "integrations",
86258
- capScope: "system",
86259
- addonId: null,
86260
- access: "create",
86261
- role: "admin"
86262
- },
86263
86334
  "integrations.testConnection": {
86264
86335
  capName: "integrations",
86265
86336
  capScope: "system",
@@ -87254,6 +87325,13 @@ Object.freeze({
87254
87325
  access: "view",
87255
87326
  role: "admin"
87256
87327
  },
87328
+ "migrations.clearLegacyIntegrationRows": {
87329
+ capName: "migrations",
87330
+ capScope: "system",
87331
+ addonId: null,
87332
+ access: "delete",
87333
+ role: "admin"
87334
+ },
87257
87335
  "migrations.declareOwner": {
87258
87336
  capName: "migrations",
87259
87337
  capScope: "system",
@@ -87261,6 +87339,13 @@ Object.freeze({
87261
87339
  access: "create",
87262
87340
  role: "admin"
87263
87341
  },
87342
+ "migrations.deleteLegacyIntegrationRowKeys": {
87343
+ capName: "migrations",
87344
+ capScope: "system",
87345
+ addonId: null,
87346
+ access: "delete",
87347
+ role: "admin"
87348
+ },
87264
87349
  "migrations.dryRun": {
87265
87350
  capName: "migrations",
87266
87351
  capScope: "system",
@@ -87296,6 +87381,20 @@ Object.freeze({
87296
87381
  access: "create",
87297
87382
  role: "admin"
87298
87383
  },
87384
+ "migrations.patchLegacyIntegrationRows": {
87385
+ capName: "migrations",
87386
+ capScope: "system",
87387
+ addonId: null,
87388
+ access: "create",
87389
+ role: "admin"
87390
+ },
87391
+ "migrations.readLegacyIntegrationRows": {
87392
+ capName: "migrations",
87393
+ capScope: "system",
87394
+ addonId: null,
87395
+ access: "view",
87396
+ role: "admin"
87397
+ },
87299
87398
  "migrations.restorePreMigration": {
87300
87399
  capName: "migrations",
87301
87400
  capScope: "system",
@@ -88556,6 +88655,13 @@ Object.freeze({
88556
88655
  access: "view",
88557
88656
  role: "viewer"
88558
88657
  },
88658
+ "pipelineAnalytics.getMediaLocationCensus": {
88659
+ capName: "pipeline-analytics",
88660
+ capScope: "device",
88661
+ addonId: null,
88662
+ access: "view",
88663
+ role: "admin"
88664
+ },
88559
88665
  "pipelineAnalytics.getMediaReclaimStatus": {
88560
88666
  capName: "pipeline-analytics",
88561
88667
  capScope: "device",
@@ -97563,6 +97669,100 @@ object({
97563
97669
  delay: number().nonnegative().optional(),
97564
97670
  commands: record(string(), unknown())
97565
97671
  });
97672
+ entryBody({ brokerId: string() });
97673
+ function secretsOverMigrationApi(api) {
97674
+ return {
97675
+ resolve: async (refs) => {
97676
+ const { values } = await api.secrets.resolve({ refs: [...refs] });
97677
+ const out = {};
97678
+ for (const ref of refs) {
97679
+ const value = values[ref];
97680
+ out[ref] = value === null || value === void 0 ? { kind: "missing" } : {
97681
+ kind: "value",
97682
+ value,
97683
+ version: 0
97684
+ };
97685
+ }
97686
+ return out;
97687
+ },
97688
+ delete: async (refs) => (await api.secrets.delete({ refs: [...refs] })).deleted
97689
+ };
97690
+ }
97691
+ /** `ctx.records` as the migration API offers it: bind a kind, read and write by id. */
97692
+ function recordsOverMigrationApi(api) {
97693
+ return { kind: (definition) => {
97694
+ const handle = api.records.kind(definition);
97695
+ return {
97696
+ get: (id) => handle.get({ id }),
97697
+ getResolved: (id) => handle.getResolved({ id }),
97698
+ put: (input, opts) => handle.put({
97699
+ ...input,
97700
+ ...opts?.ifRev === void 0 ? {} : { ifRev: opts.ifRev }
97701
+ }),
97702
+ delete: (id) => handle.delete({ id })
97703
+ };
97704
+ } };
97705
+ }
97706
+ function integrationRowsApiOver(api) {
97707
+ return {
97708
+ list: async () => (await api.integrations.list({})).integrations,
97709
+ readLegacyIntegrationRows: (input) => api.legacyRows.read(input),
97710
+ clearLegacyIntegrationRows: (input) => api.legacyRows.clear(input)
97711
+ };
97712
+ }
97713
+ /**
97714
+ * One serial lane per migration API: a log line is delivered after the line before it, so the
97715
+ * order the steps wrote them in is the order the hub reads them in. A delivery that fails
97716
+ * is reported once through the same door; if that fails too nothing is left to tell, and the
97717
+ * line is dropped rather than thrown into a migration step or left as an unhandled rejection.
97718
+ */
97719
+ var lanes = /* @__PURE__ */ new WeakMap();
97720
+ function deliver(api, send) {
97721
+ const next = (lanes.get(api) ?? Promise.resolve()).then(send).then(() => void 0).catch(async (error) => {
97722
+ const reason = error instanceof Error ? error.message : String(error);
97723
+ await api.log.warn({ message: `a migration log line could not be delivered: ${reason}` }).catch(() => void 0);
97724
+ });
97725
+ lanes.set(api, next);
97726
+ return next;
97727
+ }
97728
+ function warnOver(api) {
97729
+ return (message) => deliver(api, () => api.log.warn({ message }));
97730
+ }
97731
+ /** What the rows-to-records move needs, over the migration API. */
97732
+ function rowsDepsOver(api) {
97733
+ return {
97734
+ api: integrationRowsApiOver(api),
97735
+ records: recordsOverMigrationApi(api),
97736
+ secrets: secretsOverMigrationApi(api),
97737
+ warn: warnOver(api)
97738
+ };
97739
+ }
97740
+ function defaultIntegrationApiOver$1(api) {
97741
+ return {
97742
+ ...integrationRowsApiOver(api),
97743
+ listDevices: async (addonId) => (await api.devices.list({ addonId })).devices,
97744
+ readDeviceHasConfig: async (deviceId) => (await api.devices.get({ deviceId }))?.hasConfig ?? false,
97745
+ setDeviceIntegrationId: async (deviceId, integrationId) => {
97746
+ await api.devices.setIntegrationId({
97747
+ deviceId,
97748
+ integrationId
97749
+ });
97750
+ },
97751
+ readDeviceIntegrationId: async (deviceId) => (await api.devices.get({ deviceId }))?.integrationId ?? null
97752
+ };
97753
+ }
97754
+ /** What the rows-to-records move and the device claim need, over the migration API. */
97755
+ function defaultIntegrationDepsOver(api) {
97756
+ return {
97757
+ ...rowsDepsOver(api),
97758
+ api: defaultIntegrationApiOver$1(api),
97759
+ info: (message) => deliver(api, () => api.log.info({ message })),
97760
+ warnDevice: (message, deviceId) => deliver(api, () => api.log.warn({
97761
+ message,
97762
+ tags: { deviceId }
97763
+ }))
97764
+ };
97765
+ }
97566
97766
  //#endregion
97567
97767
  //#region src/rtsp-instances.ts
97568
97768
  /**
@@ -98210,18 +98410,13 @@ var RtspProviderAddon = class extends BaseDeviceProvider {
98210
98410
  const records = this.ctx.records;
98211
98411
  if (records === void 0) throw new Error("provider-rtsp: ctx.records is required (hub-only addon)");
98212
98412
  await records.declare(rtspRecordKinds);
98213
- const defaultIntegration = defineAddonMigrations(defaultIntegrationMigrations({
98413
+ await this.runMigrations(defaultIntegrationMigrations({
98214
98414
  addonId: this.addonId,
98215
98415
  date: "2026-11-04"
98216
- }), defaultIntegrationPortsOver({
98217
- api: defaultIntegrationApiOver(this.ctx.api),
98218
- records,
98219
- kind: rtspConnectionsEntryKind,
98220
- info: (message) => this.ctx.logger.info(message),
98221
- warn: (message) => this.ctx.logger.warn(message),
98222
- warnDevice: (message, deviceId) => this.ctx.logger.warn(message, { tags: { deviceId } })
98416
+ }), (api) => defaultIntegrationPortsOver({
98417
+ ...defaultIntegrationDepsOver(api),
98418
+ kind: rtspConnectionsEntryKind
98223
98419
  }));
98224
- await this.runMigrations(defaultIntegration);
98225
98420
  return { providers: [
98226
98421
  ...regs,
98227
98422
  {