@mhome/app-facade-protocol 1.89.0 → 1.91.0

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.
@@ -44,10 +44,23 @@ Standard device facts currently cover `state.water` (`wet` / `dry`) and
44
44
  `state.smoke` (`detected` / `clear`). Other readings, including unknown/testing,
45
45
  do not create an incident and do not resolve an existing incident. The small
46
46
  retained device projection preserves the episode across restarts and Space
47
- execution-mode migration. Its revision is owned by Attention, independent of
48
- rebuildable Twin cache counters. Each reconciliation reads current canonical
49
- Twin state, including retained-history recovery, rather than replaying event
50
- payloads. Device metadata and retained episodes cover cold/missing caches.
47
+ execution-mode migration. Its CAS revision is owned by Attention, independent of rebuildable Twin cache
48
+ counters. Source ingestion persists a bounded set of ordered facts before
49
+ replacing the Twin cache: latest report, latest detection and latest explicit
50
+ clear. Their sequence comes from the existing durable `nowAttention` increment
51
+ namespace. A delayed detection survives a newer unknown, but cannot supersede a
52
+ newer explicit clear. A delayed clear cannot remove a newer detection. Unknown
53
+ changes verification only. The incident ID remains stable until another clear
54
+ boundary is observed. Delayed notification delivery does not replay source facts.
55
+
56
+ List reconciliation only bootstraps an absent source record from canonical Twin
57
+ state, including retained-history recovery; a live source record wins a racing
58
+ bootstrap. Afterwards the durable source facts own lifecycle state. Device
59
+ metadata and retained episodes cover cold/missing caches. The portable Attention
60
+ payload contains `entityId`, `capabilityId`, and nullable `latest`, `detected`,
61
+ `cleared` observations (`sequence`, `timestamp`, `value`). Imports preserve the
62
+ source sequence floor as well as the separate CAS revision. This keeps source
63
+ ordering valid after a restart, a cache rebuild and execution-mode migration.
51
64
 
52
65
  All timestamps are epoch milliseconds. `sourceUpdatedAt` is the underlying
53
66
  state-change timestamp, not a heartbeat or proof of current connectivity.
@@ -56,17 +56,43 @@ observation and graph reads preserve their bounded business fields in Essential.
56
56
 
57
57
  ## UI-only reads
58
58
 
59
- - `/app/device/view/get`, `{deviceId}`: `{deviceId, primaryViewId, views, widgets}`.
59
+ - `/app/device/view/get`, `{deviceId}`: `{deviceId, primaryViewId, views, widgets, twin}`.
60
60
  Reads the unique hidden Device render definition, preserving exact Widget/Twin
61
61
  bindings. Disabled devices can have empty definitions. Multiple anchors fail rather
62
- than choosing an arbitrary visible Card. Widgets contain configuration, never values.
62
+ than choosing an arbitrary visible Card. Widgets contain configuration, never values. The optional `twin` metadata is
63
+ returned even when a known device has no render anchor.
63
64
  - `/app/device/history/series/get`, `{deviceId, capabilityIds?: []}`:
64
65
  `{deviceId, series}` with per-capability decoded `states`, `events`, `logs`. Empty
65
66
  capability selection reads all series. This preserves chart structure; the shared
66
67
  Device history Facade remains a bounded, merged business timeline.
67
68
  - `/app/dashboard/canvas/content/get`, `{canvasId}`: Canvas metadata plus Cards' static
68
69
  titles, view bindings, layouts, settings, metadata and selected Widget definitions.
69
- No observations, history, related automations or suggestions are loaded.
70
+ Each entity Card includes the same `twin` metadata, loaded once per entity in the
71
+ batch. No observations, history, related automations or suggestions are loaded.
72
+
73
+ `twin` contains `entityId`, `entityType`, `integration`, `capabilities`,
74
+ `blueprintIds`, `attributes`, `available`, and optional `problem`. Attributes use canonical data capability IDs as
75
+ keys and preserve typed JSON values, including units, false, zero and empty arrays.
76
+ Only immutable entity-only data is cached here; mutable or parameterized data is
77
+ invoked explicitly when needed. These reads never fetch providers. A missing key
78
+ means no cached value is available, not a null/empty result. Group `available` is
79
+ true only for a ready definition whose runtime revision matches its Twin projection.
80
+ Applying/failed/mismatched projections return empty `attributes` and a `problem`,
81
+ while retaining identity and capability/Blueprint IDs for rendering. Clients disable
82
+ actions while unavailable, including retained Widget controls. This availability is
83
+ projection usability, not device reachability or on/off state. Retained observations
84
+ and history are not deleted by projection failure.
85
+
86
+ Ordinary source synchronization preserves immutable caches for unchanged entity
87
+ identity/connection/capabilities. Schema publication does not expire them. Explicit
88
+ device refresh rereads attributes and reconciles related Groups. These are write-side
89
+ operations; definition reads remain cache-only.
90
+
91
+ `/twin/entityUpdate` pushes `{scopeId, entityIds}` after cached metadata changes.
92
+ Open views refresh their definition batches; other Spaces and unrelated entities
93
+ are ignored. A new definition owns its capability membership: old observations
94
+ for removed capabilities must not be merged back into the current view. History
95
+ remains available through its separate read.
70
96
 
71
97
  The frontend batches selected observations after reading definitions. Automation Cards
72
98
  also read selected Automation definitions for their name and type. Canvas IDs are
@@ -0,0 +1,335 @@
1
+ [
2
+ {
3
+ "name": "unknown alone does not create water incident",
4
+ "capabilityId": "basic.device.state.water",
5
+ "observations": [
6
+ {
7
+ "sequence": 1,
8
+ "timestamp": 1000,
9
+ "value": "unknown"
10
+ }
11
+ ],
12
+ "active": false,
13
+ "verificationRequired": false,
14
+ "episodeClearSequence": 0,
15
+ "sourceUpdatedAt": 1000,
16
+ "persisted": {
17
+ "entityId": "sensor",
18
+ "capabilityId": "basic.device.state.water",
19
+ "latest": {
20
+ "sequence": 1,
21
+ "timestamp": 1000,
22
+ "value": "unknown"
23
+ },
24
+ "detected": null,
25
+ "cleared": null
26
+ }
27
+ },
28
+ {
29
+ "name": "delayed first wet survives unknown",
30
+ "capabilityId": "basic.device.state.water",
31
+ "observations": [
32
+ {
33
+ "sequence": 2,
34
+ "timestamp": 2000,
35
+ "value": "unknown"
36
+ },
37
+ {
38
+ "sequence": 1,
39
+ "timestamp": 1000,
40
+ "value": "wet"
41
+ }
42
+ ],
43
+ "active": true,
44
+ "verificationRequired": true,
45
+ "episodeClearSequence": 0,
46
+ "sourceUpdatedAt": 2000,
47
+ "persisted": {
48
+ "entityId": "sensor",
49
+ "capabilityId": "basic.device.state.water",
50
+ "latest": {
51
+ "sequence": 2,
52
+ "timestamp": 2000,
53
+ "value": "unknown"
54
+ },
55
+ "detected": {
56
+ "sequence": 1,
57
+ "timestamp": 1000,
58
+ "value": "wet"
59
+ },
60
+ "cleared": null
61
+ }
62
+ },
63
+ {
64
+ "name": "delayed wet cannot revive cleared incident",
65
+ "capabilityId": "basic.device.state.water",
66
+ "observations": [
67
+ {
68
+ "sequence": 3,
69
+ "timestamp": 3000,
70
+ "value": "unknown"
71
+ },
72
+ {
73
+ "sequence": 2,
74
+ "timestamp": 2000,
75
+ "value": "dry"
76
+ },
77
+ {
78
+ "sequence": 1,
79
+ "timestamp": 1000,
80
+ "value": "wet"
81
+ }
82
+ ],
83
+ "active": false,
84
+ "verificationRequired": false,
85
+ "episodeClearSequence": 0,
86
+ "sourceUpdatedAt": 1000,
87
+ "persisted": {
88
+ "entityId": "sensor",
89
+ "capabilityId": "basic.device.state.water",
90
+ "latest": {
91
+ "sequence": 3,
92
+ "timestamp": 3000,
93
+ "value": "unknown"
94
+ },
95
+ "detected": {
96
+ "sequence": 1,
97
+ "timestamp": 1000,
98
+ "value": "wet"
99
+ },
100
+ "cleared": {
101
+ "sequence": 2,
102
+ "timestamp": 2000,
103
+ "value": "dry"
104
+ }
105
+ }
106
+ },
107
+ {
108
+ "name": "delayed clear cannot remove newer wet",
109
+ "capabilityId": "basic.device.state.water",
110
+ "observations": [
111
+ {
112
+ "sequence": 3,
113
+ "timestamp": 3000,
114
+ "value": "wet"
115
+ },
116
+ {
117
+ "sequence": 1,
118
+ "timestamp": 1000,
119
+ "value": "wet"
120
+ },
121
+ {
122
+ "sequence": 2,
123
+ "timestamp": 2000,
124
+ "value": "dry"
125
+ }
126
+ ],
127
+ "active": true,
128
+ "verificationRequired": false,
129
+ "episodeClearSequence": 2,
130
+ "sourceUpdatedAt": 3000,
131
+ "persisted": {
132
+ "entityId": "sensor",
133
+ "capabilityId": "basic.device.state.water",
134
+ "latest": {
135
+ "sequence": 3,
136
+ "timestamp": 3000,
137
+ "value": "wet"
138
+ },
139
+ "detected": {
140
+ "sequence": 3,
141
+ "timestamp": 3000,
142
+ "value": "wet"
143
+ },
144
+ "cleared": {
145
+ "sequence": 2,
146
+ "timestamp": 2000,
147
+ "value": "dry"
148
+ }
149
+ }
150
+ },
151
+ {
152
+ "name": "new episode remains under verification",
153
+ "capabilityId": "basic.device.state.water",
154
+ "observations": [
155
+ {
156
+ "sequence": 3,
157
+ "timestamp": 3000,
158
+ "value": "unknown"
159
+ },
160
+ {
161
+ "sequence": 2,
162
+ "timestamp": 2000,
163
+ "value": "wet"
164
+ },
165
+ {
166
+ "sequence": 1,
167
+ "timestamp": 1000,
168
+ "value": "dry"
169
+ }
170
+ ],
171
+ "active": true,
172
+ "verificationRequired": true,
173
+ "episodeClearSequence": 1,
174
+ "sourceUpdatedAt": 3000,
175
+ "persisted": {
176
+ "entityId": "sensor",
177
+ "capabilityId": "basic.device.state.water",
178
+ "latest": {
179
+ "sequence": 3,
180
+ "timestamp": 3000,
181
+ "value": "unknown"
182
+ },
183
+ "detected": {
184
+ "sequence": 2,
185
+ "timestamp": 2000,
186
+ "value": "wet"
187
+ },
188
+ "cleared": {
189
+ "sequence": 1,
190
+ "timestamp": 1000,
191
+ "value": "dry"
192
+ }
193
+ }
194
+ },
195
+ {
196
+ "name": "duplicate wet is not proof of freshness",
197
+ "capabilityId": "basic.device.state.water",
198
+ "observations": [
199
+ {
200
+ "sequence": 1,
201
+ "timestamp": 1000,
202
+ "value": "wet"
203
+ },
204
+ {
205
+ "sequence": 2,
206
+ "timestamp": 2000,
207
+ "value": "wet"
208
+ }
209
+ ],
210
+ "active": true,
211
+ "verificationRequired": false,
212
+ "episodeClearSequence": 0,
213
+ "sourceUpdatedAt": 1000,
214
+ "persisted": {
215
+ "entityId": "sensor",
216
+ "capabilityId": "basic.device.state.water",
217
+ "latest": {
218
+ "sequence": 2,
219
+ "timestamp": 1000,
220
+ "value": "wet"
221
+ },
222
+ "detected": {
223
+ "sequence": 2,
224
+ "timestamp": 1000,
225
+ "value": "wet"
226
+ },
227
+ "cleared": null
228
+ }
229
+ },
230
+ {
231
+ "name": "testing alone does not create smoke incident",
232
+ "capabilityId": "basic.device.state.smoke",
233
+ "observations": [
234
+ {
235
+ "sequence": 1,
236
+ "timestamp": 1000,
237
+ "value": "testing"
238
+ }
239
+ ],
240
+ "active": false,
241
+ "verificationRequired": false,
242
+ "episodeClearSequence": 0,
243
+ "sourceUpdatedAt": 1000,
244
+ "persisted": {
245
+ "entityId": "sensor",
246
+ "capabilityId": "basic.device.state.smoke",
247
+ "latest": {
248
+ "sequence": 1,
249
+ "timestamp": 1000,
250
+ "value": "testing"
251
+ },
252
+ "detected": null,
253
+ "cleared": null
254
+ }
255
+ },
256
+ {
257
+ "name": "smoke testing preserves detected incident",
258
+ "capabilityId": "basic.device.state.smoke",
259
+ "observations": [
260
+ {
261
+ "sequence": 1,
262
+ "timestamp": 1000,
263
+ "value": "detected"
264
+ },
265
+ {
266
+ "sequence": 2,
267
+ "timestamp": 2000,
268
+ "value": "testing"
269
+ }
270
+ ],
271
+ "active": true,
272
+ "verificationRequired": true,
273
+ "episodeClearSequence": 0,
274
+ "sourceUpdatedAt": 2000,
275
+ "persisted": {
276
+ "entityId": "sensor",
277
+ "capabilityId": "basic.device.state.smoke",
278
+ "latest": {
279
+ "sequence": 2,
280
+ "timestamp": 2000,
281
+ "value": "testing"
282
+ },
283
+ "detected": {
284
+ "sequence": 1,
285
+ "timestamp": 1000,
286
+ "value": "detected"
287
+ },
288
+ "cleared": null
289
+ }
290
+ },
291
+ {
292
+ "name": "clear ends smoke incident",
293
+ "capabilityId": "basic.device.state.smoke",
294
+ "observations": [
295
+ {
296
+ "sequence": 1,
297
+ "timestamp": 1000,
298
+ "value": "detected"
299
+ },
300
+ {
301
+ "sequence": 2,
302
+ "timestamp": 2000,
303
+ "value": "clear"
304
+ },
305
+ {
306
+ "sequence": 3,
307
+ "timestamp": 3000,
308
+ "value": "unknown"
309
+ }
310
+ ],
311
+ "active": false,
312
+ "verificationRequired": false,
313
+ "episodeClearSequence": 0,
314
+ "sourceUpdatedAt": 1000,
315
+ "persisted": {
316
+ "entityId": "sensor",
317
+ "capabilityId": "basic.device.state.smoke",
318
+ "latest": {
319
+ "sequence": 3,
320
+ "timestamp": 3000,
321
+ "value": "unknown"
322
+ },
323
+ "detected": {
324
+ "sequence": 1,
325
+ "timestamp": 1000,
326
+ "value": "detected"
327
+ },
328
+ "cleared": {
329
+ "sequence": 2,
330
+ "timestamp": 2000,
331
+ "value": "clear"
332
+ }
333
+ }
334
+ }
335
+ ]
@@ -34,7 +34,7 @@
34
34
  "active": false,
35
35
  "data": {},
36
36
  "capabilities": [],
37
- "twinTags": [],
37
+ "blueprintIds": [],
38
38
  "cardCount": 0,
39
39
  "issue": false
40
40
  },
@@ -266,7 +266,7 @@
266
266
  "message": "Member is unavailable"
267
267
  }
268
268
  ],
269
- "twinSig": "internal"
269
+ "blueprintSignature": "internal"
270
270
  }
271
271
  },
272
272
  {
@@ -458,7 +458,7 @@
458
458
  "manufacturerId": "demo",
459
459
  "manufacturerName": "Example maker",
460
460
  "availabilityStatus": "unavailable",
461
- "twinTags": [
461
+ "blueprintIds": [
462
462
  "light"
463
463
  ]
464
464
  },
@@ -492,7 +492,7 @@
492
492
  "group": false,
493
493
  "integration": "demo.device",
494
494
  "availabilityStatus": "unavailable",
495
- "twinTags": [
495
+ "blueprintIds": [
496
496
  "light"
497
497
  ]
498
498
  },
@@ -546,7 +546,7 @@
546
546
  "manufacturerId": "demo",
547
547
  "manufacturerName": "Example maker",
548
548
  "availabilityStatus": "unavailable",
549
- "twinTags": [
549
+ "blueprintIds": [
550
550
  "light"
551
551
  ],
552
552
  "entityIds": []
@@ -600,7 +600,7 @@
600
600
  "manufacturerId": "demo",
601
601
  "manufacturerName": "Example maker",
602
602
  "availabilityStatus": "unavailable",
603
- "twinTags": [
603
+ "blueprintIds": [
604
604
  "light"
605
605
  ],
606
606
  "entityIds": []
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.app-facade.v1",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "callSchema": "schema/facade-call.v1.schema.json",
5
5
  "routing": "manifest/routing.v1.json",
6
6
  "routingSchema": "schema/routing.v1.schema.json",
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.89.0",
2
+ "contractVersion": "1.91.0",
3
3
  "operations": {
4
4
  "/app/agent/context/get": "spaceRead",
5
5
  "/app/agent/credit/usage": "spaceRead",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.host-provision.targets.v1",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "contract": "contract/host-provisioning-v1.md",
5
5
  "localTargets": [
6
6
  "/local/host/provision/start",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.hub.targets.v1",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "appTargets": [
5
5
  "/app/hub/get",
6
6
  "/app/hub/remove",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.pod.targets.v1",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "contract": "contract/pod-commissioning-v1.md",
5
5
  "localTargets": [
6
6
  "/local/pod/discovery/start",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.app-facade.routing.v1",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "targetPrefix": "/app/",
5
5
  "default": {
6
6
  "execution": "scopeMode"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.messaging.targets.v1",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "requestTargets": [
5
5
  "/app/messaging/provider/list",
6
6
  "/app/messaging/provider-account/list",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhome/app-facade-protocol",
3
- "version": "1.89.0",
3
+ "version": "1.91.0",
4
4
  "description": "Build-time protocol artifacts for mhome.app-facade",
5
5
  "license": "MIT",
6
6
  "repository": {
package/protocol.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocol": "mhome.app-facade",
3
- "contractVersion": "1.89.0",
3
+ "contractVersion": "1.91.0",
4
4
  "manifest": "manifest/app-facade.v1.json",
5
5
  "schemas": {
6
6
  "nowAttention": "schema/now-attention.v1.schema.json",
@@ -552,22 +552,19 @@
552
552
  },
553
553
  "headers": {
554
554
  "type": "object",
555
- "additionalProperties": false,
556
- "required": [
557
- "Authorization",
558
- "Content-Type"
559
- ],
560
- "properties": {
561
- "Authorization": {
562
- "type": "string",
563
- "minLength": 1,
564
- "maxLength": 512
565
- },
566
- "Content-Type": {
567
- "type": "string",
568
- "minLength": 1,
569
- "maxLength": 512
570
- }
555
+ "minProperties": 1,
556
+ "maxProperties": 32,
557
+ "propertyNames": {
558
+ "type": "string",
559
+ "minLength": 1,
560
+ "maxLength": 128,
561
+ "pattern": "^[!#$%&'*+.^_`|~0-9A-Za-z-]+$"
562
+ },
563
+ "additionalProperties": {
564
+ "type": "string",
565
+ "minLength": 1,
566
+ "maxLength": 8192,
567
+ "pattern": "^[^\\r\\n]*$"
571
568
  }
572
569
  }
573
570
  }