@camstack/addon-provider-rtsp 1.2.151 → 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-9TIHFj1w.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 });
@@ -8045,6 +8091,7 @@ var BaseAddon = class {
8045
8091
  * resolved blob back is a no-op for every unchanged secret.
8046
8092
  */
8047
8093
  async resolveGlobalStore(nodeId, cap) {
8094
+ await this.loadScopedSchemas(cap);
8048
8095
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8049
8096
  return this.projectForNode(raw, nodeId, cap);
8050
8097
  }
@@ -8055,6 +8102,7 @@ var BaseAddon = class {
8055
8102
  }
8056
8103
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8057
8104
  async readRawProjectedStore(nodeId, cap) {
8105
+ await this.loadScopedSchemas(cap);
8058
8106
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8059
8107
  return this.projectForNode(raw, nodeId, cap);
8060
8108
  }
@@ -8157,6 +8205,7 @@ var BaseAddon = class {
8157
8205
  await this.applyGlobalPatch(patch, nodeId);
8158
8206
  }
8159
8207
  async applyGlobalPatch(barePatch, nodeId) {
8208
+ await this.loadScopedSchemas();
8160
8209
  const keys = this.perNodeKeys();
8161
8210
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8162
8211
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8170,7 +8219,8 @@ var BaseAddon = class {
8170
8219
  await this.resolveConfig();
8171
8220
  await this.onConfigChanged();
8172
8221
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8173
- 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)]);
8174
8224
  }
8175
8225
  /**
8176
8226
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8178,14 +8228,67 @@ var BaseAddon = class {
8178
8228
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8179
8229
  * settings API behaves exactly like the legacy node-agnostic one.
8180
8230
  */
8181
- _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
+ */
8182
8281
  perNodeKeys(cap) {
8183
8282
  const cacheKey = cap ?? "";
8184
- const cached = this._perNodeKeysCache.get(cacheKey);
8283
+ const cached = this._nodeKeysByCap.get(cacheKey);
8185
8284
  if (cached) return cached;
8186
- const schema = this.globalSettingsSchema(cap);
8187
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8188
- 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);
8189
8292
  return keys;
8190
8293
  }
8191
8294
  /**
@@ -8195,10 +8298,9 @@ var BaseAddon = class {
8195
8298
  * time to return its response before the addon is torn down and
8196
8299
  * re-initialised by `AddonRegistryService.restartAddon`.
8197
8300
  */
8198
- maybeAutoRestart(patchKeys, schema) {
8199
- if (!schema) return;
8301
+ maybeAutoRestart(patchKeys, schemas) {
8200
8302
  const restartKeys = /* @__PURE__ */ new Set();
8201
- 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) {
8202
8304
  if (field.type === "separator" || field.type === "info") continue;
8203
8305
  if (field.requiresRestart) restartKeys.add(field.key);
8204
8306
  }
@@ -8394,12 +8496,14 @@ var BaseAddon = class {
8394
8496
  * here, in the addon's own process, so the hub keeps no copy of it.
8395
8497
  */
8396
8498
  async getDeclaredSettingsKeys() {
8499
+ await this.loadScopedSchemas();
8397
8500
  return { keys: [...this.declaredKeySet()].toSorted() };
8398
8501
  }
8399
8502
  declaredKeySet() {
8400
8503
  const keys = new Set(Object.keys(this.defaults));
8401
8504
  const schema = this.globalSettingsSchema();
8402
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);
8403
8507
  for (const key of this._declaredStateKeys) keys.add(key);
8404
8508
  for (const path of this.secretStatePaths) {
8405
8509
  const root = path.split(/[.[]/, 1)[0];
@@ -8495,7 +8599,11 @@ var BaseAddon = class {
8495
8599
  }
8496
8600
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8497
8601
  addonSecretTree() {
8498
- 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
+ }
8499
8607
  return this._addonSecretTree;
8500
8608
  }
8501
8609
  deviceSecretTree() {
@@ -8745,6 +8853,23 @@ function dropPerNodeFields(fields) {
8745
8853
  }
8746
8854
  return kept;
8747
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
+ }
8748
8873
  function collectFieldKeys(fields) {
8749
8874
  const collected = [];
8750
8875
  for (const field of fields) {
@@ -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`);
@@ -14858,13 +14990,13 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14858
14990
  };
14859
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`);
14860
14992
  for (const device of orphans) if (resolve(device, new Set([device.id])) === null) unresolved.add(device.id);
14861
- 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);
14862
14994
  for (const [deviceId, integrationId] of plan) await ports.setIntegrationId(deviceId, integrationId);
14863
14995
  const wrong = [];
14864
14996
  for (const [deviceId, integrationId] of plan) if (await ports.readIntegrationId(deviceId) !== integrationId) wrong.push(deviceId);
14865
14997
  if (wrong.length > 0) throw new Error(`device(s) ${wrong.join(", ")} did not read back their integration after the claim`);
14866
14998
  const claimed = plan.size;
14867
- 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`);
14868
15000
  return {
14869
15001
  outcome: claimed > 0 ? "done" : "already-done",
14870
15002
  summary: {
@@ -14990,25 +15122,6 @@ function defaultIntegrationMigrations(spec) {
14990
15122
  ...spec.eligible !== void 0 ? { eligible: spec.eligible } : {}
14991
15123
  })];
14992
15124
  }
14993
- function defaultIntegrationApiOver(api) {
14994
- return {
14995
- list: () => api.integrations.list.query(),
14996
- readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14997
- clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14998
- listDevices: (addonId) => api.deviceManager.listAll.query({
14999
- addonId,
15000
- projection: "slim"
15001
- }),
15002
- setDeviceIntegrationId: async (deviceId, integrationId) => {
15003
- await api.deviceManager.setIntegrationId.mutate({
15004
- deviceId,
15005
- integrationId
15006
- });
15007
- },
15008
- readDeviceHasConfig: async (deviceId) => Object.keys((await api.deviceManager.getDevice.query({ deviceId }))?.config ?? {}).length > 0,
15009
- readDeviceIntegrationId: async (deviceId) => (await api.deviceManager.getDevice.query({ deviceId }))?.integrationId ?? null
15010
- };
15011
- }
15012
15125
  /**
15013
15126
  * The trainer contract — what the hub hands the cloud trainer and what it
15014
15127
  * gets back (cloud training, D805+).
@@ -16129,6 +16242,39 @@ var UnstampedEventMediaCountSchema = object({
16129
16242
  /** Sum across both, or `null` when either lane could not be counted. */
16130
16243
  total: number().int().nonnegative().nullable()
16131
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
+ });
16132
16278
  var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
16133
16279
  /** The independently selectable logical storage classes — every class
16134
16280
  * `storage.listLocationDeclarations` reports, so an operator never meets a
@@ -47165,52 +47311,7 @@ onBrightnessChanged: { data: object({
47165
47311
  minIntervalMs: 0
47166
47312
  } } }
47167
47313
  };
47168
- /**
47169
- * `broker` — unified pub/sub broker registry, system-scoped collection.
47170
- *
47171
- * The cap models any kind-tagged message broker the user wants to
47172
- * register and that other addons might consume. The first two kinds
47173
- * are MQTT (mosquitto / aedes embedded / cloud bridge) and
47174
- * Home Assistant (HA WebSocket — subscribe_entities + call_service).
47175
- * Future kinds (Zigbee2MQTT bridge, ZHA, KNX, Telegram, …) plug in
47176
- * the same surface.
47177
- *
47178
- * Why one cap, not one cap per kind:
47179
- * - The integrations page wants a single table of "every broker"
47180
- * across kinds; a unified cap drives it without join logic.
47181
- * - Generic admin operations (add / remove / test / status) are
47182
- * identical across kinds — duplicating them per cap was busywork.
47183
- * - The `network-access` cap precedent: same interface, many
47184
- * `providerKind: 'ingress'` implementations (Tailscale, ngrok, …).
47185
- *
47186
- * What stays kind-specific:
47187
- * - `getBrokerConfig` payload (kind decides whether it returns
47188
- * mqtt URL+creds, HA baseUrl+token, …) — typed as
47189
- * `Record<string, unknown>` at the wire; consumers narrow.
47190
- * - `publish` / `subscribe` / `getState` arguments (target / filter /
47191
- * key) — also typed as `Record<string, unknown>` so each kind can
47192
- * evolve its surface (MQTT topic+qos vs HA entity_id+domain)
47193
- * without rebreaking the cap signature.
47194
- * - `add` `settings` payload (different fields per kind, validated by
47195
- * the kind-specific provider).
47196
- *
47197
- * Bidirectionality:
47198
- * - `publish` is RPC — synchronous result (provider-defined).
47199
- * - `subscribe` returns a subscription handle; the actual message
47200
- * stream flows over the typed event-bus as `broker.message` events
47201
- * keyed by `(brokerId, subscriptionId)`. Consumers filter in their
47202
- * event handler. Subscriptions persist across reconnects — the
47203
- * provider re-subscribes upstream after a transport drop.
47204
- *
47205
- * Why event-bus push (not a subscription RPC stream):
47206
- * - Matches the rest of CamStack's D8 contract — events for telemetry,
47207
- * RPC for loss-is-a-bug. Broker messages are telemetry (a single
47208
- * drop is recoverable via `getState({key})`).
47209
- * - Frees `broker.subscribe` from holding a long-lived RPC channel.
47210
- * - Cross-process: events already route through `$event-bus`; no
47211
- * per-broker wire-shaping needed.
47212
- */
47213
- var BrokerStatusEnum = _enum([
47314
+ _enum([
47214
47315
  "connected",
47215
47316
  "disconnected",
47216
47317
  "connecting",
@@ -47218,141 +47319,12 @@ var BrokerStatusEnum = _enum([
47218
47319
  "unreachable",
47219
47320
  "error"
47220
47321
  ]);
47221
- var BrokerInfoSchema = object({
47222
- /** Stable broker id. Persisted; survives addon restarts. */
47223
- id: string(),
47224
- /** Addon id of the provider that OWNS this broker.
47225
- *
47226
- * The `broker` cap is a system-scoped collection: several addons
47227
- * register a `broker` provider, each owning a DISJOINT set of
47228
- * brokers (mqtt-broker owns `mqtt_*`, provider-homeassistant owns
47229
- * `ha_*`). A broker therefore belongs to exactly one addon — like a
47230
- * device belongs to one integration. The admin UI threads this id
47231
- * back as the `{ addonId }` system-collection selector on every
47232
- * id-keyed call (`get` / `getSettings` / `setSettings` / `remove` /
47233
- * `testConnection`), so the call routes to the OWNING provider
47234
- * instead of defaulting to the first-registered one. */
47235
- addonId: string(),
47236
- /** Human-readable name (operator-chosen at add-time). */
47237
- name: string(),
47238
- /** Provider-defined kind tag — `mqtt` / `home-assistant` / future. */
47239
- kind: string(),
47240
- status: BrokerStatusEnum,
47241
- /** Free-form provider-specific info (HA version, MQTT broker
47242
- * flavour, latency, mTLS active, …). */
47243
- info: record(string(), unknown()),
47244
- /** Ms epoch of the last connection probe / status update. */
47245
- lastCheckedAt: number().nullable(),
47246
- /** Last error message, when `status` indicates failure. */
47247
- error: string().nullable()
47248
- });
47249
47322
  var RegistryStatusSchema = object({
47250
47323
  brokerCount: number().int().nonnegative(),
47251
47324
  connectedCount: number().int().nonnegative()
47252
47325
  });
47253
- /**
47254
- * One entry per `broker` provider — which addon provides which broker
47255
- * kind(s). The unified create picker reads this to know, for each
47256
- * (addonId, kind) pair the operator can pick, where to route the
47257
- * follow-up `getSettingsSchema` / `testSettings` / `add` calls (via the
47258
- * `{ addonId }` selector). A provider only advertises a kind here when
47259
- * it implements `getSettingsSchema` for that kind (so the picker never
47260
- * offers a kind whose creation form can't be rendered).
47261
- */
47262
- var BrokerProviderInfoSchema = object({
47263
- /** Addon id of the `broker` provider this entry describes. */
47264
- addonId: string(),
47265
- /** Broker kinds this provider can create, with a display label. */
47266
- kinds: array(object({
47267
- kind: string(),
47268
- label: string(),
47269
- /**
47270
- * Relative hub path to start an interactive account link (OAuth popup).
47271
- * May include `{{brokerId}}`; admin-ui substitutes and appends `?popup=1`.
47272
- */
47273
- interactiveConnectPath: string().min(1).optional(),
47274
- /** Relative hub path to disconnect the linked account (same popup pattern). */
47275
- interactiveDisconnectPath: string().min(1).optional(),
47276
- /**
47277
- * The kind signs in through a server-held interactive session instead of
47278
- * (or before) typed credentials. `basePath` is a hub-relative route set
47279
- * served by the owning addon (`/addon/<id>/...`) speaking the sign-in
47280
- * contract: `POST {basePath}/start` (body: the form's settings) answers
47281
- * `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
47282
- * `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
47283
- * 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
47284
- * (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
47285
- * existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
47286
- * (`{ sessionId }`) drops the session. The dialog that edits a broker
47287
- * offers it as the default path; the fields the session fills are
47288
- * marked `filledBySignIn` in the kind's settings schema.
47289
- *
47290
- * `methods` lists the ways the session can sign in, recommended first:
47291
- * `'device-code'` adds `POST {basePath}/code/start` (body: the form's
47292
- * settings) answering `{ sessionId, userCode, verificationUrl,
47293
- * expiresAt }` — the operator types `userCode` at `verificationUrl` on
47294
- * any device, and the same `status` / `commit` / `cancel` routes follow
47295
- * (status then carries `method`, `userCode`, `verificationUrl`,
47296
- * `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
47297
- * `'proxy'` is the `{basePath}/start` flow above. Absent means
47298
- * `['proxy']`.
47299
- */
47300
- signIn: object({
47301
- basePath: string().min(1),
47302
- methods: array(_enum(["device-code", "proxy"])).optional()
47303
- }).optional()
47304
- }))
47305
- });
47306
- var ListInputSchema = object({
47307
- /** Optional kind filter — `list({kind:'home-assistant'})` returns
47308
- * only HA brokers. Omit to list every broker. */
47309
- kind: string().optional() });
47310
47326
  var GetInputSchema = object({ id: string() });
47311
- var AddInputSchema = object({
47312
- kind: string().min(1),
47313
- name: string().min(1),
47314
- /**
47315
- * ADOPT an existing id instead of minting a new one.
47316
- *
47317
- * Written the day this cost an outage. A broker's id is not a detail: HA
47318
- * devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
47319
- * lost from config and re-added as `ha_001` leaves every one of its devices
47320
- * bound to a broker that no longer exists. Re-entering the password under the
47321
- * ORIGINAL id turns a multi-step device migration back into re-entering a
47322
- * password.
47323
- *
47324
- * A provider MUST refuse an id that is already in use — adopting a live
47325
- * broker's id would silently take it over.
47326
- */
47327
- id: string().min(1).optional(),
47328
- /** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
47329
- * `{baseUrl,accessToken}`). Validated by the kind-specific provider
47330
- * branch on receipt — invalid shape rejects the add. */
47331
- settings: record(string(), unknown())
47332
- });
47333
- var AddResultSchema = object({ id: string() });
47334
- var RemoveInputSchema = object({ id: string() });
47335
- var TestConnectionResultSchema = discriminatedUnion("ok", [object({
47336
- ok: literal(true),
47337
- latencyMs: number().nonnegative()
47338
- }), object({
47339
- ok: literal(false),
47340
- error: string()
47341
- })]);
47342
47327
  var SettingsRecordSchema$1 = record(string(), unknown());
47343
- var SettingsSchemaInputSchema = object({ kind: string() });
47344
- var TestSettingsInputSchema = object({
47345
- kind: string(),
47346
- settings: SettingsRecordSchema$1
47347
- });
47348
- var TestSettingsResultSchema = discriminatedUnion("ok", [object({
47349
- ok: literal(true),
47350
- latencyMs: number().nonnegative().optional()
47351
- }).strict(), object({
47352
- ok: literal(false),
47353
- error: string()
47354
- })]);
47355
- var SettingsSchemaResultSchema = unknown().nullable();
47356
47328
  var PublishInputSchema = object({
47357
47329
  brokerId: string(),
47358
47330
  /** Kind-specific routing target.
@@ -47386,25 +47358,7 @@ var GetStateInputSchema = object({
47386
47358
  * HA: entity_id (returns the cached entity state). */
47387
47359
  key: string()
47388
47360
  });
47389
- method(ListInputSchema, array(BrokerInfoSchema)), method(GetInputSchema, BrokerInfoSchema.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
47390
- kind: "mutation",
47391
- auth: "admin"
47392
- }), method(RemoveInputSchema, _void(), {
47393
- kind: "mutation",
47394
- auth: "admin"
47395
- }), method(GetInputSchema, TestConnectionResultSchema, {
47396
- kind: "mutation",
47397
- auth: "admin"
47398
- }), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(object({
47399
- id: string(),
47400
- settings: SettingsRecordSchema$1
47401
- }), _void(), {
47402
- kind: "mutation",
47403
- auth: "admin"
47404
- }), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }), method(TestSettingsInputSchema, TestSettingsResultSchema, {
47405
- kind: "mutation",
47406
- auth: "admin"
47407
- }), method(PublishInputSchema, unknown(), {
47361
+ method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(PublishInputSchema, unknown(), {
47408
47362
  kind: "mutation",
47409
47363
  auth: "admin"
47410
47364
  }), method(SubscribeInputSchema, SubscribeResultSchema, {
@@ -52212,7 +52166,14 @@ var DiscoveryCandidateSchema = object({
52212
52166
  /** `stableId` of the gateway candidate this device was found BEHIND. */
52213
52167
  parentStableId: string().optional(),
52214
52168
  /** `false` = shown for context but not pickable. Default true. */
52215
- 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()
52216
52177
  });
52217
52178
  /**
52218
52179
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -52339,8 +52300,14 @@ var deviceProviderCapability = {
52339
52300
  * provider does not support manual creation. Lets the Add-Device dialog
52340
52301
  * pick the right type instead of assuming Camera.
52341
52302
  */
52342
- getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
52343
- 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, {
52344
52311
  kind: "mutation",
52345
52312
  auth: "admin"
52346
52313
  }),
@@ -52351,10 +52318,16 @@ var deviceProviderCapability = {
52351
52318
  * devices of that type. The output is a `ConfigUISchema` — the
52352
52319
  * router type-asserts it at the boundary.
52353
52320
  */
52354
- 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),
52355
52326
  createDevice: method(object({
52356
52327
  type: _enum(DeviceType),
52357
- 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()
52358
52331
  }), DeviceSummarySchema, {
52359
52332
  kind: "mutation",
52360
52333
  auth: "admin"
@@ -53079,7 +53052,10 @@ method(object({
53079
53052
  auth: "admin"
53080
53053
  }), method(object({
53081
53054
  addonId: string(),
53082
- 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()
53083
53059
  }), unknown().nullable()), method(object({
53084
53060
  addonId: string(),
53085
53061
  type: _enum(DeviceType),
@@ -53163,7 +53139,11 @@ method(object({
53163
53139
  kind: "mutation",
53164
53140
  auth: "admin",
53165
53141
  timeoutMs: 12e4
53166
- }), 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({
53167
53147
  deviceId: number(),
53168
53148
  key: string(),
53169
53149
  value: unknown()
@@ -56443,7 +56423,7 @@ var TestConnectionStatusEnum = _enum([
56443
56423
  "inconclusive",
56444
56424
  "unsupported"
56445
56425
  ]);
56446
- var TestConnectionResultSchema$1 = object({
56426
+ var TestConnectionResultSchema = object({
56447
56427
  /** True ONLY for `validated`. Never true for a test that did not run. */
56448
56428
  success: boolean(),
56449
56429
  error: string().optional(),
@@ -56488,7 +56468,7 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56488
56468
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56489
56469
  addonId: string(),
56490
56470
  settings: record(string(), unknown())
56491
- }), TestConnectionResultSchema$1, {
56471
+ }), TestConnectionResultSchema, {
56492
56472
  kind: "mutation",
56493
56473
  auth: "admin"
56494
56474
  });
@@ -56515,6 +56495,14 @@ var ConfigSchemaPassthrough$3 = custom();
56515
56495
  var IntegrationInstanceIdSchema = string().min(1);
56516
56496
  var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
56517
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
+ /**
56518
56506
  * One kind of instance a provider can create (an adapter, an account type). The sign-in and
56519
56507
  * connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
56520
56508
  * hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
@@ -56525,6 +56513,22 @@ var IntegrationInstanceKindSchema = object({
56525
56513
  /** Stable id of the kind, sent back as `createInstance.kind`. */
56526
56514
  id: string().min(1),
56527
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(),
56528
56532
  /** The kind signs in through a server-held interactive session. */
56529
56533
  signIn: object({
56530
56534
  basePath: string().min(1),
@@ -56537,11 +56541,7 @@ var IntegrationInstanceKindSchema = object({
56537
56541
  });
56538
56542
  var IntegrationInstanceDescriptionSchema = object({
56539
56543
  /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
56540
- role: _enum([
56541
- "devices",
56542
- "services",
56543
- "both"
56544
- ]),
56544
+ role: IntegrationInstanceRoleSchema,
56545
56545
  /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
56546
56546
  discovery: object({
56547
56547
  mode: _enum([
@@ -56616,7 +56616,11 @@ var integrationInstancesCapability = {
56616
56616
  methods: {
56617
56617
  describe: method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }),
56618
56618
  /** The form; `id: null` is the create form. */
56619
- 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" }),
56620
56624
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
56621
56625
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
56622
56626
  /**
@@ -56626,7 +56630,9 @@ var integrationInstancesCapability = {
56626
56630
  createInstance: method(object({
56627
56631
  id: IntegrationInstanceIdSchema.optional(),
56628
56632
  name: string().min(1),
56629
- 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()
56630
56636
  }), object({ id: string() }), {
56631
56637
  kind: "mutation",
56632
56638
  auth: "admin"
@@ -56653,8 +56659,10 @@ var integrationInstancesCapability = {
56653
56659
  }),
56654
56660
  testInstance: optionalMethod(object({
56655
56661
  id: IntegrationInstanceIdSchema.nullable(),
56656
- values: record(string(), unknown())
56657
- }), 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, {
56658
56666
  kind: "mutation",
56659
56667
  auth: "admin"
56660
56668
  })
@@ -59411,6 +59419,380 @@ var SystemMetricsSchema = object({
59411
59419
  gpuMemoryPercent: number().optional()
59412
59420
  });
59413
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({});
59414
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({
59415
59797
  id: string().min(1),
59416
59798
  error: string().min(1)
@@ -59440,7 +59822,9 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59440
59822
  "written",
59441
59823
  "refused"
59442
59824
  ]),
59443
- 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()
59444
59828
  }), {
59445
59829
  kind: "mutation",
59446
59830
  auth: "admin"
@@ -62107,7 +62491,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
62107
62491
  }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
62108
62492
  kind: "mutation",
62109
62493
  auth: "admin"
62110
- }), 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, {
62111
62498
  kind: "query",
62112
62499
  auth: "admin"
62113
62500
  }), method(object({}), array(RelocateJobSchema).readonly(), {
@@ -64762,7 +65149,7 @@ var BrokerStatusSchema$1 = _enum([
64762
65149
  "unreachable",
64763
65150
  "tls-error"
64764
65151
  ]);
64765
- var BrokerInfoSchema$1 = object({
65152
+ var BrokerInfoSchema = object({
64766
65153
  id: string(),
64767
65154
  name: string(),
64768
65155
  url: string(),
@@ -64825,7 +65212,7 @@ var StatusSchema = object({
64825
65212
  brokerCount: number(),
64826
65213
  embeddedRunning: boolean()
64827
65214
  });
64828
- 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, {
64829
65216
  kind: "mutation",
64830
65217
  auth: "admin"
64831
65218
  }), method(IdInputSchema, _void(), {
@@ -72007,253 +72394,6 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, met
72007
72394
  auth: "admin"
72008
72395
  });
72009
72396
  /**
72010
- * records - an addon's own facts and documents (D881): ledgers, audit rows,
72011
- * config documents, mirrors. The third storage API next to `ctx.settings`
72012
- * (operator intent, D870) and device-history (numbers over time, D850).
72013
- *
72014
- * Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
72015
- * crossed a process and stated by a hub in-process caller (`ctx.records` does
72016
- * that with the addon id closed over). It is deliberately NOT in any input
72017
- * schema, so a session can never supply it, and a call with no stamp is refused
72018
- * `records/no-caller`. A kind is readable and writable only by its owner; other
72019
- * addons go through the owner's capability.
72020
- *
72021
- * Every read is three-way: a row, `null`, or the call THROWS. A failed read is
72022
- * never `[]`. Secret fields are never on the wire: a read shows the
72023
- * redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
72024
- * resolves itself, and a write may carry only a keychain marker bound to the
72025
- * row's own ref.
72026
- *
72027
- * Internal: addons reach it through `ctx.records`, never directly.
72028
- */
72029
- var RecordIdSchema = string().min(1).max(256);
72030
- /** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
72031
- var RecordBodySchema = record(string(), unknown());
72032
- var RecordInputSchema = object({
72033
- id: RecordIdSchema,
72034
- /** The device this row belongs to (D865 removal, per-device reads). */
72035
- deviceId: number().int().optional(),
72036
- /** The domain clock, ms: observed / created / opened. */
72037
- at: number().int().optional(),
72038
- /** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
72039
- expiresAt: number().int().optional(),
72040
- data: RecordBodySchema
72041
- });
72042
- var RecordViewSchema = object({
72043
- id: string(),
72044
- deviceId: number().int().nullable(),
72045
- at: number().nullable(),
72046
- expiresAt: number().nullable(),
72047
- rev: number().int(),
72048
- updatedAt: number(),
72049
- data: RecordBodySchema
72050
- });
72051
- /** Declared index fields only. Anything else does not compile, by design. */
72052
- var RecordWhereSchema = record(RecordFieldPathSchema, union([
72053
- string(),
72054
- number(),
72055
- boolean(),
72056
- object({ in: array(union([
72057
- string(),
72058
- number(),
72059
- boolean()
72060
- ])).min(1).max(RECORD_BATCH_MAX) }),
72061
- object({
72062
- gt: union([string(), number()]).optional(),
72063
- gte: union([string(), number()]).optional(),
72064
- lt: union([string(), number()]).optional(),
72065
- lte: union([string(), number()]).optional()
72066
- }).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
72067
- ]));
72068
- var KindShape = { kind: RecordKindNameSchema };
72069
- var TimeWindowShape = {
72070
- deviceId: number().int().optional(),
72071
- atFrom: number().optional(),
72072
- atTo: number().optional()
72073
- };
72074
- var RecordQueryInputSchema = object({
72075
- ...KindShape,
72076
- ...TimeWindowShape,
72077
- where: RecordWhereSchema.optional(),
72078
- /** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
72079
- order: _enum(["at", "id"]).optional(),
72080
- desc: boolean().optional(),
72081
- limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
72082
- /** Opaque keyset cursor from a previous answer. There is no offset. */
72083
- cursor: string().min(1).optional()
72084
- });
72085
- var RecordQueryResultSchema = object({
72086
- records: array(RecordViewSchema),
72087
- nextCursor: string().nullable()
72088
- });
72089
- var RecordKindStateSchema = _enum([
72090
- "created",
72091
- "unchanged",
72092
- "updated"
72093
- ]);
72094
- var DeclareKindResultSchema = object({
72095
- kindId: number().int(),
72096
- state: RecordKindStateSchema,
72097
- createdIndexes: array(string())
72098
- });
72099
- /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
72100
- var SecretRefsList = array(string());
72101
- /**
72102
- * One op of a `transact`: the single-row writes, each on a kind of the caller.
72103
- * All ops of one call share ONE database file and ONE `transactFamily`.
72104
- */
72105
- var RecordTransactOpSchema = discriminatedUnion("op", [
72106
- object({
72107
- op: literal("put"),
72108
- kind: RecordKindNameSchema,
72109
- record: RecordInputSchema,
72110
- ifRev: number().int().min(0).optional()
72111
- }),
72112
- object({
72113
- op: literal("patch"),
72114
- kind: RecordKindNameSchema,
72115
- id: RecordIdSchema,
72116
- data: RecordBodySchema,
72117
- at: number().int().nullable().optional(),
72118
- expiresAt: number().int().nullable().optional(),
72119
- ifRev: number().int().min(1).optional()
72120
- }),
72121
- object({
72122
- op: literal("delete"),
72123
- kind: RecordKindNameSchema,
72124
- id: RecordIdSchema
72125
- })
72126
- ]);
72127
- var RecordSecretRefsSchema = record(string(), record(string(), string()));
72128
- method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
72129
- kind: "mutation",
72130
- auth: "admin"
72131
- }), method(object({
72132
- ...KindShape,
72133
- id: RecordIdSchema
72134
- }), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
72135
- ...KindShape,
72136
- ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
72137
- }), array(RecordViewSchema), { auth: "admin" }), method(object({
72138
- ...KindShape,
72139
- ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
72140
- }), RecordSecretRefsSchema, { auth: "admin" }), method(object({
72141
- ...KindShape,
72142
- record: RecordInputSchema,
72143
- /** Compare-and-set. `0` means "the row must not exist". */
72144
- ifRev: number().int().min(0).optional()
72145
- }), object({
72146
- rev: number().int(),
72147
- releasedRefs: SecretRefsList
72148
- }), {
72149
- kind: "mutation",
72150
- auth: "admin"
72151
- }), method(object({
72152
- ...KindShape,
72153
- records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
72154
- }), object({
72155
- count: number().int(),
72156
- releasedRefs: SecretRefsList
72157
- }), {
72158
- kind: "mutation",
72159
- auth: "admin"
72160
- }), method(object({
72161
- ...KindShape,
72162
- id: RecordIdSchema,
72163
- data: RecordBodySchema,
72164
- at: number().int().nullable().optional(),
72165
- expiresAt: number().int().nullable().optional(),
72166
- ifRev: number().int().min(1).optional()
72167
- }), object({
72168
- rev: number().int(),
72169
- releasedRefs: SecretRefsList
72170
- }), {
72171
- kind: "mutation",
72172
- auth: "admin"
72173
- }), method(object({
72174
- ...KindShape,
72175
- id: RecordIdSchema
72176
- }), object({
72177
- deleted: boolean(),
72178
- secretRefs: SecretRefsList
72179
- }), {
72180
- kind: "mutation",
72181
- auth: "admin"
72182
- }), method(object({
72183
- ...KindShape,
72184
- ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
72185
- }), object({
72186
- deleted: number().int(),
72187
- secretRefs: SecretRefsList
72188
- }), {
72189
- kind: "mutation",
72190
- auth: "admin"
72191
- }), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
72192
- ...KindShape,
72193
- ...TimeWindowShape,
72194
- where: RecordWhereSchema.optional()
72195
- }), object({ count: number().int() }), { auth: "admin" }), method(object({
72196
- ...KindShape,
72197
- deviceId: number().int().optional(),
72198
- from: number(),
72199
- to: number(),
72200
- bucketMs: number().int().positive()
72201
- }), object({ buckets: array(object({
72202
- bucketStart: number(),
72203
- count: number().int()
72204
- })) }), { auth: "admin" }), method(object({
72205
- ...KindShape,
72206
- op: _enum([
72207
- "sum",
72208
- "min",
72209
- "max",
72210
- "count"
72211
- ]),
72212
- field: RecordFieldPathSchema.optional(),
72213
- groupBy: RecordFieldPathSchema.optional(),
72214
- where: RecordWhereSchema.optional()
72215
- }), object({ rows: array(object({
72216
- group: union([
72217
- string(),
72218
- number(),
72219
- _null()
72220
- ]),
72221
- value: number().nullable()
72222
- })) }), { auth: "admin" }), method(object({
72223
- ...KindShape,
72224
- deviceId: number().int().optional(),
72225
- atBefore: number().optional(),
72226
- where: RecordWhereSchema.optional()
72227
- }), object({
72228
- deleted: number().int(),
72229
- secretRefs: SecretRefsList
72230
- }), {
72231
- kind: "mutation",
72232
- auth: "admin"
72233
- }), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
72234
- results: array(object({
72235
- rev: number().int().nullable(),
72236
- deleted: boolean()
72237
- })),
72238
- releasedRefs: SecretRefsList,
72239
- secretRefs: SecretRefsList
72240
- }), {
72241
- kind: "mutation",
72242
- auth: "admin"
72243
- }), method(object({
72244
- deviceId: number().int(),
72245
- kinds: array(RecordKindNameSchema).optional()
72246
- }), object({
72247
- deleted: record(string(), number().int()),
72248
- secretRefs: SecretRefsList
72249
- }), {
72250
- kind: "mutation",
72251
- auth: "admin"
72252
- }), method(object(KindShape), object({ id: number().int() }), {
72253
- kind: "mutation",
72254
- auth: "admin"
72255
- });
72256
- /**
72257
72397
  * record-entries - the ONE admin surface for every list of user-created entries
72258
72398
  * (D881): notification targets, brokers, LLM profiles, anything an addon
72259
72399
  * declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
@@ -82085,7 +82225,7 @@ var BaseDeviceProvider = class extends BaseAddon {
82085
82225
  * `deviceClasses` map (first registered type). `null` when manual creation is
82086
82226
  * unsupported. Lets the Add-Device dialog pick the right type per provider.
82087
82227
  */
82088
- async getManualCreationType() {
82228
+ async getManualCreationType(_input) {
82089
82229
  if (!await this.supportsManualCreation()) return { deviceType: null };
82090
82230
  return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
82091
82231
  }
@@ -82096,7 +82236,7 @@ var BaseDeviceProvider = class extends BaseAddon {
82096
82236
  return true;
82097
82237
  }
82098
82238
  async getChildCreationSchema(input) {
82099
- return this.onGetCreationSchema(input.type);
82239
+ return this.onGetCreationSchema(input.type, { integrationId: input.integrationId });
82100
82240
  }
82101
82241
  /**
82102
82242
  * Default kernel-orchestrated `createDevice` implementation. The
@@ -82108,7 +82248,7 @@ var BaseDeviceProvider = class extends BaseAddon {
82108
82248
  * `deviceClasses` instead.
82109
82249
  */
82110
82250
  async createDevice(input) {
82111
- 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 } : {} });
82112
82252
  const Class = this.deviceClasses[spec.meta.type];
82113
82253
  if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
82114
82254
  const stableId = this.generateStableId(spec.meta.type, spec.config);
@@ -83517,20 +83657,6 @@ Object.freeze({
83517
83657
  access: "create",
83518
83658
  role: "viewer"
83519
83659
  },
83520
- "broker.add": {
83521
- capName: "broker",
83522
- capScope: "system",
83523
- addonId: null,
83524
- access: "create",
83525
- role: "admin"
83526
- },
83527
- "broker.get": {
83528
- capName: "broker",
83529
- capScope: "system",
83530
- addonId: null,
83531
- access: "view",
83532
- role: "admin"
83533
- },
83534
83660
  "broker.getBrokerConfig": {
83535
83661
  capName: "broker",
83536
83662
  capScope: "system",
@@ -83538,20 +83664,6 @@ Object.freeze({
83538
83664
  access: "view",
83539
83665
  role: "admin"
83540
83666
  },
83541
- "broker.getSettings": {
83542
- capName: "broker",
83543
- capScope: "system",
83544
- addonId: null,
83545
- access: "view",
83546
- role: "admin"
83547
- },
83548
- "broker.getSettingsSchema": {
83549
- capName: "broker",
83550
- capScope: "system",
83551
- addonId: null,
83552
- access: "view",
83553
- role: "admin"
83554
- },
83555
83667
  "broker.getState": {
83556
83668
  capName: "broker",
83557
83669
  capScope: "system",
@@ -83566,20 +83678,6 @@ Object.freeze({
83566
83678
  access: "view",
83567
83679
  role: "admin"
83568
83680
  },
83569
- "broker.list": {
83570
- capName: "broker",
83571
- capScope: "system",
83572
- addonId: null,
83573
- access: "view",
83574
- role: "admin"
83575
- },
83576
- "broker.listProviders": {
83577
- capName: "broker",
83578
- capScope: "system",
83579
- addonId: null,
83580
- access: "view",
83581
- role: "admin"
83582
- },
83583
83681
  "broker.publish": {
83584
83682
  capName: "broker",
83585
83683
  capScope: "system",
@@ -83587,20 +83685,6 @@ Object.freeze({
83587
83685
  access: "create",
83588
83686
  role: "admin"
83589
83687
  },
83590
- "broker.remove": {
83591
- capName: "broker",
83592
- capScope: "system",
83593
- addonId: null,
83594
- access: "delete",
83595
- role: "admin"
83596
- },
83597
- "broker.setSettings": {
83598
- capName: "broker",
83599
- capScope: "system",
83600
- addonId: null,
83601
- access: "create",
83602
- role: "admin"
83603
- },
83604
83688
  "broker.subscribe": {
83605
83689
  capName: "broker",
83606
83690
  capScope: "system",
@@ -83608,20 +83692,6 @@ Object.freeze({
83608
83692
  access: "create",
83609
83693
  role: "admin"
83610
83694
  },
83611
- "broker.testConnection": {
83612
- capName: "broker",
83613
- capScope: "system",
83614
- addonId: null,
83615
- access: "create",
83616
- role: "admin"
83617
- },
83618
- "broker.testSettings": {
83619
- capName: "broker",
83620
- capScope: "system",
83621
- addonId: null,
83622
- access: "create",
83623
- role: "admin"
83624
- },
83625
83695
  "broker.unsubscribe": {
83626
83696
  capName: "broker",
83627
83697
  capScope: "system",
@@ -88585,6 +88655,13 @@ Object.freeze({
88585
88655
  access: "view",
88586
88656
  role: "viewer"
88587
88657
  },
88658
+ "pipelineAnalytics.getMediaLocationCensus": {
88659
+ capName: "pipeline-analytics",
88660
+ capScope: "device",
88661
+ addonId: null,
88662
+ access: "view",
88663
+ role: "admin"
88664
+ },
88588
88665
  "pipelineAnalytics.getMediaReclaimStatus": {
88589
88666
  capName: "pipeline-analytics",
88590
88667
  capScope: "device",
@@ -97592,6 +97669,100 @@ object({
97592
97669
  delay: number().nonnegative().optional(),
97593
97670
  commands: record(string(), unknown())
97594
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
+ }
97595
97766
  //#endregion
97596
97767
  //#region src/rtsp-instances.ts
97597
97768
  /**
@@ -98239,19 +98410,13 @@ var RtspProviderAddon = class extends BaseDeviceProvider {
98239
98410
  const records = this.ctx.records;
98240
98411
  if (records === void 0) throw new Error("provider-rtsp: ctx.records is required (hub-only addon)");
98241
98412
  await records.declare(rtspRecordKinds);
98242
- const defaultIntegration = defineAddonMigrations(defaultIntegrationMigrations({
98413
+ await this.runMigrations(defaultIntegrationMigrations({
98243
98414
  addonId: this.addonId,
98244
98415
  date: "2026-11-04"
98245
- }), defaultIntegrationPortsOver({
98246
- api: defaultIntegrationApiOver(this.ctx.api),
98247
- secrets: this.ctx.secrets ?? null,
98248
- records,
98249
- kind: rtspConnectionsEntryKind,
98250
- info: (message) => this.ctx.logger.info(message),
98251
- warn: (message) => this.ctx.logger.warn(message),
98252
- warnDevice: (message, deviceId) => this.ctx.logger.warn(message, { tags: { deviceId } })
98416
+ }), (api) => defaultIntegrationPortsOver({
98417
+ ...defaultIntegrationDepsOver(api),
98418
+ kind: rtspConnectionsEntryKind
98253
98419
  }));
98254
- await this.runMigrations(defaultIntegration);
98255
98420
  return { providers: [
98256
98421
  ...regs,
98257
98422
  {