@camstack/addon-provider-bosch 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
5981
5981
  var ZodFirstPartyTypeKind;
5982
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5983
5983
  //#endregion
5984
- //#region ../types/dist/sleep-D1jjWAr8.mjs
5984
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
5985
5985
  /**
5986
5986
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5987
5987
  * window to float samples (D455).
@@ -6790,6 +6790,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6790
6790
  }
6791
6791
  return out;
6792
6792
  }
6793
+ /** Every scope, in the order the guard and the docs list them. */
6794
+ var SCOPED_SETTINGS_SCOPES = [
6795
+ "cluster",
6796
+ "device-defaults",
6797
+ "node"
6798
+ ];
6793
6799
  /**
6794
6800
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6795
6801
  *
@@ -7681,6 +7687,13 @@ var BaseAddon = class {
7681
7687
  */
7682
7688
  secretStatePaths = [];
7683
7689
  _addonSecretTree = null;
7690
+ /**
7691
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7692
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7693
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7694
+ */
7695
+ _scopedByCap = /* @__PURE__ */ new Map();
7696
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7684
7697
  _deviceSecretTree = null;
7685
7698
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7686
7699
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7750,6 +7763,7 @@ var BaseAddon = class {
7750
7763
  this._ctx = context;
7751
7764
  this._secretSettings = null;
7752
7765
  }
7766
+ await this.loadScopedSchemas();
7753
7767
  await this.resolveConfig();
7754
7768
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7755
7769
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7759,6 +7773,7 @@ var BaseAddon = class {
7759
7773
  const normalized = normalizeAddonInitResult(result);
7760
7774
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7761
7775
  this._registeredCapNames = providers.map((p) => p.capability.name);
7776
+ await this.loadScopedSchemas();
7762
7777
  return normalized;
7763
7778
  }
7764
7779
  /**
@@ -7847,6 +7862,16 @@ var BaseAddon = class {
7847
7862
  schema(schema) {
7848
7863
  return schema;
7849
7864
  }
7865
+ /**
7866
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7867
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7868
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7869
+ * must already know which keys are per node. Names the providers register at init are added
7870
+ * once known.
7871
+ */
7872
+ scopedSettingsCapabilities() {
7873
+ return [];
7874
+ }
7850
7875
  /** Override to provide global-level settings UI schema. */
7851
7876
  globalSettingsSchema(_cap) {
7852
7877
  return null;
@@ -7890,7 +7915,8 @@ var BaseAddon = class {
7890
7915
  return [];
7891
7916
  }
7892
7917
  async getGlobalSettings(overlay, cap, nodeId) {
7893
- const schema = this.globalSettingsSchema(cap);
7918
+ await this.loadScopedSchemas(cap);
7919
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7894
7920
  if (!schema) return { sections: [] };
7895
7921
  const projected = await this.readRawProjectedStore(nodeId, cap);
7896
7922
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -7925,10 +7951,9 @@ var BaseAddon = class {
7925
7951
  *
7926
7952
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7927
7953
  * needs its connection fields, the default integration the addon's own sections. It is the
7928
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7929
- * unchanged (`integrations.setSettings` for an account integration).
7954
+ * SHAPE of the form only — the values live on the instance record.
7930
7955
  */
7931
- async integrationSettingsSchema(_instance) {
7956
+ async integrationFormSchema(_instance) {
7932
7957
  return null;
7933
7958
  }
7934
7959
  /**
@@ -7974,7 +7999,7 @@ var BaseAddon = class {
7974
7999
  * per-instance schema when it declares one, else its global schema (what they always showed).
7975
8000
  */
7976
8001
  async getIntegrationFormSchema(instance, nodeId) {
7977
- const scoped = await this.integrationSettingsSchema(instance);
8002
+ const scoped = await this.integrationFormSchema(instance);
7978
8003
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7979
8004
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7980
8005
  }
@@ -8022,6 +8047,7 @@ var BaseAddon = class {
8022
8047
  * resolved blob back is a no-op for every unchanged secret.
8023
8048
  */
8024
8049
  async resolveGlobalStore(nodeId, cap) {
8050
+ await this.loadScopedSchemas(cap);
8025
8051
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8026
8052
  return this.projectForNode(raw, nodeId, cap);
8027
8053
  }
@@ -8032,6 +8058,7 @@ var BaseAddon = class {
8032
8058
  }
8033
8059
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8034
8060
  async readRawProjectedStore(nodeId, cap) {
8061
+ await this.loadScopedSchemas(cap);
8035
8062
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8036
8063
  return this.projectForNode(raw, nodeId, cap);
8037
8064
  }
@@ -8134,6 +8161,7 @@ var BaseAddon = class {
8134
8161
  await this.applyGlobalPatch(patch, nodeId);
8135
8162
  }
8136
8163
  async applyGlobalPatch(barePatch, nodeId) {
8164
+ await this.loadScopedSchemas();
8137
8165
  const keys = this.perNodeKeys();
8138
8166
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8139
8167
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8147,7 +8175,8 @@ var BaseAddon = class {
8147
8175
  await this.resolveConfig();
8148
8176
  await this.onConfigChanged();
8149
8177
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8150
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8178
+ const legacy = this.globalSettingsSchema();
8179
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8151
8180
  }
8152
8181
  /**
8153
8182
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8155,14 +8184,67 @@ var BaseAddon = class {
8155
8184
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8156
8185
  * settings API behaves exactly like the legacy node-agnostic one.
8157
8186
  */
8158
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8187
+ /**
8188
+ * Load the scoped schemas of every capability this addon declares (the override and the
8189
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8190
+ * derived from the previous set. Idempotent.
8191
+ */
8192
+ async loadScopedSchemas(extraCap) {
8193
+ const caps = new Set([
8194
+ ...this.scopedSettingsCapabilities(),
8195
+ ...this._registeredCapNames,
8196
+ ...extraCap === void 0 ? [] : [extraCap]
8197
+ ]);
8198
+ let added = false;
8199
+ for (const cap of caps) {
8200
+ if (this._scopedByCap.has(cap)) continue;
8201
+ const loaded = [];
8202
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8203
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8204
+ if (schema !== null) loaded.push({
8205
+ scope,
8206
+ schema
8207
+ });
8208
+ }
8209
+ this._scopedByCap.set(cap, loaded);
8210
+ added = true;
8211
+ }
8212
+ if (!added) return;
8213
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8214
+ this._addonSecretTree = null;
8215
+ }
8216
+ /**
8217
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8218
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8219
+ */
8220
+ scopedFormSchema(cap) {
8221
+ const byId = /* @__PURE__ */ new Map();
8222
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8223
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8224
+ }
8225
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8226
+ loadedScopedSchemas(cap) {
8227
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8228
+ return [...this._scopedByCap.values()].flat();
8229
+ }
8230
+ /**
8231
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8232
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8233
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8234
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8235
+ * Call {@link loadScopedSchemas} first (the async seams do).
8236
+ */
8159
8237
  perNodeKeys(cap) {
8160
8238
  const cacheKey = cap ?? "";
8161
- const cached = this._perNodeKeysCache.get(cacheKey);
8239
+ const cached = this._nodeKeysByCap.get(cacheKey);
8162
8240
  if (cached) return cached;
8163
- const schema = this.globalSettingsSchema(cap);
8164
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8165
- this._perNodeKeysCache.set(cacheKey, keys);
8241
+ const legacy = this.globalSettingsSchema(cap);
8242
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8243
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8244
+ if (scope !== "node") continue;
8245
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8246
+ }
8247
+ this._nodeKeysByCap.set(cacheKey, keys);
8166
8248
  return keys;
8167
8249
  }
8168
8250
  /**
@@ -8172,10 +8254,9 @@ var BaseAddon = class {
8172
8254
  * time to return its response before the addon is torn down and
8173
8255
  * re-initialised by `AddonRegistryService.restartAddon`.
8174
8256
  */
8175
- maybeAutoRestart(patchKeys, schema) {
8176
- if (!schema) return;
8257
+ maybeAutoRestart(patchKeys, schemas) {
8177
8258
  const restartKeys = /* @__PURE__ */ new Set();
8178
- for (const section of schema.sections) for (const field of section.fields) {
8259
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8179
8260
  if (field.type === "separator" || field.type === "info") continue;
8180
8261
  if (field.requiresRestart) restartKeys.add(field.key);
8181
8262
  }
@@ -8371,12 +8452,14 @@ var BaseAddon = class {
8371
8452
  * here, in the addon's own process, so the hub keeps no copy of it.
8372
8453
  */
8373
8454
  async getDeclaredSettingsKeys() {
8455
+ await this.loadScopedSchemas();
8374
8456
  return { keys: [...this.declaredKeySet()].toSorted() };
8375
8457
  }
8376
8458
  declaredKeySet() {
8377
8459
  const keys = new Set(Object.keys(this.defaults));
8378
8460
  const schema = this.globalSettingsSchema();
8379
8461
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8462
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8380
8463
  for (const key of this._declaredStateKeys) keys.add(key);
8381
8464
  for (const path of this.secretStatePaths) {
8382
8465
  const root = path.split(/[.[]/, 1)[0];
@@ -8472,7 +8555,11 @@ var BaseAddon = class {
8472
8555
  }
8473
8556
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8474
8557
  addonSecretTree() {
8475
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8558
+ if (this._addonSecretTree === null) {
8559
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8560
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8561
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8562
+ }
8476
8563
  return this._addonSecretTree;
8477
8564
  }
8478
8565
  deviceSecretTree() {
@@ -8722,6 +8809,23 @@ function dropPerNodeFields(fields) {
8722
8809
  }
8723
8810
  return kept;
8724
8811
  }
8812
+ /** The keys of the value-holding fields, recursing into layout containers. */
8813
+ function collectValueFieldKeys(fields) {
8814
+ const collected = [];
8815
+ for (const field of fields) {
8816
+ if (field.type === "group") {
8817
+ collected.push(...collectValueFieldKeys(field.fields));
8818
+ continue;
8819
+ }
8820
+ if (field.type === "sub-tabs") {
8821
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8822
+ continue;
8823
+ }
8824
+ if (field.type === "separator" || field.type === "info") continue;
8825
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8826
+ }
8827
+ return collected;
8828
+ }
8725
8829
  function collectFieldKeys(fields) {
8726
8830
  const collected = [];
8727
8831
  for (const field of fields) {
@@ -11956,7 +12060,7 @@ object({
11956
12060
  params: record(string(), union([string(), number()])).optional()
11957
12061
  });
11958
12062
  //#endregion
11959
- //#region ../types/dist/composition-CRNwcFYY.mjs
12063
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11960
12064
  /**
11961
12065
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11962
12066
  *
@@ -13428,12 +13532,22 @@ var RecordIntegrationInstances = class {
13428
13532
  ...this.hooks.description,
13429
13533
  mintsId: true
13430
13534
  };
13431
- instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13535
+ /**
13536
+ * The record an id names. A caller holding the `integrations` registry id (the Integrations
13537
+ * page) reaches a kind whose body links to it through `integrationId`; an id that is itself a
13538
+ * record id answers as it is. An id that names nothing comes back unchanged.
13539
+ */
13540
+ async resolveRecordId(id) {
13541
+ if (await this.handle.get(id) !== null) return id;
13542
+ const linked = (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.find((record) => record.data["integrationId"] === id);
13543
+ return linked === void 0 ? id : linked.id;
13544
+ }
13545
+ instanceFormSchema = async ({ id }) => this.hooks.formSchema(id === null ? null : await this.resolveRecordId(id));
13432
13546
  listInstances = async () => {
13433
13547
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
13434
13548
  };
13435
13549
  getInstance = async ({ id }) => {
13436
- const record = await this.handle.get(id);
13550
+ const record = await this.handle.get(await this.resolveRecordId(id));
13437
13551
  return record === null ? null : this.viewOf(record);
13438
13552
  };
13439
13553
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
@@ -13450,7 +13564,8 @@ var RecordIntegrationInstances = class {
13450
13564
  await this.hooks.onChanged?.(minted, "created");
13451
13565
  return { id: minted };
13452
13566
  };
13453
- updateInstance = async ({ id, name, values }) => {
13567
+ updateInstance = async ({ id: askedId, name, values }) => {
13568
+ const id = await this.resolveRecordId(askedId);
13454
13569
  const patch = {};
13455
13570
  for (const [key, value] of Object.entries(values)) {
13456
13571
  if (value === void 0) continue;
@@ -13485,7 +13600,10 @@ var RecordIntegrationInstances = class {
13485
13600
  status: "unsupported",
13486
13601
  error: "This integration declares no connection test"
13487
13602
  };
13488
- return this.hooks.test(input);
13603
+ return this.hooks.test(input.id === null ? input : {
13604
+ ...input,
13605
+ id: await this.resolveRecordId(input.id)
13606
+ });
13489
13607
  };
13490
13608
  /** The flat provider object to hand to `registerProvider`. */
13491
13609
  toProvider() {
@@ -14664,20 +14782,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14664
14782
  };
14665
14783
  }
14666
14784
  /**
14667
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14668
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14669
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14670
- * the new record through the addon's own records handle. The hub deletes the rows and their
14671
- * keychain values; the addon never touches the registry.
14672
- */
14785
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14786
+ * from its own process, so the leftover rows are reached through the migration-only door
14787
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14788
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14789
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14790
+ * door to delete the rows. The addon's own record kind is its records handle.
14791
+ */
14792
+ async function resolvedRows(stored, secrets) {
14793
+ if (secrets === null) return stored;
14794
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14795
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14796
+ return value;
14797
+ }
14673
14798
  function integrationRowsPortsOverApi(deps) {
14799
+ const secrets = deps.secrets ?? null;
14674
14800
  return {
14675
14801
  warn: (message) => deps.log.warn(message),
14676
14802
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14677
14803
  id: i.id,
14678
14804
  name: i.name
14679
14805
  })),
14680
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14806
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14681
14807
  putRecord: async (id, body) => {
14682
14808
  if (await deps.records.get(id) !== null) return "exists";
14683
14809
  try {
@@ -14696,15 +14822,17 @@ function integrationRowsPortsOverApi(deps) {
14696
14822
  await deps.records.delete(id);
14697
14823
  },
14698
14824
  clearRows: async (integrationId) => {
14699
- await deps.api.clearSettings({ id: integrationId });
14825
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14826
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14827
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14700
14828
  }
14701
14829
  };
14702
14830
  }
14703
14831
  function defaultIntegrationApiOver(api) {
14704
14832
  return {
14705
14833
  list: () => api.integrations.list.query(),
14706
- getSettings: (input) => api.integrations.getSettings.query(input),
14707
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14834
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14835
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14708
14836
  listDevices: (addonId) => api.deviceManager.listAll.query({
14709
14837
  addonId,
14710
14838
  projection: "slim"
@@ -50390,6 +50518,9 @@ var RunningProposalEvidenceSchema = object({
50390
50518
  runsFound: number().int().nonnegative(),
50391
50519
  pausesFound: number().int().nonnegative(),
50392
50520
  longestPauseMs: number().nonnegative(),
50521
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50522
+ pulsesFound: number().int().nonnegative(),
50523
+ longestPulseMs: number().nonnegative(),
50393
50524
  idleFloorW: number(),
50394
50525
  noiseCeilingW: number(),
50395
50526
  plateauW: number(),
@@ -51983,7 +52114,14 @@ var DiscoveryCandidateSchema = object({
51983
52114
  /** `stableId` of the gateway candidate this device was found BEHIND. */
51984
52115
  parentStableId: string().optional(),
51985
52116
  /** `false` = shown for context but not pickable. Default true. */
51986
- selectable: boolean().optional()
52117
+ selectable: boolean().optional(),
52118
+ /**
52119
+ * Keys of the provider's creation form (`getChildCreationSchema`) this candidate
52120
+ * cannot be adopted without — a key for an encrypted board, a pairing code. The
52121
+ * admin asks for exactly these, on the creation form prefilled with
52122
+ * `prefilledConfig`, before it adopts. Absent or empty = adopt as it is.
52123
+ */
52124
+ needsParams: array(string()).optional()
51987
52125
  });
51988
52126
  /**
51989
52127
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -52111,7 +52249,11 @@ var deviceProviderCapability = {
52111
52249
  * pick the right type instead of assuming Camera.
52112
52250
  */
52113
52251
  getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
52114
- adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
52252
+ adoptDiscoveredDevice: method(object({
52253
+ candidate: DiscoveryCandidateSchema,
52254
+ /** The integration the operator adopts into; a provider with several instances needs it. */
52255
+ integrationId: string().optional()
52256
+ }), DeviceSummarySchema, {
52115
52257
  kind: "mutation",
52116
52258
  auth: "admin"
52117
52259
  }),
@@ -52125,7 +52267,9 @@ var deviceProviderCapability = {
52125
52267
  getChildCreationSchema: method(object({ type: _enum(DeviceType) }), CreationSchemaOutputSchema),
52126
52268
  createDevice: method(object({
52127
52269
  type: _enum(DeviceType),
52128
- config: record(string(), unknown())
52270
+ config: record(string(), unknown()),
52271
+ /** The integration the operator adds the device to; a provider with several instances needs it. */
52272
+ integrationId: string().optional()
52129
52273
  }), DeviceSummarySchema, {
52130
52274
  kind: "mutation",
52131
52275
  auth: "admin"
@@ -56235,11 +56379,7 @@ var CreateIntegrationInputSchema = object({
56235
56379
  info: record(string(), unknown()).optional(),
56236
56380
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56237
56381
  originStableId: string().optional(),
56238
- settings: record(string(), unknown()).optional(),
56239
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56240
- * the keychain, not the table (D787). */
56241
- secretKeys: array(string()).optional(),
56242
- skipRestart: boolean().optional()
56382
+ settings: record(string(), unknown()).optional()
56243
56383
  });
56244
56384
  var UpdateIntegrationInputSchema = object({
56245
56385
  id: string(),
@@ -56260,22 +56400,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56260
56400
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56261
56401
  kind: "mutation",
56262
56402
  auth: "admin"
56263
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56264
- id: string(),
56265
- settings: record(string(), unknown()),
56266
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56267
- secretKeys: array(string()).optional(),
56268
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56269
- skipRestart: boolean().optional()
56270
- }), object({ success: boolean() }), {
56271
- kind: "mutation",
56272
- auth: "admin"
56273
- }), method(object({ id: string() }), object({
56274
- rows: number().int().nonnegative(),
56275
- secrets: number().int().nonnegative()
56276
- }), {
56277
- kind: "mutation",
56278
- auth: "admin"
56279
56403
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56280
56404
  addonId: string(),
56281
56405
  settings: record(string(), unknown())
@@ -59252,6 +59376,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59252
59376
  }), {
59253
59377
  kind: "mutation",
59254
59378
  auth: "admin"
59379
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59380
+ rows: number().int().nonnegative(),
59381
+ secrets: number().int().nonnegative()
59382
+ }), {
59383
+ kind: "mutation",
59384
+ auth: "admin"
59385
+ }), method(object({
59386
+ integrationId: string().min(1),
59387
+ patch: record(string(), unknown())
59388
+ }), object({ written: boolean() }), {
59389
+ kind: "mutation",
59390
+ auth: "admin"
59391
+ }), method(object({
59392
+ integrationId: string().min(1),
59393
+ keys: array(string().min(1))
59394
+ }), object({
59395
+ present: boolean(),
59396
+ removed: record(string(), unknown())
59397
+ }), {
59398
+ kind: "mutation",
59399
+ auth: "admin"
59255
59400
  });
59256
59401
  method(object({
59257
59402
  sourceUrl: string(),
@@ -80617,11 +80762,15 @@ function runningFromDishwasherOperation(operation, program, previous, fetchedAt,
80617
80762
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80618
80763
  * near F. A device that is not near one level most of the time has no floor.
80619
80764
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80620
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80621
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80765
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80766
+ * display waking): it is neither a run nor a pause. Among the remaining
80767
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80768
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80769
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80770
+ * is no run.
80622
80771
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80623
- * grid a person would type; stop dwell covers the longest pause; start dwell
80624
- * covers the longest standby blip above T.
80772
+ * grid a person would type; stop dwell covers the longest pause and the
80773
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80625
80774
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80626
80775
  * run must hold exactly one true interval and nothing may flap outside.
80627
80776
  */
@@ -80632,6 +80781,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80632
80781
  30 * SECOND_MS;
80633
80782
  60 * SECOND_MS;
80634
80783
  20 * MINUTE_MS$1;
80784
+ 90 * SECOND_MS;
80785
+ 30 * MINUTE_MS$1;
80786
+ 10 * MINUTE_MS$1;
80787
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80635
80788
  10 * SECOND_MS;
80636
80789
  10 * SECOND_MS;
80637
80790
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -81805,7 +81958,7 @@ var BaseDeviceProvider = class extends BaseAddon {
81805
81958
  * `deviceClasses` instead.
81806
81959
  */
81807
81960
  async createDevice(input) {
81808
- const spec = await this.onCreateDevice(input.type, input.config);
81961
+ const spec = await this.onCreateDevice(input.type, input.config, { ...input.integrationId !== void 0 ? { integrationId: input.integrationId } : {} });
81809
81962
  const Class = this.deviceClasses[spec.meta.type];
81810
81963
  if (!Class) throw new Error(`${this.providerName} provider: no device class registered for type "${spec.meta.type}" — add it to the deviceClasses map`);
81811
81964
  const stableId = this.generateStableId(spec.meta.type, spec.config);
@@ -85916,13 +86069,6 @@ Object.freeze({
85916
86069
  access: "create",
85917
86070
  role: "admin"
85918
86071
  },
85919
- "integrations.clearSettings": {
85920
- capName: "integrations",
85921
- capScope: "system",
85922
- addonId: null,
85923
- access: "delete",
85924
- role: "admin"
85925
- },
85926
86072
  "integrations.create": {
85927
86073
  capName: "integrations",
85928
86074
  capScope: "system",
@@ -85958,13 +86104,6 @@ Object.freeze({
85958
86104
  access: "view",
85959
86105
  role: "admin"
85960
86106
  },
85961
- "integrations.getSettings": {
85962
- capName: "integrations",
85963
- capScope: "system",
85964
- addonId: null,
85965
- access: "view",
85966
- role: "admin"
85967
- },
85968
86107
  "integrations.list": {
85969
86108
  capName: "integrations",
85970
86109
  capScope: "system",
@@ -85972,13 +86111,6 @@ Object.freeze({
85972
86111
  access: "view",
85973
86112
  role: "admin"
85974
86113
  },
85975
- "integrations.setSettings": {
85976
- capName: "integrations",
85977
- capScope: "system",
85978
- addonId: null,
85979
- access: "create",
85980
- role: "admin"
85981
- },
85982
86114
  "integrations.testConnection": {
85983
86115
  capName: "integrations",
85984
86116
  capScope: "system",
@@ -86973,6 +87105,13 @@ Object.freeze({
86973
87105
  access: "view",
86974
87106
  role: "admin"
86975
87107
  },
87108
+ "migrations.clearLegacyIntegrationRows": {
87109
+ capName: "migrations",
87110
+ capScope: "system",
87111
+ addonId: null,
87112
+ access: "delete",
87113
+ role: "admin"
87114
+ },
86976
87115
  "migrations.declareOwner": {
86977
87116
  capName: "migrations",
86978
87117
  capScope: "system",
@@ -86980,6 +87119,13 @@ Object.freeze({
86980
87119
  access: "create",
86981
87120
  role: "admin"
86982
87121
  },
87122
+ "migrations.deleteLegacyIntegrationRowKeys": {
87123
+ capName: "migrations",
87124
+ capScope: "system",
87125
+ addonId: null,
87126
+ access: "delete",
87127
+ role: "admin"
87128
+ },
86983
87129
  "migrations.dryRun": {
86984
87130
  capName: "migrations",
86985
87131
  capScope: "system",
@@ -87015,6 +87161,20 @@ Object.freeze({
87015
87161
  access: "create",
87016
87162
  role: "admin"
87017
87163
  },
87164
+ "migrations.patchLegacyIntegrationRows": {
87165
+ capName: "migrations",
87166
+ capScope: "system",
87167
+ addonId: null,
87168
+ access: "create",
87169
+ role: "admin"
87170
+ },
87171
+ "migrations.readLegacyIntegrationRows": {
87172
+ capName: "migrations",
87173
+ capScope: "system",
87174
+ addonId: null,
87175
+ access: "view",
87176
+ role: "admin"
87177
+ },
87018
87178
  "migrations.restorePreMigration": {
87019
87179
  capName: "migrations",
87020
87180
  capScope: "system",
@@ -97408,6 +97568,15 @@ function rewriteLocation(location, currentUpstream, route) {
97408
97568
  /** Characters that may precede a URL that STARTS a value (never `=`, `?`, `&`, `%3D`). */
97409
97569
  var VALUE_START = String.raw`(?<=^|["'(\s>,\[{])`;
97410
97570
  /**
97571
+ * Subresource Integrity pins the bytes of a script or stylesheet. The proxy rewrites vendor
97572
+ * URLs inside those bytes (and re-serves them from another origin), so the hash can no longer
97573
+ * match and the browser refuses the file: the page renders with no script at all. The hub
97574
+ * already drops the CSP that would have enforced the same pin; the attribute goes with it.
97575
+ */
97576
+ function stripIntegrity(html) {
97577
+ return html.replace(/\s+integrity\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
97578
+ }
97579
+ /**
97411
97580
  * Rewrite vendor URLs in a text body: absolute (`https://host/…`), protocol-relative
97412
97581
  * (`//host/…`) and JSON-escaped (`https:\/\/host\/…`) when they start a value; and, in
97413
97582
  * HTML/CSS, root-relative references (`href="/x"`, `url(/x)`) which would otherwise
@@ -97423,6 +97592,7 @@ function rewriteBody(body, kind, currentUpstream, route) {
97423
97592
  const escaped = new RegExp(`${VALUE_START}(?:https?:)?\\\\/\\\\/${host}(?=\\\\/|["'?#\\s)\\]},<]|$)`, "g");
97424
97593
  out = out.replace(escaped, escapedBase);
97425
97594
  }
97595
+ if (kind === "html") out = stripIntegrity(out);
97426
97596
  const current = `${route.basePath}/${currentUpstream.host}/`;
97427
97597
  const notMounted = `(?!/)(?!${escapeRegex(route.basePath.slice(1))}/)`;
97428
97598
  if (kind === "html") {
@@ -98082,6 +98252,7 @@ function boschRowsMigration(spec) {
98082
98252
  function boschRowsPorts(deps) {
98083
98253
  return integrationRowsPortsOverApi({
98084
98254
  api: deps.api,
98255
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
98085
98256
  records: deps.records.kind({
98086
98257
  name: deps.kind.name,
98087
98258
  declaration: deps.kind.declaration,