@camstack/addon-provider-amcrest 0.2.165 → 0.2.166

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.166] - 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.2.165] - 2026-11-04
11
17
 
12
18
  ### Changed
package/dist/addon.js CHANGED
@@ -6015,7 +6015,7 @@ var ZodIssueCode = {
6015
6015
  var ZodFirstPartyTypeKind;
6016
6016
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6017
6017
  //#endregion
6018
- //#region ../types/dist/sleep-Cv4Zmie9.mjs
6018
+ //#region ../types/dist/sleep-9TIHFj1w.mjs
6019
6019
  /**
6020
6020
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6021
6021
  * window to float samples (D455).
@@ -7959,10 +7959,9 @@ var BaseAddon = class {
7959
7959
  *
7960
7960
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7961
7961
  * needs its connection fields, the default integration the addon's own sections. It is the
7962
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7963
- * unchanged (`integrations.setSettings` for an account integration).
7962
+ * SHAPE of the form only — the values live on the instance record.
7964
7963
  */
7965
- async integrationSettingsSchema(_instance) {
7964
+ async integrationFormSchema(_instance) {
7966
7965
  return null;
7967
7966
  }
7968
7967
  /**
@@ -8008,7 +8007,7 @@ var BaseAddon = class {
8008
8007
  * per-instance schema when it declares one, else its global schema (what they always showed).
8009
8008
  */
8010
8009
  async getIntegrationFormSchema(instance, nodeId) {
8011
- const scoped = await this.integrationSettingsSchema(instance);
8010
+ const scoped = await this.integrationFormSchema(instance);
8012
8011
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
8013
8012
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
8014
8013
  }
@@ -9298,6 +9297,7 @@ function defineEntryKind(name, spec) {
9298
9297
  class: "config",
9299
9298
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9300
9299
  retention: { none: "an entry goes only when the operator deletes it" },
9300
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9301
9301
  ...spec.database === void 0 ? {} : { database: spec.database },
9302
9302
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9303
9303
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -11981,7 +11981,7 @@ function userError(code, params, englishFallback) {
11981
11981
  return new CamstackError(code, params, englishFallback);
11982
11982
  }
11983
11983
  //#endregion
11984
- //#region ../types/dist/composition-CRNwcFYY.mjs
11984
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11985
11985
  /**
11986
11986
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11987
11987
  *
@@ -14709,20 +14709,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14709
14709
  };
14710
14710
  }
14711
14711
  /**
14712
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14713
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14714
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14715
- * the new record through the addon's own records handle. The hub deletes the rows and their
14716
- * keychain values; the addon never touches the registry.
14712
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14713
+ * from its own process, so the leftover rows are reached through the migration-only door
14714
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14715
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14716
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14717
+ * door to delete the rows. The addon's own record kind is its records handle.
14717
14718
  */
14719
+ async function resolvedRows(stored, secrets) {
14720
+ if (secrets === null) return stored;
14721
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14722
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14723
+ return value;
14724
+ }
14718
14725
  function integrationRowsPortsOverApi(deps) {
14726
+ const secrets = deps.secrets ?? null;
14719
14727
  return {
14720
14728
  warn: (message) => deps.log.warn(message),
14721
14729
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14722
14730
  id: i.id,
14723
14731
  name: i.name
14724
14732
  })),
14725
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14733
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14726
14734
  putRecord: async (id, body) => {
14727
14735
  if (await deps.records.get(id) !== null) return "exists";
14728
14736
  try {
@@ -14741,7 +14749,9 @@ function integrationRowsPortsOverApi(deps) {
14741
14749
  await deps.records.delete(id);
14742
14750
  },
14743
14751
  clearRows: async (integrationId) => {
14744
- await deps.api.clearSettings({ id: integrationId });
14752
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14753
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14754
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14745
14755
  }
14746
14756
  };
14747
14757
  }
@@ -14831,14 +14841,14 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14831
14841
  * 2. `claim-devices` (tolerate): `claimDevicesToDefaultIntegration`; with no integration to claim
14832
14842
  * to it fails and is retried on the next boot.
14833
14843
  *
14834
- * Both run in the addon process through the hub API (`ctx.api`), BEFORE the addon registers its
14835
- * `integration-instances` provider: while a provider is registered the hub answers `getSettings`
14836
- * from the record, not from the rows.
14844
+ * Both run in the addon process through the hub API (`ctx.api`). The leftover rows are read
14845
+ * through the migration-only door, which answers empty once the store is gone.
14837
14846
  */
14838
14847
  function defaultIntegrationPortsOver(deps) {
14839
14848
  return {
14840
14849
  ...integrationRowsPortsOverApi({
14841
14850
  api: deps.api,
14851
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14842
14852
  records: deps.records.kind({
14843
14853
  name: deps.kind.name,
14844
14854
  declaration: deps.kind.declaration,
@@ -14938,8 +14948,8 @@ function defaultIntegrationMigrations(spec) {
14938
14948
  function defaultIntegrationApiOver(api) {
14939
14949
  return {
14940
14950
  list: () => api.integrations.list.query(),
14941
- getSettings: (input) => api.integrations.getSettings.query(input),
14942
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14951
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14952
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14943
14953
  listDevices: (addonId) => api.deviceManager.listAll.query({
14944
14954
  addonId,
14945
14955
  projection: "slim"
@@ -50556,6 +50566,9 @@ var RunningProposalEvidenceSchema = object({
50556
50566
  runsFound: number().int().nonnegative(),
50557
50567
  pausesFound: number().int().nonnegative(),
50558
50568
  longestPauseMs: number().nonnegative(),
50569
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50570
+ pulsesFound: number().int().nonnegative(),
50571
+ longestPulseMs: number().nonnegative(),
50559
50572
  idleFloorW: number(),
50560
50573
  noiseCeilingW: number(),
50561
50574
  plateauW: number(),
@@ -56405,11 +56418,7 @@ var CreateIntegrationInputSchema = object({
56405
56418
  info: record(string(), unknown()).optional(),
56406
56419
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56407
56420
  originStableId: string().optional(),
56408
- settings: record(string(), unknown()).optional(),
56409
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56410
- * the keychain, not the table (D787). */
56411
- secretKeys: array(string()).optional(),
56412
- skipRestart: boolean().optional()
56421
+ settings: record(string(), unknown()).optional()
56413
56422
  });
56414
56423
  var UpdateIntegrationInputSchema = object({
56415
56424
  id: string(),
@@ -56430,22 +56439,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56430
56439
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56431
56440
  kind: "mutation",
56432
56441
  auth: "admin"
56433
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56434
- id: string(),
56435
- settings: record(string(), unknown()),
56436
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56437
- secretKeys: array(string()).optional(),
56438
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56439
- skipRestart: boolean().optional()
56440
- }), object({ success: boolean() }), {
56441
- kind: "mutation",
56442
- auth: "admin"
56443
- }), method(object({ id: string() }), object({
56444
- rows: number().int().nonnegative(),
56445
- secrets: number().int().nonnegative()
56446
- }), {
56447
- kind: "mutation",
56448
- auth: "admin"
56449
56442
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56450
56443
  addonId: string(),
56451
56444
  settings: record(string(), unknown())
@@ -59422,6 +59415,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59422
59415
  }), {
59423
59416
  kind: "mutation",
59424
59417
  auth: "admin"
59418
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59419
+ rows: number().int().nonnegative(),
59420
+ secrets: number().int().nonnegative()
59421
+ }), {
59422
+ kind: "mutation",
59423
+ auth: "admin"
59424
+ }), method(object({
59425
+ integrationId: string().min(1),
59426
+ patch: record(string(), unknown())
59427
+ }), object({ written: boolean() }), {
59428
+ kind: "mutation",
59429
+ auth: "admin"
59430
+ }), method(object({
59431
+ integrationId: string().min(1),
59432
+ keys: array(string().min(1))
59433
+ }), object({
59434
+ present: boolean(),
59435
+ removed: record(string(), unknown())
59436
+ }), {
59437
+ kind: "mutation",
59438
+ auth: "admin"
59425
59439
  });
59426
59440
  method(object({
59427
59441
  sourceUrl: string(),
@@ -81815,11 +81829,15 @@ discriminatedUnion("kind", [object({
81815
81829
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
81816
81830
  * near F. A device that is not near one level most of the time has no floor.
81817
81831
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
81818
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
81819
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
81832
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
81833
+ * display waking): it is neither a run nor a pause. Among the remaining
81834
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
81835
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
81836
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
81837
+ * is no run.
81820
81838
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
81821
- * grid a person would type; stop dwell covers the longest pause; start dwell
81822
- * covers the longest standby blip above T.
81839
+ * grid a person would type; stop dwell covers the longest pause and the
81840
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
81823
81841
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
81824
81842
  * run must hold exactly one true interval and nothing may flap outside.
81825
81843
  */
@@ -81830,6 +81848,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
81830
81848
  30 * SECOND_MS;
81831
81849
  60 * SECOND_MS;
81832
81850
  20 * MINUTE_MS$1;
81851
+ 90 * SECOND_MS;
81852
+ 30 * MINUTE_MS$1;
81853
+ 10 * MINUTE_MS$1;
81854
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
81833
81855
  10 * SECOND_MS;
81834
81856
  10 * SECOND_MS;
81835
81857
  /**
@@ -87251,13 +87273,6 @@ Object.freeze({
87251
87273
  access: "create",
87252
87274
  role: "admin"
87253
87275
  },
87254
- "integrations.clearSettings": {
87255
- capName: "integrations",
87256
- capScope: "system",
87257
- addonId: null,
87258
- access: "delete",
87259
- role: "admin"
87260
- },
87261
87276
  "integrations.create": {
87262
87277
  capName: "integrations",
87263
87278
  capScope: "system",
@@ -87293,13 +87308,6 @@ Object.freeze({
87293
87308
  access: "view",
87294
87309
  role: "admin"
87295
87310
  },
87296
- "integrations.getSettings": {
87297
- capName: "integrations",
87298
- capScope: "system",
87299
- addonId: null,
87300
- access: "view",
87301
- role: "admin"
87302
- },
87303
87311
  "integrations.list": {
87304
87312
  capName: "integrations",
87305
87313
  capScope: "system",
@@ -87307,13 +87315,6 @@ Object.freeze({
87307
87315
  access: "view",
87308
87316
  role: "admin"
87309
87317
  },
87310
- "integrations.setSettings": {
87311
- capName: "integrations",
87312
- capScope: "system",
87313
- addonId: null,
87314
- access: "create",
87315
- role: "admin"
87316
- },
87317
87318
  "integrations.testConnection": {
87318
87319
  capName: "integrations",
87319
87320
  capScope: "system",
@@ -88308,6 +88309,13 @@ Object.freeze({
88308
88309
  access: "view",
88309
88310
  role: "admin"
88310
88311
  },
88312
+ "migrations.clearLegacyIntegrationRows": {
88313
+ capName: "migrations",
88314
+ capScope: "system",
88315
+ addonId: null,
88316
+ access: "delete",
88317
+ role: "admin"
88318
+ },
88311
88319
  "migrations.declareOwner": {
88312
88320
  capName: "migrations",
88313
88321
  capScope: "system",
@@ -88315,6 +88323,13 @@ Object.freeze({
88315
88323
  access: "create",
88316
88324
  role: "admin"
88317
88325
  },
88326
+ "migrations.deleteLegacyIntegrationRowKeys": {
88327
+ capName: "migrations",
88328
+ capScope: "system",
88329
+ addonId: null,
88330
+ access: "delete",
88331
+ role: "admin"
88332
+ },
88318
88333
  "migrations.dryRun": {
88319
88334
  capName: "migrations",
88320
88335
  capScope: "system",
@@ -88350,6 +88365,20 @@ Object.freeze({
88350
88365
  access: "create",
88351
88366
  role: "admin"
88352
88367
  },
88368
+ "migrations.patchLegacyIntegrationRows": {
88369
+ capName: "migrations",
88370
+ capScope: "system",
88371
+ addonId: null,
88372
+ access: "create",
88373
+ role: "admin"
88374
+ },
88375
+ "migrations.readLegacyIntegrationRows": {
88376
+ capName: "migrations",
88377
+ capScope: "system",
88378
+ addonId: null,
88379
+ access: "view",
88380
+ role: "admin"
88381
+ },
88353
88382
  "migrations.restorePreMigration": {
88354
88383
  capName: "migrations",
88355
88384
  capScope: "system",
@@ -106886,6 +106915,7 @@ var AmcrestProviderAddon = class extends BaseDeviceProvider {
106886
106915
  date: "2026-11-04"
106887
106916
  }), defaultIntegrationPortsOver({
106888
106917
  api: defaultIntegrationApiOver(this.ctx.api),
106918
+ secrets: this.ctx.secrets ?? null,
106889
106919
  records,
106890
106920
  kind: amcrestConnectionsEntryKind,
106891
106921
  info: (message) => this.ctx.logger.info(message),
package/dist/addon.mjs CHANGED
@@ -5990,7 +5990,7 @@ var ZodIssueCode = {
5990
5990
  var ZodFirstPartyTypeKind;
5991
5991
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5992
5992
  //#endregion
5993
- //#region ../types/dist/sleep-Cv4Zmie9.mjs
5993
+ //#region ../types/dist/sleep-9TIHFj1w.mjs
5994
5994
  /**
5995
5995
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5996
5996
  * window to float samples (D455).
@@ -7934,10 +7934,9 @@ var BaseAddon = class {
7934
7934
  *
7935
7935
  * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7936
7936
  * needs its connection fields, the default integration the addon's own sections. It is the
7937
- * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7938
- * unchanged (`integrations.setSettings` for an account integration).
7937
+ * SHAPE of the form only — the values live on the instance record.
7939
7938
  */
7940
- async integrationSettingsSchema(_instance) {
7939
+ async integrationFormSchema(_instance) {
7941
7940
  return null;
7942
7941
  }
7943
7942
  /**
@@ -7983,7 +7982,7 @@ var BaseAddon = class {
7983
7982
  * per-instance schema when it declares one, else its global schema (what they always showed).
7984
7983
  */
7985
7984
  async getIntegrationFormSchema(instance, nodeId) {
7986
- const scoped = await this.integrationSettingsSchema(instance);
7985
+ const scoped = await this.integrationFormSchema(instance);
7987
7986
  if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7988
7987
  return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7989
7988
  }
@@ -9273,6 +9272,7 @@ function defineEntryKind(name, spec) {
9273
9272
  class: "config",
9274
9273
  writeTrigger: spec.writeTrigger ?? "an operator adds or edits an entry in the entries UI",
9275
9274
  retention: { none: "an entry goes only when the operator deletes it" },
9275
+ ...spec.maxDataBytes === void 0 ? {} : { maxDataBytes: spec.maxDataBytes },
9276
9276
  ...spec.database === void 0 ? {} : { database: spec.database },
9277
9277
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9278
9278
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
@@ -11956,7 +11956,7 @@ function userError(code, params, englishFallback) {
11956
11956
  return new CamstackError(code, params, englishFallback);
11957
11957
  }
11958
11958
  //#endregion
11959
- //#region ../types/dist/composition-CRNwcFYY.mjs
11959
+ //#region ../types/dist/composition-pOHbTzRB.mjs
11960
11960
  /**
11961
11961
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11962
11962
  *
@@ -14684,20 +14684,28 @@ async function moveIntegrationRowsToRecords(ports, spec) {
14684
14684
  };
14685
14685
  }
14686
14686
  /**
14687
- * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416): an addon runs the move
14688
- * from its own process, so the legacy rows are reached through the hub (`ctx.api.integrations`:
14689
- * `list`, `getSettings` - which answers the OWNER its secrets resolved - and `clearSettings`) and
14690
- * the new record through the addon's own records handle. The hub deletes the rows and their
14691
- * keychain values; the addon never touches the registry.
14687
+ * The addon-side ports of `moveIntegrationRowsToRecords` (planning#416). An addon runs the move
14688
+ * from its own process, so the leftover rows are reached through the migration-only door
14689
+ * (`migrations.readLegacyIntegrationRows` / `clearLegacyIntegrationRows`) on the hub's connection.
14690
+ * The door returns markers. This process is the owner, so it resolves and releases them with
14691
+ * `resolveSecretMarkers` — the same call the old settings door used — and only then asks the
14692
+ * door to delete the rows. The addon's own record kind is its records handle.
14692
14693
  */
14694
+ async function resolvedRows(stored, secrets) {
14695
+ if (secrets === null) return stored;
14696
+ const { value, unresolved } = await resolveSecretMarkers(stored, secrets);
14697
+ if (unresolved.length > 0) throw new Error(`${unresolved.length} secret(s) cannot be read from the keychain`);
14698
+ return value;
14699
+ }
14693
14700
  function integrationRowsPortsOverApi(deps) {
14701
+ const secrets = deps.secrets ?? null;
14694
14702
  return {
14695
14703
  warn: (message) => deps.log.warn(message),
14696
14704
  listIntegrations: async (addonId) => (await deps.api.list()).filter((i) => i.addonId === addonId).map((i) => ({
14697
14705
  id: i.id,
14698
14706
  name: i.name
14699
14707
  })),
14700
- readRows: (integrationId) => deps.api.getSettings({ id: integrationId }),
14708
+ readRows: async (integrationId) => resolvedRows(await deps.api.readLegacyIntegrationRows({ integrationId }), secrets),
14701
14709
  putRecord: async (id, body) => {
14702
14710
  if (await deps.records.get(id) !== null) return "exists";
14703
14711
  try {
@@ -14716,7 +14724,9 @@ function integrationRowsPortsOverApi(deps) {
14716
14724
  await deps.records.delete(id);
14717
14725
  },
14718
14726
  clearRows: async (integrationId) => {
14719
- await deps.api.clearSettings({ id: integrationId });
14727
+ const refs = collectSecretMarkerRefs(await deps.api.readLegacyIntegrationRows({ integrationId }));
14728
+ if (refs.length > 0 && secrets !== null) await secrets.delete(refs);
14729
+ await deps.api.clearLegacyIntegrationRows({ integrationId });
14720
14730
  }
14721
14731
  };
14722
14732
  }
@@ -14806,14 +14816,14 @@ async function claimDevicesToDefaultIntegration(ports, spec) {
14806
14816
  * 2. `claim-devices` (tolerate): `claimDevicesToDefaultIntegration`; with no integration to claim
14807
14817
  * to it fails and is retried on the next boot.
14808
14818
  *
14809
- * Both run in the addon process through the hub API (`ctx.api`), BEFORE the addon registers its
14810
- * `integration-instances` provider: while a provider is registered the hub answers `getSettings`
14811
- * from the record, not from the rows.
14819
+ * Both run in the addon process through the hub API (`ctx.api`). The leftover rows are read
14820
+ * through the migration-only door, which answers empty once the store is gone.
14812
14821
  */
14813
14822
  function defaultIntegrationPortsOver(deps) {
14814
14823
  return {
14815
14824
  ...integrationRowsPortsOverApi({
14816
14825
  api: deps.api,
14826
+ ...deps.secrets !== void 0 ? { secrets: deps.secrets } : {},
14817
14827
  records: deps.records.kind({
14818
14828
  name: deps.kind.name,
14819
14829
  declaration: deps.kind.declaration,
@@ -14913,8 +14923,8 @@ function defaultIntegrationMigrations(spec) {
14913
14923
  function defaultIntegrationApiOver(api) {
14914
14924
  return {
14915
14925
  list: () => api.integrations.list.query(),
14916
- getSettings: (input) => api.integrations.getSettings.query(input),
14917
- clearSettings: (input) => api.integrations.clearSettings.mutate(input),
14926
+ readLegacyIntegrationRows: (input) => api.migrations.readLegacyIntegrationRows.query(input),
14927
+ clearLegacyIntegrationRows: (input) => api.migrations.clearLegacyIntegrationRows.mutate(input),
14918
14928
  listDevices: (addonId) => api.deviceManager.listAll.query({
14919
14929
  addonId,
14920
14930
  projection: "slim"
@@ -50531,6 +50541,9 @@ var RunningProposalEvidenceSchema = object({
50531
50541
  runsFound: number().int().nonnegative(),
50532
50542
  pausesFound: number().int().nonnegative(),
50533
50543
  longestPauseMs: number().nonnegative(),
50544
+ /** Episodes shorter than ~90 s (an anti-crease pulse, a display waking): never a run, never a pause. */
50545
+ pulsesFound: number().int().nonnegative(),
50546
+ longestPulseMs: number().nonnegative(),
50534
50547
  idleFloorW: number(),
50535
50548
  noiseCeilingW: number(),
50536
50549
  plateauW: number(),
@@ -56380,11 +56393,7 @@ var CreateIntegrationInputSchema = object({
56380
56393
  info: record(string(), unknown()).optional(),
56381
56394
  /** `stableId` of the discovery candidate the form was seeded from (D984). */
56382
56395
  originStableId: string().optional(),
56383
- settings: record(string(), unknown()).optional(),
56384
- /** Keys of `settings` the form's schema declares secret (by type) — kept in
56385
- * the keychain, not the table (D787). */
56386
- secretKeys: array(string()).optional(),
56387
- skipRestart: boolean().optional()
56396
+ settings: record(string(), unknown()).optional()
56388
56397
  });
56389
56398
  var UpdateIntegrationInputSchema = object({
56390
56399
  id: string(),
@@ -56405,22 +56414,6 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
56405
56414
  }), method(object({ id: string() }), DeleteIntegrationResultSchema, {
56406
56415
  kind: "mutation",
56407
56416
  auth: "admin"
56408
- }), method(object({ id: string() }), record(string(), unknown()), { auth: "admin" }), method(object({
56409
- id: string(),
56410
- settings: record(string(), unknown()),
56411
- /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
56412
- secretKeys: array(string()).optional(),
56413
- /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
56414
- skipRestart: boolean().optional()
56415
- }), object({ success: boolean() }), {
56416
- kind: "mutation",
56417
- auth: "admin"
56418
- }), method(object({ id: string() }), object({
56419
- rows: number().int().nonnegative(),
56420
- secrets: number().int().nonnegative()
56421
- }), {
56422
- kind: "mutation",
56423
- auth: "admin"
56424
56417
  }), method(_void(), array(AvailableIntegrationTypeSchema), { auth: "admin" }), method(object({
56425
56418
  addonId: string(),
56426
56419
  settings: record(string(), unknown())
@@ -59397,6 +59390,27 @@ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { aut
59397
59390
  }), {
59398
59391
  kind: "mutation",
59399
59392
  auth: "admin"
59393
+ }), method(object({ integrationId: string().min(1) }), record(string(), unknown()), { auth: "admin" }), method(object({ integrationId: string().min(1) }), object({
59394
+ rows: number().int().nonnegative(),
59395
+ secrets: number().int().nonnegative()
59396
+ }), {
59397
+ kind: "mutation",
59398
+ auth: "admin"
59399
+ }), method(object({
59400
+ integrationId: string().min(1),
59401
+ patch: record(string(), unknown())
59402
+ }), object({ written: boolean() }), {
59403
+ kind: "mutation",
59404
+ auth: "admin"
59405
+ }), method(object({
59406
+ integrationId: string().min(1),
59407
+ keys: array(string().min(1))
59408
+ }), object({
59409
+ present: boolean(),
59410
+ removed: record(string(), unknown())
59411
+ }), {
59412
+ kind: "mutation",
59413
+ auth: "admin"
59400
59414
  });
59401
59415
  method(object({
59402
59416
  sourceUrl: string(),
@@ -81790,11 +81804,15 @@ discriminatedUnion("kind", [object({
81790
81804
  * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
81791
81805
  * near F. A device that is not near one level most of the time has no floor.
81792
81806
  * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
81793
- * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
81794
- * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
81807
+ * 3. an episode shorter than `PULSE_MAX_MS` is a PULSE (an anti-crease tumble, a
81808
+ * display waking): it is neither a run nor a pause. Among the remaining
81809
+ * episodes, gaps over `PAUSE_MAX_MS` always separate runs; the rest split in
81810
+ * two by the largest log-scale jump (>= 3x): the short ones are pauses INSIDE
81811
+ * a run (a soak), the long ones separate runs. A stretch under `MIN_RUN_MS`
81812
+ * is no run.
81795
81813
  * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
81796
- * grid a person would type; stop dwell covers the longest pause; start dwell
81797
- * covers the longest standby blip above T.
81814
+ * grid a person would type; stop dwell covers the longest pause and the
81815
+ * kind's floor; start dwell covers the longest pulse and standby blip above T.
81798
81816
  * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
81799
81817
  * run must hold exactly one true interval and nothing may flap outside.
81800
81818
  */
@@ -81805,6 +81823,10 @@ var MINUTE_MS$1 = 60 * SECOND_MS;
81805
81823
  30 * SECOND_MS;
81806
81824
  60 * SECOND_MS;
81807
81825
  20 * MINUTE_MS$1;
81826
+ 90 * SECOND_MS;
81827
+ 30 * MINUTE_MS$1;
81828
+ 10 * MINUTE_MS$1;
81829
+ DeviceType.Dryer, 3 * MINUTE_MS$1, DeviceType.Dishwasher, 3 * MINUTE_MS$1, DeviceType.Washer, 10 * MINUTE_MS$1;
81808
81830
  10 * SECOND_MS;
81809
81831
  10 * SECOND_MS;
81810
81832
  /**
@@ -87226,13 +87248,6 @@ Object.freeze({
87226
87248
  access: "create",
87227
87249
  role: "admin"
87228
87250
  },
87229
- "integrations.clearSettings": {
87230
- capName: "integrations",
87231
- capScope: "system",
87232
- addonId: null,
87233
- access: "delete",
87234
- role: "admin"
87235
- },
87236
87251
  "integrations.create": {
87237
87252
  capName: "integrations",
87238
87253
  capScope: "system",
@@ -87268,13 +87283,6 @@ Object.freeze({
87268
87283
  access: "view",
87269
87284
  role: "admin"
87270
87285
  },
87271
- "integrations.getSettings": {
87272
- capName: "integrations",
87273
- capScope: "system",
87274
- addonId: null,
87275
- access: "view",
87276
- role: "admin"
87277
- },
87278
87286
  "integrations.list": {
87279
87287
  capName: "integrations",
87280
87288
  capScope: "system",
@@ -87282,13 +87290,6 @@ Object.freeze({
87282
87290
  access: "view",
87283
87291
  role: "admin"
87284
87292
  },
87285
- "integrations.setSettings": {
87286
- capName: "integrations",
87287
- capScope: "system",
87288
- addonId: null,
87289
- access: "create",
87290
- role: "admin"
87291
- },
87292
87293
  "integrations.testConnection": {
87293
87294
  capName: "integrations",
87294
87295
  capScope: "system",
@@ -88283,6 +88284,13 @@ Object.freeze({
88283
88284
  access: "view",
88284
88285
  role: "admin"
88285
88286
  },
88287
+ "migrations.clearLegacyIntegrationRows": {
88288
+ capName: "migrations",
88289
+ capScope: "system",
88290
+ addonId: null,
88291
+ access: "delete",
88292
+ role: "admin"
88293
+ },
88286
88294
  "migrations.declareOwner": {
88287
88295
  capName: "migrations",
88288
88296
  capScope: "system",
@@ -88290,6 +88298,13 @@ Object.freeze({
88290
88298
  access: "create",
88291
88299
  role: "admin"
88292
88300
  },
88301
+ "migrations.deleteLegacyIntegrationRowKeys": {
88302
+ capName: "migrations",
88303
+ capScope: "system",
88304
+ addonId: null,
88305
+ access: "delete",
88306
+ role: "admin"
88307
+ },
88293
88308
  "migrations.dryRun": {
88294
88309
  capName: "migrations",
88295
88310
  capScope: "system",
@@ -88325,6 +88340,20 @@ Object.freeze({
88325
88340
  access: "create",
88326
88341
  role: "admin"
88327
88342
  },
88343
+ "migrations.patchLegacyIntegrationRows": {
88344
+ capName: "migrations",
88345
+ capScope: "system",
88346
+ addonId: null,
88347
+ access: "create",
88348
+ role: "admin"
88349
+ },
88350
+ "migrations.readLegacyIntegrationRows": {
88351
+ capName: "migrations",
88352
+ capScope: "system",
88353
+ addonId: null,
88354
+ access: "view",
88355
+ role: "admin"
88356
+ },
88328
88357
  "migrations.restorePreMigration": {
88329
88358
  capName: "migrations",
88330
88359
  capScope: "system",
@@ -106861,6 +106890,7 @@ var AmcrestProviderAddon = class extends BaseDeviceProvider {
106861
106890
  date: "2026-11-04"
106862
106891
  }), defaultIntegrationPortsOver({
106863
106892
  api: defaultIntegrationApiOver(this.ctx.api),
106893
+ secrets: this.ctx.secrets ?? null,
106864
106894
  records,
106865
106895
  kind: amcrestConnectionsEntryKind,
106866
106896
  info: (message) => this.ctx.logger.info(message),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-amcrest",
3
- "version": "0.2.165",
3
+ "version": "0.2.166",
4
4
  "description": "Amcrest/Dahua camera device provider addon for CamStack — Dahua CGI over HTTP(S) with digest auth (snapshot, RTSP catalog, PTZ, image/day-night config)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -85,7 +85,7 @@
85
85
  "onvif"
86
86
  ],
87
87
  "requires": {
88
- "server": ">=1.2.764"
88
+ "server": ">=1.2.770"
89
89
  }
90
90
  },
91
91
  "files": [