@camstack/addon-matter-broker 0.2.156 → 0.2.157

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
+ ## [0.2.157] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - A Matter integration links to its fabric through the fabric record
15
+
10
16
  ## [0.2.156] - 2026-11-04
11
17
 
12
18
  ### Changed
package/dist/addon.js CHANGED
@@ -5995,7 +5995,7 @@ var ZodIssueCode = {
5995
5995
  var ZodFirstPartyTypeKind;
5996
5996
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5997
5997
  //#endregion
5998
- //#region ../types/dist/sleep-BaWhXZFm.mjs
5998
+ //#region ../types/dist/sleep-9TIHFj1w.mjs
5999
5999
  /**
6000
6000
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6001
6001
  * window to float samples (D455).
@@ -6595,7 +6595,7 @@ var NOT_RUN = {
6595
6595
  * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6596
6596
  * because the hub (and this addon's boot) must not hang on a migration.
6597
6597
  */
6598
- async function runAddonMigrations(host, migrations, logger) {
6598
+ async function runAddonMigrations(host, migrations, logger, options = {}) {
6599
6599
  const first = migrations[0];
6600
6600
  if (first === void 0) return NOT_RUN;
6601
6601
  const owner = first.meta.owner;
@@ -6624,7 +6624,9 @@ async function runAddonMigrations(host, migrations, logger) {
6624
6624
  else if (status === "blocked") blocked.push(id);
6625
6625
  else skipped.push(id);
6626
6626
  };
6627
+ const selected = options.only === void 0 ? null : new Set(options.only);
6627
6628
  for (const step of declaration.steps) {
6629
+ if (selected !== null && !selected.has(step.id)) continue;
6628
6630
  const bound = byId.get(step.id);
6629
6631
  if (bound === void 0 || step.action === "skip") {
6630
6632
  skipped.push(step.id);
@@ -7784,14 +7786,18 @@ var BaseAddon = class {
7784
7786
  * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7785
7787
  * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7786
7788
  * serve data whose shape it cannot trust; `tolerate` migrations never do.
7789
+ *
7790
+ * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7791
+ * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7792
+ * omitted applied ids "newer").
7787
7793
  */
7788
- async runMigrations(migrations) {
7794
+ async runMigrations(migrations, options) {
7789
7795
  const api = this.ctx.api.migrations;
7790
7796
  const report = await runAddonMigrations({
7791
7797
  declareOwner: (input) => api.declareOwner.mutate(input),
7792
7798
  startMigration: (input) => api.startMigration.mutate(input),
7793
7799
  finishMigration: (input) => api.finishMigration.mutate(input)
7794
- }, migrations, this.ctx.logger);
7800
+ }, migrations, this.ctx.logger, options);
7795
7801
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7796
7802
  return report;
7797
7803
  }
@@ -7933,10 +7939,9 @@ var BaseAddon = class {
7933
7939
  *
7934
7940
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7935
7941
  * needs its connection fields, the default integration the addon's own sections. It is the
7936
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7937
- * unchanged (`integrations.setSettings` for an account integration).
7942
+ * SHAPE of the form only — the values live on the instance record.
7938
7943
  */
7939
- async integrationSettingsSchema(_instance) {
7944
+ async integrationFormSchema(_instance) {
7940
7945
  return null;
7941
7946
  }
7942
7947
  /**
@@ -7982,7 +7987,7 @@ var BaseAddon = class {
7982
7987
  * per-instance schema when it declares one, else its global schema (what they always showed).
7983
7988
  */
7984
7989
  async getIntegrationFormSchema(instance, nodeId) {
7985
- const scoped = await this.integrationSettingsSchema(instance);
7990
+ const scoped = await this.integrationFormSchema(instance);
7986
7991
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7987
7992
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7988
7993
  }
@@ -9256,6 +9261,7 @@ function defineEntryKind(name, spec) {
9256
9261
  class: "config",
9257
9262
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9258
9263
  retention: { none: "an entry goes only when the operator deletes it" },
9264
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9259
9265
  ...spec.database === void 0 ? {} : { database: spec.database },
9260
9266
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9261
9267
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -11925,7 +11931,7 @@ object({
11925
11931
  params: record(string$2(), union([string$2(), number()])).optional()
11926
11932
  });
11927
11933
  //#endregion
11928
- //#region ../types/dist/composition-CRNwcFYY.mjs
11934
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11929
11935
  /**
11930
11936
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11931
11937
  *
@@ -14506,7 +14512,13 @@ var MigrationLedgerEntrySchema = object({
14506
14512
  * joined by the engine from the catalogues it knows this boot. Absent when no
14507
14513
  * catalogue lists the id (a newer database).
14508
14514
  */
14509
- meta: MigrationMetaSchema.optional()
14515
+ meta: MigrationMetaSchema.optional(),
14516
+ /**
14517
+ * Set when this is an applied row of an addon owner whose catalogue the answering
14518
+ * process has not loaded (the owner has not declared yet): the row is applied, the
14519
+ * process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
14520
+ */
14521
+ ownerNotLoaded: boolean().optional()
14510
14522
  });
14511
14523
  /** A catalogued migration that is not applied, with where it stands. */
14512
14524
  var MigrationPendingEntrySchema = object({
@@ -50241,6 +50253,9 @@ var RunningProposalEvidenceSchema = object({
50241
50253
  runsFound: number().int().nonnegative(),
50242
50254
  pausesFound: number().int().nonnegative(),
50243
50255
  longestPauseMs: number().nonnegative(),
50256
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50257
+ pulsesFound: number().int().nonnegative(),
50258
+ longestPulseMs: number().nonnegative(),
50244
50259
  idleFloorW: number(),
50245
50260
  noiseCeilingW: number(),
50246
50261
  plateauW: number(),
@@ -56086,11 +56101,7 @@ var CreateIntegrationInputSchema = object({
56086
56101
  info: record(string$2(), unknown()).optional(),
56087
56102
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56088
56103
  originStableId: string$2().optional(),
56089
- settings: record(string$2(), unknown()).optional(),
56090
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56091
- * the keychain, not the table (D787). */
56092
- secretKeys: array(string$2()).optional(),
56093
- skipRestart: boolean().optional()
56104
+ settings: record(string$2(), unknown()).optional()
56094
56105
  });
56095
56106
  var UpdateIntegrationInputSchema = object({
56096
56107
  id: string$2(),
@@ -56111,16 +56122,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
56111
56122
  }), method(object({ id: string$2() }), DeleteIntegrationResultSchema, {
56112
56123
  kind: "mutation",
56113
56124
  auth: "admin"
56114
- }), method(object({ id: string$2() }), record(string$2(), unknown()), { auth: "admin" }), method(object({
56115
- id: string$2(),
56116
- settings: record(string$2(), unknown()),
56117
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56118
- secretKeys: array(string$2()).optional(),
56119
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56120
- skipRestart: boolean().optional()
56121
- }), object({ success: boolean() }), {
56122
- kind: "mutation",
56123
- auth: "admin"
56124
56125
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56125
56126
  addonId: string$2(),
56126
56127
  settings: record(string$2(), unknown())
@@ -59097,6 +59098,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59097
59098
  }), {
59098
59099
  kind: "mutation",
59099
59100
  auth: "admin"
59101
+ }), method(object({ integrationId: string$2().min(1) }), record(string$2(), unknown()), { auth: "admin" }), method(object({ integrationId: string$2().min(1) }), object({
59102
+ rows: number().int().nonnegative(),
59103
+ secrets: number().int().nonnegative()
59104
+ }), {
59105
+ kind: "mutation",
59106
+ auth: "admin"
59107
+ }), method(object({
59108
+ integrationId: string$2().min(1),
59109
+ patch: record(string$2(), unknown())
59110
+ }), object({ written: boolean() }), {
59111
+ kind: "mutation",
59112
+ auth: "admin"
59113
+ }), method(object({
59114
+ integrationId: string$2().min(1),
59115
+ keys: array(string$2().min(1))
59116
+ }), object({
59117
+ present: boolean(),
59118
+ removed: record(string$2(), unknown())
59119
+ }), {
59120
+ kind: "mutation",
59121
+ auth: "admin"
59100
59122
  });
59101
59123
  method(object({
59102
59124
  sourceUrl: string$2(),
@@ -80423,11 +80445,15 @@ discriminatedUnion("kind", [object({
80423
80445
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80424
80446
  * near F. A device that is not near one level most of the time has no floor.
80425
80447
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80426
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80427
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80448
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80449
+ * display waking): it is neither a run nor a pause. Among the remaining
80450
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80451
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80452
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80453
+ * is no run.
80428
80454
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80429
- * grid a person would type; stop dwell covers the longest pause; start dwell
80430
- * covers the longest standby blip above T.
80455
+ * grid a person would type; stop dwell covers the longest pause and the
80456
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80431
80457
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80432
80458
  * run must hold exactly one true interval and nothing may flap outside.
80433
80459
  */
@@ -80438,6 +80464,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80438
80464
  30 * SECOND_MS;
80439
80465
  60 * SECOND_MS;
80440
80466
  20 * MINUTE_MS$1;
80467
+ 90 * SECOND_MS;
80468
+ 30 * MINUTE_MS$1;
80469
+ 10 * MINUTE_MS$1;
80470
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80441
80471
  10 * SECOND_MS;
80442
80472
  10 * SECOND_MS;
80443
80473
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -85757,13 +85787,6 @@ Object.freeze({
85757
85787
  access: "view",
85758
85788
  role: "admin"
85759
85789
  },
85760
- "integrations.getSettings": {
85761
- capName: "integrations",
85762
- capScope: "system",
85763
- addonId: null,
85764
- access: "view",
85765
- role: "admin"
85766
- },
85767
85790
  "integrations.list": {
85768
85791
  capName: "integrations",
85769
85792
  capScope: "system",
@@ -85771,13 +85794,6 @@ Object.freeze({
85771
85794
  access: "view",
85772
85795
  role: "admin"
85773
85796
  },
85774
- "integrations.setSettings": {
85775
- capName: "integrations",
85776
- capScope: "system",
85777
- addonId: null,
85778
- access: "create",
85779
- role: "admin"
85780
- },
85781
85797
  "integrations.testConnection": {
85782
85798
  capName: "integrations",
85783
85799
  capScope: "system",
@@ -86772,6 +86788,13 @@ Object.freeze({
86772
86788
  access: "view",
86773
86789
  role: "admin"
86774
86790
  },
86791
+ "migrations.clearLegacyIntegrationRows": {
86792
+ capName: "migrations",
86793
+ capScope: "system",
86794
+ addonId: null,
86795
+ access: "delete",
86796
+ role: "admin"
86797
+ },
86775
86798
  "migrations.declareOwner": {
86776
86799
  capName: "migrations",
86777
86800
  capScope: "system",
@@ -86779,6 +86802,13 @@ Object.freeze({
86779
86802
  access: "create",
86780
86803
  role: "admin"
86781
86804
  },
86805
+ "migrations.deleteLegacyIntegrationRowKeys": {
86806
+ capName: "migrations",
86807
+ capScope: "system",
86808
+ addonId: null,
86809
+ access: "delete",
86810
+ role: "admin"
86811
+ },
86782
86812
  "migrations.dryRun": {
86783
86813
  capName: "migrations",
86784
86814
  capScope: "system",
@@ -86814,6 +86844,20 @@ Object.freeze({
86814
86844
  access: "create",
86815
86845
  role: "admin"
86816
86846
  },
86847
+ "migrations.patchLegacyIntegrationRows": {
86848
+ capName: "migrations",
86849
+ capScope: "system",
86850
+ addonId: null,
86851
+ access: "create",
86852
+ role: "admin"
86853
+ },
86854
+ "migrations.readLegacyIntegrationRows": {
86855
+ capName: "migrations",
86856
+ capScope: "system",
86857
+ addonId: null,
86858
+ access: "view",
86859
+ role: "admin"
86860
+ },
86817
86861
  "migrations.restorePreMigration": {
86818
86862
  capName: "migrations",
86819
86863
  capScope: "system",
@@ -209100,18 +209144,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
209100
209144
  try {
209101
209145
  const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
209102
209146
  const surviving = new Set(mine.map((i) => i.id));
209103
- for (const integration of mine) {
209104
- const settings = await this.ctx.api.integrations.getSettings.query({ id: integration.id });
209105
- const brokerId = typeof settings["brokerId"] === "string" ? settings["brokerId"] : void 0;
209106
- if (brokerId === void 0) continue;
209107
- if (!this.activeBrokers.some((b) => b.id === brokerId)) {
209108
- this.ctx.logger.warn("Matter integration→broker: linked broker not found", { meta: {
209109
- integrationId: integration.id,
209110
- brokerId
209111
- } });
209112
- continue;
209113
- }
209114
- this.requireRegistry().linkIntegration(integration.id, brokerId);
209147
+ for (const broker of this.activeBrokers) {
209148
+ const integrationId = broker.integrationId;
209149
+ if (integrationId === void 0 || !surviving.has(integrationId)) continue;
209150
+ this.requireRegistry().linkIntegration(integrationId, broker.id);
209115
209151
  }
209116
209152
  const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
209117
209153
  if (toRemove.length === 0) return;
@@ -209152,9 +209188,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
209152
209188
  },
209153
209189
  logger: this.ctx.logger,
209154
209190
  getBrokerIdForIntegration: async (id) => {
209155
- const brokerId = (await this.ctx.api.integrations.getSettings.query({ id }))["brokerId"];
209156
- if (typeof brokerId !== "string") throw new Error(`integration ${id} has no linked brokerId`);
209157
- return brokerId;
209191
+ const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
209192
+ if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
209193
+ return broker.id;
209158
209194
  },
209159
209195
  listAdoptedMatter: async () => {
209160
209196
  const reg = this.ctx.kernel.deviceRegistry;
package/dist/addon.mjs CHANGED
@@ -5993,7 +5993,7 @@ var ZodIssueCode = {
5993
5993
  var ZodFirstPartyTypeKind;
5994
5994
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5995
5995
  //#endregion
5996
- //#region ../types/dist/sleep-BaWhXZFm.mjs
5996
+ //#region ../types/dist/sleep-9TIHFj1w.mjs
5997
5997
  /**
5998
5998
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5999
5999
  * window to float samples (D455).
@@ -6593,7 +6593,7 @@ var NOT_RUN = {
6593
6593
  * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6594
6594
  * because the hub (and this addon's boot) must not hang on a migration.
6595
6595
  */
6596
- async function runAddonMigrations(host, migrations, logger) {
6596
+ async function runAddonMigrations(host, migrations, logger, options = {}) {
6597
6597
  const first = migrations[0];
6598
6598
  if (first === void 0) return NOT_RUN;
6599
6599
  const owner = first.meta.owner;
@@ -6622,7 +6622,9 @@ async function runAddonMigrations(host, migrations, logger) {
6622
6622
  else if (status === "blocked") blocked.push(id);
6623
6623
  else skipped.push(id);
6624
6624
  };
6625
+ const selected = options.only === void 0 ? null : new Set(options.only);
6625
6626
  for (const step of declaration.steps) {
6627
+ if (selected !== null && !selected.has(step.id)) continue;
6626
6628
  const bound = byId.get(step.id);
6627
6629
  if (bound === void 0 || step.action === "skip") {
6628
6630
  skipped.push(step.id);
@@ -7782,14 +7784,18 @@ var BaseAddon = class {
7782
7784
  * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7783
7785
  * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7784
7786
  * serve data whose shape it cannot trust; `tolerate` migrations never do.
7787
+ *
7788
+ * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7789
+ * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7790
+ * omitted applied ids "newer").
7785
7791
  */
7786
- async runMigrations(migrations) {
7792
+ async runMigrations(migrations, options) {
7787
7793
  const api = this.ctx.api.migrations;
7788
7794
  const report = await runAddonMigrations({
7789
7795
  declareOwner: (input) => api.declareOwner.mutate(input),
7790
7796
  startMigration: (input) => api.startMigration.mutate(input),
7791
7797
  finishMigration: (input) => api.finishMigration.mutate(input)
7792
- }, migrations, this.ctx.logger);
7798
+ }, migrations, this.ctx.logger, options);
7793
7799
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7794
7800
  return report;
7795
7801
  }
@@ -7931,10 +7937,9 @@ var BaseAddon = class {
7931
7937
  *
7932
7938
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7933
7939
  * needs its connection fields, the default integration the addon's own sections. It is the
7934
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7935
- * unchanged (`integrations.setSettings` for an account integration).
7940
+ * SHAPE of the form only — the values live on the instance record.
7936
7941
  */
7937
- async integrationSettingsSchema(_instance) {
7942
+ async integrationFormSchema(_instance) {
7938
7943
  return null;
7939
7944
  }
7940
7945
  /**
@@ -7980,7 +7985,7 @@ var BaseAddon = class {
7980
7985
  * per-instance schema when it declares one, else its global schema (what they always showed).
7981
7986
  */
7982
7987
  async getIntegrationFormSchema(instance, nodeId) {
7983
- const scoped = await this.integrationSettingsSchema(instance);
7988
+ const scoped = await this.integrationFormSchema(instance);
7984
7989
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7985
7990
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7986
7991
  }
@@ -9254,6 +9259,7 @@ function defineEntryKind(name, spec) {
9254
9259
  class: "config",
9255
9260
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9256
9261
  retention: { none: "an entry goes only when the operator deletes it" },
9262
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9257
9263
  ...spec.database === void 0 ? {} : { database: spec.database },
9258
9264
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9259
9265
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -11923,7 +11929,7 @@ object({
11923
11929
  params: record(string$2(), union([string$2(), number()])).optional()
11924
11930
  });
11925
11931
  //#endregion
11926
- //#region ../types/dist/composition-CRNwcFYY.mjs
11932
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11927
11933
  /**
11928
11934
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11929
11935
  *
@@ -14504,7 +14510,13 @@ var MigrationLedgerEntrySchema = object({
14504
14510
  * joined by the engine from the catalogues it knows this boot. Absent when no
14505
14511
  * catalogue lists the id (a newer database).
14506
14512
  */
14507
- meta: MigrationMetaSchema.optional()
14513
+ meta: MigrationMetaSchema.optional(),
14514
+ /**
14515
+ * Set when this is an applied row of an addon owner whose catalogue the answering
14516
+ * process has not loaded (the owner has not declared yet): the row is applied, the
14517
+ * process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
14518
+ */
14519
+ ownerNotLoaded: boolean().optional()
14508
14520
  });
14509
14521
  /** A catalogued migration that is not applied, with where it stands. */
14510
14522
  var MigrationPendingEntrySchema = object({
@@ -50239,6 +50251,9 @@ var RunningProposalEvidenceSchema = object({
50239
50251
  runsFound: number().int().nonnegative(),
50240
50252
  pausesFound: number().int().nonnegative(),
50241
50253
  longestPauseMs: number().nonnegative(),
50254
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50255
+ pulsesFound: number().int().nonnegative(),
50256
+ longestPulseMs: number().nonnegative(),
50242
50257
  idleFloorW: number(),
50243
50258
  noiseCeilingW: number(),
50244
50259
  plateauW: number(),
@@ -56084,11 +56099,7 @@ var CreateIntegrationInputSchema = object({
56084
56099
  info: record(string$2(), unknown()).optional(),
56085
56100
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56086
56101
  originStableId: string$2().optional(),
56087
- settings: record(string$2(), unknown()).optional(),
56088
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56089
- * the keychain, not the table (D787). */
56090
- secretKeys: array(string$2()).optional(),
56091
- skipRestart: boolean().optional()
56102
+ settings: record(string$2(), unknown()).optional()
56092
56103
  });
56093
56104
  var UpdateIntegrationInputSchema = object({
56094
56105
  id: string$2(),
@@ -56109,16 +56120,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string$2
56109
56120
  }), method(object({ id: string$2() }), DeleteIntegrationResultSchema, {
56110
56121
  kind: "mutation",
56111
56122
  auth: "admin"
56112
- }), method(object({ id: string$2() }), record(string$2(), unknown()), { auth: "admin" }), method(object({
56113
- id: string$2(),
56114
- settings: record(string$2(), unknown()),
56115
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56116
- secretKeys: array(string$2()).optional(),
56117
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56118
- skipRestart: boolean().optional()
56119
- }), object({ success: boolean() }), {
56120
- kind: "mutation",
56121
- auth: "admin"
56122
56123
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56123
56124
  addonId: string$2(),
56124
56125
  settings: record(string$2(), unknown())
@@ -59095,6 +59096,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59095
59096
  }), {
59096
59097
  kind: "mutation",
59097
59098
  auth: "admin"
59099
+ }), method(object({ integrationId: string$2().min(1) }), record(string$2(), unknown()), { auth: "admin" }), method(object({ integrationId: string$2().min(1) }), object({
59100
+ rows: number().int().nonnegative(),
59101
+ secrets: number().int().nonnegative()
59102
+ }), {
59103
+ kind: "mutation",
59104
+ auth: "admin"
59105
+ }), method(object({
59106
+ integrationId: string$2().min(1),
59107
+ patch: record(string$2(), unknown())
59108
+ }), object({ written: boolean() }), {
59109
+ kind: "mutation",
59110
+ auth: "admin"
59111
+ }), method(object({
59112
+ integrationId: string$2().min(1),
59113
+ keys: array(string$2().min(1))
59114
+ }), object({
59115
+ present: boolean(),
59116
+ removed: record(string$2(), unknown())
59117
+ }), {
59118
+ kind: "mutation",
59119
+ auth: "admin"
59098
59120
  });
59099
59121
  method(object({
59100
59122
  sourceUrl: string$2(),
@@ -80421,11 +80443,15 @@ discriminatedUnion("kind", [object({
80421
80443
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80422
80444
  * near F. A device that is not near one level most of the time has no floor.
80423
80445
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80424
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80425
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80446
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80447
+ * display waking): it is neither a run nor a pause. Among the remaining
80448
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80449
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80450
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80451
+ * is no run.
80426
80452
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80427
- * grid a person would type; stop dwell covers the longest pause; start dwell
80428
- * covers the longest standby blip above T.
80453
+ * grid a person would type; stop dwell covers the longest pause and the
80454
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80429
80455
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80430
80456
  * run must hold exactly one true interval and nothing may flap outside.
80431
80457
  */
@@ -80436,6 +80462,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80436
80462
  30 * SECOND_MS;
80437
80463
  60 * SECOND_MS;
80438
80464
  20 * MINUTE_MS$1;
80465
+ 90 * SECOND_MS;
80466
+ 30 * MINUTE_MS$1;
80467
+ 10 * MINUTE_MS$1;
80468
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80439
80469
  10 * SECOND_MS;
80440
80470
  10 * SECOND_MS;
80441
80471
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -85755,13 +85785,6 @@ Object.freeze({
85755
85785
  access: "view",
85756
85786
  role: "admin"
85757
85787
  },
85758
- "integrations.getSettings": {
85759
- capName: "integrations",
85760
- capScope: "system",
85761
- addonId: null,
85762
- access: "view",
85763
- role: "admin"
85764
- },
85765
85788
  "integrations.list": {
85766
85789
  capName: "integrations",
85767
85790
  capScope: "system",
@@ -85769,13 +85792,6 @@ Object.freeze({
85769
85792
  access: "view",
85770
85793
  role: "admin"
85771
85794
  },
85772
- "integrations.setSettings": {
85773
- capName: "integrations",
85774
- capScope: "system",
85775
- addonId: null,
85776
- access: "create",
85777
- role: "admin"
85778
- },
85779
85795
  "integrations.testConnection": {
85780
85796
  capName: "integrations",
85781
85797
  capScope: "system",
@@ -86770,6 +86786,13 @@ Object.freeze({
86770
86786
  access: "view",
86771
86787
  role: "admin"
86772
86788
  },
86789
+ "migrations.clearLegacyIntegrationRows": {
86790
+ capName: "migrations",
86791
+ capScope: "system",
86792
+ addonId: null,
86793
+ access: "delete",
86794
+ role: "admin"
86795
+ },
86773
86796
  "migrations.declareOwner": {
86774
86797
  capName: "migrations",
86775
86798
  capScope: "system",
@@ -86777,6 +86800,13 @@ Object.freeze({
86777
86800
  access: "create",
86778
86801
  role: "admin"
86779
86802
  },
86803
+ "migrations.deleteLegacyIntegrationRowKeys": {
86804
+ capName: "migrations",
86805
+ capScope: "system",
86806
+ addonId: null,
86807
+ access: "delete",
86808
+ role: "admin"
86809
+ },
86780
86810
  "migrations.dryRun": {
86781
86811
  capName: "migrations",
86782
86812
  capScope: "system",
@@ -86812,6 +86842,20 @@ Object.freeze({
86812
86842
  access: "create",
86813
86843
  role: "admin"
86814
86844
  },
86845
+ "migrations.patchLegacyIntegrationRows": {
86846
+ capName: "migrations",
86847
+ capScope: "system",
86848
+ addonId: null,
86849
+ access: "create",
86850
+ role: "admin"
86851
+ },
86852
+ "migrations.readLegacyIntegrationRows": {
86853
+ capName: "migrations",
86854
+ capScope: "system",
86855
+ addonId: null,
86856
+ access: "view",
86857
+ role: "admin"
86858
+ },
86815
86859
  "migrations.restorePreMigration": {
86816
86860
  capName: "migrations",
86817
86861
  capScope: "system",
@@ -209098,18 +209142,10 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
209098
209142
  try {
209099
209143
  const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
209100
209144
  const surviving = new Set(mine.map((i) => i.id));
209101
- for (const integration of mine) {
209102
- const settings = await this.ctx.api.integrations.getSettings.query({ id: integration.id });
209103
- const brokerId = typeof settings["brokerId"] === "string" ? settings["brokerId"] : void 0;
209104
- if (brokerId === void 0) continue;
209105
- if (!this.activeBrokers.some((b) => b.id === brokerId)) {
209106
- this.ctx.logger.warn("Matter integration→broker: linked broker not found", { meta: {
209107
- integrationId: integration.id,
209108
- brokerId
209109
- } });
209110
- continue;
209111
- }
209112
- this.requireRegistry().linkIntegration(integration.id, brokerId);
209145
+ for (const broker of this.activeBrokers) {
209146
+ const integrationId = broker.integrationId;
209147
+ if (integrationId === void 0 || !surviving.has(integrationId)) continue;
209148
+ this.requireRegistry().linkIntegration(integrationId, broker.id);
209113
209149
  }
209114
209150
  const toRemove = this.activeBrokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
209115
209151
  if (toRemove.length === 0) return;
@@ -209150,9 +209186,9 @@ var MatterBrokerAddon = class extends BaseDeviceProvider {
209150
209186
  },
209151
209187
  logger: this.ctx.logger,
209152
209188
  getBrokerIdForIntegration: async (id) => {
209153
- const brokerId = (await this.ctx.api.integrations.getSettings.query({ id }))["brokerId"];
209154
- if (typeof brokerId !== "string") throw new Error(`integration ${id} has no linked brokerId`);
209155
- return brokerId;
209189
+ const broker = this.activeBrokers.find((entry) => entry.integrationId === id);
209190
+ if (broker === void 0) throw new Error(`integration ${id} has no linked brokerId`);
209191
+ return broker.id;
209156
209192
  },
209157
209193
  listAdoptedMatter: async () => {
209158
209194
  const reg = this.ctx.kernel.deviceRegistry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.156",
3
+ "version": "0.2.157",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -65,7 +65,7 @@
65
65
  }
66
66
  ],
67
67
  "requires": {
68
- "server": ">=1.2.762"
68
+ "server": ">=1.2.770"
69
69
  }
70
70
  },
71
71
  "files": [