@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.
@@ -1,6 +1,6 @@
1
1
  # Pod commissioning v1
2
2
 
3
- A pod is a MeowLink end device that acts as an App client of one user. This
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 current credential API below is the existing personal Pod credential; this
13
- transport change does not implement the separately planned Space Client issuer.
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": [{ "scopeId": "scope-1", "name": "Home" }],
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.app-facade.v1",
3
- "contractVersion": "1.41.0",
3
+ "contractVersion": "1.42.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,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.host-provision.targets.v1",
3
- "contractVersion": "1.41.0",
3
+ "contractVersion": "1.42.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.41.0",
3
+ "contractVersion": "1.42.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.41.0",
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.app-facade.routing.v1",
3
- "contractVersion": "1.41.0",
3
+ "contractVersion": "1.42.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.41.0",
3
+ "contractVersion": "1.42.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.41.0",
3
+ "version": "1.42.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.41.0",
3
+ "contractVersion": "1.42.0",
4
4
  "manifest": "manifest/app-facade.v1.json",
5
5
  "schemas": {
6
6
  "capabilityDescriptor": "schema/capability-descriptor.v1.schema.json",
@@ -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
  },