@mhome/app-facade-protocol 1.41.0 → 1.42.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/pod-commissioning-v1.md +67 -5
- package/fixtures/pod-commission.awaiting-authorization.json +7 -1
- package/manifest/app-facade.v1.json +1 -1
- package/manifest/host-provision-targets.v1.json +1 -1
- package/manifest/hub-targets.v1.json +1 -1
- package/manifest/pod-targets.v1.json +11 -2
- 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/pod-commission.v1.schema.json +3 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pod commissioning v1
|
|
2
2
|
|
|
3
|
-
A pod is a MeowLink end device that acts as
|
|
3
|
+
A pod is a MeowLink end device that acts as a User Client or a Space Client. This
|
|
4
4
|
contract is implementation-neutral: ESP-IDF on BLE-capable ESP32 chips is the
|
|
5
5
|
first implementation, not a requirement. Commissioners are the native Clients:
|
|
6
6
|
desktop/CLI Clientd, Android and iOS. The shared UI and CLI never talk BLE; they
|
|
@@ -9,8 +9,10 @@ call the `/local/pod/*` targets of their native Client.
|
|
|
9
9
|
Network provisioning and activation are separate transactions. BLE provisions
|
|
10
10
|
Wi-Fi; Ethernet and already-connected Wi-Fi skip it. Every initialization then
|
|
11
11
|
uses a direct native Client ↔ Pod LAN security-2 session. No Core relay is used.
|
|
12
|
-
The
|
|
13
|
-
|
|
12
|
+
The commissioner explicitly chooses `clientMode: "user" | "space"` during
|
|
13
|
+
`/local/pod/commission/authorize`. User mode uses the personal Pod credential.
|
|
14
|
+
Space mode uses a local Core credential signed by Lion; it carries no user
|
|
15
|
+
identity and has no cloud access/refresh token. Changing mode requires reset.
|
|
14
16
|
|
|
15
17
|
## 1. Device identity
|
|
16
18
|
|
|
@@ -128,7 +130,7 @@ the response also carries `"mode":"reprovision"` and `wifiConfigured` is false
|
|
|
128
130
|
|
|
129
131
|
`pod-credential` requests:
|
|
130
132
|
|
|
131
|
-
- `{"op":"deliver","podId","refreshToken","scopeId","cloudApi","cloudWs"}` →
|
|
133
|
+
- `{"op":"deliver","clientMode":"user","podId","refreshToken","scopeId","cloudApi","cloudWs"}` →
|
|
132
134
|
`{"ok":true,"state":"activating"}`. Accepted only in `wifi_connected` outside a
|
|
133
135
|
Wi-Fi change window; otherwise `{"ok":false,"state":<current pod state>,"reason"}`
|
|
134
136
|
with `reason` `invalid_payload` (a missing or malformed field, including a
|
|
@@ -149,7 +151,7 @@ the response also carries `"mode":"reprovision"` and `wifiConfigured` is false
|
|
|
149
151
|
best-effort: the commissioner records `completed` before sending it.
|
|
150
152
|
|
|
151
153
|
Activation error codes: `time_sync_failed`, `cloud_unreachable`,
|
|
152
|
-
`refresh_rejected`, `auth_rejected`, `storage_failed` (the final commit, or in a
|
|
154
|
+
`local_hub_unavailable`, `refresh_rejected`, `auth_rejected`, `storage_failed` (the final commit, or in a
|
|
153
155
|
Wi-Fi change window saving the new network, failed).
|
|
154
156
|
|
|
155
157
|
The Pod commits Wi-Fi immediately after a successful join, before any credential
|
|
@@ -472,3 +474,63 @@ without revoking credentials. Retry over LAN after an uncertain activation outco
|
|
|
472
474
|
|
|
473
475
|
The schemas are `schema/pod-discovery.v1.schema.json` and
|
|
474
476
|
`schema/pod-commission.v1.schema.json`.
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
## 8. Space Client local credential
|
|
480
|
+
|
|
481
|
+
`POST /api/v1/pod/space/credential/issue` uses the assisting App's user
|
|
482
|
+
`Authorization` and `ScopeId`, with the same device identity request as personal
|
|
483
|
+
issuance. Any current member may assist. The Space must be local and have one
|
|
484
|
+
published Hub with business and Host TLS identities. Cloud Spaces must complete
|
|
485
|
+
Hub setup first. The issuer creates a fresh `appClientId` (`a:<random id>`) and
|
|
486
|
+
signs a local JWT using the existing Space signing key and local-client audience.
|
|
487
|
+
|
|
488
|
+
Claims are `token_type: local_space_client_access`, `sub: <scopeId>`,
|
|
489
|
+
`tenant_id`, `scope_id`, `hub_id`, `app_client_id`, `jti`, `iat`, `exp`, `is_test`.
|
|
490
|
+
The expiry follows existing long-lived User local credentials. No `userId`,
|
|
491
|
+
initializer identity, `podId` or `issuedScopeId` grants authority. User local
|
|
492
|
+
credentials retain `local_client_access` and must not contain `app_client_id`.
|
|
493
|
+
Personal Pod issuance no longer stores `issuedScopeId`.
|
|
494
|
+
|
|
495
|
+
Lion sends `{jwtToken, clientSource:"pod"}` to the authenticated Hub Bridge
|
|
496
|
+
`/hub/client/space/register`. Core verifies signature, audience, issuer, current
|
|
497
|
+
Hub, environment, Space and canonical installation ID, and stores a pending
|
|
498
|
+
credential. First authentication must occur within ten minutes of JWT issuance.
|
|
499
|
+
The response is `{credentialId, status:"pending", activateBefore}`. Lion returns
|
|
500
|
+
`{credentialId, appClientId, jwtToken, activateBefore, hubIdentity}` only after
|
|
501
|
+
registration succeeds. The Hub identity includes its business P-256 key and
|
|
502
|
+
Host TLS public key, bound to `tenantId`, `scopeId`, `hubId` and `hostId`.
|
|
503
|
+
|
|
504
|
+
The secured LAN `pod-credential` delivery is:
|
|
505
|
+
|
|
506
|
+
```json
|
|
507
|
+
{"op":"deliver","clientMode":"space","credentialId":"…","appClientId":"a:…","jwtToken":"…","tenantId":"…","scopeId":"…","hubIdentity":{}}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
The actual `hubIdentity` is the complete issuer response. User credential fields
|
|
511
|
+
(`refreshToken`, cloud endpoints, `podId`) are rejected in this variant. Space
|
|
512
|
+
credential fields are rejected in the User variant. LAN credential plaintext is
|
|
513
|
+
bounded to 8192 bytes; BLE credential delivery remains disabled.
|
|
514
|
+
|
|
515
|
+
The Pod discovers the matching runtime environment and Space's Hub, pins TLS,
|
|
516
|
+
verifies the signed Hub proof, and authenticates with the local JWT and signed
|
|
517
|
+
`appClientId`. Core atomically activates the pending credential after all binding
|
|
518
|
+
checks. The Pod requires a Space principal for the same Space, installation and
|
|
519
|
+
credential in the authenticated response, then durably saves the profile. Only
|
|
520
|
+
then does LAN setup report completion. The LAN status field named `podId` is an
|
|
521
|
+
enrollment correlation ID: for Space mode it carries `credentialId`, never the
|
|
522
|
+
installation ID or acting principal.
|
|
523
|
+
|
|
524
|
+
After activation, reconnects do not require Lion or renew a cloud session. The
|
|
525
|
+
same Core verifies the JWT and local registration offline, with its cached Scope
|
|
526
|
+
verification key. Failed activation preserves working Wi-Fi and leaves LAN
|
|
527
|
+
commissioning available. Retry may require the pairing code again. Unused pending
|
|
528
|
+
credentials cannot activate after the deadline. Factory reset deletes the local
|
|
529
|
+
credential and identity key; no server-side revoke workflow is introduced.
|
|
530
|
+
|
|
531
|
+
Core carries `AuthenticatedClient` through WS, dispatch, plugin admission,
|
|
532
|
+
prepared actions, dialogs and presence. A Space Client can READ/WRITE its Space;
|
|
533
|
+
MANAGE, personal resources and Host user administration are rejected. It joins
|
|
534
|
+
the canonical Space-client conversation group, never an initializer's threads.
|
|
535
|
+
The billing owner fallback is not an acting identity. Host management tokens are
|
|
536
|
+
independent and are never issued by this flow.
|
|
@@ -24,7 +24,13 @@
|
|
|
24
24
|
"authorization": {
|
|
25
25
|
"userId": "user-1",
|
|
26
26
|
"userName": "Ada",
|
|
27
|
-
"scopes": [
|
|
27
|
+
"scopes": [
|
|
28
|
+
{
|
|
29
|
+
"scopeId": "scope-1",
|
|
30
|
+
"name": "Home",
|
|
31
|
+
"local": true
|
|
32
|
+
}
|
|
33
|
+
],
|
|
28
34
|
"defaultScopeId": "scope-1"
|
|
29
35
|
}
|
|
30
36
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": "mhome.pod.targets.v1",
|
|
3
|
-
"contractVersion": "1.
|
|
3
|
+
"contractVersion": "1.42.0",
|
|
4
4
|
"contract": "contract/pod-commissioning-v1.md",
|
|
5
5
|
"localTargets": [
|
|
6
6
|
"/local/pod/discovery/start",
|
|
@@ -67,11 +67,13 @@
|
|
|
67
67
|
"cloud": {
|
|
68
68
|
"issue": "pod/credential/issue",
|
|
69
69
|
"status": "pod/credential/status",
|
|
70
|
-
"statusSchema": "schema/pod-credential-status.v1.schema.json"
|
|
70
|
+
"statusSchema": "schema/pod-credential-status.v1.schema.json",
|
|
71
|
+
"spaceIssue": "pod/space/credential/issue"
|
|
71
72
|
},
|
|
72
73
|
"activationErrorCodes": [
|
|
73
74
|
"time_sync_failed",
|
|
74
75
|
"cloud_unreachable",
|
|
76
|
+
"local_hub_unavailable",
|
|
75
77
|
"refresh_rejected",
|
|
76
78
|
"auth_rejected",
|
|
77
79
|
"storage_failed"
|
|
@@ -96,5 +98,12 @@
|
|
|
96
98
|
"scope_not_offered",
|
|
97
99
|
"cloud_unreachable_for_device",
|
|
98
100
|
"busy"
|
|
101
|
+
],
|
|
102
|
+
"lan": {
|
|
103
|
+
"credentialMaxBytes": 8192
|
|
104
|
+
},
|
|
105
|
+
"clientModes": [
|
|
106
|
+
"user",
|
|
107
|
+
"space"
|
|
99
108
|
]
|
|
100
109
|
}
|
package/manifest/routing.v1.json
CHANGED
package/manifest/targets.v1.json
CHANGED
package/package.json
CHANGED
package/protocol.json
CHANGED
|
@@ -66,10 +66,11 @@
|
|
|
66
66
|
"items": {
|
|
67
67
|
"type": "object",
|
|
68
68
|
"additionalProperties": false,
|
|
69
|
-
"required": ["scopeId", "name"],
|
|
69
|
+
"required": ["scopeId", "name", "local"],
|
|
70
70
|
"properties": {
|
|
71
71
|
"scopeId": { "type": "string", "minLength": 1 },
|
|
72
|
-
"name": { "type": "string" }
|
|
72
|
+
"name": { "type": "string" },
|
|
73
|
+
"local": { "type": "boolean" }
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
},
|