@mhome/app-facade-protocol 1.34.0 → 1.36.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.
- package/contract/host-provisioning-v1.md +3 -5
- package/contract/pod-commissioning-v1.md +44 -43
- package/manifest/app-facade.v1.json +1 -1
- package/manifest/host-provision-targets.v1.json +1 -3
- package/manifest/hub-targets.v1.json +1 -1
- package/manifest/pod-targets.v1.json +1 -6
- package/manifest/routing.v1.json +1 -1
- package/manifest/targets.v1.json +1 -1
- package/package.json +1 -1
- package/protocol.json +1 -1
- package/schema/host-provision.v1.schema.json +0 -1
- package/schema/pod-commission.v1.schema.json +0 -1
|
@@ -119,20 +119,18 @@ Candidates come from the shared discovery (`/local/pod/discovery/*`) with
|
|
|
119
119
|
| `/local/host/provision/code` | `{"sessionId","code"}` | session snapshot |
|
|
120
120
|
| `/local/host/provision/wifi` | `{"sessionId","ssid","password"?}` | session snapshot |
|
|
121
121
|
| `/local/host/provision/wifi/scan` | `{"sessionId"}` | session snapshot |
|
|
122
|
-
| `/local/host/provision/cancel` | `{"sessionId"}` | session snapshot |
|
|
123
|
-
| `/local/host/provision/renew` | `{"sessionId"}` | session snapshot |
|
|
124
122
|
| `/local/host/provision/status` | `{}` | `{"session": snapshot or null}` |
|
|
125
123
|
|
|
126
124
|
Event `/local/host/provision/changed` carries a session snapshot. One session
|
|
127
125
|
per native Client, and none while a pod session is active; discovery scanning
|
|
128
126
|
pauses during either. Rejected requests use the error envelope and the stable
|
|
129
127
|
`details.reason` values of `pod-commissioning-v1.md` section 7. The session is
|
|
130
|
-
owned
|
|
131
|
-
|
|
128
|
+
owned by the native core until success, failure or the session deadline.
|
|
129
|
+
There is no cancellation or renewal endpoint; leaving the UI does not end it.
|
|
132
130
|
|
|
133
131
|
Session states, in order: `connecting`, `awaiting_code`, `securing`,
|
|
134
132
|
`reading_info`, `awaiting_wifi`, `joining_wifi`, `completed`; terminal `failed`,
|
|
135
|
-
`
|
|
133
|
+
`timed_out`. `completed` carries `host`, the reported `addresses`
|
|
136
134
|
(IPv4 only, possibly empty) and `claimToken` from the `finish` response; the
|
|
137
135
|
commissioner sends `finish` (up to 3 times while the secure session lasts)
|
|
138
136
|
before reporting it. `claimToken` is absent when `finish` could not be
|
|
@@ -8,7 +8,8 @@ call the `/local/pod/*` targets of their native Client.
|
|
|
8
8
|
|
|
9
9
|
Commissioning order is fixed: secure BLE session, Wi-Fi joined, user
|
|
10
10
|
authorization, credential issue, delivery, activation. A credential is issued only
|
|
11
|
-
after the pod reports a joined network.
|
|
11
|
+
after the pod reports a joined network. Failed setup releases the connection;
|
|
12
|
+
pending cloud credentials expire, and a fresh successful claim cleans up old credentials.
|
|
12
13
|
|
|
13
14
|
## 1. Device identity
|
|
14
15
|
|
|
@@ -24,7 +25,7 @@ after the pod reports a joined network. Any failure after issue revokes it.
|
|
|
24
25
|
## 2. BLE advertising
|
|
25
26
|
|
|
26
27
|
Pods advertise only while unprovisioned, while re-commissioning after logout or
|
|
27
|
-
|
|
28
|
+
credential invalidation, or inside a re-provision window. A commissioned pod does not advertise.
|
|
28
29
|
|
|
29
30
|
- Service UUID (128-bit, shared by every MeowLink commissionable device):
|
|
30
31
|
`a17a7ad5-9b2f-4010-9cf0-aa15ac54c40e`.
|
|
@@ -157,7 +158,7 @@ unprovisioned ──connect──▶ session_open ──handshake──▶ secur
|
|
|
157
158
|
secured ──prov-config apply──▶ wifi_joining ──▶ wifi_connected | wifi_failed
|
|
158
159
|
wifi_failed ──retry──▶ wifi_joining
|
|
159
160
|
wifi_connected ──deliver──▶ activating ──time sync, refresh, commit──▶ commissioned | failed
|
|
160
|
-
any pre-commit state ──timeout/disconnect
|
|
161
|
+
any pre-commit state ──timeout/disconnect──▶ unprovisioned
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
Runtime states after commit:
|
|
@@ -176,8 +177,9 @@ Runtime states after commit:
|
|
|
176
177
|
- `needs_recommission`: logout, or refresh rejected because the credential was
|
|
177
178
|
revoked or the user no longer exists. Credential and local JWTs are erased,
|
|
178
179
|
Wi-Fi is kept, advertising resumes with the Wi-Fi-configured flag.
|
|
179
|
-
- `factory_resetting`:
|
|
180
|
-
|
|
180
|
+
- `factory_resetting`: purely offline, erase all persisted data including the
|
|
181
|
+
identity key and cached Hub tokens, reboot to `unprovisioned`. No cloud
|
|
182
|
+
request is needed. Logout also clears local credentials without cloud revocation.
|
|
181
183
|
|
|
182
184
|
Time sync uses SNTP; if NTP is unreachable the pod may use the `Date` header of a
|
|
183
185
|
Lion HTTPS response. Activation proofs require a synced clock.
|
|
@@ -227,7 +229,7 @@ claims `iss` and `sub` = `podId`, a single-valued `aud` = `pod-token-refresh`,
|
|
|
227
229
|
proof can be accepted; a reused `jti` in that window fails with
|
|
228
230
|
`pod_proof_replayed`.
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
Before the first successful refresh moves `pending` to `active`, Lion deletes every other
|
|
231
233
|
credential with the same `identity.keyId` (whichever user holds it) and the same
|
|
232
234
|
user's other credentials with the same `deviceId`. A `pending` credential past
|
|
233
235
|
`activateBefore` is rejected and deleted. Revoked credentials are deleted, so a
|
|
@@ -241,17 +243,16 @@ later refresh fails with `pod_credential_invalid`. Response
|
|
|
241
243
|
`absent` (schema `schema/pod-credential-status.v1.schema.json`). `absent`
|
|
242
244
|
covers no such credential, revoked, pending past `activateBefore`, and a
|
|
243
245
|
credential owned by another user, so the answer never reveals other users'
|
|
244
|
-
pods. `activatedAt` (epoch ms) is present when `active`.
|
|
245
|
-
|
|
246
|
+
pods. `activatedAt` (epoch ms) is present when `active`. It is informational;
|
|
247
|
+
commissioning success requires the device confirmation described in section 7.
|
|
246
248
|
|
|
247
|
-
###
|
|
249
|
+
### Reset and cleanup
|
|
248
250
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
Response `{"ok":true}`.
|
|
251
|
+
There is no public Pod credential revocation endpoint and no App action that
|
|
252
|
+
withdraws a Pod's access without contacting the device. Factory reset executes
|
|
253
|
+
on the Pod itself. Remote reset, when provided, must reach an online Pod; a
|
|
254
|
+
cloud-side deletion is not a successful reset. This contract currently exposes
|
|
255
|
+
no remote reset target. Each setup creates a new Pod credential.
|
|
255
256
|
|
|
256
257
|
### List and rename
|
|
257
258
|
|
|
@@ -282,8 +283,11 @@ and unexpired pending pods, each
|
|
|
282
283
|
channel. Either close only ends that connection; whether the pod is revoked
|
|
283
284
|
is decided by its next refresh.
|
|
284
285
|
- `/api/v1/hub/token/exchange` with a pod access token returns a Hub JWT with
|
|
285
|
-
`clientKind` = `pod` and `podId` claims and
|
|
286
|
-
|
|
286
|
+
`clientKind` = `pod` and `podId` claims and the same lifetime as a Hub JWT
|
|
287
|
+
for a user token.
|
|
288
|
+
- Pod and ordinary Client local Hub JWTs have the same 100-year lifetime
|
|
289
|
+
(100 * 365 days). There is no per-Pod Hub revocation list or cloud-to-Hub
|
|
290
|
+
revocation sync. Local tokens are cleared on the Pod by reset/logout.
|
|
287
291
|
|
|
288
292
|
## 6. Pod runtime authentication
|
|
289
293
|
|
|
@@ -299,11 +303,14 @@ and unexpired pending pods, each
|
|
|
299
303
|
re-sends `/focus`; it keeps one Hub JWT and Hub key per Hub and Space.
|
|
300
304
|
- Local Hub: the Hub identity endpoint `POST /api/v1/hub/identity/get`
|
|
301
305
|
(`{"hubId","tenantId","scopeId"}`) is public and needs no token. The pod
|
|
302
|
-
proves the Hub identity,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
Hub JWT
|
|
306
|
+
proves the Hub identity, then authenticates to the local Hub the way a Client
|
|
307
|
+
does: the first local `/auth` carries its pod access token with
|
|
308
|
+
`source=cloud` (`clientSource=pod`, `deviceId`, `tenantId`, `scopeId`,
|
|
309
|
+
`hubId`). The Hub exchanges that token with the cloud and returns a
|
|
310
|
+
long-lived local Hub JWT in `jwtToken`, which the pod stores per Hub. Later
|
|
311
|
+
local `/auth` requests use only that JWT with `source=local`. When the stored
|
|
312
|
+
JWT is about to expire or the Hub rejects it, the pod discards it and
|
|
313
|
+
authenticates with `source=cloud` again.
|
|
307
314
|
|
|
308
315
|
## 7. Native Client targets
|
|
309
316
|
|
|
@@ -346,8 +353,6 @@ never show these values or `message` verbatim.
|
|
|
346
353
|
| `/local/pod/commission/wifi` | `{"sessionId","ssid","password"?}` | session snapshot |
|
|
347
354
|
| `/local/pod/commission/wifi/scan` | `{"sessionId"}` | session snapshot |
|
|
348
355
|
| `/local/pod/commission/authorize` | `{"sessionId","scopeId"}` | session snapshot |
|
|
349
|
-
| `/local/pod/commission/cancel` | `{"sessionId"}` | session snapshot |
|
|
350
|
-
| `/local/pod/commission/renew` | `{"sessionId"}` | session snapshot |
|
|
351
356
|
| `/local/pod/commission/status` | `{}` | `{"session": snapshot or null}` |
|
|
352
357
|
| `/local/pod/bluetooth/settings` | `{}` | `{"opened"}` |
|
|
353
358
|
|
|
@@ -369,10 +374,10 @@ UIs reconcile with `status` and discovery `list` on start, resume, reconnect and
|
|
|
369
374
|
when they become visible, and a snapshot with a lower `revision` than one
|
|
370
375
|
already seen for the same session is stale.
|
|
371
376
|
|
|
372
|
-
The
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
The native core owns the session until success or failure, bounded by
|
|
378
|
+
`expiresAtMs`. There is no user cancellation or session renewal. Leaving the
|
|
379
|
+
page does not end setup; returning to it reads `status`. Waiting for user input
|
|
380
|
+
also counts toward the deadline. Discovery leases still control scanning.
|
|
376
381
|
|
|
377
382
|
Sign-in and Spaces are checked at `start` (`not_signed_in`,
|
|
378
383
|
`scope_not_offered`) and again when the authorization prompt is built.
|
|
@@ -391,7 +396,7 @@ pod's Wi-Fi rather than adding a pod.
|
|
|
391
396
|
Session states, in order: `connecting`, `awaiting_code`, `securing`,
|
|
392
397
|
`reading_info`, `awaiting_wifi`, `joining_wifi`, `awaiting_authorization`,
|
|
393
398
|
`issuing`, `delivering`, `activating`, `completed`; terminal `failed`,
|
|
394
|
-
`
|
|
399
|
+
`timed_out`. Wi-Fi states are skipped when the pod reports a
|
|
395
400
|
configured network and `prov-config` status reports it connected; while that
|
|
396
401
|
status is `connecting` the commissioner keeps polling, and only after a failure
|
|
397
402
|
does it reset Wi-Fi (`prov-ctrl`) and offer new credentials. The commissioner
|
|
@@ -401,18 +406,15 @@ scan runs waits for it. `revision` increases on every observable change.
|
|
|
401
406
|
`expiresAtMs` is 10 minutes after `start` until delivery starts; from
|
|
402
407
|
`delivering` on it is `activateBefore` plus 30 seconds.
|
|
403
408
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
- `pending`: ask again every 5 seconds until `activateBefore` plus 30 seconds,
|
|
410
|
-
then revoke and fail with `activation_failed`.
|
|
411
|
-
- `absent`: fail with `activation_failed`.
|
|
409
|
+
Only the device's durable `commissioned` response confirms successful setup.
|
|
410
|
+
Lost BLE delivery confirmation or activation status fails the attempt, even
|
|
411
|
+
when Lion has already activated its credential. Expiry fails or times out the
|
|
412
|
+
attempt. The commissioner does not recover interrupted setup through Lion's
|
|
413
|
+
credential status. The user resets the device and starts a fresh attempt.
|
|
412
414
|
|
|
413
415
|
A refused `deliver` fails with `delivery_failed` carrying the pod's `reason`
|
|
414
|
-
(`invalid_payload` or `wrong_state`) in `detail
|
|
415
|
-
itself reports `failed`, the commissioner
|
|
416
|
+
(`invalid_payload` or `wrong_state`) in `detail`. When the pod
|
|
417
|
+
itself reports `failed`, the commissioner fails with
|
|
416
418
|
`activation_failed` carrying the pod's code. `activation_failed` without a pod
|
|
417
419
|
code carries `not_activated`. The commissioner records `completed` before
|
|
418
420
|
sending `finish`. Native Clients keep a non-terminal session running while the
|
|
@@ -426,10 +428,9 @@ Session error codes: `bluetooth_unavailable`, `device_busy`, `disconnected`,
|
|
|
426
428
|
`detail`), `session_timeout`. `code_locked` is terminal: the device shows a new
|
|
427
429
|
code once it accepts handshakes again. `failed` and `timed_out` always carry `error`. A rejected
|
|
428
430
|
code returns to `awaiting_code` with `code_rejected`; a failed join returns to
|
|
429
|
-
`awaiting_wifi` with the `wifi_*` code. No other state carries `error`.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
credential.
|
|
431
|
+
`awaiting_wifi` with the `wifi_*` code. No other state carries `error`. Terminal states release the link and radio
|
|
432
|
+
without revoking credentials. Reset the Pod and start over after an uncertain
|
|
433
|
+
activation outcome; unused pending cloud credentials expire automatically.
|
|
433
434
|
|
|
434
435
|
The schemas are `schema/pod-discovery.v1.schema.json` and
|
|
435
436
|
`schema/pod-commission.v1.schema.json`.
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": "mhome.host-provision.targets.v1",
|
|
3
|
-
"contractVersion": "1.
|
|
3
|
+
"contractVersion": "1.36.0",
|
|
4
4
|
"contract": "contract/host-provisioning-v1.md",
|
|
5
5
|
"localTargets": [
|
|
6
6
|
"/local/host/provision/start",
|
|
7
7
|
"/local/host/provision/code",
|
|
8
8
|
"/local/host/provision/wifi",
|
|
9
9
|
"/local/host/provision/wifi/scan",
|
|
10
|
-
"/local/host/provision/cancel",
|
|
11
|
-
"/local/host/provision/renew",
|
|
12
10
|
"/local/host/provision/status"
|
|
13
11
|
],
|
|
14
12
|
"eventTargets": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": "mhome.pod.targets.v1",
|
|
3
|
-
"contractVersion": "1.
|
|
3
|
+
"contractVersion": "1.36.0",
|
|
4
4
|
"contract": "contract/pod-commissioning-v1.md",
|
|
5
5
|
"localTargets": [
|
|
6
6
|
"/local/pod/discovery/start",
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
"/local/pod/commission/wifi",
|
|
13
13
|
"/local/pod/commission/wifi/scan",
|
|
14
14
|
"/local/pod/commission/authorize",
|
|
15
|
-
"/local/pod/commission/cancel",
|
|
16
|
-
"/local/pod/commission/renew",
|
|
17
15
|
"/local/pod/commission/status",
|
|
18
16
|
"/local/pod/bluetooth/settings"
|
|
19
17
|
],
|
|
@@ -63,15 +61,12 @@
|
|
|
63
61
|
},
|
|
64
62
|
"session": {
|
|
65
63
|
"lifetimeMs": 600000,
|
|
66
|
-
"leaseMs": 30000,
|
|
67
|
-
"renewIntervalMs": 10000,
|
|
68
64
|
"activationGraceMs": 30000,
|
|
69
65
|
"credentialStatusPollMs": 5000
|
|
70
66
|
},
|
|
71
67
|
"cloud": {
|
|
72
68
|
"issue": "pod/credential/issue",
|
|
73
69
|
"status": "pod/credential/status",
|
|
74
|
-
"revoke": "pod/credential/revoke",
|
|
75
70
|
"statusSchema": "schema/pod-credential-status.v1.schema.json"
|
|
76
71
|
},
|
|
77
72
|
"activationErrorCodes": [
|
package/manifest/routing.v1.json
CHANGED
package/manifest/targets.v1.json
CHANGED
package/package.json
CHANGED
package/protocol.json
CHANGED