homebridge-securitysystem 0.0.1-matter

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 (257) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.yml +46 -0
  3. package/.github/ISSUE_TEMPLATE/feature-request.yml +17 -0
  4. package/.github/ISSUE_TEMPLATE/questions-answers.yml +40 -0
  5. package/.github/demo.jpg +0 -0
  6. package/.github/dependabot.yml +19 -0
  7. package/.github/release.yml +36 -0
  8. package/.github/workflows/build-package.yml +49 -0
  9. package/.github/workflows/bump-version.yml +59 -0
  10. package/.github/workflows/dependency-review.yml +26 -0
  11. package/.github/workflows/publish.yml +155 -0
  12. package/.github/workflows/stale-activity.yml +27 -0
  13. package/AGENTS.md +247 -0
  14. package/CHANGELOG.md +572 -0
  15. package/CODE_OF_CONDUCT.md +128 -0
  16. package/CONTRIBUTING.md +5 -0
  17. package/LICENSE +21 -0
  18. package/README.md +47 -0
  19. package/SECURITY.md +12 -0
  20. package/config.schema.json +934 -0
  21. package/dist/conditions/already-triggered-condition.d.ts +10 -0
  22. package/dist/conditions/already-triggered-condition.js +22 -0
  23. package/dist/conditions/already-triggered-condition.js.map +1 -0
  24. package/dist/conditions/arming-in-progress-condition.d.ts +10 -0
  25. package/dist/conditions/arming-in-progress-condition.js +21 -0
  26. package/dist/conditions/arming-in-progress-condition.js.map +1 -0
  27. package/dist/conditions/arming-lock-condition.d.ts +10 -0
  28. package/dist/conditions/arming-lock-condition.js +43 -0
  29. package/dist/conditions/arming-lock-condition.js.map +1 -0
  30. package/dist/conditions/condition.d.ts +22 -0
  31. package/dist/conditions/condition.js +19 -0
  32. package/dist/conditions/condition.js.map +1 -0
  33. package/dist/conditions/double-knock-condition.d.ts +24 -0
  34. package/dist/conditions/double-knock-condition.js +73 -0
  35. package/dist/conditions/double-knock-condition.js.map +1 -0
  36. package/dist/conditions/not-armed-condition.d.ts +10 -0
  37. package/dist/conditions/not-armed-condition.js +26 -0
  38. package/dist/conditions/not-armed-condition.js.map +1 -0
  39. package/dist/conditions/trigger-already-running-condition.d.ts +10 -0
  40. package/dist/conditions/trigger-already-running-condition.js +21 -0
  41. package/dist/conditions/trigger-already-running-condition.js.map +1 -0
  42. package/dist/constants/default-constant.d.ts +24 -0
  43. package/dist/constants/default-constant.js +27 -0
  44. package/dist/constants/default-constant.js.map +1 -0
  45. package/dist/constants/homekit-constant.d.ts +2 -0
  46. package/dist/constants/homekit-constant.js +3 -0
  47. package/dist/constants/homekit-constant.js.map +1 -0
  48. package/dist/constants/matter-constant.d.ts +21 -0
  49. package/dist/constants/matter-constant.js +61 -0
  50. package/dist/constants/matter-constant.js.map +1 -0
  51. package/dist/handlers/sensor-handler.d.ts +24 -0
  52. package/dist/handlers/sensor-handler.js +63 -0
  53. package/dist/handlers/sensor-handler.js.map +1 -0
  54. package/dist/handlers/state-handler.d.ts +49 -0
  55. package/dist/handlers/state-handler.js +204 -0
  56. package/dist/handlers/state-handler.js.map +1 -0
  57. package/dist/handlers/switch-handler.d.ts +28 -0
  58. package/dist/handlers/switch-handler.js +95 -0
  59. package/dist/handlers/switch-handler.js.map +1 -0
  60. package/dist/handlers/trip-handler.d.ts +44 -0
  61. package/dist/handlers/trip-handler.js +156 -0
  62. package/dist/handlers/trip-handler.js.map +1 -0
  63. package/dist/homekit/homekit-registrar.d.ts +12 -0
  64. package/dist/homekit/homekit-registrar.js +23 -0
  65. package/dist/homekit/homekit-registrar.js.map +1 -0
  66. package/dist/homekit/service-factory.d.ts +12 -0
  67. package/dist/homekit/service-factory.js +24 -0
  68. package/dist/homekit/service-factory.js.map +1 -0
  69. package/dist/index.d.ts +3 -0
  70. package/dist/index.js +7 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/interfaces/condition-context-interface.d.ts +14 -0
  73. package/dist/interfaces/condition-context-interface.js +2 -0
  74. package/dist/interfaces/condition-context-interface.js.map +1 -0
  75. package/dist/interfaces/hap-types-interface.d.ts +9 -0
  76. package/dist/interfaces/hap-types-interface.js +2 -0
  77. package/dist/interfaces/hap-types-interface.js.map +1 -0
  78. package/dist/interfaces/mqtt-status-payload-interface.d.ts +6 -0
  79. package/dist/interfaces/mqtt-status-payload-interface.js +2 -0
  80. package/dist/interfaces/mqtt-status-payload-interface.js.map +1 -0
  81. package/dist/interfaces/options-interface.d.ts +87 -0
  82. package/dist/interfaces/options-interface.js +2 -0
  83. package/dist/interfaces/options-interface.js.map +1 -0
  84. package/dist/interfaces/persisted-state-interface.d.ts +6 -0
  85. package/dist/interfaces/persisted-state-interface.js +2 -0
  86. package/dist/interfaces/persisted-state-interface.js.map +1 -0
  87. package/dist/interfaces/service-registry-interface.d.ts +10 -0
  88. package/dist/interfaces/service-registry-interface.js +2 -0
  89. package/dist/interfaces/service-registry-interface.js.map +1 -0
  90. package/dist/interfaces/system-state-interface.d.ts +22 -0
  91. package/dist/interfaces/system-state-interface.js +2 -0
  92. package/dist/interfaces/system-state-interface.js.map +1 -0
  93. package/dist/schemas/arming-lock-schema.d.ts +10 -0
  94. package/dist/schemas/arming-lock-schema.js +12 -0
  95. package/dist/schemas/arming-lock-schema.js.map +1 -0
  96. package/dist/schemas/error-schema.d.ts +4 -0
  97. package/dist/schemas/error-schema.js +7 -0
  98. package/dist/schemas/error-schema.js.map +1 -0
  99. package/dist/schemas/mode-request-schema.d.ts +10 -0
  100. package/dist/schemas/mode-request-schema.js +18 -0
  101. package/dist/schemas/mode-request-schema.js.map +1 -0
  102. package/dist/schemas/status-response-schema.d.ts +7 -0
  103. package/dist/schemas/status-response-schema.js +10 -0
  104. package/dist/schemas/status-response-schema.js.map +1 -0
  105. package/dist/schemas/trip-mode-request-schema.d.ts +9 -0
  106. package/dist/schemas/trip-mode-request-schema.js +24 -0
  107. package/dist/schemas/trip-mode-request-schema.js.map +1 -0
  108. package/dist/security-system-platform.d.ts +19 -0
  109. package/dist/security-system-platform.js +56 -0
  110. package/dist/security-system-platform.js.map +1 -0
  111. package/dist/security-system.d.ts +28 -0
  112. package/dist/security-system.js +169 -0
  113. package/dist/security-system.js.map +1 -0
  114. package/dist/services/command-service.d.ts +16 -0
  115. package/dist/services/command-service.js +65 -0
  116. package/dist/services/command-service.js.map +1 -0
  117. package/dist/services/configuration-service.d.ts +24 -0
  118. package/dist/services/configuration-service.js +189 -0
  119. package/dist/services/configuration-service.js.map +1 -0
  120. package/dist/services/event-bus-service.d.ts +12 -0
  121. package/dist/services/event-bus-service.js +17 -0
  122. package/dist/services/event-bus-service.js.map +1 -0
  123. package/dist/services/matter-service.d.ts +69 -0
  124. package/dist/services/matter-service.js +363 -0
  125. package/dist/services/matter-service.js.map +1 -0
  126. package/dist/services/mqtt-service.d.ts +16 -0
  127. package/dist/services/mqtt-service.js +97 -0
  128. package/dist/services/mqtt-service.js.map +1 -0
  129. package/dist/services/server-service.d.ts +28 -0
  130. package/dist/services/server-service.js +275 -0
  131. package/dist/services/server-service.js.map +1 -0
  132. package/dist/services/storage-service.d.ts +14 -0
  133. package/dist/services/storage-service.js +70 -0
  134. package/dist/services/storage-service.js.map +1 -0
  135. package/dist/services/webhook-service.d.ts +16 -0
  136. package/dist/services/webhook-service.js +68 -0
  137. package/dist/services/webhook-service.js.map +1 -0
  138. package/dist/tests/conditions.test.d.ts +1 -0
  139. package/dist/tests/conditions.test.js +175 -0
  140. package/dist/tests/conditions.test.js.map +1 -0
  141. package/dist/tests/matter-service.test.d.ts +1 -0
  142. package/dist/tests/matter-service.test.js +475 -0
  143. package/dist/tests/matter-service.test.js.map +1 -0
  144. package/dist/tests/mqtt-service.test.d.ts +1 -0
  145. package/dist/tests/mqtt-service.test.js +168 -0
  146. package/dist/tests/mqtt-service.test.js.map +1 -0
  147. package/dist/tests/sensor-handler.test.d.ts +1 -0
  148. package/dist/tests/sensor-handler.test.js +87 -0
  149. package/dist/tests/sensor-handler.test.js.map +1 -0
  150. package/dist/tests/server-service.test.d.ts +1 -0
  151. package/dist/tests/server-service.test.js +188 -0
  152. package/dist/tests/server-service.test.js.map +1 -0
  153. package/dist/tests/state-handler.test.d.ts +1 -0
  154. package/dist/tests/state-handler.test.js +276 -0
  155. package/dist/tests/state-handler.test.js.map +1 -0
  156. package/dist/tests/switch-handler.test.d.ts +1 -0
  157. package/dist/tests/switch-handler.test.js +186 -0
  158. package/dist/tests/switch-handler.test.js.map +1 -0
  159. package/dist/tests/trip-handler.test.d.ts +1 -0
  160. package/dist/tests/trip-handler.test.js +179 -0
  161. package/dist/tests/trip-handler.test.js.map +1 -0
  162. package/dist/timers/timer-manager.d.ts +31 -0
  163. package/dist/timers/timer-manager.js +118 -0
  164. package/dist/timers/timer-manager.js.map +1 -0
  165. package/dist/types/event-payload-map-type.d.ts +15 -0
  166. package/dist/types/event-payload-map-type.js +2 -0
  167. package/dist/types/event-payload-map-type.js.map +1 -0
  168. package/dist/types/event-type.d.ts +44 -0
  169. package/dist/types/event-type.js +20 -0
  170. package/dist/types/event-type.js.map +1 -0
  171. package/dist/types/matter-accessory-key-type.d.ts +7 -0
  172. package/dist/types/matter-accessory-key-type.js +2 -0
  173. package/dist/types/matter-accessory-key-type.js.map +1 -0
  174. package/dist/types/mode-type.d.ts +4 -0
  175. package/dist/types/mode-type.js +2 -0
  176. package/dist/types/mode-type.js.map +1 -0
  177. package/dist/types/origin-type.d.ts +9 -0
  178. package/dist/types/origin-type.js +11 -0
  179. package/dist/types/origin-type.js.map +1 -0
  180. package/dist/types/security-state-type.d.ts +11 -0
  181. package/dist/types/security-state-type.js +13 -0
  182. package/dist/types/security-state-type.js.map +1 -0
  183. package/dist/types/sensor-kind-type.d.ts +2 -0
  184. package/dist/types/sensor-kind-type.js +2 -0
  185. package/dist/types/sensor-kind-type.js.map +1 -0
  186. package/dist/types/service-result-type.d.ts +4 -0
  187. package/dist/types/service-result-type.js +2 -0
  188. package/dist/types/service-result-type.js.map +1 -0
  189. package/dist/utils/arming-util.d.ts +19 -0
  190. package/dist/utils/arming-util.js +24 -0
  191. package/dist/utils/arming-util.js.map +1 -0
  192. package/dist/utils/state-util.d.ts +8 -0
  193. package/dist/utils/state-util.js +29 -0
  194. package/dist/utils/state-util.js.map +1 -0
  195. package/docs/MQTT.md +50 -0
  196. package/package.json +64 -0
  197. package/src/@types/homebridge-lib.d.ts +11 -0
  198. package/src/conditions/already-triggered-condition.ts +24 -0
  199. package/src/conditions/arming-in-progress-condition.ts +23 -0
  200. package/src/conditions/arming-lock-condition.ts +50 -0
  201. package/src/conditions/condition.ts +31 -0
  202. package/src/conditions/double-knock-condition.ts +84 -0
  203. package/src/conditions/not-armed-condition.ts +30 -0
  204. package/src/conditions/trigger-already-running-condition.ts +23 -0
  205. package/src/constants/default-constant.ts +28 -0
  206. package/src/constants/homekit-constant.ts +2 -0
  207. package/src/constants/matter-constant.ts +69 -0
  208. package/src/handlers/sensor-handler.ts +78 -0
  209. package/src/handlers/state-handler.ts +257 -0
  210. package/src/handlers/switch-handler.ts +111 -0
  211. package/src/handlers/trip-handler.ts +193 -0
  212. package/src/homekit/homekit-registrar.ts +28 -0
  213. package/src/homekit/service-factory.ts +35 -0
  214. package/src/index.ts +9 -0
  215. package/src/interfaces/condition-context-interface.ts +15 -0
  216. package/src/interfaces/hap-types-interface.ts +11 -0
  217. package/src/interfaces/mqtt-status-payload-interface.ts +6 -0
  218. package/src/interfaces/options-interface.ts +116 -0
  219. package/src/interfaces/persisted-state-interface.ts +7 -0
  220. package/src/interfaces/service-registry-interface.ts +11 -0
  221. package/src/interfaces/system-state-interface.ts +25 -0
  222. package/src/schemas/arming-lock-schema.ts +12 -0
  223. package/src/schemas/error-schema.ts +7 -0
  224. package/src/schemas/mode-request-schema.ts +18 -0
  225. package/src/schemas/status-response-schema.ts +10 -0
  226. package/src/schemas/trip-mode-request-schema.ts +24 -0
  227. package/src/security-system-platform.ts +68 -0
  228. package/src/security-system.ts +199 -0
  229. package/src/services/command-service.ts +76 -0
  230. package/src/services/configuration-service.ts +224 -0
  231. package/src/services/event-bus-service.ts +21 -0
  232. package/src/services/matter-service.ts +446 -0
  233. package/src/services/mqtt-service.ts +123 -0
  234. package/src/services/server-service.ts +327 -0
  235. package/src/services/storage-service.ts +80 -0
  236. package/src/services/webhook-service.ts +77 -0
  237. package/src/tests/conditions.test.ts +212 -0
  238. package/src/tests/matter-service.test.ts +610 -0
  239. package/src/tests/mqtt-service.test.ts +231 -0
  240. package/src/tests/sensor-handler.test.ts +116 -0
  241. package/src/tests/server-service.test.ts +238 -0
  242. package/src/tests/state-handler.test.ts +371 -0
  243. package/src/tests/switch-handler.test.ts +234 -0
  244. package/src/tests/trip-handler.test.ts +244 -0
  245. package/src/timers/timer-manager.ts +141 -0
  246. package/src/types/event-payload-map-type.ts +25 -0
  247. package/src/types/event-type.ts +61 -0
  248. package/src/types/matter-accessory-key-type.ts +33 -0
  249. package/src/types/mode-type.ts +4 -0
  250. package/src/types/origin-type.ts +9 -0
  251. package/src/types/security-state-type.ts +11 -0
  252. package/src/types/sensor-kind-type.ts +2 -0
  253. package/src/types/service-result-type.ts +4 -0
  254. package/src/utils/arming-util.ts +44 -0
  255. package/src/utils/state-util.ts +32 -0
  256. package/tsconfig.json +26 -0
  257. package/vitest.config.ts +8 -0
package/AGENTS.md ADDED
@@ -0,0 +1,247 @@
1
+ # AGENTS.md — Architecture & Development Rules
2
+
3
+ This document defines the architecture, naming conventions, and rules that all contributors and AI agents must follow when working on this codebase.
4
+
5
+ ---
6
+
7
+ ## Project Overview
8
+
9
+ `homebridge-securitysystem` is a Homebridge accessory plugin (not a platform plugin) that exposes a fully-featured security system to HomeKit. It is written in TypeScript with ESM modules.
10
+
11
+ The plugin:
12
+ - Exposes one `SecuritySystem` HAP service plus a configurable set of optional switch/sensor accessories (trip, mode, and arming-lock switches; motion sensors).
13
+ - Uses an event-driven architecture: core state changes emit domain events; side-effect services (webhook, command, MQTT) listen and react.
14
+ - Uses an abstract `Condition` class hierarchy to encapsulate all blocking-logic decisions.
15
+ - Provides an optional Hono HTTP server (zod-validated routes with OpenAPI and Scalar docs) for remote control.
16
+ - Optionally publishes status updates over MQTT.
17
+
18
+ ---
19
+
20
+ ## Source Layout
21
+
22
+ ```
23
+ src/
24
+ @types/ Third-party type declarations
25
+ conditions/ Abstract Condition base + concrete condition classes
26
+ constants/ Compile-time constants (no logic)
27
+ handlers/ Stateful handlers wired by security-system.ts
28
+ homekit/ HomeKit service construction + characteristic registration
29
+ interfaces/ TypeScript interfaces (plain object shapes)
30
+ schemas/ Zod schemas for HTTP server request/response validation
31
+ services/ Stateful services (webhook, command, MQTT, storage, server, event bus)
32
+ tests/ Vitest test suites
33
+ timers/ Centralised timer/interval ownership (TimerManager)
34
+ types/ TypeScript enums and type aliases
35
+ utils/ Pure utility functions
36
+ index.ts Homebridge plugin entry point
37
+ security-system.ts Root security-system class (hosts services on a PlatformAccessory)
38
+ security-system-platform.ts Dynamic platform plugin (DynamicPlatformPlugin)
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Layer Rules
44
+
45
+ | Layer | What belongs here | What does NOT belong |
46
+ |---|---|---|
47
+ | `types/` | Enums, type aliases | Logic, classes |
48
+ | `interfaces/` | Plain object shape interfaces | Logic, classes, enums |
49
+ | `constants/` | `const` objects, literal values | Logic, mutable state |
50
+ | `utils/` | Pure functions with no side effects | Classes, state, I/O |
51
+ | `conditions/` | Classes extending `Condition` | Handlers, services |
52
+ | `schemas/` | Zod schemas for HTTP validation | Logic, I/O |
53
+ | `timers/` | Timer/interval ownership (`TimerManager`) | Timer logic embedded in handlers |
54
+ | `homekit/` | HomeKit service construction, characteristic registration | State-machine logic |
55
+ | `services/` | Stateful singleton classes, I/O | Embedded types, enums, interfaces |
56
+ | `handlers/` | State-machine logic classes | Embedded types, enums, interfaces |
57
+
58
+ **Types, enums, and interfaces must never be defined inside a service or handler file.** Always create a separate file in `types/` or `interfaces/` and import it.
59
+
60
+ ---
61
+
62
+ ## File Naming Conventions
63
+
64
+ All filenames use **kebab-case** with a mandatory suffix describing their kind:
65
+
66
+ | Kind | Suffix | Example |
67
+ |---|---|---|
68
+ | Enum or type alias | `-type.ts` | `security-state-type.ts` |
69
+ | Interface | `-interface.ts` | `system-state-interface.ts` |
70
+ | Constant object | `-constant.ts` | `homekit-constant.ts` |
71
+ | Utility functions | `-util.ts` | `state-util.ts` |
72
+ | Zod schema | `-schema.ts` | `error-schema.ts` |
73
+ | Service class | `-service.ts` | `webhook-service.ts` |
74
+ | Handler class | `-handler.ts` | `state-handler.ts` |
75
+ | Condition class | `-condition.ts` | `double-knock-condition.ts` |
76
+ | HomeKit registrar class | `-registrar.ts` | `homekit-registrar.ts` |
77
+ | Factory function | `-factory.ts` | `service-factory.ts` |
78
+ | Timer manager class | `-manager.ts` | `timer-manager.ts` |
79
+ | Platform class | `-platform.ts` | `security-system-platform.ts` |
80
+ | Test suite | `.test.ts` | `conditions.test.ts` |
81
+
82
+ ---
83
+
84
+ ## Naming Conventions
85
+
86
+ ### No Abbreviations
87
+
88
+ **Variable names, parameter names, and type names must not use abbreviations.**
89
+
90
+ This rule applies everywhere: source files, test files, and any new code.
91
+
92
+ Forbidden examples and their correct replacements:
93
+
94
+ | Forbidden | Use instead |
95
+ |---|---|
96
+ | `s`, `st` for state | `state` |
97
+ | `o`, `opts` for options | `options` |
98
+ | `v` for value | `value` |
99
+ | `req` for request | `request` |
100
+ | `res` for response | `response` |
101
+ | `c` for context | `context` |
102
+ | `e` for error | `error` |
103
+ | `cb` for callback | `callback` |
104
+ | `fn` for function | the actual semantic name |
105
+ | `svc` for service | `service` |
106
+ | `char` for characteristic | `characteristic` |
107
+ | `Char` for Characteristic constructor | `Characteristic` |
108
+ | `Svc` for Service constructor | `Service` |
109
+ | `proc` for process | `process` |
110
+ | `dir` for directory | `directory` |
111
+ | `msg` for message | `message` |
112
+ | `buf` for buffer | `buffer` |
113
+ | `idx` for index | `index` |
114
+ | `len` for length | `length` |
115
+ | `num` for number | `number` or a semantic name |
116
+ | `str` for string | the semantic name |
117
+ | `tmp` for temporary | the semantic name |
118
+ | `args` for arguments | `arguments` (or a semantic name) |
119
+
120
+ **Exception:** loop variables `i`, `j`, `k` in tight numeric loops where the name carries no domain meaning are acceptable. All other names must be descriptive.
121
+
122
+ ### Class Names
123
+
124
+ - PascalCase, no abbreviations.
125
+ - Suffix matches the layer: `...Service`, `...Handler`, `...Condition`, `...Registrar`, `...Manager`.
126
+
127
+ ### Enum Members
128
+
129
+ - UPPER_SNAKE_CASE (e.g. `SecurityState.TRIGGERED`).
130
+
131
+ ### Interface Names
132
+
133
+ - PascalCase, no `I` prefix.
134
+ - Suffix: `...Interface` is not used in the filename's export — the filename carries it.
135
+
136
+ ---
137
+
138
+ ## Architecture: Event-Driven Side Effects
139
+
140
+ The core state machine (`StateHandler`, `TripHandler`, `SwitchHandler`) never calls the bus-attached side-effect services directly. Instead it emits domain events via `EventBusService`:
141
+
142
+ ```
143
+ StateHandler → bus.emit(EventType.CURRENT_CHANGED, payload)
144
+ ↓ ↓ ↓
145
+ WebhookService CommandService MqttService
146
+ ```
147
+
148
+ `StorageService` is constructor-injected into the handlers that need it; `WebhookService`, `CommandService`, and `MqttService` are purely event-driven and call `attachToBus(bus)` during construction in `security-system.ts`.
149
+
150
+ **New side effects must follow this pattern** — never add direct calls from handlers to services. The MQTT client is disconnected on shutdown via an `api.on('shutdown', ...)` hook wired in `security-system.ts`.
151
+
152
+ Events and their payload types are defined in `src/types/event-type.ts`. The mapping from event to payload is in `src/types/event-payload-map-type.ts`.
153
+
154
+ ---
155
+
156
+ ## Architecture: Condition System
157
+
158
+ All trip-blocking decisions use the `Condition` abstract base class:
159
+
160
+ ```typescript
161
+ abstract class Condition {
162
+ abstract readonly name: string;
163
+ protected _failureReason: string | undefined;
164
+ get failureReason(): string | undefined;
165
+ protected clearFailureReason(): void;
166
+ abstract evaluate(context: ConditionContext): boolean;
167
+ }
168
+ ```
169
+
170
+ `evaluate` returns `true` to **block** the action, `false` to **allow** it. Implementations must call `this.clearFailureReason()` at the top of `evaluate()` and set `this._failureReason` before returning `true`; callers read it via `failureReason` to surface the blocking reason to the user.
171
+
172
+ The context is the `ConditionContext` interface in `src/interfaces/condition-context-interface.ts` (`state`, `services`, `options`, `value`, `origin`, `log`).
173
+
174
+ Conditions are instantiated once inside `TripHandler` and evaluated in order: `NotArmedCondition`, `ArmingInProgressCondition`, `DoubleKnockCondition`, `AlreadyTriggeredCondition`, `TriggerAlreadyRunningCondition`. To add a new blocking rule, create a new file in `conditions/`, extend `Condition`, and add it to the list in `TripHandler`.
175
+
176
+ ---
177
+
178
+ ## Architecture: Handler Wiring & Dependency Resolution
179
+
180
+ Handlers are constructed in a fixed order in `security-system.ts` so no two handlers hold constructor references to each other:
181
+
182
+ 1. `SensorHandler` (leaf — no handler dependencies)
183
+ 2. `StateHandler` (depends on `SensorHandler`)
184
+ 3. `SwitchHandler` (one-way constructor dependency on `StateHandler`)
185
+ 4. `TripHandler` (depends on `SensorHandler`; no `StateHandler` reference)
186
+
187
+ Coordination between `StateHandler` ↔ `TripHandler` and `StateHandler` ↔ `SwitchHandler` is done through the event bus rather than setter injection:
188
+
189
+ - `switchHandler.subscribeToStateEvents(bus)` registers listeners for `RESET_MODE_SWITCHES` / `UPDATE_MODE_SWITCHES`.
190
+ - `security-system.ts` subscribes to `TRIGGER_FIRED` (calls `stateHandler.setCurrentState(TRIGGERED, origin)`) and `TRIP_CANCELLED` (falls back to OFF or `stateHandler.resetTimers()`).
191
+
192
+ Do not introduce constructor cycles or setter injection between handlers — wire cross-handler coordination through the bus.
193
+
194
+ ---
195
+
196
+ ## Code Style Rules
197
+
198
+ ### File Length
199
+ Maximum **400 lines** per file. Split into smaller focused files if the limit is reached.
200
+
201
+ ### Inline Guards
202
+ Use early-return guard clauses rather than deep nesting:
203
+ ```typescript
204
+ if (!value) {
205
+ return false;
206
+ }
207
+ ```
208
+
209
+ ### Imports
210
+ - Use `import type` for types and interfaces that are not needed at runtime.
211
+ - Always use `.js` extensions on relative imports (required for ESM).
212
+
213
+ ### Async
214
+ - Use `async`/`await`. Avoid `.then()` chains except when fire-and-forget is intentional.
215
+
216
+ ### Error Handling
217
+ - Validate only at system boundaries (config parsing, HTTP input, external storage).
218
+ - Do not add defensive null-checks for values guaranteed by the type system.
219
+
220
+ ### Timers
221
+ State-machine timers and intervals (arm, trigger, pause, double-knock, reset, tripped/triggered sensor polling) are owned by `TimerManager` (`src/timers/timer-manager.ts`). Handlers call its `set...Timer()` / `clear...Timer()` methods instead of creating raw timer handles, and never store timer handles in `SystemState`. Short-lived one-off delays (sensor pulses, HTTP-side trip deferral) may use raw `setTimeout`.
222
+
223
+ ---
224
+
225
+ ## Testing
226
+
227
+ - Package manager is **pnpm** (`pnpm-lock.yaml`).
228
+ - Framework: **Vitest** (`pnpm test`).
229
+ - Lint with **Oxlint** (`pnpm run lint`); typecheck with `pnpm run typecheck` (`tsc --noEmit`); build with `pnpm run build`.
230
+ - Test files live in `src/tests/` and are named `<subject>.test.ts`.
231
+ - Tests cover: conditions (all blocking paths), handler logic, and event-driven interactions.
232
+ - Mock only what is strictly necessary. Prefer structural mocks over full mock libraries.
233
+ - Do not test private methods directly — test through the public API.
234
+ - The HTTP server exposes its Hono app via `get app` for integration tests.
235
+
236
+ ---
237
+
238
+ ## Adding a New Feature
239
+
240
+ 1. **Types/interfaces first** — create files in `types/` or `interfaces/` before writing logic.
241
+ 2. **Condition** — if the feature blocks a trip, add a `Condition` subclass and register it in `TripHandler`.
242
+ 3. **Timer** — if the feature needs delays or polling, add methods to `TimerManager` rather than raw `setTimeout`/`setInterval` calls.
243
+ 4. **Service** — if the feature is a side effect, implement `attachToBus(bus)` and wire it in `security-system.ts`. If it holds a connection (e.g. MQTT), clean it up on `api.on('shutdown')`.
244
+ 5. **HTTP endpoint** — if the feature is exposed over the server, add a zod schema in `schemas/` and a route in `server-service.ts`.
245
+ 6. **Handler** — if the feature changes state-machine logic, modify the relevant handler and coordinate via the event bus.
246
+ 7. **Tests** — add a test covering the happy path and the main blocking/edge case.
247
+ 8. **No abbreviations** — all new identifiers must be fully spelled out.