@camstack/addon-provider-rtsp 1.2.150 → 1.2.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.151] - 2026-11-05
11
+
12
+ ### Changed
13
+
14
+ - 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
  ## [1.2.150] - 2026-11-04
11
17
 
12
18
  ### Changed
package/dist/addon.js CHANGED
@@ -6005,7 +6005,7 @@ var ZodIssueCode = {
6005
6005
  var ZodFirstPartyTypeKind;
6006
6006
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6007
6007
  //#endregion
6008
- //#region ../types/dist/sleep-Cv4Zmie9.mjs
6008
+ //#region ../types/dist/sleep-9TIHFj1w.mjs
6009
6009
  /**
6010
6010
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6011
6011
  * window to float samples (D455).
@@ -7949,10 +7949,9 @@ var BaseAddon = class {
7949
7949
  *
7950
7950
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7951
7951
  * needs its connection fields, the default integration the addon's own sections. It is the
7952
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7953
- * unchanged (`integrations.setSettings` for an account integration).
7952
+ * SHAPE of the form only — the values live on the instance record.
7954
7953
  */
7955
- async integrationSettingsSchema(_instance) {
7954
+ async integrationFormSchema(_instance) {
7956
7955
  return null;
7957
7956
  }
7958
7957
  /**
@@ -7998,7 +7997,7 @@ var BaseAddon = class {
7998
7997
  * per-instance schema when it declares one, else its global schema (what they always showed).
7999
7998
  */
8000
7999
  async getIntegrationFormSchema(instance, nodeId) {
8001
- const scoped = await this.integrationSettingsSchema(instance);
8000
+ const scoped = await this.integrationFormSchema(instance);
8002
8001
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8003
8002
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8004
8003
  }
@@ -9288,6 +9287,7 @@ function defineEntryKind(name, spec) {
9288
9287
  class: "config",
9289
9288
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9290
9289
  retention: { none: "an entry goes only when the operator deletes it" },
9290
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9291
9291
  ...spec.database === void 0 ? {} : { database: spec.database },
9292
9292
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9293
9293
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -12046,7 +12046,7 @@ function maskUrlCredentials(rawUrl) {
12046
12046
  }
12047
12047
  }
12048
12048
  //#endregion
12049
- //#region ../types/dist/composition-CRNwcFYY.mjs
12049
+ //#region ../types/dist/composition-pOHbTzRB.mjs
12050
12050
  /**
12051
12051
  * The ffmpeg build every CamStack node downloads, per platform and arch.
12052
12052
  *
@@ -14754,20 +14754,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14754
14754
  };
14755
14755
  }
14756
14756
  /**
14757
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14758
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14759
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14760
- * the new record through the addon's own records handle. The hub deletes the rows and their
14761
- * keychain values; the addon never touches the registry.
14762
- */
14757
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14758
+ * from its own process, so the leftover rows are reached through the migration-only door
14759
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14760
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14761
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14762
+ * door to delete the rows. The addon's own record kind is its records handle.
14763
+ */
14764
+ async function resolvedRows(stored, secrets) {
14765
+ if (secrets === null) return stored;
14766
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14767
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14768
+ return value;
14769
+ }
14763
14770
  function integrationRowsPortsOverApi(deps) {
14771
+ const secrets = deps.secrets ?? null;
14764
14772
  return {
14765
14773
  warn: (message) => deps.log.warn(message),
14766
14774
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14767
14775
  id: i.id,
14768
14776
  name: i.name
14769
14777
  })),
14770
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14778
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14771
14779
  putRecord: async (id, body) => {
14772
14780
  if (await deps.records.get(id) !== null) return "exists";
14773
14781
  try {
@@ -14786,7 +14794,9 @@ function integrationRowsPortsOverApi(deps) {
14786
14794
  await deps.records.delete(id);
14787
14795
  },
14788
14796
  clearRows: async (integrationId) => {
14789
- await deps.api.clearSettings({ id: integrationId });
14797
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14798
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14799
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14790
14800
  }
14791
14801
  };
14792
14802
  }
@@ -14876,14 +14886,14 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14876
14886
  * 2. `claim-devices` (tolerate): `claimDevicesToDefaultIntegration`; with no integration to claim
14877
14887
  * to it fails and is retried on the next boot.
14878
14888
  *
14879
- * Both run in the addon process through the hub API (`ctx.api`), BEFORE the addon registers its
14880
- * `integration-instances` provider: while a provider is registered the hub answers `getSettings`
14881
- * from the record, not from the rows.
14889
+ * Both run in the addon process through the hub API (`ctx.api`). The leftover rows are read
14890
+ * through the migration-only door, which answers empty once the store is gone.
14882
14891
  */
14883
14892
  function defaultIntegrationPortsOver(deps) {
14884
14893
  return {
14885
14894
  ...integrationRowsPortsOverApi({
14886
14895
  api: deps.api,
14896
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14887
14897
  records: deps.records.kind({
14888
14898
  name: deps.kind.name,
14889
14899
  declaration: deps.kind.declaration,
@@ -14983,8 +14993,8 @@ function defaultIntegrationMigrations(spec) {
14983
14993
  function defaultIntegrationApiOver(api) {
14984
14994
  return {
14985
14995
  list: () => api.integrations.list.query(),
14986
- getSettings: (input) => api.integrations.getSettings.query(input),
14987
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14996
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14997
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14988
14998
  listDevices: (addonId) => api.deviceManager.listAll.query({
14989
14999
  addonId,
14990
15000
  projection: "slim"
@@ -50623,6 +50633,9 @@ var RunningProposalEvidenceSchema = object({
50623
50633
  runsFound: number().int().nonnegative(),
50624
50634
  pausesFound: number().int().nonnegative(),
50625
50635
  longestPauseMs: number().nonnegative(),
50636
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50637
+ pulsesFound: number().int().nonnegative(),
50638
+ longestPulseMs: number().nonnegative(),
50626
50639
  idleFloorW: number(),
50627
50640
  noiseCeilingW: number(),
50628
50641
  plateauW: number(),
@@ -56451,11 +56464,7 @@ var CreateIntegrationInputSchema = object({
56451
56464
  info: record(string(), unknown()).optional(),
56452
56465
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56453
56466
  originStableId: string().optional(),
56454
- settings: record(string(), unknown()).optional(),
56455
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56456
- * the keychain, not the table (D787). */
56457
- secretKeys: array(string()).optional(),
56458
- skipRestart: boolean().optional()
56467
+ settings: record(string(), unknown()).optional()
56459
56468
  });
56460
56469
  var UpdateIntegrationInputSchema = object({
56461
56470
  id: string(),
@@ -56476,22 +56485,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56476
56485
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56477
56486
  kind: "mutation",
56478
56487
  auth: "admin"
56479
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56480
- id: string(),
56481
- settings: record(string(), unknown()),
56482
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56483
- secretKeys: array(string()).optional(),
56484
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56485
- skipRestart: boolean().optional()
56486
- }), object({ success: boolean() }), {
56487
- kind: "mutation",
56488
- auth: "admin"
56489
- }), method(object({ id: string() }), object({
56490
- rows: number().int().nonnegative(),
56491
- secrets: number().int().nonnegative()
56492
- }), {
56493
- kind: "mutation",
56494
- auth: "admin"
56495
56488
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56496
56489
  addonId: string(),
56497
56490
  settings: record(string(), unknown())
@@ -59468,6 +59461,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59468
59461
  }), {
59469
59462
  kind: "mutation",
59470
59463
  auth: "admin"
59464
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59465
+ rows: number().int().nonnegative(),
59466
+ secrets: number().int().nonnegative()
59467
+ }), {
59468
+ kind: "mutation",
59469
+ auth: "admin"
59470
+ }), method(object({
59471
+ integrationId: string().min(1),
59472
+ patch: record(string(), unknown())
59473
+ }), object({ written: boolean() }), {
59474
+ kind: "mutation",
59475
+ auth: "admin"
59476
+ }), method(object({
59477
+ integrationId: string().min(1),
59478
+ keys: array(string().min(1))
59479
+ }), object({
59480
+ present: boolean(),
59481
+ removed: record(string(), unknown())
59482
+ }), {
59483
+ kind: "mutation",
59484
+ auth: "admin"
59471
59485
  });
59472
59486
  method(object({
59473
59487
  sourceUrl: string(),
@@ -80898,11 +80912,15 @@ discriminatedUnion("kind", [object({
80898
80912
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80899
80913
  * near F. A device that is not near one level most of the time has no floor.
80900
80914
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80901
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80902
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80915
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80916
+ * display waking): it is neither a run nor a pause. Among the remaining
80917
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80918
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80919
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80920
+ * is no run.
80903
80921
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80904
- * grid a person would type; stop dwell covers the longest pause; start dwell
80905
- * covers the longest standby blip above T.
80922
+ * grid a person would type; stop dwell covers the longest pause and the
80923
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80906
80924
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80907
80925
  * run must hold exactly one true interval and nothing may flap outside.
80908
80926
  */
@@ -80913,6 +80931,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80913
80931
  30 * SECOND_MS;
80914
80932
  60 * SECOND_MS;
80915
80933
  20 * MINUTE_MS$1;
80934
+ 90 * SECOND_MS;
80935
+ 30 * MINUTE_MS$1;
80936
+ 10 * MINUTE_MS$1;
80937
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80916
80938
  10 * SECOND_MS;
80917
80939
  10 * SECOND_MS;
80918
80940
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -86197,13 +86219,6 @@ Object.freeze({
86197
86219
  access: "create",
86198
86220
  role: "admin"
86199
86221
  },
86200
- "integrations.clearSettings": {
86201
- capName: "integrations",
86202
- capScope: "system",
86203
- addonId: null,
86204
- access: "delete",
86205
- role: "admin"
86206
- },
86207
86222
  "integrations.create": {
86208
86223
  capName: "integrations",
86209
86224
  capScope: "system",
@@ -86239,13 +86254,6 @@ Object.freeze({
86239
86254
  access: "view",
86240
86255
  role: "admin"
86241
86256
  },
86242
- "integrations.getSettings": {
86243
- capName: "integrations",
86244
- capScope: "system",
86245
- addonId: null,
86246
- access: "view",
86247
- role: "admin"
86248
- },
86249
86257
  "integrations.list": {
86250
86258
  capName: "integrations",
86251
86259
  capScope: "system",
@@ -86253,13 +86261,6 @@ Object.freeze({
86253
86261
  access: "view",
86254
86262
  role: "admin"
86255
86263
  },
86256
- "integrations.setSettings": {
86257
- capName: "integrations",
86258
- capScope: "system",
86259
- addonId: null,
86260
- access: "create",
86261
- role: "admin"
86262
- },
86263
86264
  "integrations.testConnection": {
86264
86265
  capName: "integrations",
86265
86266
  capScope: "system",
@@ -87254,6 +87255,13 @@ Object.freeze({
87254
87255
  access: "view",
87255
87256
  role: "admin"
87256
87257
  },
87258
+ "migrations.clearLegacyIntegrationRows": {
87259
+ capName: "migrations",
87260
+ capScope: "system",
87261
+ addonId: null,
87262
+ access: "delete",
87263
+ role: "admin"
87264
+ },
87257
87265
  "migrations.declareOwner": {
87258
87266
  capName: "migrations",
87259
87267
  capScope: "system",
@@ -87261,6 +87269,13 @@ Object.freeze({
87261
87269
  access: "create",
87262
87270
  role: "admin"
87263
87271
  },
87272
+ "migrations.deleteLegacyIntegrationRowKeys": {
87273
+ capName: "migrations",
87274
+ capScope: "system",
87275
+ addonId: null,
87276
+ access: "delete",
87277
+ role: "admin"
87278
+ },
87264
87279
  "migrations.dryRun": {
87265
87280
  capName: "migrations",
87266
87281
  capScope: "system",
@@ -87296,6 +87311,20 @@ Object.freeze({
87296
87311
  access: "create",
87297
87312
  role: "admin"
87298
87313
  },
87314
+ "migrations.patchLegacyIntegrationRows": {
87315
+ capName: "migrations",
87316
+ capScope: "system",
87317
+ addonId: null,
87318
+ access: "create",
87319
+ role: "admin"
87320
+ },
87321
+ "migrations.readLegacyIntegrationRows": {
87322
+ capName: "migrations",
87323
+ capScope: "system",
87324
+ addonId: null,
87325
+ access: "view",
87326
+ role: "admin"
87327
+ },
87299
87328
  "migrations.restorePreMigration": {
87300
87329
  capName: "migrations",
87301
87330
  capScope: "system",
@@ -98215,6 +98244,7 @@ var RtspProviderAddon = class extends BaseDeviceProvider {
98215
98244
  date: "2026-11-04"
98216
98245
  }), defaultIntegrationPortsOver({
98217
98246
  api: defaultIntegrationApiOver(this.ctx.api),
98247
+ secrets: this.ctx.secrets ?? null,
98218
98248
  records,
98219
98249
  kind: rtspConnectionsEntryKind,
98220
98250
  info: (message) => this.ctx.logger.info(message),
package/dist/addon.mjs CHANGED
@@ -5981,7 +5981,7 @@ var ZodIssueCode = {
5981
5981
  var ZodFirstPartyTypeKind;
5982
5982
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5983
5983
  //#endregion
5984
- //#region ../types/dist/sleep-Cv4Zmie9.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
  }
@@ -9264,6 +9263,7 @@ function defineEntryKind(name, spec) {
9264
9263
  class: "config",
9265
9264
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9266
9265
  retention: { none: "an entry goes only when the operator deletes it" },
9266
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9267
9267
  ...spec.database === void 0 ? {} : { database: spec.database },
9268
9268
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9269
9269
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -12022,7 +12022,7 @@ function maskUrlCredentials(rawUrl) {
12022
12022
  }
12023
12023
  }
12024
12024
  //#endregion
12025
- //#region ../types/dist/composition-CRNwcFYY.mjs
12025
+ //#region ../types/dist/composition-pOHbTzRB.mjs
12026
12026
  /**
12027
12027
  * The ffmpeg build every CamStack node downloads, per platform and arch.
12028
12028
  *
@@ -14730,20 +14730,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14730
14730
  };
14731
14731
  }
14732
14732
  /**
14733
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14734
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14735
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14736
- * the new record through the addon's own records handle. The hub deletes the rows and their
14737
- * keychain values; the addon never touches the registry.
14738
- */
14733
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14734
+ * from its own process, so the leftover rows are reached through the migration-only door
14735
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14736
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14737
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14738
+ * door to delete the rows. The addon's own record kind is its records handle.
14739
+ */
14740
+ async function resolvedRows(stored, secrets) {
14741
+ if (secrets === null) return stored;
14742
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14743
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14744
+ return value;
14745
+ }
14739
14746
  function integrationRowsPortsOverApi(deps) {
14747
+ const secrets = deps.secrets ?? null;
14740
14748
  return {
14741
14749
  warn: (message) => deps.log.warn(message),
14742
14750
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14743
14751
  id: i.id,
14744
14752
  name: i.name
14745
14753
  })),
14746
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14754
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14747
14755
  putRecord: async (id, body) => {
14748
14756
  if (await deps.records.get(id) !== null) return "exists";
14749
14757
  try {
@@ -14762,7 +14770,9 @@ function integrationRowsPortsOverApi(deps) {
14762
14770
  await deps.records.delete(id);
14763
14771
  },
14764
14772
  clearRows: async (integrationId) => {
14765
- await deps.api.clearSettings({ id: integrationId });
14773
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14774
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14775
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14766
14776
  }
14767
14777
  };
14768
14778
  }
@@ -14852,14 +14862,14 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14852
14862
  * 2. `claim-devices` (tolerate): `claimDevicesToDefaultIntegration`; with no integration to claim
14853
14863
  * to it fails and is retried on the next boot.
14854
14864
  *
14855
- * Both run in the addon process through the hub API (`ctx.api`), BEFORE the addon registers its
14856
- * `integration-instances` provider: while a provider is registered the hub answers `getSettings`
14857
- * from the record, not from the rows.
14865
+ * Both run in the addon process through the hub API (`ctx.api`). The leftover rows are read
14866
+ * through the migration-only door, which answers empty once the store is gone.
14858
14867
  */
14859
14868
  function defaultIntegrationPortsOver(deps) {
14860
14869
  return {
14861
14870
  ...integrationRowsPortsOverApi({
14862
14871
  api: deps.api,
14872
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14863
14873
  records: deps.records.kind({
14864
14874
  name: deps.kind.name,
14865
14875
  declaration: deps.kind.declaration,
@@ -14959,8 +14969,8 @@ function defaultIntegrationMigrations(spec) {
14959
14969
  function defaultIntegrationApiOver(api) {
14960
14970
  return {
14961
14971
  list: () => api.integrations.list.query(),
14962
- getSettings: (input) => api.integrations.getSettings.query(input),
14963
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14972
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14973
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14964
14974
  listDevices: (addonId) => api.deviceManager.listAll.query({
14965
14975
  addonId,
14966
14976
  projection: "slim"
@@ -50599,6 +50609,9 @@ var RunningProposalEvidenceSchema = object({
50599
50609
  runsFound: number().int().nonnegative(),
50600
50610
  pausesFound: number().int().nonnegative(),
50601
50611
  longestPauseMs: number().nonnegative(),
50612
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50613
+ pulsesFound: number().int().nonnegative(),
50614
+ longestPulseMs: number().nonnegative(),
50602
50615
  idleFloorW: number(),
50603
50616
  noiseCeilingW: number(),
50604
50617
  plateauW: number(),
@@ -56427,11 +56440,7 @@ var CreateIntegrationInputSchema = object({
56427
56440
  info: record(string(), unknown()).optional(),
56428
56441
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56429
56442
  originStableId: string().optional(),
56430
- settings: record(string(), unknown()).optional(),
56431
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56432
- * the keychain, not the table (D787). */
56433
- secretKeys: array(string()).optional(),
56434
- skipRestart: boolean().optional()
56443
+ settings: record(string(), unknown()).optional()
56435
56444
  });
56436
56445
  var UpdateIntegrationInputSchema = object({
56437
56446
  id: string(),
@@ -56452,22 +56461,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56452
56461
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56453
56462
  kind: "mutation",
56454
56463
  auth: "admin"
56455
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56456
- id: string(),
56457
- settings: record(string(), unknown()),
56458
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56459
- secretKeys: array(string()).optional(),
56460
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56461
- skipRestart: boolean().optional()
56462
- }), object({ success: boolean() }), {
56463
- kind: "mutation",
56464
- auth: "admin"
56465
- }), method(object({ id: string() }), object({
56466
- rows: number().int().nonnegative(),
56467
- secrets: number().int().nonnegative()
56468
- }), {
56469
- kind: "mutation",
56470
- auth: "admin"
56471
56464
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56472
56465
  addonId: string(),
56473
56466
  settings: record(string(), unknown())
@@ -59444,6 +59437,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59444
59437
  }), {
59445
59438
  kind: "mutation",
59446
59439
  auth: "admin"
59440
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59441
+ rows: number().int().nonnegative(),
59442
+ secrets: number().int().nonnegative()
59443
+ }), {
59444
+ kind: "mutation",
59445
+ auth: "admin"
59446
+ }), method(object({
59447
+ integrationId: string().min(1),
59448
+ patch: record(string(), unknown())
59449
+ }), object({ written: boolean() }), {
59450
+ kind: "mutation",
59451
+ auth: "admin"
59452
+ }), method(object({
59453
+ integrationId: string().min(1),
59454
+ keys: array(string().min(1))
59455
+ }), object({
59456
+ present: boolean(),
59457
+ removed: record(string(), unknown())
59458
+ }), {
59459
+ kind: "mutation",
59460
+ auth: "admin"
59447
59461
  });
59448
59462
  method(object({
59449
59463
  sourceUrl: string(),
@@ -80874,11 +80888,15 @@ discriminatedUnion("kind", [object({
80874
80888
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
80875
80889
  * near F. A device that is not near one level most of the time has no floor.
80876
80890
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
80877
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
80878
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
80891
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
80892
+ * display waking): it is neither a run nor a pause. Among the remaining
80893
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
80894
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
80895
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
80896
+ * is no run.
80879
80897
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
80880
- * grid a person would type; stop dwell covers the longest pause; start dwell
80881
- * covers the longest standby blip above T.
80898
+ * grid a person would type; stop dwell covers the longest pause and the
80899
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
80882
80900
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
80883
80901
  * run must hold exactly one true interval and nothing may flap outside.
80884
80902
  */
@@ -80889,6 +80907,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
80889
80907
  30 * SECOND_MS;
80890
80908
  60 * SECOND_MS;
80891
80909
  20 * MINUTE_MS$1;
80910
+ 90 * SECOND_MS;
80911
+ 30 * MINUTE_MS$1;
80912
+ 10 * MINUTE_MS$1;
80913
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
80892
80914
  10 * SECOND_MS;
80893
80915
  10 * SECOND_MS;
80894
80916
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
@@ -86173,13 +86195,6 @@ Object.freeze({
86173
86195
  access: "create",
86174
86196
  role: "admin"
86175
86197
  },
86176
- "integrations.clearSettings": {
86177
- capName: "integrations",
86178
- capScope: "system",
86179
- addonId: null,
86180
- access: "delete",
86181
- role: "admin"
86182
- },
86183
86198
  "integrations.create": {
86184
86199
  capName: "integrations",
86185
86200
  capScope: "system",
@@ -86215,13 +86230,6 @@ Object.freeze({
86215
86230
  access: "view",
86216
86231
  role: "admin"
86217
86232
  },
86218
- "integrations.getSettings": {
86219
- capName: "integrations",
86220
- capScope: "system",
86221
- addonId: null,
86222
- access: "view",
86223
- role: "admin"
86224
- },
86225
86233
  "integrations.list": {
86226
86234
  capName: "integrations",
86227
86235
  capScope: "system",
@@ -86229,13 +86237,6 @@ Object.freeze({
86229
86237
  access: "view",
86230
86238
  role: "admin"
86231
86239
  },
86232
- "integrations.setSettings": {
86233
- capName: "integrations",
86234
- capScope: "system",
86235
- addonId: null,
86236
- access: "create",
86237
- role: "admin"
86238
- },
86239
86240
  "integrations.testConnection": {
86240
86241
  capName: "integrations",
86241
86242
  capScope: "system",
@@ -87230,6 +87231,13 @@ Object.freeze({
87230
87231
  access: "view",
87231
87232
  role: "admin"
87232
87233
  },
87234
+ "migrations.clearLegacyIntegrationRows": {
87235
+ capName: "migrations",
87236
+ capScope: "system",
87237
+ addonId: null,
87238
+ access: "delete",
87239
+ role: "admin"
87240
+ },
87233
87241
  "migrations.declareOwner": {
87234
87242
  capName: "migrations",
87235
87243
  capScope: "system",
@@ -87237,6 +87245,13 @@ Object.freeze({
87237
87245
  access: "create",
87238
87246
  role: "admin"
87239
87247
  },
87248
+ "migrations.deleteLegacyIntegrationRowKeys": {
87249
+ capName: "migrations",
87250
+ capScope: "system",
87251
+ addonId: null,
87252
+ access: "delete",
87253
+ role: "admin"
87254
+ },
87240
87255
  "migrations.dryRun": {
87241
87256
  capName: "migrations",
87242
87257
  capScope: "system",
@@ -87272,6 +87287,20 @@ Object.freeze({
87272
87287
  access: "create",
87273
87288
  role: "admin"
87274
87289
  },
87290
+ "migrations.patchLegacyIntegrationRows": {
87291
+ capName: "migrations",
87292
+ capScope: "system",
87293
+ addonId: null,
87294
+ access: "create",
87295
+ role: "admin"
87296
+ },
87297
+ "migrations.readLegacyIntegrationRows": {
87298
+ capName: "migrations",
87299
+ capScope: "system",
87300
+ addonId: null,
87301
+ access: "view",
87302
+ role: "admin"
87303
+ },
87275
87304
  "migrations.restorePreMigration": {
87276
87305
  capName: "migrations",
87277
87306
  capScope: "system",
@@ -98191,6 +98220,7 @@ var RtspProviderAddon = class extends BaseDeviceProvider {
98191
98220
  date: "2026-11-04"
98192
98221
  }), defaultIntegrationPortsOver({
98193
98222
  api: defaultIntegrationApiOver(this.ctx.api),
98223
+ secrets: this.ctx.secrets ?? null,
98194
98224
  records,
98195
98225
  kind: rtspConnectionsEntryKind,
98196
98226
  info: (message) => this.ctx.logger.info(message),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.2.150",
3
+ "version": "1.2.151",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",
@@ -62,7 +62,7 @@
62
62
  }
63
63
  ],
64
64
  "requires": {
65
- "server": ">=1.2.764"
65
+ "server": ">=1.2.770"
66
66
  }
67
67
  },
68
68
  "files": [