@camstack/addon-provider-enpal 0.1.8 → 0.1.9

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.9] - 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.8] - 2026-11-04
11
17
 
12
18
  ### Changed
package/dist/addon.js CHANGED
@@ -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
  }
@@ -11934,7 +11933,7 @@ object({
11934
11933
  params: record(string(), union([string(), number()])).optional()
11935
11934
  });
11936
11935
  //#endregion
11937
- //#region ../types/dist/composition-CRNwcFYY.mjs
11936
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11938
11937
  /**
11939
11938
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11940
11939
  *
@@ -14642,20 +14641,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14642
14641
  };
14643
14642
  }
14644
14643
  /**
14645
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14646
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14647
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14648
- * the new record through the addon's own records handle. The hub deletes the rows and their
14649
- * keychain values; the addon never touches the registry.
14650
- */
14644
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14645
+ * from its own process, so the leftover rows are reached through the migration-only door
14646
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14647
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14648
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14649
+ * door to delete the rows. The addon's own record kind is its records handle.
14650
+ */
14651
+ async function resolvedRows(stored, secrets) {
14652
+ if (secrets === null) return stored;
14653
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14654
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14655
+ return value;
14656
+ }
14651
14657
  function integrationRowsPortsOverApi(deps) {
14658
+ const secrets = deps.secrets ?? null;
14652
14659
  return {
14653
14660
  warn: (message) => deps.log.warn(message),
14654
14661
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14655
14662
  id: i.id,
14656
14663
  name: i.name
14657
14664
  })),
14658
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14665
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14659
14666
  putRecord: async (id, body) => {
14660
14667
  if (await deps.records.get(id) !== null) return "exists";
14661
14668
  try {
@@ -14674,7 +14681,9 @@ function integrationRowsPortsOverApi(deps) {
14674
14681
  await deps.records.delete(id);
14675
14682
  },
14676
14683
  clearRows: async (integrationId) => {
14677
- await deps.api.clearSettings({ id: integrationId });
14684
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14685
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14686
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14678
14687
  }
14679
14688
  };
14680
14689
  }
@@ -14742,6 +14751,7 @@ function integrationRowsMigration(spec) {
14742
14751
  function integrationRowsPortsOver(deps) {
14743
14752
  return integrationRowsPortsOverApi({
14744
14753
  api: deps.api,
14754
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14745
14755
  records: deps.records.kind({
14746
14756
  name: deps.kind.name,
14747
14757
  declaration: deps.kind.declaration,
@@ -14753,8 +14763,8 @@ function integrationRowsPortsOver(deps) {
14753
14763
  function defaultIntegrationApiOver(api) {
14754
14764
  return {
14755
14765
  list: () => api.integrations.list.query(),
14756
- getSettings: (input) => api.integrations.getSettings.query(input),
14757
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14766
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14767
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14758
14768
  listDevices: (addonId) => api.deviceManager.listAll.query({
14759
14769
  addonId,
14760
14770
  projection: "slim"
@@ -50432,6 +50442,9 @@ var RunningProposalEvidenceSchema = object({
50432
50442
  runsFound: number().int().nonnegative(),
50433
50443
  pausesFound: number().int().nonnegative(),
50434
50444
  longestPauseMs: number().nonnegative(),
50445
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50446
+ pulsesFound: number().int().nonnegative(),
50447
+ longestPulseMs: number().nonnegative(),
50435
50448
  idleFloorW: number(),
50436
50449
  noiseCeilingW: number(),
50437
50450
  plateauW: number(),
@@ -56298,11 +56311,7 @@ var CreateIntegrationInputSchema = object({
56298
56311
  info: record(string(), unknown()).optional(),
56299
56312
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56300
56313
  originStableId: string().optional(),
56301
- settings: record(string(), unknown()).optional(),
56302
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56303
- * the keychain, not the table (D787). */
56304
- secretKeys: array(string()).optional(),
56305
- skipRestart: boolean().optional()
56314
+ settings: record(string(), unknown()).optional()
56306
56315
  });
56307
56316
  var UpdateIntegrationInputSchema = object({
56308
56317
  id: string(),
@@ -56323,22 +56332,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56323
56332
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56324
56333
  kind: "mutation",
56325
56334
  auth: "admin"
56326
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56327
- id: string(),
56328
- settings: record(string(), unknown()),
56329
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56330
- secretKeys: array(string()).optional(),
56331
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56332
- skipRestart: boolean().optional()
56333
- }), object({ success: boolean() }), {
56334
- kind: "mutation",
56335
- auth: "admin"
56336
- }), method(object({ id: string() }), object({
56337
- rows: number().int().nonnegative(),
56338
- secrets: number().int().nonnegative()
56339
- }), {
56340
- kind: "mutation",
56341
- auth: "admin"
56342
56335
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56343
56336
  addonId: string(),
56344
56337
  settings: record(string(), unknown())
@@ -59315,6 +59308,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59315
59308
  }), {
59316
59309
  kind: "mutation",
59317
59310
  auth: "admin"
59311
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59312
+ rows: number().int().nonnegative(),
59313
+ secrets: number().int().nonnegative()
59314
+ }), {
59315
+ kind: "mutation",
59316
+ auth: "admin"
59317
+ }), method(object({
59318
+ integrationId: string().min(1),
59319
+ patch: record(string(), unknown())
59320
+ }), object({ written: boolean() }), {
59321
+ kind: "mutation",
59322
+ auth: "admin"
59323
+ }), method(object({
59324
+ integrationId: string().min(1),
59325
+ keys: array(string().min(1))
59326
+ }), object({
59327
+ present: boolean(),
59328
+ removed: record(string(), unknown())
59329
+ }), {
59330
+ kind: "mutation",
59331
+ auth: "admin"
59318
59332
  });
59319
59333
  method(object({
59320
59334
  sourceUrl: string(),
@@ -80733,11 +80747,15 @@ discriminatedUnion("kind", [object({
80733
80747
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80734
80748
  * near F. A device that is not near one level most of the time has no floor.
80735
80749
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80736
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80737
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80750
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80751
+ * display waking): it is neither a run nor a pause. Among the remaining
80752
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80753
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80754
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80755
+ * is no run.
80738
80756
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80739
- * grid a person would type; stop dwell covers the longest pause; start dwell
80740
- * covers the longest standby blip above T.
80757
+ * grid a person would type; stop dwell covers the longest pause and the
80758
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80741
80759
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80742
80760
  * run must hold exactly one true interval and nothing may flap outside.
80743
80761
  */
@@ -80748,6 +80766,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80748
80766
  30 * SECOND_MS;
80749
80767
  60 * SECOND_MS;
80750
80768
  20 * MINUTE_MS$1;
80769
+ 90 * SECOND_MS;
80770
+ 30 * MINUTE_MS$1;
80771
+ 10 * MINUTE_MS$1;
80772
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80751
80773
  10 * SECOND_MS;
80752
80774
  10 * SECOND_MS;
80753
80775
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -86032,13 +86054,6 @@ Object.freeze({
86032
86054
  access: "create",
86033
86055
  role: "admin"
86034
86056
  },
86035
- "integrations.clearSettings": {
86036
- capName: "integrations",
86037
- capScope: "system",
86038
- addonId: null,
86039
- access: "delete",
86040
- role: "admin"
86041
- },
86042
86057
  "integrations.create": {
86043
86058
  capName: "integrations",
86044
86059
  capScope: "system",
@@ -86074,13 +86089,6 @@ Object.freeze({
86074
86089
  access: "view",
86075
86090
  role: "admin"
86076
86091
  },
86077
- "integrations.getSettings": {
86078
- capName: "integrations",
86079
- capScope: "system",
86080
- addonId: null,
86081
- access: "view",
86082
- role: "admin"
86083
- },
86084
86092
  "integrations.list": {
86085
86093
  capName: "integrations",
86086
86094
  capScope: "system",
@@ -86088,13 +86096,6 @@ Object.freeze({
86088
86096
  access: "view",
86089
86097
  role: "admin"
86090
86098
  },
86091
- "integrations.setSettings": {
86092
- capName: "integrations",
86093
- capScope: "system",
86094
- addonId: null,
86095
- access: "create",
86096
- role: "admin"
86097
- },
86098
86099
  "integrations.testConnection": {
86099
86100
  capName: "integrations",
86100
86101
  capScope: "system",
@@ -87089,6 +87090,13 @@ Object.freeze({
87089
87090
  access: "view",
87090
87091
  role: "admin"
87091
87092
  },
87093
+ "migrations.clearLegacyIntegrationRows": {
87094
+ capName: "migrations",
87095
+ capScope: "system",
87096
+ addonId: null,
87097
+ access: "delete",
87098
+ role: "admin"
87099
+ },
87092
87100
  "migrations.declareOwner": {
87093
87101
  capName: "migrations",
87094
87102
  capScope: "system",
@@ -87096,6 +87104,13 @@ Object.freeze({
87096
87104
  access: "create",
87097
87105
  role: "admin"
87098
87106
  },
87107
+ "migrations.deleteLegacyIntegrationRowKeys": {
87108
+ capName: "migrations",
87109
+ capScope: "system",
87110
+ addonId: null,
87111
+ access: "delete",
87112
+ role: "admin"
87113
+ },
87099
87114
  "migrations.dryRun": {
87100
87115
  capName: "migrations",
87101
87116
  capScope: "system",
@@ -87131,6 +87146,20 @@ Object.freeze({
87131
87146
  access: "create",
87132
87147
  role: "admin"
87133
87148
  },
87149
+ "migrations.patchLegacyIntegrationRows": {
87150
+ capName: "migrations",
87151
+ capScope: "system",
87152
+ addonId: null,
87153
+ access: "create",
87154
+ role: "admin"
87155
+ },
87156
+ "migrations.readLegacyIntegrationRows": {
87157
+ capName: "migrations",
87158
+ capScope: "system",
87159
+ addonId: null,
87160
+ access: "view",
87161
+ role: "admin"
87162
+ },
87134
87163
  "migrations.restorePreMigration": {
87135
87164
  capName: "migrations",
87136
87165
  capScope: "system",
@@ -99232,6 +99261,7 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
99232
99261
  fixtureMark: "host"
99233
99262
  })], integrationRowsPortsOver({
99234
99263
  api: defaultIntegrationApiOver(this.ctx.api),
99264
+ secrets: this.ctx.secrets ?? null,
99235
99265
  records,
99236
99266
  kind: enpalConnectionsEntryKind,
99237
99267
  warn: (message) => this.ctx.logger.warn(message)
package/dist/addon.mjs CHANGED
@@ -5980,7 +5980,7 @@ var ZodIssueCode = {
5980
5980
  var ZodFirstPartyTypeKind;
5981
5981
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5982
5982
  //#endregion
5983
- //#region ../types/dist/sleep-D1jjWAr8.mjs
5983
+ //#region ../types/dist/sleep-9TIHFj1w.mjs
5984
5984
  /**
5985
5985
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5986
5986
  * window to float samples (D455).
@@ -7924,10 +7924,9 @@ var BaseAddon = class {
7924
7924
  *
7925
7925
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7926
7926
  * needs its connection fields, the default integration the addon's own sections. It is the
7927
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7928
- * unchanged (`integrations.setSettings` for an account integration).
7927
+ * SHAPE of the form only — the values live on the instance record.
7929
7928
  */
7930
- async integrationSettingsSchema(_instance) {
7929
+ async integrationFormSchema(_instance) {
7931
7930
  return null;
7932
7931
  }
7933
7932
  /**
@@ -7973,7 +7972,7 @@ var BaseAddon = class {
7973
7972
  * per-instance schema when it declares one, else its global schema (what they always showed).
7974
7973
  */
7975
7974
  async getIntegrationFormSchema(instance, nodeId) {
7976
- const scoped = await this.integrationSettingsSchema(instance);
7975
+ const scoped = await this.integrationFormSchema(instance);
7977
7976
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7978
7977
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7979
7978
  }
@@ -11933,7 +11932,7 @@ object({
11933
11932
  params: record(string(), union([string(), number()])).optional()
11934
11933
  });
11935
11934
  //#endregion
11936
- //#region ../types/dist/composition-CRNwcFYY.mjs
11935
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11937
11936
  /**
11938
11937
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11939
11938
  *
@@ -14641,20 +14640,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14641
14640
  };
14642
14641
  }
14643
14642
  /**
14644
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14645
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14646
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14647
- * the new record through the addon's own records handle. The hub deletes the rows and their
14648
- * keychain values; the addon never touches the registry.
14649
- */
14643
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14644
+ * from its own process, so the leftover rows are reached through the migration-only door
14645
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14646
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14647
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14648
+ * door to delete the rows. The addon's own record kind is its records handle.
14649
+ */
14650
+ async function resolvedRows(stored, secrets) {
14651
+ if (secrets === null) return stored;
14652
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14653
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14654
+ return value;
14655
+ }
14650
14656
  function integrationRowsPortsOverApi(deps) {
14657
+ const secrets = deps.secrets ?? null;
14651
14658
  return {
14652
14659
  warn: (message) => deps.log.warn(message),
14653
14660
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14654
14661
  id: i.id,
14655
14662
  name: i.name
14656
14663
  })),
14657
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14664
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14658
14665
  putRecord: async (id, body) => {
14659
14666
  if (await deps.records.get(id) !== null) return "exists";
14660
14667
  try {
@@ -14673,7 +14680,9 @@ function integrationRowsPortsOverApi(deps) {
14673
14680
  await deps.records.delete(id);
14674
14681
  },
14675
14682
  clearRows: async (integrationId) => {
14676
- await deps.api.clearSettings({ id: integrationId });
14683
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14684
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14685
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14677
14686
  }
14678
14687
  };
14679
14688
  }
@@ -14741,6 +14750,7 @@ function integrationRowsMigration(spec) {
14741
14750
  function integrationRowsPortsOver(deps) {
14742
14751
  return integrationRowsPortsOverApi({
14743
14752
  api: deps.api,
14753
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14744
14754
  records: deps.records.kind({
14745
14755
  name: deps.kind.name,
14746
14756
  declaration: deps.kind.declaration,
@@ -14752,8 +14762,8 @@ function integrationRowsPortsOver(deps) {
14752
14762
  function defaultIntegrationApiOver(api) {
14753
14763
  return {
14754
14764
  list: () => api.integrations.list.query(),
14755
- getSettings: (input) => api.integrations.getSettings.query(input),
14756
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14765
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14766
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14757
14767
  listDevices: (addonId) => api.deviceManager.listAll.query({
14758
14768
  addonId,
14759
14769
  projection: "slim"
@@ -50431,6 +50441,9 @@ var RunningProposalEvidenceSchema = object({
50431
50441
  runsFound: number().int().nonnegative(),
50432
50442
  pausesFound: number().int().nonnegative(),
50433
50443
  longestPauseMs: number().nonnegative(),
50444
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50445
+ pulsesFound: number().int().nonnegative(),
50446
+ longestPulseMs: number().nonnegative(),
50434
50447
  idleFloorW: number(),
50435
50448
  noiseCeilingW: number(),
50436
50449
  plateauW: number(),
@@ -56297,11 +56310,7 @@ var CreateIntegrationInputSchema = object({
56297
56310
  info: record(string(), unknown()).optional(),
56298
56311
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56299
56312
  originStableId: string().optional(),
56300
- settings: record(string(), unknown()).optional(),
56301
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56302
- * the keychain, not the table (D787). */
56303
- secretKeys: array(string()).optional(),
56304
- skipRestart: boolean().optional()
56313
+ settings: record(string(), unknown()).optional()
56305
56314
  });
56306
56315
  var UpdateIntegrationInputSchema = object({
56307
56316
  id: string(),
@@ -56322,22 +56331,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56322
56331
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56323
56332
  kind: "mutation",
56324
56333
  auth: "admin"
56325
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56326
- id: string(),
56327
- settings: record(string(), unknown()),
56328
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56329
- secretKeys: array(string()).optional(),
56330
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56331
- skipRestart: boolean().optional()
56332
- }), object({ success: boolean() }), {
56333
- kind: "mutation",
56334
- auth: "admin"
56335
- }), method(object({ id: string() }), object({
56336
- rows: number().int().nonnegative(),
56337
- secrets: number().int().nonnegative()
56338
- }), {
56339
- kind: "mutation",
56340
- auth: "admin"
56341
56334
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56342
56335
  addonId: string(),
56343
56336
  settings: record(string(), unknown())
@@ -59314,6 +59307,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59314
59307
  }), {
59315
59308
  kind: "mutation",
59316
59309
  auth: "admin"
59310
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59311
+ rows: number().int().nonnegative(),
59312
+ secrets: number().int().nonnegative()
59313
+ }), {
59314
+ kind: "mutation",
59315
+ auth: "admin"
59316
+ }), method(object({
59317
+ integrationId: string().min(1),
59318
+ patch: record(string(), unknown())
59319
+ }), object({ written: boolean() }), {
59320
+ kind: "mutation",
59321
+ auth: "admin"
59322
+ }), method(object({
59323
+ integrationId: string().min(1),
59324
+ keys: array(string().min(1))
59325
+ }), object({
59326
+ present: boolean(),
59327
+ removed: record(string(), unknown())
59328
+ }), {
59329
+ kind: "mutation",
59330
+ auth: "admin"
59317
59331
  });
59318
59332
  method(object({
59319
59333
  sourceUrl: string(),
@@ -80732,11 +80746,15 @@ discriminatedUnion("kind", [object({
80732
80746
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80733
80747
  * near F. A device that is not near one level most of the time has no floor.
80734
80748
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80735
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80736
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80749
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80750
+ * display waking): it is neither a run nor a pause. Among the remaining
80751
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80752
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80753
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80754
+ * is no run.
80737
80755
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80738
- * grid a person would type; stop dwell covers the longest pause; start dwell
80739
- * covers the longest standby blip above T.
80756
+ * grid a person would type; stop dwell covers the longest pause and the
80757
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80740
80758
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80741
80759
  * run must hold exactly one true interval and nothing may flap outside.
80742
80760
  */
@@ -80747,6 +80765,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80747
80765
  30 * SECOND_MS;
80748
80766
  60 * SECOND_MS;
80749
80767
  20 * MINUTE_MS$1;
80768
+ 90 * SECOND_MS;
80769
+ 30 * MINUTE_MS$1;
80770
+ 10 * MINUTE_MS$1;
80771
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80750
80772
  10 * SECOND_MS;
80751
80773
  10 * SECOND_MS;
80752
80774
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -86031,13 +86053,6 @@ Object.freeze({
86031
86053
  access: "create",
86032
86054
  role: "admin"
86033
86055
  },
86034
- "integrations.clearSettings": {
86035
- capName: "integrations",
86036
- capScope: "system",
86037
- addonId: null,
86038
- access: "delete",
86039
- role: "admin"
86040
- },
86041
86056
  "integrations.create": {
86042
86057
  capName: "integrations",
86043
86058
  capScope: "system",
@@ -86073,13 +86088,6 @@ Object.freeze({
86073
86088
  access: "view",
86074
86089
  role: "admin"
86075
86090
  },
86076
- "integrations.getSettings": {
86077
- capName: "integrations",
86078
- capScope: "system",
86079
- addonId: null,
86080
- access: "view",
86081
- role: "admin"
86082
- },
86083
86091
  "integrations.list": {
86084
86092
  capName: "integrations",
86085
86093
  capScope: "system",
@@ -86087,13 +86095,6 @@ Object.freeze({
86087
86095
  access: "view",
86088
86096
  role: "admin"
86089
86097
  },
86090
- "integrations.setSettings": {
86091
- capName: "integrations",
86092
- capScope: "system",
86093
- addonId: null,
86094
- access: "create",
86095
- role: "admin"
86096
- },
86097
86098
  "integrations.testConnection": {
86098
86099
  capName: "integrations",
86099
86100
  capScope: "system",
@@ -87088,6 +87089,13 @@ Object.freeze({
87088
87089
  access: "view",
87089
87090
  role: "admin"
87090
87091
  },
87092
+ "migrations.clearLegacyIntegrationRows": {
87093
+ capName: "migrations",
87094
+ capScope: "system",
87095
+ addonId: null,
87096
+ access: "delete",
87097
+ role: "admin"
87098
+ },
87091
87099
  "migrations.declareOwner": {
87092
87100
  capName: "migrations",
87093
87101
  capScope: "system",
@@ -87095,6 +87103,13 @@ Object.freeze({
87095
87103
  access: "create",
87096
87104
  role: "admin"
87097
87105
  },
87106
+ "migrations.deleteLegacyIntegrationRowKeys": {
87107
+ capName: "migrations",
87108
+ capScope: "system",
87109
+ addonId: null,
87110
+ access: "delete",
87111
+ role: "admin"
87112
+ },
87098
87113
  "migrations.dryRun": {
87099
87114
  capName: "migrations",
87100
87115
  capScope: "system",
@@ -87130,6 +87145,20 @@ Object.freeze({
87130
87145
  access: "create",
87131
87146
  role: "admin"
87132
87147
  },
87148
+ "migrations.patchLegacyIntegrationRows": {
87149
+ capName: "migrations",
87150
+ capScope: "system",
87151
+ addonId: null,
87152
+ access: "create",
87153
+ role: "admin"
87154
+ },
87155
+ "migrations.readLegacyIntegrationRows": {
87156
+ capName: "migrations",
87157
+ capScope: "system",
87158
+ addonId: null,
87159
+ access: "view",
87160
+ role: "admin"
87161
+ },
87133
87162
  "migrations.restorePreMigration": {
87134
87163
  capName: "migrations",
87135
87164
  capScope: "system",
@@ -99231,6 +99260,7 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
99231
99260
  fixtureMark: "host"
99232
99261
  })], integrationRowsPortsOver({
99233
99262
  api: defaultIntegrationApiOver(this.ctx.api),
99263
+ secrets: this.ctx.secrets ?? null,
99234
99264
  records,
99235
99265
  kind: enpalConnectionsEntryKind,
99236
99266
  warn: (message) => this.ctx.logger.warn(message)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-enpal",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Enpal solar-box device-provider addon for CamStack — PV, battery, grid, house and heat-pump readings from the box's local InfluxDB (device page as fallback)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -63,7 +63,7 @@
63
63
  }
64
64
  ],
65
65
  "requires": {
66
- "server": ">=1.2.765"
66
+ "server": ">=1.2.770"
67
67
  }
68
68
  },
69
69
  "files": [