@camstack/addon-provider-bosch 0.1.36 → 0.1.37

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.1.37] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - Copying an integration into its record reads the leftover rows through the migration-only door and resolves their secrets in this addon
15
+
10
16
  ## [0.1.36] - 2026-11-04
11
17
 
12
18
  ### Changed
@@ -1,4 +1,4 @@
1
- const require_bosch_instances = require("./bosch-instances-CXmuRkef.js");
1
+ const require_bosch_instances = require("./bosch-instances-CO8yIFzJ.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/shared/oauth-callback.ts
4
4
  /** A code on its own: no scheme, no query, no separators. */
@@ -1,4 +1,4 @@
1
- import { $ as errMsg } from "./bosch-instances-BIvOtuzj.mjs";
1
+ import { $ as errMsg } from "./bosch-instances-BQiyzlPt.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  //#region src/shared/oauth-callback.ts
4
4
  /** A code on its own: no scheme, no query, no separators. */
@@ -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-9TIHFj1w.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).
@@ -7925,10 +7925,9 @@ var BaseAddon = class {
7925
7925
  *
7926
7926
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7927
7927
  * 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).
7928
+ * SHAPE of the form only — the values live on the instance record.
7930
7929
  */
7931
- async integrationSettingsSchema(_instance) {
7930
+ async integrationFormSchema(_instance) {
7932
7931
  return null;
7933
7932
  }
7934
7933
  /**
@@ -7974,7 +7973,7 @@ var BaseAddon = class {
7974
7973
  * per-instance schema when it declares one, else its global schema (what they always showed).
7975
7974
  */
7976
7975
  async getIntegrationFormSchema(instance, nodeId) {
7977
- const scoped = await this.integrationSettingsSchema(instance);
7976
+ const scoped = await this.integrationFormSchema(instance);
7978
7977
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7979
7978
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7980
7979
  }
@@ -11956,7 +11955,7 @@ object({
11956
11955
  params: record(string(), union([string(), number()])).optional()
11957
11956
  });
11958
11957
  //#endregion
11959
- //#region ../types/dist/composition-CRNwcFYY.mjs
11958
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11960
11959
  /**
11961
11960
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11962
11961
  *
@@ -14664,20 +14663,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14664
14663
  };
14665
14664
  }
14666
14665
  /**
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
- */
14666
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14667
+ * from its own process, so the leftover rows are reached through the migration-only door
14668
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14669
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14670
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14671
+ * door to delete the rows. The addon's own record kind is its records handle.
14672
+ */
14673
+ async function resolvedRows(stored, secrets) {
14674
+ if (secrets === null) return stored;
14675
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14676
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14677
+ return value;
14678
+ }
14673
14679
  function integrationRowsPortsOverApi(deps) {
14680
+ const secrets = deps.secrets ?? null;
14674
14681
  return {
14675
14682
  warn: (message) => deps.log.warn(message),
14676
14683
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14677
14684
  id: i.id,
14678
14685
  name: i.name
14679
14686
  })),
14680
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14687
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14681
14688
  putRecord: async (id, body) => {
14682
14689
  if (await deps.records.get(id) !== null) return "exists";
14683
14690
  try {
@@ -14696,15 +14703,17 @@ function integrationRowsPortsOverApi(deps) {
14696
14703
  await deps.records.delete(id);
14697
14704
  },
14698
14705
  clearRows: async (integrationId) => {
14699
- await deps.api.clearSettings({ id: integrationId });
14706
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14707
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14708
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14700
14709
  }
14701
14710
  };
14702
14711
  }
14703
14712
  function defaultIntegrationApiOver(api) {
14704
14713
  return {
14705
14714
  list: () => api.integrations.list.query(),
14706
- getSettings: (input) => api.integrations.getSettings.query(input),
14707
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14715
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14716
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14708
14717
  listDevices: (addonId) => api.deviceManager.listAll.query({
14709
14718
  addonId,
14710
14719
  projection: "slim"
@@ -50390,6 +50399,9 @@ var RunningProposalEvidenceSchema = object({
50390
50399
  runsFound: number().int().nonnegative(),
50391
50400
  pausesFound: number().int().nonnegative(),
50392
50401
  longestPauseMs: number().nonnegative(),
50402
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50403
+ pulsesFound: number().int().nonnegative(),
50404
+ longestPulseMs: number().nonnegative(),
50393
50405
  idleFloorW: number(),
50394
50406
  noiseCeilingW: number(),
50395
50407
  plateauW: number(),
@@ -56235,11 +56247,7 @@ var CreateIntegrationInputSchema = object({
56235
56247
  info: record(string(), unknown()).optional(),
56236
56248
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56237
56249
  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()
56250
+ settings: record(string(), unknown()).optional()
56243
56251
  });
56244
56252
  var UpdateIntegrationInputSchema = object({
56245
56253
  id: string(),
@@ -56260,22 +56268,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56260
56268
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56261
56269
  kind: "mutation",
56262
56270
  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
56271
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56280
56272
  addonId: string(),
56281
56273
  settings: record(string(), unknown())
@@ -59252,6 +59244,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59252
59244
  }), {
59253
59245
  kind: "mutation",
59254
59246
  auth: "admin"
59247
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59248
+ rows: number().int().nonnegative(),
59249
+ secrets: number().int().nonnegative()
59250
+ }), {
59251
+ kind: "mutation",
59252
+ auth: "admin"
59253
+ }), method(object({
59254
+ integrationId: string().min(1),
59255
+ patch: record(string(), unknown())
59256
+ }), object({ written: boolean() }), {
59257
+ kind: "mutation",
59258
+ auth: "admin"
59259
+ }), method(object({
59260
+ integrationId: string().min(1),
59261
+ keys: array(string().min(1))
59262
+ }), object({
59263
+ present: boolean(),
59264
+ removed: record(string(), unknown())
59265
+ }), {
59266
+ kind: "mutation",
59267
+ auth: "admin"
59255
59268
  });
59256
59269
  method(object({
59257
59270
  sourceUrl: string(),
@@ -80617,11 +80630,15 @@ function runningFromDishwasherOperation(operation, program, previous, fetchedAt,
80617
80630
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80618
80631
  * near F. A device that is not near one level most of the time has no floor.
80619
80632
  * 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.
80633
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80634
+ * display waking): it is neither a run nor a pause. Among the remaining
80635
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80636
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80637
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80638
+ * is no run.
80622
80639
  * 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.
80640
+ * grid a person would type; stop dwell covers the longest pause and the
80641
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80625
80642
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80626
80643
  * run must hold exactly one true interval and nothing may flap outside.
80627
80644
  */
@@ -80632,6 +80649,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80632
80649
  30 * SECOND_MS;
80633
80650
  60 * SECOND_MS;
80634
80651
  20 * MINUTE_MS$1;
80652
+ 90 * SECOND_MS;
80653
+ 30 * MINUTE_MS$1;
80654
+ 10 * MINUTE_MS$1;
80655
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80635
80656
  10 * SECOND_MS;
80636
80657
  10 * SECOND_MS;
80637
80658
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -85916,13 +85937,6 @@ Object.freeze({
85916
85937
  access: "create",
85917
85938
  role: "admin"
85918
85939
  },
85919
- "integrations.clearSettings": {
85920
- capName: "integrations",
85921
- capScope: "system",
85922
- addonId: null,
85923
- access: "delete",
85924
- role: "admin"
85925
- },
85926
85940
  "integrations.create": {
85927
85941
  capName: "integrations",
85928
85942
  capScope: "system",
@@ -85958,13 +85972,6 @@ Object.freeze({
85958
85972
  access: "view",
85959
85973
  role: "admin"
85960
85974
  },
85961
- "integrations.getSettings": {
85962
- capName: "integrations",
85963
- capScope: "system",
85964
- addonId: null,
85965
- access: "view",
85966
- role: "admin"
85967
- },
85968
85975
  "integrations.list": {
85969
85976
  capName: "integrations",
85970
85977
  capScope: "system",
@@ -85972,13 +85979,6 @@ Object.freeze({
85972
85979
  access: "view",
85973
85980
  role: "admin"
85974
85981
  },
85975
- "integrations.setSettings": {
85976
- capName: "integrations",
85977
- capScope: "system",
85978
- addonId: null,
85979
- access: "create",
85980
- role: "admin"
85981
- },
85982
85982
  "integrations.testConnection": {
85983
85983
  capName: "integrations",
85984
85984
  capScope: "system",
@@ -86973,6 +86973,13 @@ Object.freeze({
86973
86973
  access: "view",
86974
86974
  role: "admin"
86975
86975
  },
86976
+ "migrations.clearLegacyIntegrationRows": {
86977
+ capName: "migrations",
86978
+ capScope: "system",
86979
+ addonId: null,
86980
+ access: "delete",
86981
+ role: "admin"
86982
+ },
86976
86983
  "migrations.declareOwner": {
86977
86984
  capName: "migrations",
86978
86985
  capScope: "system",
@@ -86980,6 +86987,13 @@ Object.freeze({
86980
86987
  access: "create",
86981
86988
  role: "admin"
86982
86989
  },
86990
+ "migrations.deleteLegacyIntegrationRowKeys": {
86991
+ capName: "migrations",
86992
+ capScope: "system",
86993
+ addonId: null,
86994
+ access: "delete",
86995
+ role: "admin"
86996
+ },
86983
86997
  "migrations.dryRun": {
86984
86998
  capName: "migrations",
86985
86999
  capScope: "system",
@@ -87015,6 +87029,20 @@ Object.freeze({
87015
87029
  access: "create",
87016
87030
  role: "admin"
87017
87031
  },
87032
+ "migrations.patchLegacyIntegrationRows": {
87033
+ capName: "migrations",
87034
+ capScope: "system",
87035
+ addonId: null,
87036
+ access: "create",
87037
+ role: "admin"
87038
+ },
87039
+ "migrations.readLegacyIntegrationRows": {
87040
+ capName: "migrations",
87041
+ capScope: "system",
87042
+ addonId: null,
87043
+ access: "view",
87044
+ role: "admin"
87045
+ },
87018
87046
  "migrations.restorePreMigration": {
87019
87047
  capName: "migrations",
87020
87048
  capScope: "system",
@@ -98082,6 +98110,7 @@ function boschRowsMigration(spec) {
98082
98110
  function boschRowsPorts(deps) {
98083
98111
  return integrationRowsPortsOverApi({
98084
98112
  api: deps.api,
98113
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
98085
98114
  records: deps.records.kind({
98086
98115
  name: deps.kind.name,
98087
98116
  declaration: deps.kind.declaration,
@@ -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-9TIHFj1w.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).
@@ -7925,10 +7925,9 @@ var BaseAddon = class {
7925
7925
  *
7926
7926
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7927
7927
  * 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).
7928
+ * SHAPE of the form only — the values live on the instance record.
7930
7929
  */
7931
- async integrationSettingsSchema(_instance) {
7930
+ async integrationFormSchema(_instance) {
7932
7931
  return null;
7933
7932
  }
7934
7933
  /**
@@ -7974,7 +7973,7 @@ var BaseAddon = class {
7974
7973
  * per-instance schema when it declares one, else its global schema (what they always showed).
7975
7974
  */
7976
7975
  async getIntegrationFormSchema(instance, nodeId) {
7977
- const scoped = await this.integrationSettingsSchema(instance);
7976
+ const scoped = await this.integrationFormSchema(instance);
7978
7977
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7979
7978
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7980
7979
  }
@@ -11956,7 +11955,7 @@ object({
11956
11955
  params: record(string(), union([string(), number()])).optional()
11957
11956
  });
11958
11957
  //#endregion
11959
- //#region ../types/dist/composition-CRNwcFYY.mjs
11958
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11960
11959
  /**
11961
11960
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11962
11961
  *
@@ -14664,20 +14663,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14664
14663
  };
14665
14664
  }
14666
14665
  /**
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
- */
14666
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14667
+ * from its own process, so the leftover rows are reached through the migration-only door
14668
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14669
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14670
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14671
+ * door to delete the rows. The addon's own record kind is its records handle.
14672
+ */
14673
+ async function resolvedRows(stored, secrets) {
14674
+ if (secrets === null) return stored;
14675
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14676
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14677
+ return value;
14678
+ }
14673
14679
  function integrationRowsPortsOverApi(deps) {
14680
+ const secrets = deps.secrets ?? null;
14674
14681
  return {
14675
14682
  warn: (message) => deps.log.warn(message),
14676
14683
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14677
14684
  id: i.id,
14678
14685
  name: i.name
14679
14686
  })),
14680
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14687
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14681
14688
  putRecord: async (id, body) => {
14682
14689
  if (await deps.records.get(id) !== null) return "exists";
14683
14690
  try {
@@ -14696,15 +14703,17 @@ function integrationRowsPortsOverApi(deps) {
14696
14703
  await deps.records.delete(id);
14697
14704
  },
14698
14705
  clearRows: async (integrationId) => {
14699
- await deps.api.clearSettings({ id: integrationId });
14706
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14707
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14708
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14700
14709
  }
14701
14710
  };
14702
14711
  }
14703
14712
  function defaultIntegrationApiOver(api) {
14704
14713
  return {
14705
14714
  list: () => api.integrations.list.query(),
14706
- getSettings: (input) => api.integrations.getSettings.query(input),
14707
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14715
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14716
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14708
14717
  listDevices: (addonId) => api.deviceManager.listAll.query({
14709
14718
  addonId,
14710
14719
  projection: "slim"
@@ -50390,6 +50399,9 @@ var RunningProposalEvidenceSchema = object({
50390
50399
  runsFound: number().int().nonnegative(),
50391
50400
  pausesFound: number().int().nonnegative(),
50392
50401
  longestPauseMs: number().nonnegative(),
50402
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50403
+ pulsesFound: number().int().nonnegative(),
50404
+ longestPulseMs: number().nonnegative(),
50393
50405
  idleFloorW: number(),
50394
50406
  noiseCeilingW: number(),
50395
50407
  plateauW: number(),
@@ -56235,11 +56247,7 @@ var CreateIntegrationInputSchema = object({
56235
56247
  info: record(string(), unknown()).optional(),
56236
56248
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56237
56249
  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()
56250
+ settings: record(string(), unknown()).optional()
56243
56251
  });
56244
56252
  var UpdateIntegrationInputSchema = object({
56245
56253
  id: string(),
@@ -56260,22 +56268,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56260
56268
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56261
56269
  kind: "mutation",
56262
56270
  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
56271
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56280
56272
  addonId: string(),
56281
56273
  settings: record(string(), unknown())
@@ -59252,6 +59244,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59252
59244
  }), {
59253
59245
  kind: "mutation",
59254
59246
  auth: "admin"
59247
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59248
+ rows: number().int().nonnegative(),
59249
+ secrets: number().int().nonnegative()
59250
+ }), {
59251
+ kind: "mutation",
59252
+ auth: "admin"
59253
+ }), method(object({
59254
+ integrationId: string().min(1),
59255
+ patch: record(string(), unknown())
59256
+ }), object({ written: boolean() }), {
59257
+ kind: "mutation",
59258
+ auth: "admin"
59259
+ }), method(object({
59260
+ integrationId: string().min(1),
59261
+ keys: array(string().min(1))
59262
+ }), object({
59263
+ present: boolean(),
59264
+ removed: record(string(), unknown())
59265
+ }), {
59266
+ kind: "mutation",
59267
+ auth: "admin"
59255
59268
  });
59256
59269
  method(object({
59257
59270
  sourceUrl: string(),
@@ -80617,11 +80630,15 @@ function runningFromDishwasherOperation(operation, program, previous, fetchedAt,
80617
80630
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80618
80631
  * near F. A device that is not near one level most of the time has no floor.
80619
80632
  * 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.
80633
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80634
+ * display waking): it is neither a run nor a pause. Among the remaining
80635
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80636
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80637
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80638
+ * is no run.
80622
80639
  * 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.
80640
+ * grid a person would type; stop dwell covers the longest pause and the
80641
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80625
80642
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80626
80643
  * run must hold exactly one true interval and nothing may flap outside.
80627
80644
  */
@@ -80632,6 +80649,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80632
80649
  30 * SECOND_MS;
80633
80650
  60 * SECOND_MS;
80634
80651
  20 * MINUTE_MS$1;
80652
+ 90 * SECOND_MS;
80653
+ 30 * MINUTE_MS$1;
80654
+ 10 * MINUTE_MS$1;
80655
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80635
80656
  10 * SECOND_MS;
80636
80657
  10 * SECOND_MS;
80637
80658
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -85916,13 +85937,6 @@ Object.freeze({
85916
85937
  access: "create",
85917
85938
  role: "admin"
85918
85939
  },
85919
- "integrations.clearSettings": {
85920
- capName: "integrations",
85921
- capScope: "system",
85922
- addonId: null,
85923
- access: "delete",
85924
- role: "admin"
85925
- },
85926
85940
  "integrations.create": {
85927
85941
  capName: "integrations",
85928
85942
  capScope: "system",
@@ -85958,13 +85972,6 @@ Object.freeze({
85958
85972
  access: "view",
85959
85973
  role: "admin"
85960
85974
  },
85961
- "integrations.getSettings": {
85962
- capName: "integrations",
85963
- capScope: "system",
85964
- addonId: null,
85965
- access: "view",
85966
- role: "admin"
85967
- },
85968
85975
  "integrations.list": {
85969
85976
  capName: "integrations",
85970
85977
  capScope: "system",
@@ -85972,13 +85979,6 @@ Object.freeze({
85972
85979
  access: "view",
85973
85980
  role: "admin"
85974
85981
  },
85975
- "integrations.setSettings": {
85976
- capName: "integrations",
85977
- capScope: "system",
85978
- addonId: null,
85979
- access: "create",
85980
- role: "admin"
85981
- },
85982
85982
  "integrations.testConnection": {
85983
85983
  capName: "integrations",
85984
85984
  capScope: "system",
@@ -86973,6 +86973,13 @@ Object.freeze({
86973
86973
  access: "view",
86974
86974
  role: "admin"
86975
86975
  },
86976
+ "migrations.clearLegacyIntegrationRows": {
86977
+ capName: "migrations",
86978
+ capScope: "system",
86979
+ addonId: null,
86980
+ access: "delete",
86981
+ role: "admin"
86982
+ },
86976
86983
  "migrations.declareOwner": {
86977
86984
  capName: "migrations",
86978
86985
  capScope: "system",
@@ -86980,6 +86987,13 @@ Object.freeze({
86980
86987
  access: "create",
86981
86988
  role: "admin"
86982
86989
  },
86990
+ "migrations.deleteLegacyIntegrationRowKeys": {
86991
+ capName: "migrations",
86992
+ capScope: "system",
86993
+ addonId: null,
86994
+ access: "delete",
86995
+ role: "admin"
86996
+ },
86983
86997
  "migrations.dryRun": {
86984
86998
  capName: "migrations",
86985
86999
  capScope: "system",
@@ -87015,6 +87029,20 @@ Object.freeze({
87015
87029
  access: "create",
87016
87030
  role: "admin"
87017
87031
  },
87032
+ "migrations.patchLegacyIntegrationRows": {
87033
+ capName: "migrations",
87034
+ capScope: "system",
87035
+ addonId: null,
87036
+ access: "create",
87037
+ role: "admin"
87038
+ },
87039
+ "migrations.readLegacyIntegrationRows": {
87040
+ capName: "migrations",
87041
+ capScope: "system",
87042
+ addonId: null,
87043
+ access: "view",
87044
+ role: "admin"
87045
+ },
87018
87046
  "migrations.restorePreMigration": {
87019
87047
  capName: "migrations",
87020
87048
  capScope: "system",
@@ -98082,6 +98110,7 @@ function boschRowsMigration(spec) {
98082
98110
  function boschRowsPorts(deps) {
98083
98111
  return integrationRowsPortsOverApi({
98084
98112
  api: deps.api,
98113
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
98085
98114
  records: deps.records.kind({
98086
98115
  name: deps.kind.name,
98087
98116
  declaration: deps.kind.declaration,
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-CXmuRkef.js");
3
- const require_account_integration_offline = require("../account-integration-offline-Bnm17sAE.js");
2
+ const require_bosch_instances = require("../bosch-instances-CO8yIFzJ.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-VItxeiJg.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  let node_tls = require("node:tls");
6
6
  let node_zlib = require("node:zlib");
@@ -5383,6 +5383,7 @@ var BoschHomeConnectProviderAddon = class extends require_bosch_instances.BaseDe
5383
5383
  await records.declare(homeConnectRecordKinds);
5384
5384
  await this.runMigrations(require_bosch_instances.defineAddonMigrations(HOME_CONNECT_MIGRATIONS, homeConnectRowsPorts({
5385
5385
  api: require_bosch_instances.defaultIntegrationApiOver(this.ctx.api),
5386
+ secrets: this.ctx.secrets ?? null,
5386
5387
  records,
5387
5388
  warn: (message) => this.ctx.logger.warn(message)
5388
5389
  })));
@@ -1,5 +1,5 @@
1
- import { $ as errMsg, B as isSecretMarkerLike, C as ingestReading, D as quantityFromUnit, E as powerMeterCapability, F as defineAddonMigrations, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, N as createEvent, O as runningFromDishwasherOperation, P as customAction, Q as DeviceType, R as entryBody, T as numericSensorCapability, U as boolean, V as _enum, W as literal, X as unknown, Y as union, Z as DeviceRole, _ as defaultIntegrationApiOver, a as BaseDevice, b as enumSensorCapability, c as RUNNING_STATE_COLD, d as VendorLoginProxy, et as EventCategory, g as consumablesCapability, h as connectionTestCapability, i as testBoschInstance, k as runningStateCapability, l as RecordIntegrationInstances, n as boschRowsPorts, o as BaseDeviceProvider, p as binaryCapability, q as record, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability, x as eventEmitterCapability, y as dishwasherCapability, z as isSecretMarker } from "../bosch-instances-BIvOtuzj.mjs";
2
- import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-9ooJuNrN.mjs";
1
+ import { $ as errMsg, B as isSecretMarkerLike, C as ingestReading, D as quantityFromUnit, E as powerMeterCapability, F as defineAddonMigrations, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, N as createEvent, O as runningFromDishwasherOperation, P as customAction, Q as DeviceType, R as entryBody, T as numericSensorCapability, U as boolean, V as _enum, W as literal, X as unknown, Y as union, Z as DeviceRole, _ as defaultIntegrationApiOver, a as BaseDevice, b as enumSensorCapability, c as RUNNING_STATE_COLD, d as VendorLoginProxy, et as EventCategory, g as consumablesCapability, h as connectionTestCapability, i as testBoschInstance, k as runningStateCapability, l as RecordIntegrationInstances, n as boschRowsPorts, o as BaseDeviceProvider, p as binaryCapability, q as record, r as keepBodyKeys, s as DishwasherOperationStateSchema, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability, x as eventEmitterCapability, y as dishwasherCapability, z as isSecretMarker } from "../bosch-instances-BQiyzlPt.mjs";
2
+ import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-nChgZUlC.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { connect } from "node:tls";
5
5
  import { inflateRawSync } from "node:zlib";
@@ -5382,6 +5382,7 @@ var BoschHomeConnectProviderAddon = class extends BaseDeviceProvider {
5382
5382
  await records.declare(homeConnectRecordKinds);
5383
5383
  await this.runMigrations(defineAddonMigrations(HOME_CONNECT_MIGRATIONS, homeConnectRowsPorts({
5384
5384
  api: defaultIntegrationApiOver(this.ctx.api),
5385
+ secrets: this.ctx.secrets ?? null,
5385
5386
  records,
5386
5387
  warn: (message) => this.ctx.logger.warn(message)
5387
5388
  })));
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_bosch_instances = require("../bosch-instances-CXmuRkef.js");
3
- const require_account_integration_offline = require("../account-integration-offline-Bnm17sAE.js");
2
+ const require_bosch_instances = require("../bosch-instances-CO8yIFzJ.js");
3
+ const require_account_integration_offline = require("../account-integration-offline-VItxeiJg.js");
4
4
  let node_crypto = require("node:crypto");
5
5
  //#region src/homecom/config.ts
6
6
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -2193,6 +2193,7 @@ var BoschHomeComProviderAddon = class extends require_bosch_instances.BaseDevice
2193
2193
  await records.declare(homeComRecordKinds);
2194
2194
  await this.runMigrations(require_bosch_instances.defineAddonMigrations(HOMECOM_MIGRATIONS, homeComRowsPorts({
2195
2195
  api: require_bosch_instances.defaultIntegrationApiOver(this.ctx.api),
2196
+ secrets: this.ctx.secrets ?? null,
2196
2197
  records,
2197
2198
  warn: (message) => this.ctx.logger.warn(message)
2198
2199
  })));
@@ -1,5 +1,5 @@
1
- import { $ as errMsg, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, M as waterHeaterCapability, P as customAction, Q as DeviceType, R as entryBody, U as boolean, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, d as VendorLoginProxy, et as EventCategory, h as connectionTestCapability, i as testBoschInstance, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-BIvOtuzj.mjs";
2
- import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-9ooJuNrN.mjs";
1
+ import { $ as errMsg, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, M as waterHeaterCapability, P as customAction, Q as DeviceType, R as entryBody, U as boolean, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, d as VendorLoginProxy, et as EventCategory, h as connectionTestCapability, i as testBoschInstance, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-BQiyzlPt.mjs";
2
+ import { a as createSignInAttemptStore, i as openSignInProxy, n as cascadeRemoveDevicesForIntegration, o as loginFailureForAttempt, r as createSignInClaims, s as parseOAuthCallback, t as setIntegrationDevicesOnline } from "../account-integration-offline-nChgZUlC.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  //#region src/homecom/config.ts
5
5
  var HOMECOM_BRANDS = ["bosch", "buderus"];
@@ -2192,6 +2192,7 @@ var BoschHomeComProviderAddon = class extends BaseDeviceProvider {
2192
2192
  await records.declare(homeComRecordKinds);
2193
2193
  await this.runMigrations(defineAddonMigrations(HOMECOM_MIGRATIONS, homeComRowsPorts({
2194
2194
  api: defaultIntegrationApiOver(this.ctx.api),
2195
+ secrets: this.ctx.secrets ?? null,
2195
2196
  records,
2196
2197
  warn: (message) => this.ctx.logger.warn(message)
2197
2198
  })));
package/dist/shc/addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
- const require_bosch_instances = require("../bosch-instances-CXmuRkef.js");
5
+ const require_bosch_instances = require("../bosch-instances-CO8yIFzJ.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_https = require("node:https");
8
8
  let node_child_process = require("node:child_process");
@@ -23845,6 +23845,7 @@ var BoschShcProviderAddon = class extends require_bosch_instances.BaseDeviceProv
23845
23845
  await records.declare(shcRecordKinds);
23846
23846
  await this.runMigrations(require_bosch_instances.defineAddonMigrations(SHC_MIGRATIONS, shcRowsPorts({
23847
23847
  api: require_bosch_instances.defaultIntegrationApiOver(this.ctx.api),
23848
+ secrets: this.ctx.secrets ?? null,
23848
23849
  records,
23849
23850
  warn: (message) => this.ctx.logger.warn(message)
23850
23851
  })));
@@ -1,4 +1,4 @@
1
- import { $ as errMsg, A as switchCapability, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, P as customAction, Q as DeviceType, R as entryBody, S as heatingValveCapability, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, et as EventCategory, f as batteryCapability, h as connectionTestCapability, i as testBoschInstance, j as temperatureSensorCapability, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-BIvOtuzj.mjs";
1
+ import { $ as errMsg, A as switchCapability, F as defineAddonMigrations, G as number, H as array, I as defineCustomActions, J as string, K as object, L as defineEntryKind, P as customAction, Q as DeviceType, R as entryBody, S as heatingValveCapability, V as _enum, W as literal, _ as defaultIntegrationApiOver, a as BaseDevice, et as EventCategory, f as batteryCapability, h as connectionTestCapability, i as testBoschInstance, j as temperatureSensorCapability, l as RecordIntegrationInstances, m as climateControlCapability, n as boschRowsPorts, o as BaseDeviceProvider, r as keepBodyKeys, t as boschRowsMigration, v as deviceAdoptionCapability, w as integrationInstancesCapability } from "../bosch-instances-BQiyzlPt.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
@@ -23846,6 +23846,7 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23846
23846
  await records.declare(shcRecordKinds);
23847
23847
  await this.runMigrations(defineAddonMigrations(SHC_MIGRATIONS, shcRowsPorts({
23848
23848
  api: defaultIntegrationApiOver(this.ctx.api),
23849
+ secrets: this.ctx.secrets ?? null,
23849
23850
  records,
23850
23851
  warn: (message) => this.ctx.logger.warn(message)
23851
23852
  })));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Bosch smart-home device-provider addons for CamStack — SHC, Home Connect, and HomeCom cloud integrations (scaffold)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -141,7 +141,7 @@
141
141
  }
142
142
  ],
143
143
  "requires": {
144
- "server": ">=1.2.765"
144
+ "server": ">=1.2.770"
145
145
  }
146
146
  },
147
147
  "files": [