@camstack/addon-mqtt-broker 1.2.150 → 1.2.151

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.151] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - MQTT embedded broker settings are declared on the cluster hook; the form and stored values are unchanged.
15
+
10
16
  ## [1.2.150] - 2026-11-04
11
17
 
12
18
  ### Changed
@@ -6028,7 +6028,7 @@ var ZodIssueCode = {
6028
6028
  var ZodFirstPartyTypeKind;
6029
6029
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6030
6030
  //#endregion
6031
- //#region ../types/dist/sleep-BaWhXZFm.mjs
6031
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
6032
6032
  /**
6033
6033
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6034
6034
  * window to float samples (D455).
@@ -6628,7 +6628,7 @@ var NOT_RUN = {
6628
6628
  * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6629
6629
  * because the hub (and this addon's boot) must not hang on a migration.
6630
6630
  */
6631
- async function runAddonMigrations(host, migrations, logger) {
6631
+ async function runAddonMigrations(host, migrations, logger, options = {}) {
6632
6632
  const first = migrations[0];
6633
6633
  if (first === void 0) return NOT_RUN;
6634
6634
  const owner = first.meta.owner;
@@ -6657,7 +6657,9 @@ async function runAddonMigrations(host, migrations, logger) {
6657
6657
  else if (status === "blocked") blocked.push(id);
6658
6658
  else skipped.push(id);
6659
6659
  };
6660
+ const selected = options.only === void 0 ? null : new Set(options.only);
6660
6661
  for (const step of declaration.steps) {
6662
+ if (selected !== null && !selected.has(step.id)) continue;
6661
6663
  const bound = byId.get(step.id);
6662
6664
  if (bound === void 0 || step.action === "skip") {
6663
6665
  skipped.push(step.id);
@@ -6835,6 +6837,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6835
6837
  }
6836
6838
  return out;
6837
6839
  }
6840
+ /** Every scope, in the order the guard and the docs list them. */
6841
+ var SCOPED_SETTINGS_SCOPES = [
6842
+ "cluster",
6843
+ "device-defaults",
6844
+ "node"
6845
+ ];
6838
6846
  /**
6839
6847
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6840
6848
  *
@@ -7726,6 +7734,13 @@ var BaseAddon = class {
7726
7734
  */
7727
7735
  secretStatePaths = [];
7728
7736
  _addonSecretTree = null;
7737
+ /**
7738
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7739
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7740
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7741
+ */
7742
+ _scopedByCap = /* @__PURE__ */ new Map();
7743
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7729
7744
  _deviceSecretTree = null;
7730
7745
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7731
7746
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7795,6 +7810,7 @@ var BaseAddon = class {
7795
7810
  this._ctx = context;
7796
7811
  this._secretSettings = null;
7797
7812
  }
7813
+ await this.loadScopedSchemas();
7798
7814
  await this.resolveConfig();
7799
7815
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7800
7816
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7804,6 +7820,7 @@ var BaseAddon = class {
7804
7820
  const normalized = normalizeAddonInitResult(result);
7805
7821
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7806
7822
  this._registeredCapNames = providers.map((p) => p.capability.name);
7823
+ await this.loadScopedSchemas();
7807
7824
  return normalized;
7808
7825
  }
7809
7826
  /**
@@ -7817,14 +7834,18 @@ var BaseAddon = class {
7817
7834
  * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7818
7835
  * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7819
7836
  * serve data whose shape it cannot trust; `tolerate` migrations never do.
7837
+ *
7838
+ * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7839
+ * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7840
+ * omitted applied ids "newer").
7820
7841
  */
7821
- async runMigrations(migrations) {
7842
+ async runMigrations(migrations, options) {
7822
7843
  const api = this.ctx.api.migrations;
7823
7844
  const report = await runAddonMigrations({
7824
7845
  declareOwner: (input) => api.declareOwner.mutate(input),
7825
7846
  startMigration: (input) => api.startMigration.mutate(input),
7826
7847
  finishMigration: (input) => api.finishMigration.mutate(input)
7827
- }, migrations, this.ctx.logger);
7848
+ }, migrations, this.ctx.logger, options);
7828
7849
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7829
7850
  return report;
7830
7851
  }
@@ -7888,6 +7909,16 @@ var BaseAddon = class {
7888
7909
  schema(schema) {
7889
7910
  return schema;
7890
7911
  }
7912
+ /**
7913
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7914
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7915
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7916
+ * must already know which keys are per node. Names the providers register at init are added
7917
+ * once known.
7918
+ */
7919
+ scopedSettingsCapabilities() {
7920
+ return [];
7921
+ }
7891
7922
  /** Override to provide global-level settings UI schema. */
7892
7923
  globalSettingsSchema(_cap) {
7893
7924
  return null;
@@ -7931,7 +7962,8 @@ var BaseAddon = class {
7931
7962
  return [];
7932
7963
  }
7933
7964
  async getGlobalSettings(overlay, cap, nodeId) {
7934
- const schema = this.globalSettingsSchema(cap);
7965
+ await this.loadScopedSchemas(cap);
7966
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7935
7967
  if (!schema) return { sections: [] };
7936
7968
  const projected = await this.readRawProjectedStore(nodeId, cap);
7937
7969
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -7966,10 +7998,9 @@ var BaseAddon = class {
7966
7998
  *
7967
7999
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7968
8000
  * needs its connection fields, the default integration the addon's own sections. It is the
7969
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7970
- * unchanged (`integrations.setSettings` for an account integration).
8001
+ * SHAPE of the form only — the values live on the instance record.
7971
8002
  */
7972
- async integrationSettingsSchema(_instance) {
8003
+ async integrationFormSchema(_instance) {
7973
8004
  return null;
7974
8005
  }
7975
8006
  /**
@@ -8015,7 +8046,7 @@ var BaseAddon = class {
8015
8046
  * per-instance schema when it declares one, else its global schema (what they always showed).
8016
8047
  */
8017
8048
  async getIntegrationFormSchema(instance, nodeId) {
8018
- const scoped = await this.integrationSettingsSchema(instance);
8049
+ const scoped = await this.integrationFormSchema(instance);
8019
8050
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8020
8051
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8021
8052
  }
@@ -8063,6 +8094,7 @@ var BaseAddon = class {
8063
8094
  * resolved blob back is a no-op for every unchanged secret.
8064
8095
  */
8065
8096
  async resolveGlobalStore(nodeId, cap) {
8097
+ await this.loadScopedSchemas(cap);
8066
8098
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8067
8099
  return this.projectForNode(raw, nodeId, cap);
8068
8100
  }
@@ -8073,6 +8105,7 @@ var BaseAddon = class {
8073
8105
  }
8074
8106
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8075
8107
  async readRawProjectedStore(nodeId, cap) {
8108
+ await this.loadScopedSchemas(cap);
8076
8109
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8077
8110
  return this.projectForNode(raw, nodeId, cap);
8078
8111
  }
@@ -8175,6 +8208,7 @@ var BaseAddon = class {
8175
8208
  await this.applyGlobalPatch(patch, nodeId);
8176
8209
  }
8177
8210
  async applyGlobalPatch(barePatch, nodeId) {
8211
+ await this.loadScopedSchemas();
8178
8212
  const keys = this.perNodeKeys();
8179
8213
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8180
8214
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8188,7 +8222,8 @@ var BaseAddon = class {
8188
8222
  await this.resolveConfig();
8189
8223
  await this.onConfigChanged();
8190
8224
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8191
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8225
+ const legacy = this.globalSettingsSchema();
8226
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8192
8227
  }
8193
8228
  /**
8194
8229
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8196,14 +8231,67 @@ var BaseAddon = class {
8196
8231
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8197
8232
  * settings API behaves exactly like the legacy node-agnostic one.
8198
8233
  */
8199
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8234
+ /**
8235
+ * Load the scoped schemas of every capability this addon declares (the override and the
8236
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8237
+ * derived from the previous set. Idempotent.
8238
+ */
8239
+ async loadScopedSchemas(extraCap) {
8240
+ const caps = new Set([
8241
+ ...this.scopedSettingsCapabilities(),
8242
+ ...this._registeredCapNames,
8243
+ ...extraCap === void 0 ? [] : [extraCap]
8244
+ ]);
8245
+ let added = false;
8246
+ for (const cap of caps) {
8247
+ if (this._scopedByCap.has(cap)) continue;
8248
+ const loaded = [];
8249
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8250
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8251
+ if (schema !== null) loaded.push({
8252
+ scope,
8253
+ schema
8254
+ });
8255
+ }
8256
+ this._scopedByCap.set(cap, loaded);
8257
+ added = true;
8258
+ }
8259
+ if (!added) return;
8260
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8261
+ this._addonSecretTree = null;
8262
+ }
8263
+ /**
8264
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8265
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8266
+ */
8267
+ scopedFormSchema(cap) {
8268
+ const byId = /* @__PURE__ */ new Map();
8269
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8270
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8271
+ }
8272
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8273
+ loadedScopedSchemas(cap) {
8274
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8275
+ return [...this._scopedByCap.values()].flat();
8276
+ }
8277
+ /**
8278
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8279
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8280
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8281
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8282
+ * Call {@link loadScopedSchemas} first (the async seams do).
8283
+ */
8200
8284
  perNodeKeys(cap) {
8201
8285
  const cacheKey = cap ?? "";
8202
- const cached = this._perNodeKeysCache.get(cacheKey);
8286
+ const cached = this._nodeKeysByCap.get(cacheKey);
8203
8287
  if (cached) return cached;
8204
- const schema = this.globalSettingsSchema(cap);
8205
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8206
- this._perNodeKeysCache.set(cacheKey, keys);
8288
+ const legacy = this.globalSettingsSchema(cap);
8289
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8290
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8291
+ if (scope !== "node") continue;
8292
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8293
+ }
8294
+ this._nodeKeysByCap.set(cacheKey, keys);
8207
8295
  return keys;
8208
8296
  }
8209
8297
  /**
@@ -8213,10 +8301,9 @@ var BaseAddon = class {
8213
8301
  * time to return its response before the addon is torn down and
8214
8302
  * re-initialised by `AddonRegistryService.restartAddon`.
8215
8303
  */
8216
- maybeAutoRestart(patchKeys, schema) {
8217
- if (!schema) return;
8304
+ maybeAutoRestart(patchKeys, schemas) {
8218
8305
  const restartKeys = /* @__PURE__ */ new Set();
8219
- for (const section of schema.sections) for (const field of section.fields) {
8306
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8220
8307
  if (field.type === "separator" || field.type === "info") continue;
8221
8308
  if (field.requiresRestart) restartKeys.add(field.key);
8222
8309
  }
@@ -8412,12 +8499,14 @@ var BaseAddon = class {
8412
8499
  * here, in the addon's own process, so the hub keeps no copy of it.
8413
8500
  */
8414
8501
  async getDeclaredSettingsKeys() {
8502
+ await this.loadScopedSchemas();
8415
8503
  return { keys: [...this.declaredKeySet()].toSorted() };
8416
8504
  }
8417
8505
  declaredKeySet() {
8418
8506
  const keys = new Set(Object.keys(this.defaults));
8419
8507
  const schema = this.globalSettingsSchema();
8420
8508
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8509
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8421
8510
  for (const key of this._declaredStateKeys) keys.add(key);
8422
8511
  for (const path of this.secretStatePaths) {
8423
8512
  const root = path.split(/[.[]/, 1)[0];
@@ -8513,7 +8602,11 @@ var BaseAddon = class {
8513
8602
  }
8514
8603
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8515
8604
  addonSecretTree() {
8516
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8605
+ if (this._addonSecretTree === null) {
8606
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8607
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8608
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8609
+ }
8517
8610
  return this._addonSecretTree;
8518
8611
  }
8519
8612
  deviceSecretTree() {
@@ -8763,6 +8856,23 @@ function dropPerNodeFields(fields) {
8763
8856
  }
8764
8857
  return kept;
8765
8858
  }
8859
+ /** The keys of the value-holding fields, recursing into layout containers. */
8860
+ function collectValueFieldKeys(fields) {
8861
+ const collected = [];
8862
+ for (const field of fields) {
8863
+ if (field.type === "group") {
8864
+ collected.push(...collectValueFieldKeys(field.fields));
8865
+ continue;
8866
+ }
8867
+ if (field.type === "sub-tabs") {
8868
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8869
+ continue;
8870
+ }
8871
+ if (field.type === "separator" || field.type === "info") continue;
8872
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8873
+ }
8874
+ return collected;
8875
+ }
8766
8876
  function collectFieldKeys(fields) {
8767
8877
  const collected = [];
8768
8878
  for (const field of fields) {
@@ -9289,6 +9399,7 @@ function defineEntryKind(name, spec) {
9289
9399
  class: "config",
9290
9400
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9291
9401
  retention: { none: "an entry goes only when the operator deletes it" },
9402
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9292
9403
  ...spec.database === void 0 ? {} : { database: spec.database },
9293
9404
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9294
9405
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -11954,7 +12065,7 @@ object({
11954
12065
  params: record(string(), union([string(), number()])).optional()
11955
12066
  });
11956
12067
  //#endregion
11957
- //#region ../types/dist/composition-CRNwcFYY.mjs
12068
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11958
12069
  /**
11959
12070
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11960
12071
  *
@@ -14545,7 +14656,13 @@ var MigrationLedgerEntrySchema = object({
14545
14656
  * joined by the engine from the catalogues it knows this boot. Absent when no
14546
14657
  * catalogue lists the id (a newer database).
14547
14658
  */
14548
- meta: MigrationMetaSchema.optional()
14659
+ meta: MigrationMetaSchema.optional(),
14660
+ /**
14661
+ * Set when this is an applied row of an addon owner whose catalogue the answering
14662
+ * process has not loaded (the owner has not declared yet): the row is applied, the
14663
+ * process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
14664
+ */
14665
+ ownerNotLoaded: boolean().optional()
14549
14666
  });
14550
14667
  /** A catalogued migration that is not applied, with where it stands. */
14551
14668
  var MigrationPendingEntrySchema = object({
@@ -49085,6 +49202,9 @@ var RunningProposalEvidenceSchema = object({
49085
49202
  runsFound: number().int().nonnegative(),
49086
49203
  pausesFound: number().int().nonnegative(),
49087
49204
  longestPauseMs: number().nonnegative(),
49205
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
49206
+ pulsesFound: number().int().nonnegative(),
49207
+ longestPulseMs: number().nonnegative(),
49088
49208
  idleFloorW: number(),
49089
49209
  noiseCeilingW: number(),
49090
49210
  plateauW: number(),
@@ -54006,11 +54126,7 @@ var CreateIntegrationInputSchema = object({
54006
54126
  info: record(string(), unknown()).optional(),
54007
54127
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
54008
54128
  originStableId: string().optional(),
54009
- settings: record(string(), unknown()).optional(),
54010
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
54011
- * the keychain, not the table (D787). */
54012
- secretKeys: array(string()).optional(),
54013
- skipRestart: boolean().optional()
54129
+ settings: record(string(), unknown()).optional()
54014
54130
  });
54015
54131
  var UpdateIntegrationInputSchema = object({
54016
54132
  id: string(),
@@ -54031,16 +54147,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
54031
54147
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
54032
54148
  kind: "mutation",
54033
54149
  auth: "admin"
54034
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
54035
- id: string(),
54036
- settings: record(string(), unknown()),
54037
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
54038
- secretKeys: array(string()).optional(),
54039
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
54040
- skipRestart: boolean().optional()
54041
- }), object({ success: boolean() }), {
54042
- kind: "mutation",
54043
- auth: "admin"
54044
54150
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
54045
54151
  addonId: string(),
54046
54152
  settings: record(string(), unknown())
@@ -56765,6 +56871,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
56765
56871
  }), {
56766
56872
  kind: "mutation",
56767
56873
  auth: "admin"
56874
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
56875
+ rows: number().int().nonnegative(),
56876
+ secrets: number().int().nonnegative()
56877
+ }), {
56878
+ kind: "mutation",
56879
+ auth: "admin"
56880
+ }), method(object({
56881
+ integrationId: string().min(1),
56882
+ patch: record(string(), unknown())
56883
+ }), object({ written: boolean() }), {
56884
+ kind: "mutation",
56885
+ auth: "admin"
56886
+ }), method(object({
56887
+ integrationId: string().min(1),
56888
+ keys: array(string().min(1))
56889
+ }), object({
56890
+ present: boolean(),
56891
+ removed: record(string(), unknown())
56892
+ }), {
56893
+ kind: "mutation",
56894
+ auth: "admin"
56768
56895
  });
56769
56896
  method(object({
56770
56897
  sourceUrl: string(),
@@ -75945,11 +76072,15 @@ discriminatedUnion("kind", [object({
75945
76072
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
75946
76073
  * near F. A device that is not near one level most of the time has no floor.
75947
76074
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
75948
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
75949
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
76075
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
76076
+ * display waking): it is neither a run nor a pause. Among the remaining
76077
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
76078
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
76079
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
76080
+ * is no run.
75950
76081
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
75951
- * grid a person would type; stop dwell covers the longest pause; start dwell
75952
- * covers the longest standby blip above T.
76082
+ * grid a person would type; stop dwell covers the longest pause and the
76083
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
75953
76084
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
75954
76085
  * run must hold exactly one true interval and nothing may flap outside.
75955
76086
  */
@@ -75960,6 +76091,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
75960
76091
  30 * SECOND_MS;
75961
76092
  60 * SECOND_MS;
75962
76093
  20 * MINUTE_MS$1;
76094
+ 90 * SECOND_MS;
76095
+ 30 * MINUTE_MS$1;
76096
+ 10 * MINUTE_MS$1;
76097
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
75963
76098
  10 * SECOND_MS;
75964
76099
  10 * SECOND_MS;
75965
76100
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -79715,13 +79850,6 @@ Object.freeze({
79715
79850
  access: "view",
79716
79851
  role: "admin"
79717
79852
  },
79718
- "integrations.getSettings": {
79719
- capName: "integrations",
79720
- capScope: "system",
79721
- addonId: null,
79722
- access: "view",
79723
- role: "admin"
79724
- },
79725
79853
  "integrations.list": {
79726
79854
  capName: "integrations",
79727
79855
  capScope: "system",
@@ -79729,13 +79857,6 @@ Object.freeze({
79729
79857
  access: "view",
79730
79858
  role: "admin"
79731
79859
  },
79732
- "integrations.setSettings": {
79733
- capName: "integrations",
79734
- capScope: "system",
79735
- addonId: null,
79736
- access: "create",
79737
- role: "admin"
79738
- },
79739
79860
  "integrations.testConnection": {
79740
79861
  capName: "integrations",
79741
79862
  capScope: "system",
@@ -80730,6 +80851,13 @@ Object.freeze({
80730
80851
  access: "view",
80731
80852
  role: "admin"
80732
80853
  },
80854
+ "migrations.clearLegacyIntegrationRows": {
80855
+ capName: "migrations",
80856
+ capScope: "system",
80857
+ addonId: null,
80858
+ access: "delete",
80859
+ role: "admin"
80860
+ },
80733
80861
  "migrations.declareOwner": {
80734
80862
  capName: "migrations",
80735
80863
  capScope: "system",
@@ -80737,6 +80865,13 @@ Object.freeze({
80737
80865
  access: "create",
80738
80866
  role: "admin"
80739
80867
  },
80868
+ "migrations.deleteLegacyIntegrationRowKeys": {
80869
+ capName: "migrations",
80870
+ capScope: "system",
80871
+ addonId: null,
80872
+ access: "delete",
80873
+ role: "admin"
80874
+ },
80740
80875
  "migrations.dryRun": {
80741
80876
  capName: "migrations",
80742
80877
  capScope: "system",
@@ -80772,6 +80907,20 @@ Object.freeze({
80772
80907
  access: "create",
80773
80908
  role: "admin"
80774
80909
  },
80910
+ "migrations.patchLegacyIntegrationRows": {
80911
+ capName: "migrations",
80912
+ capScope: "system",
80913
+ addonId: null,
80914
+ access: "create",
80915
+ role: "admin"
80916
+ },
80917
+ "migrations.readLegacyIntegrationRows": {
80918
+ capName: "migrations",
80919
+ capScope: "system",
80920
+ addonId: null,
80921
+ access: "view",
80922
+ role: "admin"
80923
+ },
80775
80924
  "migrations.restorePreMigration": {
80776
80925
  capName: "migrations",
80777
80926
  capScope: "system",
@@ -123467,7 +123616,7 @@ var MqttAclStore = class MqttAclStore {
123467
123616
  * The addon has NO standalone admin page. All configuration — external
123468
123617
  * broker entries, the embedded `aedes` broker, embedded credentials,
123469
123618
  * and the topic ACL — is exposed through the standard
123470
- * `globalSettingsSchema()` and rendered automatically inside the
123619
+ * `clusterSettingsSchema()` and rendered automatically inside the
123471
123620
  * Integrations → Brokers tab's docked Settings panel, like every other
123472
123621
  * integration-type addon.
123473
123622
  *
@@ -124538,11 +124687,15 @@ var MqttBrokerAddon = class extends BaseAddon {
124538
124687
  }
124539
124688
  };
124540
124689
  }
124690
+ scopedSettingsCapabilities() {
124691
+ return ["mqtt-broker", "broker"];
124692
+ }
124541
124693
  /**
124542
124694
  * The embedded broker, its credentials and the ACL. The external brokers are records, listed
124543
124695
  * on the Brokers page and edited as integration instances (`brokerForm`).
124544
124696
  */
124545
- globalSettingsSchema() {
124697
+ async clusterSettingsSchema(cap) {
124698
+ if (cap !== "mqtt-broker" && cap !== "broker") return null;
124546
124699
  return this.schema({ sections: [
124547
124700
  {
124548
124701
  id: "mqtt-broker-embedded",
@@ -6021,7 +6021,7 @@ var ZodIssueCode = {
6021
6021
  var ZodFirstPartyTypeKind;
6022
6022
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6023
6023
  //#endregion
6024
- //#region ../types/dist/sleep-BaWhXZFm.mjs
6024
+ //#region ../types/dist/sleep-DEZaLbiu.mjs
6025
6025
  /**
6026
6026
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6027
6027
  * window to float samples (D455).
@@ -6621,7 +6621,7 @@ var NOT_RUN = {
6621
6621
  * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6622
6622
  * because the hub (and this addon's boot) must not hang on a migration.
6623
6623
  */
6624
- async function runAddonMigrations(host, migrations, logger) {
6624
+ async function runAddonMigrations(host, migrations, logger, options = {}) {
6625
6625
  const first = migrations[0];
6626
6626
  if (first === void 0) return NOT_RUN;
6627
6627
  const owner = first.meta.owner;
@@ -6650,7 +6650,9 @@ async function runAddonMigrations(host, migrations, logger) {
6650
6650
  else if (status === "blocked") blocked.push(id);
6651
6651
  else skipped.push(id);
6652
6652
  };
6653
+ const selected = options.only === void 0 ? null : new Set(options.only);
6653
6654
  for (const step of declaration.steps) {
6655
+ if (selected !== null && !selected.has(step.id)) continue;
6654
6656
  const bound = byId.get(step.id);
6655
6657
  if (bound === void 0 || step.action === "skip") {
6656
6658
  skipped.push(step.id);
@@ -6828,6 +6830,12 @@ function projectStore(store, perNodeKeys, nodeId) {
6828
6830
  }
6829
6831
  return out;
6830
6832
  }
6833
+ /** Every scope, in the order the guard and the docs list them. */
6834
+ var SCOPED_SETTINGS_SCOPES = [
6835
+ "cluster",
6836
+ "device-defaults",
6837
+ "node"
6838
+ ];
6831
6839
  /**
6832
6840
  * The address of one keychain value: `secret://<owner>/<scope>/<fieldPath>`.
6833
6841
  *
@@ -7719,6 +7727,13 @@ var BaseAddon = class {
7719
7727
  */
7720
7728
  secretStatePaths = [];
7721
7729
  _addonSecretTree = null;
7730
+ /**
7731
+ * D986 M4: the scoped schemas this addon declared, resolved once per capability
7732
+ * (`loadScopedSchemas`) so the sync readers (`perNodeKeys`, the secret tree, the restart check)
7733
+ * see them. Empty until loaded: the readers then fall back to {@link globalSettingsSchema}.
7734
+ */
7735
+ _scopedByCap = /* @__PURE__ */ new Map();
7736
+ _nodeKeysByCap = /* @__PURE__ */ new Map();
7722
7737
  _deviceSecretTree = null;
7723
7738
  /** Refs the last resolved config pointed at — a change to one re-resolves. */
7724
7739
  _configSecretRefs = /* @__PURE__ */ new Set();
@@ -7788,6 +7803,7 @@ var BaseAddon = class {
7788
7803
  this._ctx = context;
7789
7804
  this._secretSettings = null;
7790
7805
  }
7806
+ await this.loadScopedSchemas();
7791
7807
  await this.resolveConfig();
7792
7808
  if (secrets !== void 0) this._secretChangeUnsub = secrets.onChange((ref) => {
7793
7809
  if (this._configSecretRefs.has(ref)) this.scheduleSecretReresolve(SECRET_CHANGE_DEBOUNCE_MS);
@@ -7797,6 +7813,7 @@ var BaseAddon = class {
7797
7813
  const normalized = normalizeAddonInitResult(result);
7798
7814
  const providers = normalized && "providers" in normalized && normalized.providers ? normalized.providers : [];
7799
7815
  this._registeredCapNames = providers.map((p) => p.capability.name);
7816
+ await this.loadScopedSchemas();
7800
7817
  return normalized;
7801
7818
  }
7802
7819
  /**
@@ -7810,14 +7827,18 @@ var BaseAddon = class {
7810
7827
  * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7811
7828
  * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7812
7829
  * serve data whose shape it cannot trust; `tolerate` migrations never do.
7830
+ *
7831
+ * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7832
+ * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7833
+ * omitted applied ids "newer").
7813
7834
  */
7814
- async runMigrations(migrations) {
7835
+ async runMigrations(migrations, options) {
7815
7836
  const api = this.ctx.api.migrations;
7816
7837
  const report = await runAddonMigrations({
7817
7838
  declareOwner: (input) => api.declareOwner.mutate(input),
7818
7839
  startMigration: (input) => api.startMigration.mutate(input),
7819
7840
  finishMigration: (input) => api.finishMigration.mutate(input)
7820
- }, migrations, this.ctx.logger);
7841
+ }, migrations, this.ctx.logger, options);
7821
7842
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7822
7843
  return report;
7823
7844
  }
@@ -7881,6 +7902,16 @@ var BaseAddon = class {
7881
7902
  schema(schema) {
7882
7903
  return schema;
7883
7904
  }
7905
+ /**
7906
+ * The capability names whose scoped settings schemas ({@link clusterSettingsSchema},
7907
+ * {@link nodeSettingsSchema}, {@link deviceDefaultsSchema}) this addon declares. Read BEFORE
7908
+ * `onInitialize`, when the providers it registers are not known yet: the first config resolve
7909
+ * must already know which keys are per node. Names the providers register at init are added
7910
+ * once known.
7911
+ */
7912
+ scopedSettingsCapabilities() {
7913
+ return [];
7914
+ }
7884
7915
  /** Override to provide global-level settings UI schema. */
7885
7916
  globalSettingsSchema(_cap) {
7886
7917
  return null;
@@ -7924,7 +7955,8 @@ var BaseAddon = class {
7924
7955
  return [];
7925
7956
  }
7926
7957
  async getGlobalSettings(overlay, cap, nodeId) {
7927
- const schema = this.globalSettingsSchema(cap);
7958
+ await this.loadScopedSchemas(cap);
7959
+ const schema = this.globalSettingsSchema(cap) ?? this.scopedFormSchema(cap);
7928
7960
  if (!schema) return { sections: [] };
7929
7961
  const projected = await this.readRawProjectedStore(nodeId, cap);
7930
7962
  return hydrateSchema(schema, overlay ? await this.overlayKeepingMarkers(projected, overlay) : projected, { inlineSecretsAsViews: true });
@@ -7959,10 +7991,9 @@ var BaseAddon = class {
7959
7991
  *
7960
7992
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7961
7993
  * needs its connection fields, the default integration the addon's own sections. It is the
7962
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7963
- * unchanged (`integrations.setSettings` for an account integration).
7994
+ * SHAPE of the form only — the values live on the instance record.
7964
7995
  */
7965
- async integrationSettingsSchema(_instance) {
7996
+ async integrationFormSchema(_instance) {
7966
7997
  return null;
7967
7998
  }
7968
7999
  /**
@@ -8008,7 +8039,7 @@ var BaseAddon = class {
8008
8039
  * per-instance schema when it declares one, else its global schema (what they always showed).
8009
8040
  */
8010
8041
  async getIntegrationFormSchema(instance, nodeId) {
8011
- const scoped = await this.integrationSettingsSchema(instance);
8042
+ const scoped = await this.integrationFormSchema(instance);
8012
8043
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8013
8044
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8014
8045
  }
@@ -8056,6 +8087,7 @@ var BaseAddon = class {
8056
8087
  * resolved blob back is a no-op for every unchanged secret.
8057
8088
  */
8058
8089
  async resolveGlobalStore(nodeId, cap) {
8090
+ await this.loadScopedSchemas(cap);
8059
8091
  const raw = await this._ctx?.settings?.readAddonStore() ?? {};
8060
8092
  return this.projectForNode(raw, nodeId, cap);
8061
8093
  }
@@ -8066,6 +8098,7 @@ var BaseAddon = class {
8066
8098
  }
8067
8099
  /** The PROJECTED store with its markers — the form hydrate only (D798). */
8068
8100
  async readRawProjectedStore(nodeId, cap) {
8101
+ await this.loadScopedSchemas(cap);
8069
8102
  const raw = await this.rawSettings()?.readAddonStore() ?? {};
8070
8103
  return this.projectForNode(raw, nodeId, cap);
8071
8104
  }
@@ -8168,6 +8201,7 @@ var BaseAddon = class {
8168
8201
  await this.applyGlobalPatch(patch, nodeId);
8169
8202
  }
8170
8203
  async applyGlobalPatch(barePatch, nodeId) {
8204
+ await this.loadScopedSchemas();
8171
8205
  const keys = this.perNodeKeys();
8172
8206
  const localNode = normalizeNodeId(this._ctx?.kernel?.localNodeId);
8173
8207
  const target = normalizeNodeId(nodeId ?? this._ctx?.kernel?.localNodeId);
@@ -8181,7 +8215,8 @@ var BaseAddon = class {
8181
8215
  await this.resolveConfig();
8182
8216
  await this.onConfigChanged();
8183
8217
  this.emitLifecycle(EventCategory.AddonUpdated, { level: "global" });
8184
- this.maybeAutoRestart(Object.keys(barePatch), this.globalSettingsSchema());
8218
+ const legacy = this.globalSettingsSchema();
8219
+ this.maybeAutoRestart(Object.keys(barePatch), [...legacy === null ? [] : [legacy], ...this.loadedScopedSchemas().map((entry) => entry.schema)]);
8185
8220
  }
8186
8221
  /**
8187
8222
  * The set of field keys the global settings schema declares `perNode: true`
@@ -8189,14 +8224,67 @@ var BaseAddon = class {
8189
8224
  * declarations). Empty set ⇒ every per-node code path is bypassed and the
8190
8225
  * settings API behaves exactly like the legacy node-agnostic one.
8191
8226
  */
8192
- _perNodeKeysCache = /* @__PURE__ */ new Map();
8227
+ /**
8228
+ * Load the scoped schemas of every capability this addon declares (the override and the
8229
+ * providers registered at init, plus `extraCap`) that is not loaded yet, then drop what was
8230
+ * derived from the previous set. Idempotent.
8231
+ */
8232
+ async loadScopedSchemas(extraCap) {
8233
+ const caps = new Set([
8234
+ ...this.scopedSettingsCapabilities(),
8235
+ ...this._registeredCapNames,
8236
+ ...extraCap === void 0 ? [] : [extraCap]
8237
+ ]);
8238
+ let added = false;
8239
+ for (const cap of caps) {
8240
+ if (this._scopedByCap.has(cap)) continue;
8241
+ const loaded = [];
8242
+ for (const scope of SCOPED_SETTINGS_SCOPES) {
8243
+ const schema = await this.getScopedSettingsSchema(scope, cap);
8244
+ if (schema !== null) loaded.push({
8245
+ scope,
8246
+ schema
8247
+ });
8248
+ }
8249
+ this._scopedByCap.set(cap, loaded);
8250
+ added = true;
8251
+ }
8252
+ if (!added) return;
8253
+ this._nodeKeysByCap = /* @__PURE__ */ new Map();
8254
+ this._addonSecretTree = null;
8255
+ }
8256
+ /**
8257
+ * The form the settings API serves while an addon declares no {@link globalSettingsSchema}: the
8258
+ * sections of its scoped schemas, so dropping the old hook does not blank the form.
8259
+ */
8260
+ scopedFormSchema(cap) {
8261
+ const byId = /* @__PURE__ */ new Map();
8262
+ for (const entry of this.loadedScopedSchemas(cap)) for (const section of entry.schema.sections) if (!byId.has(section.id)) byId.set(section.id, section);
8263
+ return byId.size === 0 ? null : { sections: [...byId.values()] };
8264
+ }
8265
+ /** Every loaded scoped schema of `cap`, or of every loaded capability. */
8266
+ loadedScopedSchemas(cap) {
8267
+ if (cap !== void 0) return this._scopedByCap.get(cap) ?? [];
8268
+ return [...this._scopedByCap.values()].flat();
8269
+ }
8270
+ /**
8271
+ * The field keys that are stored per node: every field of a {@link nodeSettingsSchema}, plus
8272
+ * the fields the legacy {@link globalSettingsSchema} still marks `perNode: true`. Derived once
8273
+ * per `cap` argument and memoized (schemas are static declarations). Empty set ⇒ every
8274
+ * per-node code path is bypassed and the settings API behaves like the node-agnostic one.
8275
+ * Call {@link loadScopedSchemas} first (the async seams do).
8276
+ */
8193
8277
  perNodeKeys(cap) {
8194
8278
  const cacheKey = cap ?? "";
8195
- const cached = this._perNodeKeysCache.get(cacheKey);
8279
+ const cached = this._nodeKeysByCap.get(cacheKey);
8196
8280
  if (cached) return cached;
8197
- const schema = this.globalSettingsSchema(cap);
8198
- const keys = new Set(schema ? schema.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8199
- this._perNodeKeysCache.set(cacheKey, keys);
8281
+ const legacy = this.globalSettingsSchema(cap);
8282
+ const keys = new Set(legacy ? legacy.sections.flatMap((section) => collectPerNodeFieldKeys(section.fields)) : []);
8283
+ for (const { scope, schema } of this.loadedScopedSchemas(cap)) {
8284
+ if (scope !== "node") continue;
8285
+ for (const section of schema.sections) for (const key of collectValueFieldKeys(section.fields)) keys.add(key);
8286
+ }
8287
+ this._nodeKeysByCap.set(cacheKey, keys);
8200
8288
  return keys;
8201
8289
  }
8202
8290
  /**
@@ -8206,10 +8294,9 @@ var BaseAddon = class {
8206
8294
  * time to return its response before the addon is torn down and
8207
8295
  * re-initialised by `AddonRegistryService.restartAddon`.
8208
8296
  */
8209
- maybeAutoRestart(patchKeys, schema) {
8210
- if (!schema) return;
8297
+ maybeAutoRestart(patchKeys, schemas) {
8211
8298
  const restartKeys = /* @__PURE__ */ new Set();
8212
- for (const section of schema.sections) for (const field of section.fields) {
8299
+ for (const schema of schemas) for (const section of schema.sections) for (const field of section.fields) {
8213
8300
  if (field.type === "separator" || field.type === "info") continue;
8214
8301
  if (field.requiresRestart) restartKeys.add(field.key);
8215
8302
  }
@@ -8405,12 +8492,14 @@ var BaseAddon = class {
8405
8492
  * here, in the addon's own process, so the hub keeps no copy of it.
8406
8493
  */
8407
8494
  async getDeclaredSettingsKeys() {
8495
+ await this.loadScopedSchemas();
8408
8496
  return { keys: [...this.declaredKeySet()].toSorted() };
8409
8497
  }
8410
8498
  declaredKeySet() {
8411
8499
  const keys = new Set(Object.keys(this.defaults));
8412
8500
  const schema = this.globalSettingsSchema();
8413
8501
  if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8502
+ for (const { schema: scoped } of this.loadedScopedSchemas()) for (const section of scoped.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
8414
8503
  for (const key of this._declaredStateKeys) keys.add(key);
8415
8504
  for (const path of this.secretStatePaths) {
8416
8505
  const root = path.split(/[.[]/, 1)[0];
@@ -8506,7 +8595,11 @@ var BaseAddon = class {
8506
8595
  }
8507
8596
  /** The addon store's secret tree: schema fields by TYPE, plus `secretStatePaths`. */
8508
8597
  addonSecretTree() {
8509
- this._addonSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.globalSettingsSchema()), this.secretStatePaths);
8598
+ if (this._addonSecretTree === null) {
8599
+ const tree = new Map(secretTreeFromSchema(this.globalSettingsSchema()));
8600
+ for (const { schema } of this.loadedScopedSchemas()) for (const [key, node] of secretTreeFromSchema(schema)) tree.set(key, node);
8601
+ this._addonSecretTree = mergeSecretStatePaths(tree, this.secretStatePaths);
8602
+ }
8510
8603
  return this._addonSecretTree;
8511
8604
  }
8512
8605
  deviceSecretTree() {
@@ -8756,6 +8849,23 @@ function dropPerNodeFields(fields) {
8756
8849
  }
8757
8850
  return kept;
8758
8851
  }
8852
+ /** The keys of the value-holding fields, recursing into layout containers. */
8853
+ function collectValueFieldKeys(fields) {
8854
+ const collected = [];
8855
+ for (const field of fields) {
8856
+ if (field.type === "group") {
8857
+ collected.push(...collectValueFieldKeys(field.fields));
8858
+ continue;
8859
+ }
8860
+ if (field.type === "sub-tabs") {
8861
+ for (const tab of field.tabs) collected.push(...collectValueFieldKeys(tab.fields));
8862
+ continue;
8863
+ }
8864
+ if (field.type === "separator" || field.type === "info") continue;
8865
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8866
+ }
8867
+ return collected;
8868
+ }
8759
8869
  function collectFieldKeys(fields) {
8760
8870
  const collected = [];
8761
8871
  for (const field of fields) {
@@ -9282,6 +9392,7 @@ function defineEntryKind(name, spec) {
9282
9392
  class: "config",
9283
9393
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9284
9394
  retention: { none: "an entry goes only when the operator deletes it" },
9395
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9285
9396
  ...spec.database === void 0 ? {} : { database: spec.database },
9286
9397
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9287
9398
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -11947,7 +12058,7 @@ object({
11947
12058
  params: record(string(), union([string(), number()])).optional()
11948
12059
  });
11949
12060
  //#endregion
11950
- //#region ../types/dist/composition-CRNwcFYY.mjs
12061
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11951
12062
  /**
11952
12063
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11953
12064
  *
@@ -14538,7 +14649,13 @@ var MigrationLedgerEntrySchema = object({
14538
14649
  * joined by the engine from the catalogues it knows this boot. Absent when no
14539
14650
  * catalogue lists the id (a newer database).
14540
14651
  */
14541
- meta: MigrationMetaSchema.optional()
14652
+ meta: MigrationMetaSchema.optional(),
14653
+ /**
14654
+ * Set when this is an applied row of an addon owner whose catalogue the answering
14655
+ * process has not loaded (the owner has not declared yet): the row is applied, the
14656
+ * process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
14657
+ */
14658
+ ownerNotLoaded: boolean().optional()
14542
14659
  });
14543
14660
  /** A catalogued migration that is not applied, with where it stands. */
14544
14661
  var MigrationPendingEntrySchema = object({
@@ -49078,6 +49195,9 @@ var RunningProposalEvidenceSchema = object({
49078
49195
  runsFound: number().int().nonnegative(),
49079
49196
  pausesFound: number().int().nonnegative(),
49080
49197
  longestPauseMs: number().nonnegative(),
49198
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
49199
+ pulsesFound: number().int().nonnegative(),
49200
+ longestPulseMs: number().nonnegative(),
49081
49201
  idleFloorW: number(),
49082
49202
  noiseCeilingW: number(),
49083
49203
  plateauW: number(),
@@ -53999,11 +54119,7 @@ var CreateIntegrationInputSchema = object({
53999
54119
  info: record(string(), unknown()).optional(),
54000
54120
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
54001
54121
  originStableId: string().optional(),
54002
- settings: record(string(), unknown()).optional(),
54003
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
54004
- * the keychain, not the table (D787). */
54005
- secretKeys: array(string()).optional(),
54006
- skipRestart: boolean().optional()
54122
+ settings: record(string(), unknown()).optional()
54007
54123
  });
54008
54124
  var UpdateIntegrationInputSchema = object({
54009
54125
  id: string(),
@@ -54024,16 +54140,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
54024
54140
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
54025
54141
  kind: "mutation",
54026
54142
  auth: "admin"
54027
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
54028
- id: string(),
54029
- settings: record(string(), unknown()),
54030
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
54031
- secretKeys: array(string()).optional(),
54032
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
54033
- skipRestart: boolean().optional()
54034
- }), object({ success: boolean() }), {
54035
- kind: "mutation",
54036
- auth: "admin"
54037
54143
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
54038
54144
  addonId: string(),
54039
54145
  settings: record(string(), unknown())
@@ -56758,6 +56864,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
56758
56864
  }), {
56759
56865
  kind: "mutation",
56760
56866
  auth: "admin"
56867
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
56868
+ rows: number().int().nonnegative(),
56869
+ secrets: number().int().nonnegative()
56870
+ }), {
56871
+ kind: "mutation",
56872
+ auth: "admin"
56873
+ }), method(object({
56874
+ integrationId: string().min(1),
56875
+ patch: record(string(), unknown())
56876
+ }), object({ written: boolean() }), {
56877
+ kind: "mutation",
56878
+ auth: "admin"
56879
+ }), method(object({
56880
+ integrationId: string().min(1),
56881
+ keys: array(string().min(1))
56882
+ }), object({
56883
+ present: boolean(),
56884
+ removed: record(string(), unknown())
56885
+ }), {
56886
+ kind: "mutation",
56887
+ auth: "admin"
56761
56888
  });
56762
56889
  method(object({
56763
56890
  sourceUrl: string(),
@@ -75938,11 +76065,15 @@ discriminatedUnion("kind", [object({
75938
76065
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
75939
76066
  * near F. A device that is not near one level most of the time has no floor.
75940
76067
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
75941
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
75942
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
76068
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
76069
+ * display waking): it is neither a run nor a pause. Among the remaining
76070
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
76071
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
76072
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
76073
+ * is no run.
75943
76074
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
75944
- * grid a person would type; stop dwell covers the longest pause; start dwell
75945
- * covers the longest standby blip above T.
76075
+ * grid a person would type; stop dwell covers the longest pause and the
76076
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
75946
76077
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
75947
76078
  * run must hold exactly one true interval and nothing may flap outside.
75948
76079
  */
@@ -75953,6 +76084,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
75953
76084
  30 * SECOND_MS;
75954
76085
  60 * SECOND_MS;
75955
76086
  20 * MINUTE_MS$1;
76087
+ 90 * SECOND_MS;
76088
+ 30 * MINUTE_MS$1;
76089
+ 10 * MINUTE_MS$1;
76090
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
75956
76091
  10 * SECOND_MS;
75957
76092
  10 * SECOND_MS;
75958
76093
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -79708,13 +79843,6 @@ Object.freeze({
79708
79843
  access: "view",
79709
79844
  role: "admin"
79710
79845
  },
79711
- "integrations.getSettings": {
79712
- capName: "integrations",
79713
- capScope: "system",
79714
- addonId: null,
79715
- access: "view",
79716
- role: "admin"
79717
- },
79718
79846
  "integrations.list": {
79719
79847
  capName: "integrations",
79720
79848
  capScope: "system",
@@ -79722,13 +79850,6 @@ Object.freeze({
79722
79850
  access: "view",
79723
79851
  role: "admin"
79724
79852
  },
79725
- "integrations.setSettings": {
79726
- capName: "integrations",
79727
- capScope: "system",
79728
- addonId: null,
79729
- access: "create",
79730
- role: "admin"
79731
- },
79732
79853
  "integrations.testConnection": {
79733
79854
  capName: "integrations",
79734
79855
  capScope: "system",
@@ -80723,6 +80844,13 @@ Object.freeze({
80723
80844
  access: "view",
80724
80845
  role: "admin"
80725
80846
  },
80847
+ "migrations.clearLegacyIntegrationRows": {
80848
+ capName: "migrations",
80849
+ capScope: "system",
80850
+ addonId: null,
80851
+ access: "delete",
80852
+ role: "admin"
80853
+ },
80726
80854
  "migrations.declareOwner": {
80727
80855
  capName: "migrations",
80728
80856
  capScope: "system",
@@ -80730,6 +80858,13 @@ Object.freeze({
80730
80858
  access: "create",
80731
80859
  role: "admin"
80732
80860
  },
80861
+ "migrations.deleteLegacyIntegrationRowKeys": {
80862
+ capName: "migrations",
80863
+ capScope: "system",
80864
+ addonId: null,
80865
+ access: "delete",
80866
+ role: "admin"
80867
+ },
80733
80868
  "migrations.dryRun": {
80734
80869
  capName: "migrations",
80735
80870
  capScope: "system",
@@ -80765,6 +80900,20 @@ Object.freeze({
80765
80900
  access: "create",
80766
80901
  role: "admin"
80767
80902
  },
80903
+ "migrations.patchLegacyIntegrationRows": {
80904
+ capName: "migrations",
80905
+ capScope: "system",
80906
+ addonId: null,
80907
+ access: "create",
80908
+ role: "admin"
80909
+ },
80910
+ "migrations.readLegacyIntegrationRows": {
80911
+ capName: "migrations",
80912
+ capScope: "system",
80913
+ addonId: null,
80914
+ access: "view",
80915
+ role: "admin"
80916
+ },
80768
80917
  "migrations.restorePreMigration": {
80769
80918
  capName: "migrations",
80770
80919
  capScope: "system",
@@ -123460,7 +123609,7 @@ var MqttAclStore = class MqttAclStore {
123460
123609
  * The addon has NO standalone admin page. All configuration — external
123461
123610
  * broker entries, the embedded `aedes` broker, embedded credentials,
123462
123611
  * and the topic ACL — is exposed through the standard
123463
- * `globalSettingsSchema()` and rendered automatically inside the
123612
+ * `clusterSettingsSchema()` and rendered automatically inside the
123464
123613
  * Integrations → Brokers tab's docked Settings panel, like every other
123465
123614
  * integration-type addon.
123466
123615
  *
@@ -124531,11 +124680,15 @@ var MqttBrokerAddon = class extends BaseAddon {
124531
124680
  }
124532
124681
  };
124533
124682
  }
124683
+ scopedSettingsCapabilities() {
124684
+ return ["mqtt-broker", "broker"];
124685
+ }
124534
124686
  /**
124535
124687
  * The embedded broker, its credentials and the ACL. The external brokers are records, listed
124536
124688
  * on the Brokers page and edited as integration instances (`brokerForm`).
124537
124689
  */
124538
- globalSettingsSchema() {
124690
+ async clusterSettingsSchema(cap) {
124691
+ if (cap !== "mqtt-broker" && cap !== "broker") return null;
124539
124692
  return this.schema({ sections: [
124540
124693
  {
124541
124694
  id: "mqtt-broker-embedded",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-mqtt-broker",
3
- "version": "1.2.150",
3
+ "version": "1.2.151",
4
4
  "description": "MQTT broker registry addon for CamStack — manages external broker entries + an optional embedded aedes broker. Consumers spin up their own `mqtt.js` clients via the `mqtt-broker` cap.",
5
5
  "keywords": [
6
6
  "camstack",
@@ -60,7 +60,7 @@
60
60
  "aedes"
61
61
  ],
62
62
  "requires": {
63
- "server": ">=1.2.762"
63
+ "server": ">=1.2.775"
64
64
  }
65
65
  },
66
66
  "files": [