@camstack/types 1.2.283 → 1.2.284

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 (37) hide show
  1. package/dist/addon.js +2 -2
  2. package/dist/addon.mjs +2 -2
  3. package/dist/capabilities/device-extension.cap.d.ts +8 -25
  4. package/dist/capabilities/device-manager.cap.d.ts +2 -2
  5. package/dist/capabilities/device-state.cap.d.ts +2 -2
  6. package/dist/capabilities/doorbell.cap.d.ts +13 -64
  7. package/dist/capabilities/index.d.ts +1 -1
  8. package/dist/capabilities/motion.cap.d.ts +9 -8
  9. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +147 -15
  10. package/dist/capabilities/pipeline-runner.cap.d.ts +2 -0
  11. package/dist/capabilities/ptz-autotrack.cap.d.ts +2 -3
  12. package/dist/catalogs/sensor-active-state.d.ts +8 -10
  13. package/dist/composition/presets/doorbell-feature.d.ts +1 -1
  14. package/dist/{composition-Dq42XWp0.mjs → composition-43aqsZQj.mjs} +6 -7
  15. package/dist/{composition-DG4jVTfB.js → composition-w3gBWMer.js} +6 -7
  16. package/dist/device/hub-motion-read.d.ts +22 -0
  17. package/dist/device-extension/slot-descriptor.d.ts +3 -3
  18. package/dist/enums/event-category.d.ts +9 -0
  19. package/dist/enums.js +1 -1
  20. package/dist/enums.mjs +1 -1
  21. package/dist/{event-category-C5xZWqz6.mjs → event-category-BGmz_oxW.mjs} +9 -0
  22. package/dist/{event-category-MbnB-6AN.js → event-category-DmS_tLsU.js} +9 -0
  23. package/dist/generated/extension-slots.d.ts +2 -6
  24. package/dist/index.d.ts +4 -1
  25. package/dist/index.js +252 -201
  26. package/dist/index.mjs +243 -199
  27. package/dist/interfaces/event-bus.d.ts +5 -3
  28. package/dist/interfaces/pipeline-runner-capability.d.ts +2 -0
  29. package/dist/node.js +1 -1
  30. package/dist/node.mjs +1 -1
  31. package/dist/pipeline/camera-sensors.d.ts +133 -9
  32. package/dist/pipeline/motion-ownership.d.ts +13 -0
  33. package/dist/pipeline/provider-motion.d.ts +33 -0
  34. package/dist/{sleep-gU6hqv4_.mjs → sleep-CDCONfkt.mjs} +1 -1
  35. package/dist/{sleep-DCbD8sdg.js → sleep-G4F3X9X_.js} +1 -1
  36. package/dist/utils/addon-id.d.ts +2 -2
  37. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-DCbD8sdg.js");
3
- const require_event_category = require("./event-category-MbnB-6AN.js");
4
- const require_composition = require("./composition-DG4jVTfB.js");
2
+ const require_sleep = require("./sleep-G4F3X9X_.js");
3
+ const require_event_category = require("./event-category-DmS_tLsU.js");
4
+ const require_composition = require("./composition-w3gBWMer.js");
5
5
  const require_err_msg = require("./err-msg-Dx2Kor0g.js");
6
6
  const require_enums = require("./enums.js");
7
7
  let zod = require("zod");
@@ -2538,7 +2538,7 @@ var RecordingTriggersSchema = zod.z.object({
2538
2538
  *
2539
2539
  * Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
2540
2540
  * `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
2541
- * la stessa che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
2541
+ * la stessa del campanello composto (D671). Ed è il FRONTE, non
2542
2542
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
2543
2543
  * registrare.
2544
2544
  */
@@ -14541,8 +14541,8 @@ var deviceManagerCapability = {
14541
14541
  * (`value` / `missing` / `failed`, see {@link WrapperActivationReadSchema}).
14542
14542
  *
14543
14543
  * The narrow read of the durable authority behind `getBindings`: a
14544
- * consumer that must know what the operator PERSISTED (virtual-doorbell,
14545
- * before it drops a press) asks this instead of pulling device-manager's
14544
+ * consumer that must know what the operator PERSISTED (before it drops
14545
+ * work on the strength of an unbind) asks this instead of pulling device-manager's
14546
14546
  * whole addon store through `addonSettingsRaw.getGlobal` — which answered
14547
14547
  * `{}` on every isolated-engine hub and so never worked (D675). It reads
14548
14548
  * ONE row (the activation map, ~1.6 KB), never the ~625 KB store.
@@ -14833,107 +14833,6 @@ var deviceManagerCapability = {
14833
14833
  }
14834
14834
  };
14835
14835
  //#endregion
14836
- //#region src/capabilities/doorbell.cap.ts
14837
- /**
14838
- * Doorbell button cap. Two kinds of providers coexist behind this cap
14839
- * name (same pattern as `snapshot`):
14840
- *
14841
- * - **Native** providers: registered per-device by device-driver
14842
- * addons via `ctx.registerNativeCap` — either on a
14843
- * `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
14844
- * or directly on the camera (Reolink registers at camera level).
14845
- * Emits an `onPressed` event every time the firmware pushes a
14846
- * ring; status tracks the last press and a pressCount since start
14847
- * (diagnostic).
14848
- *
14849
- * - **Wrapper** provider: the `virtual-doorbell` system builtin
14850
- * (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
14851
- * device (contact / switch / event-emitter …) into a doorbell for
14852
- * a camera. `defaultActive: false` — the operator explicitly binds
14853
- * it per camera in the device-bindings UI, then picks the source
14854
- * device + trigger in the per-device settings.
14855
- *
14856
- * The DeviceEventPropagator re-emits `onPressed` on the camera parent
14857
- * — subscribers listening at the camera level receive ring events
14858
- * with `via[]` populated. No code on the parent needed.
14859
- */
14860
- var DoorbellStatusSchema = zod.z.object({
14861
- /** Ms epoch of the last press. null = never observed since this provider started. */
14862
- lastPressedAt: zod.z.number().nullable(),
14863
- /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
14864
- pressCountSinceStart: zod.z.number()
14865
- });
14866
- var DoorbellPressEventSchema = zod.z.object({
14867
- deviceId: zod.z.number(),
14868
- timestamp: zod.z.number()
14869
- });
14870
- /**
14871
- * The `doorbell-source` extension slot's editable value (D648): the camera's
14872
- * virtual-doorbell trigger devices, exactly what the wrapper's
14873
- * `doorbellSources` persists (as `{ deviceId }` records).
14874
- */
14875
- var DoorbellSourceSlotValueSchema = zod.z.object({ deviceIds: zod.z.array(zod.z.number().int().positive()) });
14876
- /**
14877
- * The `doorbell-source` slot — which devices ring this camera's VIRTUAL
14878
- * doorbell, owned by the `virtual-doorbell` wrapper over its
14879
- * `doorbellSources` store (D648). A source rings when one of its
14880
- * `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
14881
- * different authority (`deviceManager.setWrapperActive`, D62) — the slot
14882
- * reports it, it never writes it.
14883
- */
14884
- var DOORBELL_SOURCE_SLOT = {
14885
- id: "doorbell-source",
14886
- label: "Doorbell trigger devices",
14887
- 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.",
14888
- shape: "association",
14889
- hostDeviceTypes: [require_err_msg.DeviceType.Camera],
14890
- sourceCaps: require_composition.SOURCE_CAPS,
14891
- cardinality: "many",
14892
- value: DoorbellSourceSlotValueSchema
14893
- };
14894
- var doorbellCapability = {
14895
- name: "doorbell",
14896
- scope: "device",
14897
- deviceNative: true,
14898
- mode: "singleton",
14899
- kind: "wrapper",
14900
- defaultActive: false,
14901
- deviceTypes: [require_err_msg.DeviceType.Button, require_err_msg.DeviceType.Camera],
14902
- extensionSlots: [DOORBELL_SOURCE_SLOT],
14903
- methods: {},
14904
- events: {
14905
- /**
14906
- * Fires once per physical press. Reolink delivers via Baichuan
14907
- * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
14908
- * release/duration — it's a pulse.
14909
- */
14910
- onPressed: {
14911
- data: DoorbellPressEventSchema,
14912
- fromState: {
14913
- field: "lastPressedAt",
14914
- on: "advance"
14915
- }
14916
- } },
14917
- status: {
14918
- schema: DoorbellStatusSchema,
14919
- kind: "push"
14920
- },
14921
- /**
14922
- * Runtime-state slice — last press timestamp + lifetime press count.
14923
- * Mirrored by the kernel and readable via
14924
- * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
14925
- * without subscribing.
14926
- */
14927
- runtimeState: DoorbellStatusSchema,
14928
- /**
14929
- * 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.
14930
- *
14931
- * See `RuntimeStateDurability`. Enforced by
14932
- * `scripts/check-runtime-state-durability.ts`.
14933
- */
14934
- durability: "restored"
14935
- };
14936
- //#endregion
14937
14836
  //#region src/generated/extension-slots.ts
14938
14837
  /**
14939
14838
  * AUTO-GENERATED by scripts/generate-extension-slots.ts — DO NOT EDIT.
@@ -14943,25 +14842,18 @@ onPressed: {
14943
14842
  * caps' descriptors and value schemas — never the caps barrel — so
14944
14843
  * `device-extension.cap.ts` can import this module without a cycle.
14945
14844
  *
14946
- * Coverage: 2 slot(s).
14845
+ * Coverage: 1 slot(s).
14947
14846
  */
14948
14847
  /** Every declared slot id, in table order. */
14949
- var EXTENSION_SLOT_IDS = ["doorbell-source", "linked-devices"];
14848
+ var EXTENSION_SLOT_IDS = ["linked-devices"];
14950
14849
  var ExtensionSlotIdSchema = zod.z.enum(EXTENSION_SLOT_IDS);
14951
- var EXTENSION_SLOTS = {
14952
- "doorbell-source": {
14953
- descriptor: DOORBELL_SOURCE_SLOT,
14954
- declaringCap: "doorbell",
14955
- hostDeviceTypes: [require_err_msg.DeviceType.Camera]
14956
- },
14957
- "linked-devices": {
14958
- descriptor: LINKED_DEVICES_SLOT,
14959
- declaringCap: "device-manager",
14960
- hostDeviceTypes: [require_err_msg.DeviceType.Camera]
14961
- }
14962
- };
14850
+ var EXTENSION_SLOTS = { "linked-devices": {
14851
+ descriptor: LINKED_DEVICES_SLOT,
14852
+ declaringCap: "device-manager",
14853
+ hostDeviceTypes: [require_err_msg.DeviceType.Camera]
14854
+ } };
14963
14855
  /** A slot's editable value, discriminated on `slot`. */
14964
- var ExtensionSlotValueSchema = zod.z.discriminatedUnion("slot", [DoorbellSourceSlotValueSchema.extend({ slot: zod.z.literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: zod.z.literal("linked-devices") })]);
14856
+ var ExtensionSlotValueSchema = zod.z.discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: zod.z.literal("linked-devices") })]);
14965
14857
  /** Every device type at least one slot is offered on — `device-extension`'s `deviceTypes`. */
14966
14858
  var EXTENSION_HOST_DEVICE_TYPES = [require_err_msg.DeviceType.Camera];
14967
14859
  //#endregion
@@ -14970,8 +14862,8 @@ var EXTENSION_HOST_DEVICE_TYPES = [require_err_msg.DeviceType.Camera];
14970
14862
  * `device-extension` — ONE place to see and author what a device is extended
14971
14863
  * with by OTHER devices (D648).
14972
14864
  *
14973
- * **A contract, not a store.** Every slot (`linked-devices`, `doorbell-source`)
14974
- * is declared by the cap that owns the function (`extensionSlots` in its
14865
+ * **A contract, not a store.** Every slot (today one: `linked-devices`) is
14866
+ * declared by the cap that owns the function (`extensionSlots` in its
14975
14867
  * `*.cap.ts`, D4) and SERVED by the addon that already owns its store, over
14976
14868
  * that store and its existing write path. This cap persists nothing and has no config method (guarded by
14977
14869
  * `scripts/check-extension-slots.ts`): D62 applied to the whole feature, and
@@ -14980,7 +14872,7 @@ var EXTENSION_HOST_DEVICE_TYPES = [require_err_msg.DeviceType.Camera];
14980
14872
  * **Shape.** A device-scoped wrapper COLLECTION, the D554 shape `videoclips`
14981
14873
  * uses: `resolveCapMount` gives it the `device-scoped` mount and
14982
14874
  * `requireDeviceCollection` dispatches it. Its providers are the slot owners
14983
- * (device-manager, virtual-doorbell, …), and a device's sources are its
14875
+ * (today device-manager alone), and a device's sources are its
14984
14876
  * bindings (step 0 of `getBindings`, derived, persisted nowhere).
14985
14877
  *
14986
14878
  * - `listSources` — one row per provider, the D554 source-listing convention
@@ -15040,10 +14932,11 @@ var ExtensionSlotSourceSchema = zod.z.object({
15040
14932
  });
15041
14933
  /**
15042
14934
  * A DIFFERENT authority that gates whether the slot's sources do anything —
15043
- * reported read-only, named, never written through this contract (D62). The
15044
- * virtual doorbell's sources ring only while the wrapper is bound to the
15045
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
15046
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
14935
+ * reported read-only, named, never written through this contract (D62) — e.g.
14936
+ * a slot whose sources act only while a wrapper is bound to the host, a
14937
+ * binding that belongs to `deviceManager.setWrapperActive`. No slot reports
14938
+ * one today (`linked-devices` answers `null`). `unknown` means the gate could
14939
+ * not be read; it is not "inactive" (D315).
15047
14940
  */
15048
14941
  var ExtensionSlotActivationSchema = zod.z.object({
15049
14942
  state: zod.z.enum([
@@ -15060,7 +14953,7 @@ var ExtensionSlotStateSchema = zod.z.object({
15060
14953
  /** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
15061
14954
  slotRef: zod.z.string(),
15062
14955
  slot: ExtensionSlotIdSchema,
15063
- /** Names the store written (D62), e.g. `virtual-doorbell:doorbellSources`. */
14956
+ /** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
15064
14957
  authority: zod.z.string(),
15065
14958
  activation: ExtensionSlotActivationSchema.nullable(),
15066
14959
  value: ExtensionSlotValueSchema,
@@ -15264,8 +15157,8 @@ var deviceStateCapability = {
15264
15157
  * Canonical cross-layer WRITE for one cap-keyed slice. Used by
15265
15158
  * everyone who needs to publish runtime state for a device:
15266
15159
  * device providers (via `BaseDevice.runtimeState.setCapState`
15267
- * which delegates here), system-scope addons (e.g. the pipeline
15268
- * runner writing the `motion` slice on phase transitions), and
15160
+ * which delegates here), system-scope addons (e.g. the orchestrator
15161
+ * writing a camera's effective `motion` slice, D696), and
15269
15162
  * direct cap-router callers in tests.
15270
15163
  *
15271
15164
  * Behavior on the hub:
@@ -22215,12 +22108,27 @@ var CameraSensorRefSchema = zod.z.object({
22215
22108
  function cameraSensorRefKey(ref) {
22216
22109
  return `${ref.addonId}/${ref.stableId}`;
22217
22110
  }
22218
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
22219
- var CameraSensorsModeSchema = zod.z.enum(["in-addition"]);
22220
- var CameraSensorsConfigSchema = zod.z.object({
22221
- mode: CameraSensorsModeSchema,
22222
- sensors: zod.z.array(CameraSensorRefSchema).min(1).max(8)
22223
- }).superRefine((value, ctx) => {
22111
+ /**
22112
+ * A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
22113
+ * `replace`: its motion is the camera's, and while one is READY the camera's
22114
+ * own motion no longer counts on its own. `confirm`: while one is READY the
22115
+ * camera's own motion counts only while a confirm sensor is active — with or
22116
+ * without a replace sensor (spec C2, the controller's ruling on Q1).
22117
+ */
22118
+ var CameraSensorModeSchema = zod.z.enum([
22119
+ "in-addition",
22120
+ "confirm",
22121
+ "replace"
22122
+ ]);
22123
+ /** A stored sensor with no mode — every CS-1 row — is in addition. */
22124
+ var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
22125
+ var CameraSensorEntrySchema = CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) });
22126
+ /**
22127
+ * A camera's attached sensors. A CS-1 row carried an envelope
22128
+ * `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
22129
+ * what the write stores (D672 rule 3), so it is never written again.
22130
+ */
22131
+ var CameraSensorsConfigSchema = zod.z.object({ sensors: zod.z.array(CameraSensorEntrySchema).min(1).max(8) }).superRefine((value, ctx) => {
22224
22132
  const seen = /* @__PURE__ */ new Set();
22225
22133
  value.sensors.forEach((ref, index) => {
22226
22134
  const key = cameraSensorRefKey(ref);
@@ -22232,39 +22140,41 @@ var CameraSensorsConfigSchema = zod.z.object({
22232
22140
  seen.add(key);
22233
22141
  });
22234
22142
  });
22235
- var RefFields = {
22143
+ /** Every attachment names its mode; an older orchestrator that does not is in addition. */
22144
+ var AttachmentFields = {
22236
22145
  addonId: zod.z.string(),
22237
- stableId: zod.z.string()
22146
+ stableId: zod.z.string(),
22147
+ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
22238
22148
  };
22239
22149
  var CameraSensorAttachmentSchema = zod.z.discriminatedUnion("state", [
22240
22150
  zod.z.object({
22241
- ...RefFields,
22151
+ ...AttachmentFields,
22242
22152
  state: zod.z.literal("ready"),
22243
22153
  deviceId: zod.z.number(),
22244
22154
  name: zod.z.string(),
22245
22155
  caps: zod.z.array(zod.z.string()).readonly()
22246
22156
  }),
22247
22157
  zod.z.object({
22248
- ...RefFields,
22158
+ ...AttachmentFields,
22249
22159
  state: zod.z.literal("offline"),
22250
22160
  deviceId: zod.z.number(),
22251
22161
  name: zod.z.string(),
22252
22162
  reason: zod.z.enum(["offline", "disabled"])
22253
22163
  }),
22254
22164
  zod.z.object({
22255
- ...RefFields,
22165
+ ...AttachmentFields,
22256
22166
  state: zod.z.literal("missing"),
22257
22167
  reason: zod.z.string()
22258
22168
  }),
22259
22169
  zod.z.object({
22260
- ...RefFields,
22170
+ ...AttachmentFields,
22261
22171
  state: zod.z.literal("unsupported"),
22262
22172
  deviceId: zod.z.number(),
22263
22173
  name: zod.z.string(),
22264
22174
  reason: zod.z.string()
22265
22175
  }),
22266
22176
  zod.z.object({
22267
- ...RefFields,
22177
+ ...AttachmentFields,
22268
22178
  state: zod.z.literal("unreadable"),
22269
22179
  reason: zod.z.string()
22270
22180
  })
@@ -22277,17 +22187,49 @@ var CameraSensorAttachmentSchema = zod.z.discriminatedUnion("state", [
22277
22187
  * say.
22278
22188
  */
22279
22189
  var CameraSensorsNotForwardedSchema = zod.z.enum(["onboard-not-listed", "no-active-detection"]);
22190
+ /** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
22191
+ var CameraOwnMotionSchema = zod.z.enum([
22192
+ "always",
22193
+ "when-confirmed",
22194
+ "never"
22195
+ ]);
22196
+ /** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
22197
+ var CameraSensorsNotEnforcedSchema = zod.z.enum(["provider-not-split"]);
22198
+ /**
22199
+ * The camera's OWN onboard motion (D696): the raw input the bridge combines,
22200
+ * never the effective value. The camera's `motion` slice is the effective one,
22201
+ * so a view of the camera's own row reads this.
22202
+ * - `atMs`: when the bridge received the camera's last own edge.
22203
+ * - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
22204
+ * AND it is younger than the window the bridge counts the camera's own motion
22205
+ * for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
22206
+ * sends a clear (heartbeats only) therefore reads `false` once no push came
22207
+ * within the window; a status read is a snapshot, so a live view re-asks.
22208
+ * Absent = this orchestrator has not received the camera's own motion since it
22209
+ * started (unknown, never "quiet"), or an orchestrator older than D696.
22210
+ */
22211
+ var CameraRawLevelSchema = zod.z.object({
22212
+ detected: zod.z.boolean(),
22213
+ atMs: zod.z.number()
22214
+ });
22280
22215
  var CameraSensorsStatusSchema = zod.z.discriminatedUnion("state", [
22281
- zod.z.object({ state: zod.z.literal("not-configured") }),
22216
+ zod.z.object({
22217
+ state: zod.z.literal("not-configured"),
22218
+ cameraLevel: CameraRawLevelSchema.optional()
22219
+ }),
22282
22220
  zod.z.object({
22283
22221
  state: zod.z.literal("unreadable"),
22284
- reason: zod.z.string()
22222
+ reason: zod.z.string(),
22223
+ cameraLevel: CameraRawLevelSchema.optional()
22285
22224
  }),
22286
22225
  zod.z.object({
22287
22226
  state: zod.z.literal("configured"),
22288
- mode: CameraSensorsModeSchema,
22227
+ cameraLevel: CameraRawLevelSchema.optional(),
22289
22228
  sensors: zod.z.array(CameraSensorAttachmentSchema).readonly(),
22290
- notForwarded: CameraSensorsNotForwardedSchema.optional()
22229
+ /** Absent = an orchestrator older than CS-2: it does not say (D315). */
22230
+ cameraMotion: CameraOwnMotionSchema.optional(),
22231
+ notForwarded: CameraSensorsNotForwardedSchema.optional(),
22232
+ notEnforced: CameraSensorsNotEnforcedSchema.optional()
22291
22233
  })
22292
22234
  ]);
22293
22235
  /** What a camera reads before its first settings read ANSWERED (D315). */
@@ -26631,8 +26573,10 @@ var RunnerCameraConfigSchema = zod.z.object({
26631
26573
  * - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
26632
26574
  * analyzer providers) running on decoded frames in this runner.
26633
26575
  * - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
26634
- * analytics). The provider emits ProviderMotion
26635
- * events; the orchestrator forwards to `reportMotion`.
26576
+ * analytics). The provider emits its raw push as `ProviderMotion`
26577
+ * (D673); the orchestrator's camera-sensors bridge re-emits the camera's
26578
+ * effective motion as `MotionOnMotionChanged`, which is what reaches
26579
+ * `reportMotion`.
26636
26580
  */
26637
26581
  motionSources: MotionSourcesSchema.default(["analyzer"]),
26638
26582
  /**
@@ -26997,7 +26941,15 @@ var RunnerLocalLoadSchema = zod.z.object({
26997
26941
  hasGpu: zod.z.boolean(),
26998
26942
  inferenceBackend: zod.z.string().optional(),
26999
26943
  cpuCores: zod.z.number().optional()
27000
- })
26944
+ }),
26945
+ /**
26946
+ * Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
26947
+ * runner never writes it. ABSENT = a runner older than D696, which still
26948
+ * writes the slice from its phase machine; the orchestrator does not write
26949
+ * while any runner node is absent-or-unanswered, so one slice never has two
26950
+ * writers.
26951
+ */
26952
+ motionSliceWriter: zod.z.literal("orchestrator").optional()
27001
26953
  });
27002
26954
  /**
27003
26955
  * Aggregate runtime metrics for the runner's whole local pool. Mirrors the
@@ -34859,6 +34811,72 @@ onStatusChanged: { data: zod.z.object({
34859
34811
  volatileStateFields: ["lastChangedAt"]
34860
34812
  };
34861
34813
  //#endregion
34814
+ //#region src/capabilities/doorbell.cap.ts
34815
+ var DoorbellStatusSchema = zod.z.object({
34816
+ /** Ms epoch of the last press. null = never observed since this provider started. */
34817
+ lastPressedAt: zod.z.number().nullable(),
34818
+ /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
34819
+ pressCountSinceStart: zod.z.number()
34820
+ });
34821
+ var DoorbellPressEventSchema = zod.z.object({
34822
+ deviceId: zod.z.number(),
34823
+ timestamp: zod.z.number()
34824
+ });
34825
+ /**
34826
+ * Doorbell button cap. Two kinds of provider serve it, and to every consumer
34827
+ * they are the same cap:
34828
+ *
34829
+ * - **Native** providers, registered per device by a driver addon through
34830
+ * `ctx.registerNativeCap` — a `DeviceType.Button` accessory with
34831
+ * `role: DeviceRole.Doorbell`, or the camera itself (Reolink registers at
34832
+ * camera level). They emit `onPressed` on every firmware ring.
34833
+ * - **A composed doorbell** (D670, D671): a camera's customization gives it
34834
+ * the cap, its `lastPressedAt` rising from the sources the operator
34835
+ * assigned in Customize → Doorbell, and the composer emits `onPressed`
34836
+ * from that field's advance (`fromState`).
34837
+ *
34838
+ * The DeviceEventPropagator re-emits `onPressed` on the camera parent.
34839
+ */
34840
+ var doorbellCapability = {
34841
+ name: "doorbell",
34842
+ scope: "device",
34843
+ deviceNative: true,
34844
+ mode: "singleton",
34845
+ deviceTypes: [require_err_msg.DeviceType.Button, require_err_msg.DeviceType.Camera],
34846
+ methods: {},
34847
+ events: {
34848
+ /**
34849
+ * Fires once per physical press. Reolink delivers via Baichuan
34850
+ * push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
34851
+ * release/duration — it's a pulse.
34852
+ */
34853
+ onPressed: {
34854
+ data: DoorbellPressEventSchema,
34855
+ fromState: {
34856
+ field: "lastPressedAt",
34857
+ on: "advance"
34858
+ }
34859
+ } },
34860
+ status: {
34861
+ schema: DoorbellStatusSchema,
34862
+ kind: "push"
34863
+ },
34864
+ /**
34865
+ * Runtime-state slice — last press timestamp + lifetime press count.
34866
+ * Mirrored by the kernel and readable via
34867
+ * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
34868
+ * without subscribing.
34869
+ */
34870
+ runtimeState: DoorbellStatusSchema,
34871
+ /**
34872
+ * 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.
34873
+ *
34874
+ * See `RuntimeStateDurability`. Enforced by
34875
+ * `scripts/check-runtime-state-durability.ts`.
34876
+ */
34877
+ durability: "restored"
34878
+ };
34879
+ //#endregion
34862
34880
  //#region src/capabilities/enum-sensor.cap.ts
34863
34881
  /**
34864
34882
  * Enum-state sensor — a string value picked from a finite option set.
@@ -37306,11 +37324,11 @@ authKey: zod.z.string().optional() }), zod.z.object({
37306
37324
  /**
37307
37325
  * How long after a camera's last rising edge `pipeline-analytics` closes the
37308
37326
  * motion EPISODE row it kept open for it (D475) — "il delay della cap", in
37309
- * the operator's words. One authority for the whole system: every provider
37310
- * that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
37311
- * constant while `detected: true` (never its own number — Hikvision used to
37312
- * seed its internal 3 s inactivity timer here, which is a different clock for
37313
- * a different purpose), and `MotionEpisodeTracker` imports it directly rather
37327
+ * the operator's words. One authority for the whole system: the writer of a
37328
+ * `Camera` device's slice (the orchestrator, D696) writes this SAME constant
37329
+ * in `autoClearAfterMs` while `detected: true` (never a camera's own clock —
37330
+ * Hikvision used to seed its internal 3 s inactivity timer here, a different
37331
+ * clock for a different purpose), and `MotionEpisodeTracker` imports it directly rather
37314
37332
  * than reading the live cap value on the hot path. A `Sensor` device (HA
37315
37333
  * binary sensor, Homematic) gets a genuine push both ways and has no episode
37316
37334
  * to close on a timer, so it writes `null`, never this constant.
@@ -37321,9 +37339,9 @@ var MotionStatusSchema = zod.z.object({
37321
37339
  /** Ms epoch of the last detected-true observation. Null if never detected. */
37322
37340
  lastDetectedAt: zod.z.number().nullable(),
37323
37341
  /**
37324
- * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
37325
- * `null` while false and on every `Sensor` device (D475) — see that
37326
- * constant's doc for the one-authority rule.
37342
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
37343
+ * orchestrator writes it, D696), `null` while false and on every `Sensor`
37344
+ * device (D475) — see that constant's doc for the one-authority rule.
37327
37345
  *
37328
37346
  * ## Reading this field still arms nothing
37329
37347
  *
@@ -37341,34 +37359,31 @@ var MotionStatusSchema = zod.z.object({
37341
37359
  * `addon-export-alexa/src/motion-clear-hold.ts` and
37342
37360
  * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
37343
37361
  *
37344
- * ## Who writes it
37362
+ * ## Who writes it (D696)
37345
37363
  *
37346
- * - **Cameras** — the runner's phase machine, `active → watching` on
37347
- * `cooldown_expired`, which then writes this slice with
37348
- * `detected: false` (`handlePhaseChanged` in
37349
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
37350
- * matters most for the sources that only ever push a rising one:
37351
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
37352
- * a false.
37353
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
37354
- * provider pushes the false itself, from the upstream system's own
37355
- * state change. No phase machine is involved.
37364
+ * - **Cameras** — the orchestrator, and nothing else. It writes the camera's
37365
+ * EFFECTIVE motion (`addon-pipeline-orchestrator`
37366
+ * `camera-sensors/motion-slice.ts`), alongside the effective
37367
+ * `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
37368
+ * for a split camera, an unsplit emitter's direct event, the analyzer and
37369
+ * device activity, as the camera's `motionSources` counts them. A source
37370
+ * that never sends a clear falls one window after its last `true`.
37371
+ * `lastDetectedAt` moves only on an effective rise. No camera provider
37372
+ * writes this slice (not even a boot seed), and neither does the runner's
37373
+ * phase machine: its phase is "a session is attached" for every on-motion
37374
+ * session, not motion, and it now has its own `DetectionPhaseTransition`.
37375
+ * A provider's `isDetected` answers from the hub's slice.
37376
+ * - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
37377
+ * imports) — the provider pushes both edges itself, from the upstream
37378
+ * system's own state change. That is the sensor's own fact.
37356
37379
  *
37357
- * ### The phase machine is CANONICAL, not sole — and that is a defect
37380
+ * `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
37381
+ * `motion` slice; each sensor writer is a named exemption.
37358
37382
  *
37359
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
37360
- * phase machine is the sole writer for a camera. It is not.
37361
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
37362
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
37363
- * Hikvision's comment says why: it read THIS docblock, agreed the
37364
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
37365
- * two authorities can disagree about one slice, which this repo
37366
- * forbids, and the doc said otherwise — which is worse than saying
37367
- * nothing, because it reads as verification.
37368
- *
37369
- * This predates D475 and is not fixed there: the fix touches every
37370
- * camera provider. Recorded in D475's Consequences. Do not restore the
37371
- * "sole writer" wording without also removing the other writers.
37383
+ * This replaces the D475 §4 rule ("the phase machine is the sole writer").
37384
+ * That rule was never true: Hikvision and Amcrest wrote the slice raw beside
37385
+ * it, every provider seeded it at boot, and every session left it
37386
+ * `detected: true` after its detach.
37372
37387
  */
37373
37388
  autoClearAfterMs: zod.z.number().nullable()
37374
37389
  });
@@ -37440,11 +37455,12 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
37440
37455
  */
37441
37456
  runtimeState: MotionStatusSchema,
37442
37457
  /**
37443
- * Runtime-state durability: **session** — every writer of this slice
37458
+ * Runtime-state durability: **session** — the writer of this slice
37444
37459
  * writes only on an EDGE, so a restored `detected: true` would stay
37445
37460
  * frozen until the next one instead of being corrected. The next edge
37446
- * re-publishes the real state. (On who the writers are, and why there
37447
- * is more than one, see `autoClearAfterMs` above.)
37461
+ * re-publishes the real state, and the orchestrator re-publishes every
37462
+ * camera when it starts writing (D696; on who writes it, see
37463
+ * `autoClearAfterMs` above).
37448
37464
  *
37449
37465
  * See `RuntimeStateDurability`. Enforced by
37450
37466
  * `scripts/check-runtime-state-durability.ts`.
@@ -40427,8 +40443,7 @@ var ptzAutotrackCapability = {
40427
40443
  deviceNative: true,
40428
40444
  mode: "singleton",
40429
40445
  /**
40430
- * Two kinds of provider behind one name, the same shape `snapshot` and
40431
- * `doorbell` use:
40446
+ * Two kinds of provider behind one name, the same shape `snapshot` uses:
40432
40447
  *
40433
40448
  * - **Native**: the camera's own firmware autotrack, registered per device by
40434
40449
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
@@ -40436,7 +40451,7 @@ var ptzAutotrackCapability = {
40436
40451
  * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
40437
40452
  * which follows a subject with `ptz.move` pulses driven by the detection
40438
40453
  * pipeline. `defaultActive: false` — the operator binds it per camera in
40439
- * the bindings UI, exactly as `virtual-doorbell` is bound.
40454
+ * the bindings UI.
40440
40455
  *
40441
40456
  * The binding chooses WHICH implementation answers; `setEnabled` stays the
40442
40457
  * one switch for the function either way. A camera never has both driving its
@@ -46659,10 +46674,10 @@ function feederCustomizationExample(sources) {
46659
46674
  //#endregion
46660
46675
  //#region src/composition/presets/doorbell-feature.ts
46661
46676
  /**
46662
- * The doorbell of a camera as a customization (D671). ONE builder: the
46663
- * virtual-doorbell migration and the admin UI's "Use as doorbell" preset both
46664
- * call it, so a preset can never write a shape the migration would refuse as
46665
- * "an unrelated existing block".
46677
+ * The doorbell of a camera as a customization (D671). ONE builder: the admin
46678
+ * UI's Customize → Doorbell card writes every doorbell feature through it, and
46679
+ * edits one only when it is exactly what this builds (`isDoorbellPresetFeature`)
46680
+ * — a composed doorbell has one canonical shape.
46666
46681
  */
46667
46682
  /** A rise closer than this to the previous one is the same press (D671). */
46668
46683
  var DOORBELL_RISE_DEBOUNCE_MS = 2e3;
@@ -46679,7 +46694,7 @@ function doorbellFeatureSources(source, boundCaps) {
46679
46694
  }];
46680
46695
  });
46681
46696
  }
46682
- /** The D671 feature, byte for byte what the migration writes. Throws on zero sources. */
46697
+ /** The canonical D671 doorbell feature. Throws on zero sources. */
46683
46698
  function buildDoorbellCompositionFeature(input) {
46684
46699
  const sources = [...input.sources].toSorted((a, b) => `${a.source.addonId}/${a.source.stableId}#${a.cap}`.localeCompare(`${b.source.addonId}/${b.source.stableId}#${b.cap}`));
46685
46700
  if (sources.length === 0) throw new Error("doorbell composition needs at least one source");
@@ -67150,6 +67165,35 @@ function readTimelapseGeneratedAt(rule, deviceId) {
67150
67165
  return rule.lastGeneratedAt ?? 0;
67151
67166
  }
67152
67167
  //#endregion
67168
+ //#region src/pipeline/motion-ownership.ts
67169
+ function cameraOwnsMotionViaSensor(motionSources) {
67170
+ return motionSources.some((s) => s !== "analyzer");
67171
+ }
67172
+ //#endregion
67173
+ //#region src/device/hub-motion-read.ts
67174
+ async function readHubMotionDetected(deviceState, deviceId) {
67175
+ let slice;
67176
+ try {
67177
+ slice = await deviceState.getCapSlice.query({
67178
+ deviceId,
67179
+ capName: "motion"
67180
+ });
67181
+ } catch (err) {
67182
+ const reason = err instanceof Error ? err.message : String(err);
67183
+ throw new Error(`motion state of device ${deviceId} is unreadable: ${reason}`, { cause: err });
67184
+ }
67185
+ const detected = slice?.["detected"];
67186
+ if (typeof detected !== "boolean") throw new Error(`motion state of device ${deviceId} is not published yet (unknown)`);
67187
+ return detected;
67188
+ }
67189
+ //#endregion
67190
+ //#region src/pipeline/provider-motion.ts
67191
+ /** The motion event's data without `triggeredBy`: only the bridge names a trigger. */
67192
+ var ProviderMotionDataSchema = MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
67193
+ function providerMotionEvent(source, data) {
67194
+ return require_sleep.createEvent(require_event_category.EventCategory.ProviderMotion, source, data);
67195
+ }
67196
+ //#endregion
67153
67197
  //#region src/pipeline/step-format-runnable.ts
67154
67198
  /** True when `model` ships a build for `format`. */
67155
67199
  function hasBuild(model, format) {
@@ -68039,8 +68083,8 @@ function bestLocationMatch(externalName, existing, threshold = .8) {
68039
68083
  * So `entry.providerAddonId === this.ctx.id` is silently, permanently false
68040
68084
  * for a builtin — and only for a builtin, which is why it survives a green
68041
68085
  * suite whose fake supplies the bare id. That is exactly how camera 615's
68042
- * virtual doorbell latched `unbound` for twelve hours on 2026-08-07 while its
68043
- * binding was intact ([D72](../../../../docs/decisions/adr-0072.md)).
68086
+ * virtual doorbell (retired since, D671) latched `unbound` for twelve hours on
68087
+ * 2026-08-07 while its binding was intact ([D72](../../../../docs/decisions/adr-0072.md)).
68044
68088
  *
68045
68089
  * Route every comparison between `ctx.id` and a registry/store addon id
68046
68090
  * through {@link isSameAddonId}. `scripts/check-addon-id-comparison.ts`
@@ -69466,12 +69510,16 @@ exports.CameraMetricsSchema = CameraMetricsSchema;
69466
69510
  exports.CameraMetricsWithDeviceIdSchema = CameraMetricsWithDeviceIdSchema;
69467
69511
  exports.CameraMotionStatusSchema = CameraMotionStatusSchema;
69468
69512
  exports.CameraOccupancySnapshotForDeviceSchema = CameraOccupancySnapshotForDeviceSchema;
69513
+ exports.CameraOwnMotionSchema = CameraOwnMotionSchema;
69514
+ exports.CameraRawLevelSchema = CameraRawLevelSchema;
69469
69515
  exports.CameraRecordingModeSchema = CameraRecordingModeSchema;
69470
69516
  exports.CameraRecordingStatusSchema = CameraRecordingStatusSchema;
69471
69517
  exports.CameraSensorAttachmentSchema = CameraSensorAttachmentSchema;
69518
+ exports.CameraSensorEntrySchema = CameraSensorEntrySchema;
69519
+ exports.CameraSensorModeSchema = CameraSensorModeSchema;
69472
69520
  exports.CameraSensorRefSchema = CameraSensorRefSchema;
69473
69521
  exports.CameraSensorsConfigSchema = CameraSensorsConfigSchema;
69474
- exports.CameraSensorsModeSchema = CameraSensorsModeSchema;
69522
+ exports.CameraSensorsNotEnforcedSchema = CameraSensorsNotEnforcedSchema;
69475
69523
  exports.CameraSensorsNotForwardedSchema = CameraSensorsNotForwardedSchema;
69476
69524
  exports.CameraSensorsStatusSchema = CameraSensorsStatusSchema;
69477
69525
  exports.CameraSourceStatusSchema = CameraSourceStatusSchema;
@@ -69611,6 +69659,7 @@ exports.DECLARED_DEVICE_SWEEP_LIMIT = DECLARED_DEVICE_SWEEP_LIMIT;
69611
69659
  exports.DECLARED_INTEGRATION_FIXED_KEY = DECLARED_INTEGRATION_FIXED_KEY;
69612
69660
  exports.DEFAULT_ADDON_PLACEMENT = DEFAULT_ADDON_PLACEMENT;
69613
69661
  exports.DEFAULT_AUDIO_ANALYZER_CONFIG = DEFAULT_AUDIO_ANALYZER_CONFIG;
69662
+ exports.DEFAULT_CAMERA_SENSOR_MODE = DEFAULT_CAMERA_SENSOR_MODE;
69614
69663
  exports.DEFAULT_CLUSTER_STEP_MODELS = DEFAULT_CLUSTER_STEP_MODELS;
69615
69664
  exports.DEFAULT_CLUSTER_STEP_SETTINGS = DEFAULT_CLUSTER_STEP_SETTINGS;
69616
69665
  exports.DEFAULT_DECODER_HWACCEL_CONFIG = DEFAULT_DECODER_HWACCEL_CONFIG;
@@ -69652,7 +69701,6 @@ exports.DISPLAY_ICON_KEYS = DISPLAY_ICON_KEYS;
69652
69701
  exports.DISPLAY_ICON_OFFERED_KEYS = DISPLAY_ICON_OFFERED_KEYS;
69653
69702
  exports.DISPLAY_ICON_VOCABULARY = DISPLAY_ICON_VOCABULARY;
69654
69703
  exports.DOORBELL_RISE_DEBOUNCE_MS = DOORBELL_RISE_DEBOUNCE_MS;
69655
- exports.DOORBELL_SOURCE_SLOT = DOORBELL_SOURCE_SLOT;
69656
69704
  exports.DataStoreEngineInfoSchema = EngineInfoSchema;
69657
69705
  exports.DatabaseSnapshotResultSchema = DatabaseSnapshotResultSchema;
69658
69706
  exports.DatabaseSnapshotTableSchema = DatabaseSnapshotTableSchema;
@@ -69697,7 +69745,6 @@ exports.DiscoveredTargetSchema = DiscoveredTargetSchema;
69697
69745
  exports.DiskReconcileJobSchema = DiskReconcileJobSchema;
69698
69746
  exports.DisposerChain = require_sleep.DisposerChain;
69699
69747
  exports.DoorbellPressEventSchema = DoorbellPressEventSchema;
69700
- exports.DoorbellSourceSlotValueSchema = DoorbellSourceSlotValueSchema;
69701
69748
  exports.DoorbellStatusSchema = DoorbellStatusSchema;
69702
69749
  exports.EMBEDDING_MODEL_FAILED = EMBEDDING_MODEL_FAILED;
69703
69750
  exports.EMBEDDING_REQUIREMENTS_FAILED = EMBEDDING_REQUIREMENTS_FAILED;
@@ -70290,6 +70337,7 @@ exports.PrivacyMaskStatusSchema = PrivacyMaskStatusSchema;
70290
70337
  exports.ProfileRtspEntrySchema = require_sleep.ProfileRtspEntrySchema;
70291
70338
  exports.ProfileSlotSchema = require_sleep.ProfileSlotSchema;
70292
70339
  exports.ProfileSlotStatusSchema = require_sleep.ProfileSlotStatusSchema;
70340
+ exports.ProviderMotionDataSchema = ProviderMotionDataSchema;
70293
70341
  exports.ProviderStatusSchema = ProviderStatusSchema;
70294
70342
  exports.PtzAutotrackRuntimeStateSchema = PtzAutotrackRuntimeStateSchema;
70295
70343
  exports.PtzAutotrackSettingsSchema = PtzAutotrackSettingsSchema;
@@ -70745,6 +70793,7 @@ exports.builtCapabilityBlockers = builtCapabilityBlockers;
70745
70793
  exports.buttonCapability = buttonCapability;
70746
70794
  exports.cameraCredentialsCapability = cameraCredentialsCapability;
70747
70795
  exports.cameraGridLayoutCapability = cameraGridLayoutCapability;
70796
+ exports.cameraOwnsMotionViaSensor = cameraOwnsMotionViaSensor;
70748
70797
  exports.cameraPipelineConfigCapability = cameraPipelineConfigCapability;
70749
70798
  exports.cameraSensorRefKey = cameraSensorRefKey;
70750
70799
  exports.cameraStreamsCapability = cameraStreamsCapability;
@@ -71119,6 +71168,7 @@ exports.pressureSensorCapability = pressureSensorCapability;
71119
71168
  exports.principalMayReachAddon = principalMayReachAddon;
71120
71169
  exports.privacyMaskCapability = privacyMaskCapability;
71121
71170
  exports.procedureAuthKey = procedureAuthKey;
71171
+ exports.providerMotionEvent = providerMotionEvent;
71122
71172
  exports.ptzAutotrackCapability = ptzAutotrackCapability;
71123
71173
  exports.ptzCapability = ptzCapability;
71124
71174
  exports.publicEventCategories = require_event_category.publicEventCategories;
@@ -71128,6 +71178,7 @@ exports.readClusterStepModels = readClusterStepModels;
71128
71178
  exports.readClusterStepSettings = readClusterStepSettings;
71129
71179
  exports.readDetailCropConvention = readDetailCropConvention;
71130
71180
  exports.readDeviceStateFrom = readDeviceStateFrom;
71181
+ exports.readHubMotionDetected = readHubMotionDetected;
71131
71182
  exports.readMotionTriggeredBy = readMotionTriggeredBy;
71132
71183
  exports.readNativeLeaseOverride = readNativeLeaseOverride;
71133
71184
  exports.readNodePin = require_sleep.readNodePin;