@camstack/system 1.2.329 → 1.2.331

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.
Files changed (78) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +3 -3
  10. package/dist/builtins/autotrack/index.mjs +3 -3
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  13. package/dist/builtins/camera-grid/index.js +1 -1
  14. package/dist/builtins/camera-grid/index.mjs +1 -1
  15. package/dist/builtins/composer/composer.addon.js +27 -8
  16. package/dist/builtins/composer/composer.addon.mjs +21 -3
  17. package/dist/builtins/console-logging/index.js +1 -1
  18. package/dist/builtins/console-logging/index.mjs +1 -1
  19. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  20. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  21. package/dist/builtins/device-manager/device-bindings-store.d.ts +7 -0
  22. package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -0
  23. package/dist/builtins/device-manager/device-manager.addon.js +130 -3
  24. package/dist/builtins/device-manager/device-manager.addon.mjs +130 -3
  25. package/dist/builtins/device-manager/stale-wrapper-activations.d.ts +26 -0
  26. package/dist/builtins/hub-forwarder/index.js +1 -1
  27. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  28. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  30. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  31. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  32. package/dist/builtins/local-network/local-network.addon.js +1 -1
  33. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  34. package/dist/builtins/loki-logging/index.js +1 -1
  35. package/dist/builtins/loki-logging/index.mjs +1 -1
  36. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  37. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  38. package/dist/builtins/platform-probe/index.js +1 -1
  39. package/dist/builtins/platform-probe/index.mjs +1 -1
  40. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  41. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  42. package/dist/builtins/shared/device-state-changed.d.ts +3 -4
  43. package/dist/builtins/snapshot/index.js +1 -1
  44. package/dist/builtins/snapshot/index.mjs +1 -1
  45. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  47. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  48. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  49. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  50. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  51. package/dist/builtins/system-config/system-config.addon.js +1 -1
  52. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  53. package/dist/builtins/winston-logging/index.js +1 -1
  54. package/dist/builtins/winston-logging/index.mjs +1 -1
  55. package/dist/{child-cap-dispatch-C8uts1P4.js → child-cap-dispatch-93MfsaZL.js} +1 -0
  56. package/dist/{child-cap-dispatch-C2VINHu1.mjs → child-cap-dispatch-kLIiU1CG.mjs} +1 -0
  57. package/dist/{composition-sources-B7hfTiPS.js → composition-sources-BXZorHDO.js} +1 -1
  58. package/dist/{composition-sources-BHPqf6So.mjs → composition-sources-aEEer5gE.mjs} +1 -1
  59. package/dist/{dist-CV7IqaYq.mjs → dist-CoG8lY6H.mjs} +260 -378
  60. package/dist/{dist-CSs0cFDN.js → dist-DRUzy1SF.js} +259 -425
  61. package/dist/index.js +2 -2
  62. package/dist/index.mjs +2 -2
  63. package/dist/{retired-settings-keys-CGQnAoRx.js → retired-settings-keys-BY9H1I2b.js} +1 -1
  64. package/dist/{retired-settings-keys-DSUs7U6l.mjs → retired-settings-keys-BcJDpASY.mjs} +1 -1
  65. package/package.json +2 -17
  66. package/dist/builtins/doorbell/binding-mirror.d.ts +0 -110
  67. package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +0 -47
  68. package/dist/builtins/doorbell/doorbell-extension-slot.d.ts +0 -67
  69. package/dist/builtins/doorbell/doorbell-settings.d.ts +0 -58
  70. package/dist/builtins/doorbell/index.d.ts +0 -2
  71. package/dist/builtins/doorbell/index.js +0 -8
  72. package/dist/builtins/doorbell/index.mjs +0 -2
  73. package/dist/builtins/doorbell/trigger-engine.d.ts +0 -92
  74. package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +0 -218
  75. package/dist/builtins/doorbell/virtual-doorbell.addon.js +0 -1413
  76. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +0 -1407
  77. package/dist/device-state-changed-Bk7cvTnz.js +0 -31
  78. package/dist/device-state-changed-DoBbkyEI.mjs +0 -20
@@ -1,6 +1,6 @@
1
1
  let zod = require("zod");
2
2
  require("node:crypto");
3
- //#region ../types/dist/event-category-C5xZWqz6.mjs
3
+ //#region ../types/dist/event-category-BGmz_oxW.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -533,6 +533,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
533
533
  */
534
534
  EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
535
535
  EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
536
+ /**
537
+ * A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
538
+ * data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
539
+ * consumer is the orchestrator's camera-sensors bridge, which re-emits the
540
+ * camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
541
+ * for a camera with no attached sensor). Providers must not emit
542
+ * `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
543
+ * keeps it so (camera-grid is a named, deferred exception).
544
+ */
536
545
  EventCategory["ProviderMotion"] = "provider.motion";
537
546
  EventCategory["ProviderDetection"] = "provider.detection";
538
547
  EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
@@ -701,7 +710,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
701
710
  return EventCategory;
702
711
  }({});
703
712
  //#endregion
704
- //#region ../types/dist/sleep-gU6hqv4_.mjs
713
+ //#region ../types/dist/sleep-CDCONfkt.mjs
705
714
  /**
706
715
  * The audio chunk plane's byte format, and the ONE expansion from a coded
707
716
  * window to float samples (D455).
@@ -3512,7 +3521,7 @@ function errMsg(err) {
3512
3521
  return String(err);
3513
3522
  }
3514
3523
  //#endregion
3515
- //#region ../types/dist/composition-Dq42XWp0.mjs
3524
+ //#region ../types/dist/composition-43aqsZQj.mjs
3516
3525
  var AUDIO_ENCODER_BY_CODEC = {
3517
3526
  opus: "libopus",
3518
3527
  aac: "aac",
@@ -3961,13 +3970,12 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
3961
3970
  * LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
3962
3971
  * valutatore puro del suo FRONTE.
3963
3972
  *
3964
- * Viveva dentro il builtin virtual-doorbell
3965
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
3966
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
3967
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
3968
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
3969
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
3970
- * SPOSTA qui e il doorbell la ri-esporta.
3973
+ * UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
3974
+ * trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
3975
+ * sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
3976
+ * le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
3977
+ * Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
3978
+ * sensore fa suonare il campanello ma non registra" — è esattamente D62.
3971
3979
  *
3972
3980
  * ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
3973
3981
  * due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
@@ -3994,53 +4002,6 @@ var SOURCE_CAP_ACTIVE_FIELD = {
3994
4002
  tamper: "tampered"
3995
4003
  };
3996
4004
  /**
3997
- * The same caps → the slice field carrying the ms-epoch timestamp of the
3998
- * last transition. Every source cap MUST appear here (guarded by a spec):
3999
- * without a transition timestamp the evaluator cannot tell a genuine rise
4000
- * from a boot-time hydration when the FIRST slice it ever sees is already
4001
- * active, and errs towards silence — swallowing the rise.
4002
- *
4003
- * These timestamps are UPSTREAM ones, not ingest ones: the Home Assistant
4004
- * provider derives them from `state.last_changed`, so they survive our own
4005
- * restarts and correctly read as "hours ago" for a state that has been
4006
- * active for hours. `motion` names its rise timestamp `lastDetectedAt`.
4007
- */
4008
- var SOURCE_CAP_CHANGED_AT_FIELD = {
4009
- contact: "lastChangedAt",
4010
- binary: "lastChangedAt",
4011
- switch: "lastChangedAt",
4012
- motion: "lastDetectedAt",
4013
- flood: "lastChangedAt",
4014
- gas: "lastChangedAt",
4015
- smoke: "lastChangedAt",
4016
- "carbon-monoxide": "lastChangedAt",
4017
- vibration: "lastChangedAt",
4018
- tamper: "lastChangedAt"
4019
- };
4020
- /** Cap names whose presence in a device's bindings qualify it as a source. */
4021
- var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
4022
- /** True when a cap is a recognised binary/switch source. */
4023
- function isSourceCap(capName) {
4024
- return Object.prototype.hasOwnProperty.call(SOURCE_CAP_ACTIVE_FIELD, capName);
4025
- }
4026
- /** Extract the "active" boolean a source cap's slice carries, or null when
4027
- * the cap is unknown or the field is missing / non-boolean. */
4028
- function sliceActiveValue(capName, slice) {
4029
- const field = SOURCE_CAP_ACTIVE_FIELD[capName];
4030
- if (field === void 0) return null;
4031
- const raw = slice[field];
4032
- return typeof raw === "boolean" ? raw : null;
4033
- }
4034
- /** Ms-epoch transition timestamp a source cap's slice carries, or null when
4035
- * it is absent, non-numeric or the zero "never observed" sentinel. */
4036
- function sliceChangedAt(capName, slice) {
4037
- const field = SOURCE_CAP_CHANGED_AT_FIELD[capName];
4038
- if (field === void 0) return null;
4039
- const raw = slice[field];
4040
- if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return null;
4041
- return raw;
4042
- }
4043
- /**
4044
4005
  * How recent a source's own transition timestamp must be for a FIRST
4045
4006
  * sighting that is already active to count as a genuine rise rather than a
4046
4007
  * hydration of long-standing state.
@@ -4066,65 +4027,6 @@ function evaluateSensorLevelEdge(input) {
4066
4027
  };
4067
4028
  }
4068
4029
  /**
4069
- * IL fronte. Puro: nessun orologio proprio, nessuna memoria — il chiamante
4070
- * porta `prior`, `nowMs` e il proprio `startedAtMs`.
4071
- *
4072
- * Il caso della PRIMA slice già attiva è trattato esplicitamente e vale come
4073
- * fronte solo se il timestamp UPSTREAM della transizione è posteriore a
4074
- * `startedAtMs` **e** entro `firstSightingFreshnessMs`. Entrambe le metà
4075
- * servono: la sola freschezza scatterebbe su un'idratazione al boot di uno
4076
- * stato flippato pochi secondi prima del riavvio, e il solo "dopo che abbiamo
4077
- * iniziato" scatterebbe, su un processo di lunga vita, per una sorgente
4078
- * adottata oggi il cui stato è cambiato ieri. Il caso ambiguo ERRA VERSO IL
4079
- * SILENZIO e lo dichiara (`baseline-seeded-stale-active`).
4080
- */
4081
- function evaluateSensorEdge(input) {
4082
- const value = sliceActiveValue(input.capName, input.slice);
4083
- if (value === null) return {
4084
- edge: "none",
4085
- value: null,
4086
- reason: isSourceCap(input.capName) ? "non-boolean-value" : "unknown-cap"
4087
- };
4088
- if (evaluateSensorLevelEdge({
4089
- level: value,
4090
- changedAt: sliceChangedAt(input.capName, input.slice),
4091
- priorLevel: input.prior,
4092
- startedAtMs: input.startedAtMs,
4093
- nowMs: input.nowMs,
4094
- firstSightingFreshnessMs: input.firstSightingFreshnessMs
4095
- }).kind === "rise") return {
4096
- edge: "rising",
4097
- value: true
4098
- };
4099
- if (input.prior === void 0) {
4100
- if (!value) return {
4101
- edge: "none",
4102
- value,
4103
- reason: "baseline-seeded-inactive"
4104
- };
4105
- return {
4106
- edge: "none",
4107
- value,
4108
- reason: "baseline-seeded-stale-active"
4109
- };
4110
- }
4111
- if (input.prior === value) return {
4112
- edge: "none",
4113
- value,
4114
- reason: "no-change"
4115
- };
4116
- if (!value) return {
4117
- edge: "none",
4118
- value,
4119
- reason: "falling-edge"
4120
- };
4121
- return {
4122
- edge: "none",
4123
- value,
4124
- reason: "missing-timestamp"
4125
- };
4126
- }
4127
- /**
4128
4030
  * Error types for the safe expression engine. Two distinct classes so callers
4129
4031
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
4130
4032
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -6370,7 +6272,7 @@ var RecordingTriggersSchema = zod.z.object({
6370
6272
  *
6371
6273
  * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
6372
6274
  * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
6373
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
6275
+ * la stessa del campanello composto (D671). Ed è il FRONTE, non
6374
6276
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
6375
6277
  * registrare.
6376
6278
  */
@@ -16056,8 +15958,8 @@ var deviceManagerCapability = {
16056
15958
  * (`value` / `missing` / `failed`, see {@link WrapperActivationReadSchema}).
16057
15959
  *
16058
15960
  * The narrow read of the durable authority behind `getBindings`: a
16059
- * consumer that must know what the operator PERSISTED (virtual-doorbell,
16060
- * before it drops a press) asks this instead of pulling device-manager's
15961
+ * consumer that must know what the operator PERSISTED (before it drops
15962
+ * work on the strength of an unbind) asks this instead of pulling device-manager's
16061
15963
  * whole addon store through `addonSettingsRaw.getGlobal` — which answered
16062
15964
  * `{}` on every isolated-engine hub and so never worked (D675). It reads
16063
15965
  * ONE row (the activation map, ~1.6 KB), never the ~625 KB store.
@@ -16347,120 +16249,14 @@ var deviceManagerCapability = {
16347
16249
  }), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.unknown().nullable())))
16348
16250
  }
16349
16251
  };
16350
- /**
16351
- * Doorbell button cap. Two kinds of providers coexist behind this cap
16352
- * name (same pattern as `snapshot`):
16353
- *
16354
- * - **Native** providers: registered per-device by device-driver
16355
- * addons via `ctx.registerNativeCap` — either on a
16356
- * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
16357
- * or directly on the camera (Reolink registers at camera level).
16358
- * Emits an `onPressed` event every time the firmware pushes a
16359
- * ring; status tracks the last press and a pressCount since start
16360
- * (diagnostic).
16361
- *
16362
- * - **Wrapper** provider: the `virtual-doorbell` system builtin
16363
- * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
16364
- * device (contact / switch / event-emitter …) into a doorbell for
16365
- * a camera. `defaultActive: false` — the operator explicitly binds
16366
- * it per camera in the device-bindings UI, then picks the source
16367
- * device + trigger in the per-device settings.
16368
- *
16369
- * The DeviceEventPropagator re-emits `onPressed` on the camera parent
16370
- * — subscribers listening at the camera level receive ring events
16371
- * with `via[]` populated. No code on the parent needed.
16372
- */
16373
- var DoorbellStatusSchema = zod.z.object({
16374
- /** Ms epoch of the last press. null = never observed since this provider started. */
16375
- lastPressedAt: zod.z.number().nullable(),
16376
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
16377
- pressCountSinceStart: zod.z.number()
16378
- });
16379
- var DoorbellPressEventSchema = zod.z.object({
16380
- deviceId: zod.z.number(),
16381
- timestamp: zod.z.number()
16382
- });
16383
- /**
16384
- * The `doorbell-source` extension slot's editable value (D648): the camera's
16385
- * virtual-doorbell trigger devices, exactly what the wrapper's
16386
- * `doorbellSources` persists (as `{ deviceId }` records).
16387
- */
16388
- var DoorbellSourceSlotValueSchema = zod.z.object({ deviceIds: zod.z.array(zod.z.number().int().positive()) });
16389
- /**
16390
- * The `doorbell-source` slot — which devices ring this camera's VIRTUAL
16391
- * doorbell, owned by the `virtual-doorbell` wrapper over its
16392
- * `doorbellSources` store (D648). A source rings when one of its
16393
- * `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
16394
- * different authority (`deviceManager.setWrapperActive`, D62) — the slot
16395
- * reports it, it never writes it.
16396
- */
16397
- var DOORBELL_SOURCE_SLOT = {
16398
- id: "doorbell-source",
16399
- label: "Doorbell trigger devices",
16400
- description: "Any of these becoming active rings this camera's virtual doorbell: a binary sensor when it goes active, a switch when it turns on.",
16401
- shape: "association",
16402
- hostDeviceTypes: [DeviceType.Camera],
16403
- sourceCaps: SOURCE_CAPS,
16404
- cardinality: "many",
16405
- value: DoorbellSourceSlotValueSchema
16406
- };
16407
- var doorbellCapability = {
16408
- name: "doorbell",
16409
- scope: "device",
16410
- deviceNative: true,
16411
- mode: "singleton",
16412
- kind: "wrapper",
16413
- defaultActive: false,
16414
- deviceTypes: [DeviceType.Button, DeviceType.Camera],
16415
- extensionSlots: [DOORBELL_SOURCE_SLOT],
16416
- methods: {},
16417
- events: {
16418
- /**
16419
- * Fires once per physical press. Reolink delivers via Baichuan
16420
- * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
16421
- * release/duration — it's a pulse.
16422
- */
16423
- onPressed: {
16424
- data: DoorbellPressEventSchema,
16425
- fromState: {
16426
- field: "lastPressedAt",
16427
- on: "advance"
16428
- }
16429
- } },
16430
- status: {
16431
- schema: DoorbellStatusSchema,
16432
- kind: "push"
16433
- },
16434
- /**
16435
- * Runtime-state slice — last press timestamp + lifetime press count.
16436
- * Mirrored by the kernel and readable via
16437
- * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
16438
- * without subscribing.
16439
- */
16440
- runtimeState: DoorbellStatusSchema,
16441
- /**
16442
- * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
16443
- *
16444
- * See `RuntimeStateDurability`. Enforced by
16445
- * `scripts/check-runtime-state-durability.ts`.
16446
- */
16447
- durability: "restored"
16448
- };
16449
- var ExtensionSlotIdSchema = zod.z.enum(["doorbell-source", "linked-devices"]);
16450
- var EXTENSION_SLOTS = {
16451
- "doorbell-source": {
16452
- descriptor: DOORBELL_SOURCE_SLOT,
16453
- declaringCap: "doorbell",
16454
- hostDeviceTypes: [DeviceType.Camera]
16455
- },
16456
- "linked-devices": {
16457
- descriptor: LINKED_DEVICES_SLOT,
16458
- declaringCap: "device-manager",
16459
- hostDeviceTypes: [DeviceType.Camera]
16460
- }
16461
- };
16252
+ var ExtensionSlotIdSchema = zod.z.enum(["linked-devices"]);
16253
+ var EXTENSION_SLOTS = { "linked-devices": {
16254
+ descriptor: LINKED_DEVICES_SLOT,
16255
+ declaringCap: "device-manager",
16256
+ hostDeviceTypes: [DeviceType.Camera]
16257
+ } };
16462
16258
  /** A slot's editable value, discriminated on `slot`. */
16463
- var ExtensionSlotValueSchema = zod.z.discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: zod.z.literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: zod.z.literal("linked-devices") })]);
16259
+ var ExtensionSlotValueSchema = zod.z.discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: zod.z.literal("linked-devices") })]);
16464
16260
  /** Every device type at least one slot is offered on — `device-extension`'s `deviceTypes`. */
16465
16261
  var EXTENSION_HOST_DEVICE_TYPES = [DeviceType.Camera];
16466
16262
  var ExtensionSourceAvailabilitySchema = zod.z.discriminatedUnion("state", [zod.z.object({ state: zod.z.literal("ok") }), zod.z.object({
@@ -16498,10 +16294,11 @@ var ExtensionSlotSourceSchema = zod.z.object({
16498
16294
  });
16499
16295
  /**
16500
16296
  * A DIFFERENT authority that gates whether the slot's sources do anything —
16501
- * reported read-only, named, never written through this contract (D62). The
16502
- * virtual doorbell's sources ring only while the wrapper is bound to the
16503
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
16504
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
16297
+ * reported read-only, named, never written through this contract (D62) — e.g.
16298
+ * a slot whose sources act only while a wrapper is bound to the host, a
16299
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
16300
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
16301
+ * not be read; it is not "inactive" (D315).
16505
16302
  */
16506
16303
  var ExtensionSlotActivationSchema = zod.z.object({
16507
16304
  state: zod.z.enum([
@@ -16518,7 +16315,7 @@ var ExtensionSlotStateSchema = zod.z.object({
16518
16315
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
16519
16316
  slotRef: zod.z.string(),
16520
16317
  slot: ExtensionSlotIdSchema,
16521
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
16318
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
16522
16319
  authority: zod.z.string(),
16523
16320
  activation: ExtensionSlotActivationSchema.nullable(),
16524
16321
  value: ExtensionSlotValueSchema,
@@ -16712,8 +16509,8 @@ var deviceStateCapability = {
16712
16509
  * Canonical cross-layer WRITE for one cap-keyed slice. Used by
16713
16510
  * everyone who needs to publish runtime state for a device:
16714
16511
  * device providers (via `BaseDevice.runtimeState.setCapState`
16715
- * which delegates here), system-scope addons (e.g. the pipeline
16716
- * runner writing the `motion` slice on phase transitions), and
16512
+ * which delegates here), system-scope addons (e.g. the orchestrator
16513
+ * writing a camera's effective `motion` slice, D696), and
16717
16514
  * direct cap-router callers in tests.
16718
16515
  *
16719
16516
  * Behavior on the hub:
@@ -20128,17 +19925,25 @@ var NcScheduleSchema = zod.z.object({
20128
19925
  */
20129
19926
  var NcSystemEventDeliveryModeSchema = zod.z.enum(["instant", "summary"]);
20130
19927
  /**
19928
+ * How a summary window is SENT (D698): `rule` (absent) = ONE notification with
19929
+ * every section; `section` = one notification per section with something to
19930
+ * say (an unavailable section included), each titled by its section.
19931
+ */
19932
+ var NcStateSummarySplitSchema = zod.z.enum(["rule", "section"]);
19933
+ /**
20131
19934
  * `summary`: the rule sends NOTHING per event. When a window of `schedule`
20132
19935
  * CLOSES, it sends one notification with the current state of its kinds
20133
19936
  * (low batteries, consumables to replace, devices offline, updates
20134
- * available, ...), after its own filters. Nothing to report sends nothing.
19937
+ * available, ...), after its own filters — or, with `split: 'section'`
19938
+ * (D698), one notification per section. Nothing to report sends nothing.
20135
19939
  * The schedule is the ordinary `NcSchedule` (weekly windows, IANA timezone,
20136
19940
  * midnight crossing); the send time is each window's END. Refused on the
20137
19941
  * `alarm` family, which stays instant (`systemEventDeliveryProblems`).
20138
19942
  */
20139
19943
  var NcSystemEventDeliverySchema = zod.z.discriminatedUnion("mode", [zod.z.object({ mode: zod.z.literal("instant") }), zod.z.object({
20140
19944
  mode: zod.z.literal("summary"),
20141
- schedule: NcScheduleSchema
19945
+ schedule: NcScheduleSchema,
19946
+ split: NcStateSummarySplitSchema.optional()
20142
19947
  })]);
20143
19948
  /** Fuzzy plate matcher — OCR noise makes exact match useless (spec row 12/13). */
20144
19949
  var NcPlateMatcherSchema = zod.z.object({
@@ -21137,11 +20942,23 @@ var NcStateSummaryPreviewSectionSchema = zod.z.object({
21137
20942
  reason: zod.z.string().optional()
21138
20943
  });
21139
20944
  /**
20945
+ * One notification a previewed summary would send (D698): the whole summary on
20946
+ * a `rule` split (no `section`), one section's on a `section` split.
20947
+ */
20948
+ var NcStateSummaryPreviewRowSchema = zod.z.object({
20949
+ section: zod.z.string().optional(),
20950
+ /** The section's name on a per-section row; every reported section's, joined, on a combined one. */
20951
+ title: zod.z.string(),
20952
+ text: zod.z.string(),
20953
+ count: zod.z.number().int().nonnegative()
20954
+ });
20955
+ /**
21140
20956
  * The summary a rule would send now. `empty` = nothing to report and every
21141
20957
  * section read: such a summary is NOT sent. `dropped` counts the open states
21142
20958
  * the rule's own filters (or a camera mute) left out, by reason.
21143
20959
  * `excludedGroups` names the kind groups selected that have no state to report
21144
- * (backups & exports).
20960
+ * (backups & exports). `split` and `rows` (D698) say how it would be sent —
20961
+ * optional, so an older hub's answer still parses.
21145
20962
  */
21146
20963
  var NcStateSummaryPreviewSchema = zod.z.object({
21147
20964
  empty: zod.z.boolean(),
@@ -21149,7 +20966,9 @@ var NcStateSummaryPreviewSchema = zod.z.object({
21149
20966
  text: zod.z.string(),
21150
20967
  sections: zod.z.array(NcStateSummaryPreviewSectionSchema),
21151
20968
  dropped: zod.z.record(zod.z.string(), zod.z.number().int().nonnegative()),
21152
- excludedGroups: zod.z.array(zod.z.string())
20969
+ excludedGroups: zod.z.array(zod.z.string()),
20970
+ split: NcStateSummarySplitSchema.optional(),
20971
+ rows: zod.z.array(NcStateSummaryPreviewRowSchema).optional()
21153
20972
  });
21154
20973
  var NcTestResultSchema = zod.z.object({
21155
20974
  recordId: zod.z.string(),
@@ -22360,12 +22179,27 @@ var CameraSensorRefSchema = zod.z.object({
22360
22179
  function cameraSensorRefKey(ref) {
22361
22180
  return `${ref.addonId}/${ref.stableId}`;
22362
22181
  }
22363
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
22364
- var CameraSensorsModeSchema = zod.z.enum(["in-addition"]);
22365
- var CameraSensorsConfigSchema = zod.z.object({
22366
- mode: CameraSensorsModeSchema,
22367
- sensors: zod.z.array(CameraSensorRefSchema).min(1).max(8)
22368
- }).superRefine((value, ctx) => {
22182
+ /**
22183
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
22184
+ * `replace`: its motion is the camera's, and while one is READY the camera's
22185
+ * own motion no longer counts on its own. `confirm`: while one is READY the
22186
+ * camera's own motion counts only while a confirm sensor is active — with or
22187
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
22188
+ */
22189
+ var CameraSensorModeSchema = zod.z.enum([
22190
+ "in-addition",
22191
+ "confirm",
22192
+ "replace"
22193
+ ]);
22194
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
22195
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
22196
+ var CameraSensorEntrySchema = CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) });
22197
+ /**
22198
+ * A camera's attached sensors. A CS-1 row carried an envelope
22199
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
22200
+ * what the write stores (D672 rule 3), so it is never written again.
22201
+ */
22202
+ var CameraSensorsConfigSchema = zod.z.object({ sensors: zod.z.array(CameraSensorEntrySchema).min(1).max(8) }).superRefine((value, ctx) => {
22369
22203
  const seen = /* @__PURE__ */ new Set();
22370
22204
  value.sensors.forEach((ref, index) => {
22371
22205
  const key = cameraSensorRefKey(ref);
@@ -22377,39 +22211,41 @@ var CameraSensorsConfigSchema = zod.z.object({
22377
22211
  seen.add(key);
22378
22212
  });
22379
22213
  });
22380
- var RefFields = {
22214
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
22215
+ var AttachmentFields = {
22381
22216
  addonId: zod.z.string(),
22382
- stableId: zod.z.string()
22217
+ stableId: zod.z.string(),
22218
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
22383
22219
  };
22384
22220
  var CameraSensorAttachmentSchema = zod.z.discriminatedUnion("state", [
22385
22221
  zod.z.object({
22386
- ...RefFields,
22222
+ ...AttachmentFields,
22387
22223
  state: zod.z.literal("ready"),
22388
22224
  deviceId: zod.z.number(),
22389
22225
  name: zod.z.string(),
22390
22226
  caps: zod.z.array(zod.z.string()).readonly()
22391
22227
  }),
22392
22228
  zod.z.object({
22393
- ...RefFields,
22229
+ ...AttachmentFields,
22394
22230
  state: zod.z.literal("offline"),
22395
22231
  deviceId: zod.z.number(),
22396
22232
  name: zod.z.string(),
22397
22233
  reason: zod.z.enum(["offline", "disabled"])
22398
22234
  }),
22399
22235
  zod.z.object({
22400
- ...RefFields,
22236
+ ...AttachmentFields,
22401
22237
  state: zod.z.literal("missing"),
22402
22238
  reason: zod.z.string()
22403
22239
  }),
22404
22240
  zod.z.object({
22405
- ...RefFields,
22241
+ ...AttachmentFields,
22406
22242
  state: zod.z.literal("unsupported"),
22407
22243
  deviceId: zod.z.number(),
22408
22244
  name: zod.z.string(),
22409
22245
  reason: zod.z.string()
22410
22246
  }),
22411
22247
  zod.z.object({
22412
- ...RefFields,
22248
+ ...AttachmentFields,
22413
22249
  state: zod.z.literal("unreadable"),
22414
22250
  reason: zod.z.string()
22415
22251
  })
@@ -22422,17 +22258,49 @@ var CameraSensorAttachmentSchema = zod.z.discriminatedUnion("state", [
22422
22258
  * say.
22423
22259
  */
22424
22260
  var CameraSensorsNotForwardedSchema = zod.z.enum(["onboard-not-listed", "no-active-detection"]);
22261
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
22262
+ var CameraOwnMotionSchema = zod.z.enum([
22263
+ "always",
22264
+ "when-confirmed",
22265
+ "never"
22266
+ ]);
22267
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
22268
+ var CameraSensorsNotEnforcedSchema = zod.z.enum(["provider-not-split"]);
22269
+ /**
22270
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
22271
+ * never the effective value. The camera's `motion` slice is the effective one,
22272
+ * so a view of the camera's own row reads this.
22273
+ * - `atMs`: when the bridge received the camera's last own edge.
22274
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
22275
+ * AND it is younger than the window the bridge counts the camera's own motion
22276
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
22277
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
22278
+ * within the window; a status read is a snapshot, so a live view re-asks.
22279
+ * Absent = this orchestrator has not received the camera's own motion since it
22280
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
22281
+ */
22282
+ var CameraRawLevelSchema = zod.z.object({
22283
+ detected: zod.z.boolean(),
22284
+ atMs: zod.z.number()
22285
+ });
22425
22286
  var CameraSensorsStatusSchema = zod.z.discriminatedUnion("state", [
22426
- zod.z.object({ state: zod.z.literal("not-configured") }),
22287
+ zod.z.object({
22288
+ state: zod.z.literal("not-configured"),
22289
+ cameraLevel: CameraRawLevelSchema.optional()
22290
+ }),
22427
22291
  zod.z.object({
22428
22292
  state: zod.z.literal("unreadable"),
22429
- reason: zod.z.string()
22293
+ reason: zod.z.string(),
22294
+ cameraLevel: CameraRawLevelSchema.optional()
22430
22295
  }),
22431
22296
  zod.z.object({
22432
22297
  state: zod.z.literal("configured"),
22433
- mode: CameraSensorsModeSchema,
22298
+ cameraLevel: CameraRawLevelSchema.optional(),
22434
22299
  sensors: zod.z.array(CameraSensorAttachmentSchema).readonly(),
22435
- notForwarded: CameraSensorsNotForwardedSchema.optional()
22300
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
22301
+ cameraMotion: CameraOwnMotionSchema.optional(),
22302
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
22303
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
22436
22304
  })
22437
22305
  ]);
22438
22306
  Object.freeze({
@@ -26528,8 +26396,10 @@ var RunnerCameraConfigSchema = zod.z.object({
26528
26396
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
26529
26397
  * analyzer providers) running on decoded frames in this runner.
26530
26398
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
26531
- * analytics). The provider emits ProviderMotion
26532
- * events; the orchestrator forwards to `reportMotion`.
26399
+ * analytics). The provider emits its raw push as `ProviderMotion`
26400
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
26401
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
26402
+ * `reportMotion`.
26533
26403
  */
26534
26404
  motionSources: MotionSourcesSchema.default(["analyzer"]),
26535
26405
  /**
@@ -26706,7 +26576,15 @@ var RunnerLocalLoadSchema = zod.z.object({
26706
26576
  hasGpu: zod.z.boolean(),
26707
26577
  inferenceBackend: zod.z.string().optional(),
26708
26578
  cpuCores: zod.z.number().optional()
26709
- })
26579
+ }),
26580
+ /**
26581
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
26582
+ * runner never writes it. ABSENT = a runner older than D696, which still
26583
+ * writes the slice from its phase machine; the orchestrator does not write
26584
+ * while any runner node is absent-or-unanswered, so one slice never has two
26585
+ * writers.
26586
+ */
26587
+ motionSliceWriter: zod.z.literal("orchestrator").optional()
26710
26588
  });
26711
26589
  /**
26712
26590
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -34030,6 +33908,70 @@ onStatusChanged: { data: zod.z.object({
34030
33908
  * whether persisting is worth a SQLite commit. */
34031
33909
  volatileStateFields: ["lastChangedAt"]
34032
33910
  };
33911
+ var DoorbellStatusSchema = zod.z.object({
33912
+ /** Ms epoch of the last press. null = never observed since this provider started. */
33913
+ lastPressedAt: zod.z.number().nullable(),
33914
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
33915
+ pressCountSinceStart: zod.z.number()
33916
+ });
33917
+ var DoorbellPressEventSchema = zod.z.object({
33918
+ deviceId: zod.z.number(),
33919
+ timestamp: zod.z.number()
33920
+ });
33921
+ /**
33922
+ * Doorbell button cap. Two kinds of provider serve it, and to every consumer
33923
+ * they are the same cap:
33924
+ *
33925
+ * - **Native** providers, registered per device by a driver addon through
33926
+ * `ctx.registerNativeCap` — a `DeviceType.Button` accessory with
33927
+ * `role: DeviceRole.Doorbell`, or the camera itself (Reolink registers at
33928
+ * camera level). They emit `onPressed` on every firmware ring.
33929
+ * - **A composed doorbell** (D670, D671): a camera's customization gives it
33930
+ * the cap, its `lastPressedAt` rising from the sources the operator
33931
+ * assigned in Customize → Doorbell, and the composer emits `onPressed`
33932
+ * from that field's advance (`fromState`).
33933
+ *
33934
+ * The DeviceEventPropagator re-emits `onPressed` on the camera parent.
33935
+ */
33936
+ var doorbellCapability = {
33937
+ name: "doorbell",
33938
+ scope: "device",
33939
+ deviceNative: true,
33940
+ mode: "singleton",
33941
+ deviceTypes: [DeviceType.Button, DeviceType.Camera],
33942
+ methods: {},
33943
+ events: {
33944
+ /**
33945
+ * Fires once per physical press. Reolink delivers via Baichuan
33946
+ * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
33947
+ * release/duration — it's a pulse.
33948
+ */
33949
+ onPressed: {
33950
+ data: DoorbellPressEventSchema,
33951
+ fromState: {
33952
+ field: "lastPressedAt",
33953
+ on: "advance"
33954
+ }
33955
+ } },
33956
+ status: {
33957
+ schema: DoorbellStatusSchema,
33958
+ kind: "push"
33959
+ },
33960
+ /**
33961
+ * Runtime-state slice — last press timestamp + lifetime press count.
33962
+ * Mirrored by the kernel and readable via
33963
+ * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
33964
+ * without subscribing.
33965
+ */
33966
+ runtimeState: DoorbellStatusSchema,
33967
+ /**
33968
+ * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
33969
+ *
33970
+ * See `RuntimeStateDurability`. Enforced by
33971
+ * `scripts/check-runtime-state-durability.ts`.
33972
+ */
33973
+ durability: "restored"
33974
+ };
34033
33975
  /**
34034
33976
  * Enum-state sensor — a string value picked from a finite option set.
34035
33977
  * Drives HA `sensor` entries with `state_class: enum` (HVAC action
@@ -36440,11 +36382,11 @@ authKey: zod.z.string().optional() }), zod.z.object({
36440
36382
  /**
36441
36383
  * How long after a camera's last rising edge `pipeline-analytics` closes the
36442
36384
  * motion EPISODE row it kept open for it (D475) — "il delay della cap", in
36443
- * the operator's words. One authority for the whole system: every provider
36444
- * that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
36445
- * constant while `detected: true` (never its own number — Hikvision used to
36446
- * seed its internal 3 s inactivity timer here, which is a different clock for
36447
- * a different purpose), and `MotionEpisodeTracker` imports it directly rather
36385
+ * the operator's words. One authority for the whole system: the writer of a
36386
+ * `Camera` device's slice (the orchestrator, D696) writes this SAME constant
36387
+ * in `autoClearAfterMs` while `detected: true` (never a camera's own clock —
36388
+ * Hikvision used to seed its internal 3 s inactivity timer here, a different
36389
+ * clock for a different purpose), and `MotionEpisodeTracker` imports it directly rather
36448
36390
  * than reading the live cap value on the hot path. A `Sensor` device (HA
36449
36391
  * binary sensor, Homematic) gets a genuine push both ways and has no episode
36450
36392
  * to close on a timer, so it writes `null`, never this constant.
@@ -36455,9 +36397,9 @@ var MotionStatusSchema = zod.z.object({
36455
36397
  /** Ms epoch of the last detected-true observation. Null if never detected. */
36456
36398
  lastDetectedAt: zod.z.number().nullable(),
36457
36399
  /**
36458
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
36459
- * `null` while false and on every `Sensor` device (D475) — see that
36460
- * constant's doc for the one-authority rule.
36400
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
36401
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
36402
+ * device (D475) — see that constant's doc for the one-authority rule.
36461
36403
  *
36462
36404
  * ## Reading this field still arms nothing
36463
36405
  *
@@ -36475,34 +36417,31 @@ var MotionStatusSchema = zod.z.object({
36475
36417
  * `addon-export-alexa/src/motion-clear-hold.ts` and
36476
36418
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
36477
36419
  *
36478
- * ## Who writes it
36479
- *
36480
- * - **Cameras** — the runner's phase machine, `active → watching` on
36481
- * `cooldown_expired`, which then writes this slice with
36482
- * `detected: false` (`handlePhaseChanged` in
36483
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
36484
- * matters most for the sources that only ever push a rising one:
36485
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
36486
- * a false.
36487
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
36488
- * provider pushes the false itself, from the upstream system's own
36489
- * state change. No phase machine is involved.
36490
- *
36491
- * ### The phase machine is CANONICAL, not sole — and that is a defect
36492
- *
36493
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
36494
- * phase machine is the sole writer for a camera. It is not.
36495
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
36496
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
36497
- * Hikvision's comment says why: it read THIS docblock, agreed the
36498
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
36499
- * two authorities can disagree about one slice, which this repo
36500
- * forbids, and the doc said otherwise — which is worse than saying
36501
- * nothing, because it reads as verification.
36502
- *
36503
- * This predates D475 and is not fixed there: the fix touches every
36504
- * camera provider. Recorded in D475's Consequences. Do not restore the
36505
- * "sole writer" wording without also removing the other writers.
36420
+ * ## Who writes it (D696)
36421
+ *
36422
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
36423
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
36424
+ * `camera-sensors/motion-slice.ts`), alongside the effective
36425
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
36426
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
36427
+ * device activity, as the camera's `motionSources` counts them. A source
36428
+ * that never sends a clear falls one window after its last `true`.
36429
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
36430
+ * writes this slice (not even a boot seed), and neither does the runner's
36431
+ * phase machine: its phase is "a session is attached" for every on-motion
36432
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
36433
+ * A provider's `isDetected` answers from the hub's slice.
36434
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
36435
+ * imports) — the provider pushes both edges itself, from the upstream
36436
+ * system's own state change. That is the sensor's own fact.
36437
+ *
36438
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
36439
+ * `motion` slice; each sensor writer is a named exemption.
36440
+ *
36441
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
36442
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
36443
+ * it, every provider seeded it at boot, and every session left it
36444
+ * `detected: true` after its detach.
36506
36445
  */
36507
36446
  autoClearAfterMs: zod.z.number().nullable()
36508
36447
  });
@@ -36574,11 +36513,12 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
36574
36513
  */
36575
36514
  runtimeState: MotionStatusSchema,
36576
36515
  /**
36577
- * Runtime-state durability: **session** — every writer of this slice
36516
+ * Runtime-state durability: **session** — the writer of this slice
36578
36517
  * writes only on an EDGE, so a restored `detected: true` would stay
36579
36518
  * frozen until the next one instead of being corrected. The next edge
36580
- * re-publishes the real state. (On who the writers are, and why there
36581
- * is more than one, see `autoClearAfterMs` above.)
36519
+ * re-publishes the real state, and the orchestrator re-publishes every
36520
+ * camera when it starts writing (D696; on who writes it, see
36521
+ * `autoClearAfterMs` above).
36582
36522
  *
36583
36523
  * See `RuntimeStateDurability`. Enforced by
36584
36524
  * `scripts/check-runtime-state-durability.ts`.
@@ -39294,8 +39234,7 @@ var ptzAutotrackCapability = {
39294
39234
  deviceNative: true,
39295
39235
  mode: "singleton",
39296
39236
  /**
39297
- * Two kinds of provider behind one name, the same shape `snapshot` and
39298
- * `doorbell` use:
39237
+ * Two kinds of provider behind one name, the same shape `snapshot` uses:
39299
39238
  *
39300
39239
  * - **Native**: the camera's own firmware autotrack, registered per device by
39301
39240
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
@@ -39303,7 +39242,7 @@ var ptzAutotrackCapability = {
39303
39242
  * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
39304
39243
  * which follows a subject with `ptz.move` pulses driven by the detection
39305
39244
  * pipeline. `defaultActive: false` — the operator binds it per camera in
39306
- * the bindings UI, exactly as `virtual-doorbell` is bound.
39245
+ * the bindings UI.
39307
39246
  *
39308
39247
  * The binding chooses WHICH implementation answers; `setEnabled` stays the
39309
39248
  * one switch for the function either way. A camera never has both driving its
@@ -44675,72 +44614,6 @@ function evaluateCompositionField(input) {
44675
44614
  }
44676
44615
  }
44677
44616
  /**
44678
- * The doorbell of a camera as a customization (D671). ONE builder: the
44679
- * virtual-doorbell migration and the admin UI's "Use as doorbell" preset both
44680
- * call it, so a preset can never write a shape the migration would refuse as
44681
- * "an unrelated existing block".
44682
- */
44683
- /** A rise closer than this to the previous one is the same press (D671). */
44684
- var DOORBELL_RISE_DEBOUNCE_MS = 2e3;
44685
- /** Every source cap of `source` that `SOURCE_CAP_ACTIVE_FIELD` knows AND the device has bound. */
44686
- function doorbellFeatureSources(source, boundCaps) {
44687
- return Object.keys(SOURCE_CAP_ACTIVE_FIELD).filter((cap) => Object.hasOwn(SOURCE_CAP_CHANGED_AT_FIELD, cap) && boundCaps.includes(cap)).toSorted().flatMap((cap) => {
44688
- const activeField = SOURCE_CAP_ACTIVE_FIELD[cap];
44689
- const changedAtField = SOURCE_CAP_CHANGED_AT_FIELD[cap];
44690
- return activeField === void 0 || changedAtField === void 0 ? [] : [{
44691
- source,
44692
- cap,
44693
- activeField,
44694
- changedAtField
44695
- }];
44696
- });
44697
- }
44698
- /** The D671 feature, byte for byte what the migration writes. Throws on zero sources. */
44699
- function buildDoorbellCompositionFeature(input) {
44700
- const sources = [...input.sources].toSorted((a, b) => `${a.source.addonId}/${a.source.stableId}#${a.cap}`.localeCompare(`${b.source.addonId}/${b.source.stableId}#${b.cap}`));
44701
- if (sources.length === 0) throw new Error("doorbell composition needs at least one source");
44702
- const edgeBindings = {};
44703
- sources.forEach((entry, index) => {
44704
- edgeBindings[`active${index}`] = {
44705
- kind: "from",
44706
- source: entry.source,
44707
- cap: entry.cap,
44708
- fieldPath: entry.activeField
44709
- };
44710
- edgeBindings[`changed${index}`] = {
44711
- kind: "from",
44712
- source: entry.source,
44713
- cap: entry.cap,
44714
- fieldPath: entry.changedAtField
44715
- };
44716
- });
44717
- return {
44718
- kind: "fields",
44719
- cap: "doorbell",
44720
- enabled: input.enabled,
44721
- fields: {
44722
- lastPressedAt: {
44723
- kind: "expression",
44724
- expr: `latest(${sources.map((_, index) => `rose(active${index}, changed${index}, ${DOORBELL_RISE_DEBOUNCE_MS})`).join(", ")})`,
44725
- bindings: edgeBindings
44726
- },
44727
- pressCountSinceStart: {
44728
- kind: "expression",
44729
- expr: "count(lastPressedAt)",
44730
- bindings: { lastPressedAt: {
44731
- kind: "from",
44732
- source: {
44733
- addonId: input.camera.addonId,
44734
- stableId: input.camera.stableId
44735
- },
44736
- cap: "doorbell",
44737
- fieldPath: "lastPressedAt"
44738
- } }
44739
- }
44740
- }
44741
- };
44742
- }
44743
- /**
44744
44617
  * Runtime defaults -- used by ConfigManager.get() for backward compatibility
44745
44618
  * until Plan B wires all runtime settings to the system_settings SQL table.
44746
44619
  *
@@ -57682,6 +57555,18 @@ var STATE_SUMMARY_VARS = [
57682
57555
  systemEventKinds: [...SUMMARISABLE],
57683
57556
  systemEventDeliveries: ["summary"]
57684
57557
  },
57558
+ {
57559
+ name: "summarySection",
57560
+ label: "Summary: this section",
57561
+ description: "The section a per-section summary row is about (D698); on a summary sent as one message, every section it reports, comma-separated.",
57562
+ example: "Batterie scariche",
57563
+ group: "system",
57564
+ families: ["rule"],
57565
+ fields: [...RULE_TEXT],
57566
+ kinds: ["system"],
57567
+ systemEventKinds: [...SUMMARISABLE],
57568
+ systemEventDeliveries: ["summary"]
57569
+ },
57685
57570
  ...STATE_SUMMARY_SECTION_VARS.map((v) => ({
57686
57571
  name: v.name,
57687
57572
  label: v.label,
@@ -58020,6 +57905,7 @@ TimelapseRuleInputSchema.extend({
58020
57905
  createdAt: zod.z.number(),
58021
57906
  updatedAt: zod.z.number()
58022
57907
  });
57908
+ MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
58023
57909
  zod.z.object({
58024
57910
  /**
58025
57911
  * Fraction of the box's own size added on EACH side before cutting.
@@ -58219,8 +58105,8 @@ DEFAULT_NATIVE_LEASE_SETTINGS.admission;
58219
58105
  * So `entry.providerAddonId === this.ctx.id` is silently, permanently false
58220
58106
  * for a builtin — and only for a builtin, which is why it survives a green
58221
58107
  * suite whose fake supplies the bare id. That is exactly how camera 615's
58222
- * virtual doorbell latched `unbound` for twelve hours on 2026-08-07 while its
58223
- * binding was intact ([D72](../../../../docs/decisions/adr-0072.md)).
58108
+ * virtual doorbell (retired since, D671) latched `unbound` for twelve hours on
58109
+ * 2026-08-07 while its binding was intact ([D72](../../../../docs/decisions/adr-0072.md)).
58224
58110
  *
58225
58111
  * Route every comparison between `ctx.id` and a registry/store addon id
58226
58112
  * through {@link isSameAddonId}. `scripts/check-addon-id-comparison.ts`
@@ -58232,10 +58118,6 @@ var ADDON_ID_PREFIX = "addon:";
58232
58118
  function bareAddonId(id) {
58233
58119
  return id.startsWith(ADDON_ID_PREFIX) ? id.slice(6) : id;
58234
58120
  }
58235
- /** True when both ids name the same addon, prefixed or not. */
58236
- function isSameAddonId(a, b) {
58237
- return bareAddonId(a) === bareAddonId(b);
58238
- }
58239
58121
  var MB = 1024 * 1024;
58240
58122
  1024 * MB, 3072 * MB;
58241
58123
  /**
@@ -58484,12 +58366,6 @@ Object.defineProperty(exports, "DAY_NAMES", {
58484
58366
  return DAY_NAMES;
58485
58367
  }
58486
58368
  });
58487
- Object.defineProperty(exports, "DEFAULT_FIRST_SIGHTING_FRESHNESS_MS", {
58488
- enumerable: true,
58489
- get: function() {
58490
- return DEFAULT_FIRST_SIGHTING_FRESHNESS_MS;
58491
- }
58492
- });
58493
58369
  Object.defineProperty(exports, "DERIVED_CAMERA_SILENCED_CAP_NAMES", {
58494
58370
  enumerable: true,
58495
58371
  get: function() {
@@ -58634,12 +58510,6 @@ Object.defineProperty(exports, "RecordingStatusSchema", {
58634
58510
  return RecordingStatusSchema;
58635
58511
  }
58636
58512
  });
58637
- Object.defineProperty(exports, "SOURCE_CAPS", {
58638
- enumerable: true,
58639
- get: function() {
58640
- return SOURCE_CAPS;
58641
- }
58642
- });
58643
58513
  Object.defineProperty(exports, "STORAGE_ACCESS_FALLBACK", {
58644
58514
  enumerable: true,
58645
58515
  get: function() {
@@ -58766,12 +58636,6 @@ Object.defineProperty(exports, "buildCapabilityLookup", {
58766
58636
  return buildCapabilityLookup;
58767
58637
  }
58768
58638
  });
58769
- Object.defineProperty(exports, "buildDoorbellCompositionFeature", {
58770
- enumerable: true,
58771
- get: function() {
58772
- return buildDoorbellCompositionFeature;
58773
- }
58774
- });
58775
58639
  Object.defineProperty(exports, "buildFfmpegArgs", {
58776
58640
  enumerable: true,
58777
58641
  get: function() {
@@ -58970,18 +58834,6 @@ Object.defineProperty(exports, "deviceStatusCapability", {
58970
58834
  return deviceStatusCapability;
58971
58835
  }
58972
58836
  });
58973
- Object.defineProperty(exports, "doorbellCapability", {
58974
- enumerable: true,
58975
- get: function() {
58976
- return doorbellCapability;
58977
- }
58978
- });
58979
- Object.defineProperty(exports, "doorbellFeatureSources", {
58980
- enumerable: true,
58981
- get: function() {
58982
- return doorbellFeatureSources;
58983
- }
58984
- });
58985
58837
  Object.defineProperty(exports, "emitDownForOwnedCaps", {
58986
58838
  enumerable: true,
58987
58839
  get: function() {
@@ -59024,12 +58876,6 @@ Object.defineProperty(exports, "evaluateCompositionField", {
59024
58876
  return evaluateCompositionField;
59025
58877
  }
59026
58878
  });
59027
- Object.defineProperty(exports, "evaluateSensorEdge", {
59028
- enumerable: true,
59029
- get: function() {
59030
- return evaluateSensorEdge;
59031
- }
59032
- });
59033
58879
  Object.defineProperty(exports, "evictionPolicyOfLocation", {
59034
58880
  enumerable: true,
59035
58881
  get: function() {
@@ -59120,18 +58966,6 @@ Object.defineProperty(exports, "isObjectInput", {
59120
58966
  return isObjectInput;
59121
58967
  }
59122
58968
  });
59123
- Object.defineProperty(exports, "isSameAddonId", {
59124
- enumerable: true,
59125
- get: function() {
59126
- return isSameAddonId;
59127
- }
59128
- });
59129
- Object.defineProperty(exports, "isSourceCap", {
59130
- enumerable: true,
59131
- get: function() {
59132
- return isSourceCap;
59133
- }
59134
- });
59135
58969
  Object.defineProperty(exports, "isVoidInput", {
59136
58970
  enumerable: true,
59137
58971
  get: function() {