@mhome/core-protocol 1.15.11 → 1.16.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.
@@ -0,0 +1,128 @@
1
+ # Host permissions contract
2
+
3
+ Host coordinates permissions for this computer and the services it manages. It
4
+ never performs a service's protected business operations on that service's behalf.
5
+ Client/Desktop application permissions remain outside this report. No permission
6
+ request depends on a Space or enables a plugin in a Space.
7
+
8
+ This is a direct development cutover. There is one report model and no legacy
9
+ serialization, implicit component selection or alternative compatibility route.
10
+ Update Foundation consumers together after publishing the matching packages.
11
+
12
+ ## Declaration, executor and authorization subject
13
+
14
+ Each service keeps its source-root `permission.yml`, packages it, and reports its
15
+ own requirements. Host stores installed declarations so stopped services remain
16
+ visible. A declaration says which feature needs access, not who the OS charges.
17
+
18
+ `PermissionObservation.processId` identifies the actual executing process. It is
19
+ not a grant identity. A helper may share its responsible application's consent.
20
+ A Rust service must obtain a sidecar's observation over its private transport when
21
+ that sidecar executes the protected operation; it must not substitute its own
22
+ Foundation query. Check the real backend used by that executor, including access
23
+ errors. Standalone launches report their actual launch context, which can differ
24
+ from Host-managed launches. Reading status must not start an absent sidecar.
25
+
26
+ `PermissionSubject` is an authorization scope established for the specific
27
+ permission, launch path and packaging profile. Host may group by this subject and
28
+ the full `PermissionKey`. Equal states, equal signing teams, a common parent,
29
+ service-supplied labels or environment variables alone do not establish sharing.
30
+ There is no generic public macOS query in this contract that discovers another
31
+ process's TCC grant owner. Verify responsible-application attribution using the
32
+ real signed launch chain before enabling a shared subject for a permission.
33
+ If attribution is unconfirmed, `subject` is null and that component stays separate.
34
+ Different Automation `targetBundleId` values always remain distinct grants.
35
+
36
+ A grouped row preserves every `PermissionUse`, its observation and any collection
37
+ error. There is deliberately no group-level grant/state that can erase a denial,
38
+ an unknown executor or conflicting observations. A stopped service has no current
39
+ observation. Do not copy another service's grant into that empty observation.
40
+ Host chooses `requestComponentId` from available executors; it is null when none
41
+ can handle the request. `supportedActions` describes available mechanisms only.
42
+
43
+ ## Transport and actions
44
+
45
+ - `GET /v1/permissions` returns `HostPermissions` for installed requirements,
46
+ including stopped services and declaration/collection failures.
47
+ - `POST /internal/permissions/request` performs one explicit request or probe in
48
+ the selected service's actual execution context, then refreshes the report.
49
+ - `POST /internal/permissions/open-settings` opens the platform settings pane.
50
+ Opening Settings is not a grant; status must be refreshed afterwards.
51
+ - Both POSTs take `HostPermissionRequest`. `hostId`, `componentId` and `permission`
52
+ are mandatory. The component must declare that exact key. Host derives grouping
53
+ itself; the caller cannot assert a subject, locality or an arbitrary Settings URL.
54
+ - Require valid local control credentials and matching Host identity. Native
55
+ Desktop and CLI use Client IPC. Remote Hosts accept status only. Do not send
56
+ local control credentials to discovered addresses or redirects. A future Hub
57
+ read can relay a report but cannot relay mutations or decide grants.
58
+ - A service's `GET /internal/permissions` returns `ServicePermissions`. The outer
59
+ PID identifies the reporting service; individual observations can identify its
60
+ sidecar. The service rejects requests outside its declared requirements.
61
+
62
+ Host coalesces in-flight requests for an established shared subject and key, or
63
+ for the individual executor and key when sharing is unknown. Transport timeout
64
+ must not start a duplicate native request. Refresh all affected service reports
65
+ when an operation completes. Bound collection concurrency and the whole snapshot
66
+ latency; a stalled executor must not delay every other service indefinitely.
67
+
68
+ ## Consent and actual access
69
+
70
+ `PermissionObservation.state` describes application consent. `access` is a
71
+ separate backend observation. A successful consent check does not establish that
72
+ a device is powered on, present, correctly configured or usable. A successful
73
+ scan does not prove that every connection or operation will succeed.
74
+
75
+ - `system`: passive native authorization query and its timestamp.
76
+ - `platform`: platform consent semantics, not measured resource availability.
77
+ - `probe`: historical evidence from an explicit operation, with its timestamp.
78
+ - `unavailable`: a reliable consent observation could not be obtained.
79
+ - `notRequired`: this native backend has no application-consent step. It does not
80
+ bypass users/groups, D-Bus policies, device rules, sessions, portals or sandboxes.
81
+ - `access`: the actual backend's checked condition, timestamp and result. Missing
82
+ access evidence is unknown, not proof of success. Its detail describes only the
83
+ operation/condition actually observed. Do not relabel a hardware failure as OS
84
+ user denial or a successful consent query as a successful resource probe.
85
+
86
+ Passive report reads never request authorization, probe Local Network, scan
87
+ Bluetooth, launch an Automation target, restart a service or open Settings.
88
+ Local Network's last explicit probe is historical; ordinary Refresh cannot make
89
+ it a fresh OS setting. Keep cached reports visibly stale when collection fails.
90
+ Missing/malformed declarations remain errors; an empty list is not proof of a
91
+ complete inventory or successful access. Do not grant by default on other OSes.
92
+
93
+ ## macOS and Linux
94
+
95
+ macOS keeps Host's existing responsible application and managed process tree.
96
+ Share identity only for validated permission/launch combinations. Services and
97
+ sidecars retain their own valid signatures and required hardened-runtime
98
+ entitlements. Signatures and launch association alone are not proof that an OS
99
+ grant is shared. Unsupported sharing stays a separate request; it never causes
100
+ business operations to migrate into Host. Never modify a signed plist in place.
101
+
102
+ Foundation's macOS status functions remain in-process and passive. Bluetooth's
103
+ explicit request initializes Core Bluetooth to request consent; merely opening
104
+ Settings is not a first-request implementation. When the actual user of BLE is
105
+ Matter's Node sidecar, obtain and request its state there. No Matter.js fork or
106
+ Host BLE broker is implied by this contract.
107
+
108
+ Linux native services use the configured ordinary runtime account and existing
109
+ system interfaces, including BlueZ D-Bus. Foundation reports the absence of a
110
+ native application-consent step separately from the backend's access results.
111
+ Host and services must not claim access merely because there is no macOS TCC.
112
+ An installation preflight collects concrete missing conditions. An administrator
113
+ may authorize a bounded, idempotent setup step for those conditions; service
114
+ manifests must not supply privileged commands. Runtime processes remain ordinary
115
+ users. Account/session refresh requirements remain explicit. New device or
116
+ capability requirements can require additional setup. Container and sandbox
117
+ policies are separate deployment conditions, not automatically inherited grants.
118
+
119
+ ## Acceptance
120
+
121
+ Test passive reads without prompts; real signed allow/deny/revoke/relaunch
122
+ behavior; shared grants across two executors; independent/unknown subjects;
123
+ sidecar and stopped-service observations; conflicting per-service states;
124
+ per-target Automation; cross-host mutation rejection; required component
125
+ selection; incomplete manifests; bounded collection; coalesced requests; and
126
+ Linux policy denial versus missing/disabled resources. Test Linux setup first as
127
+ a dry-run and verify effective access in the intended ordinary runtime session.
128
+ Signing fixtures and mocked status tests do not prove TCC attribution.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": "mhome.core.v1",
3
- "contractVersion": "1.15.11",
3
+ "contractVersion": "1.16.0",
4
4
  "protocols": {
5
5
  "agentGateway": {
6
6
  "submitTarget": "/agent/submit",
@@ -76,6 +76,12 @@
76
76
  "failed",
77
77
  "unknown"
78
78
  ]
79
+ },
80
+ "hostPermissions": {
81
+ "schema": "schema/host-permissions.schema.json",
82
+ "statusPath": "/v1/permissions",
83
+ "requestPath": "/internal/permissions/request",
84
+ "settingsPath": "/internal/permissions/open-settings"
79
85
  }
80
86
  },
81
87
  "hostManagement": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhome/core-protocol",
3
- "version": "1.15.11",
3
+ "version": "1.16.0",
4
4
  "description": "Build-time protocol artifacts for mhome.core",
5
5
  "license": "MIT",
6
6
  "repository": {
package/protocol.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "protocol": "mhome.core",
3
- "contractVersion": "1.15.11",
3
+ "contractVersion": "1.16.0",
4
4
  "manifest": "manifest/core.v1.json",
5
5
  "schemas": {
6
6
  "hostManagementRequest": "schema/host-management-request.v1.schema.json",
7
+ "hostPermissions": "schema/host-permissions.schema.json",
7
8
  "normalizedInbound": "schema/normalized-inbound.v4.schema.json",
8
9
  "messagingCommands": "schema/messaging-commands.v1.schema.json",
9
10
  "interactionFlowNode": "schema/interaction-flow-node.v1.schema.json",
@@ -0,0 +1,442 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.mhome.ai/core/host-permissions.schema.json",
4
+ "title": "Host permission reports and local requests",
5
+ "$defs": {
6
+ "PermissionKey": {
7
+ "oneOf": [
8
+ {
9
+ "type": "object",
10
+ "additionalProperties": false,
11
+ "required": [
12
+ "id"
13
+ ],
14
+ "properties": {
15
+ "id": {
16
+ "enum": [
17
+ "localNetwork",
18
+ "bluetooth",
19
+ "microphone",
20
+ "reminders"
21
+ ]
22
+ }
23
+ }
24
+ },
25
+ {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": [
29
+ "id",
30
+ "targetBundleId"
31
+ ],
32
+ "properties": {
33
+ "id": {
34
+ "const": "automation"
35
+ },
36
+ "targetBundleId": {
37
+ "type": "string",
38
+ "minLength": 1
39
+ }
40
+ }
41
+ }
42
+ ]
43
+ },
44
+ "PermissionAccess": {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": [
48
+ "state",
49
+ "observedAtMs",
50
+ "detail"
51
+ ],
52
+ "properties": {
53
+ "state": {
54
+ "enum": [
55
+ "unknown",
56
+ "available",
57
+ "unavailable"
58
+ ]
59
+ },
60
+ "observedAtMs": {
61
+ "anyOf": [
62
+ {
63
+ "type": "integer",
64
+ "minimum": 0
65
+ },
66
+ {
67
+ "type": "null"
68
+ }
69
+ ]
70
+ },
71
+ "detail": {
72
+ "type": "string",
73
+ "minLength": 1
74
+ }
75
+ }
76
+ },
77
+ "PermissionObservation": {
78
+ "type": "object",
79
+ "additionalProperties": false,
80
+ "required": [
81
+ "permission",
82
+ "processId",
83
+ "state",
84
+ "evidence",
85
+ "observedAtMs",
86
+ "access",
87
+ "error"
88
+ ],
89
+ "properties": {
90
+ "permission": {
91
+ "$ref": "#/$defs/PermissionKey"
92
+ },
93
+ "processId": {
94
+ "type": "integer",
95
+ "minimum": 1
96
+ },
97
+ "state": {
98
+ "enum": [
99
+ "unknown",
100
+ "notDetermined",
101
+ "granted",
102
+ "notRequired",
103
+ "denied",
104
+ "restricted",
105
+ "unsupported"
106
+ ]
107
+ },
108
+ "evidence": {
109
+ "enum": [
110
+ "system",
111
+ "platform",
112
+ "probe",
113
+ "unavailable"
114
+ ]
115
+ },
116
+ "observedAtMs": {
117
+ "anyOf": [
118
+ {
119
+ "type": "integer",
120
+ "minimum": 0
121
+ },
122
+ {
123
+ "type": "null"
124
+ }
125
+ ]
126
+ },
127
+ "access": {
128
+ "anyOf": [
129
+ {
130
+ "$ref": "#/$defs/PermissionAccess"
131
+ },
132
+ {
133
+ "type": "null"
134
+ }
135
+ ]
136
+ },
137
+ "error": {
138
+ "anyOf": [
139
+ {
140
+ "type": "string"
141
+ },
142
+ {
143
+ "type": "null"
144
+ }
145
+ ]
146
+ }
147
+ },
148
+ "allOf": [
149
+ {
150
+ "if": {
151
+ "properties": {
152
+ "evidence": {
153
+ "const": "probe"
154
+ }
155
+ }
156
+ },
157
+ "then": {
158
+ "properties": {
159
+ "observedAtMs": {
160
+ "type": "integer",
161
+ "minimum": 0
162
+ }
163
+ }
164
+ }
165
+ }
166
+ ]
167
+ },
168
+ "PermissionSubject": {
169
+ "type": "object",
170
+ "additionalProperties": false,
171
+ "required": [
172
+ "id",
173
+ "name"
174
+ ],
175
+ "properties": {
176
+ "id": {
177
+ "type": "string",
178
+ "minLength": 1
179
+ },
180
+ "name": {
181
+ "type": "string",
182
+ "minLength": 1
183
+ }
184
+ }
185
+ },
186
+ "PermissionUse": {
187
+ "type": "object",
188
+ "additionalProperties": false,
189
+ "required": [
190
+ "componentId",
191
+ "componentName",
192
+ "feature",
193
+ "reason",
194
+ "observation",
195
+ "error"
196
+ ],
197
+ "properties": {
198
+ "componentId": {
199
+ "type": "string",
200
+ "minLength": 1
201
+ },
202
+ "componentName": {
203
+ "type": "string",
204
+ "minLength": 1
205
+ },
206
+ "feature": {
207
+ "type": "string",
208
+ "minLength": 1
209
+ },
210
+ "reason": {
211
+ "type": "string",
212
+ "minLength": 1
213
+ },
214
+ "observation": {
215
+ "anyOf": [
216
+ {
217
+ "$ref": "#/$defs/PermissionObservation"
218
+ },
219
+ {
220
+ "type": "null"
221
+ }
222
+ ]
223
+ },
224
+ "error": {
225
+ "anyOf": [
226
+ {
227
+ "type": "string"
228
+ },
229
+ {
230
+ "type": "null"
231
+ }
232
+ ]
233
+ }
234
+ }
235
+ },
236
+ "HostPermission": {
237
+ "type": "object",
238
+ "additionalProperties": false,
239
+ "required": [
240
+ "permission",
241
+ "subject",
242
+ "uses",
243
+ "requestComponentId",
244
+ "supportedActions"
245
+ ],
246
+ "properties": {
247
+ "permission": {
248
+ "$ref": "#/$defs/PermissionKey"
249
+ },
250
+ "subject": {
251
+ "anyOf": [
252
+ {
253
+ "$ref": "#/$defs/PermissionSubject"
254
+ },
255
+ {
256
+ "type": "null"
257
+ }
258
+ ]
259
+ },
260
+ "uses": {
261
+ "type": "array",
262
+ "minItems": 1,
263
+ "items": {
264
+ "$ref": "#/$defs/PermissionUse"
265
+ }
266
+ },
267
+ "requestComponentId": {
268
+ "anyOf": [
269
+ {
270
+ "type": "string",
271
+ "minLength": 1
272
+ },
273
+ {
274
+ "type": "null"
275
+ }
276
+ ]
277
+ },
278
+ "supportedActions": {
279
+ "type": "array",
280
+ "uniqueItems": true,
281
+ "items": {
282
+ "enum": [
283
+ "request",
284
+ "openSettings"
285
+ ]
286
+ }
287
+ }
288
+ },
289
+ "allOf": [
290
+ {
291
+ "if": {
292
+ "properties": {
293
+ "subject": {
294
+ "type": "null"
295
+ }
296
+ }
297
+ },
298
+ "then": {
299
+ "properties": {
300
+ "uses": {
301
+ "maxItems": 1
302
+ }
303
+ }
304
+ }
305
+ },
306
+ {
307
+ "if": {
308
+ "properties": {
309
+ "requestComponentId": {
310
+ "type": "null"
311
+ }
312
+ }
313
+ },
314
+ "then": {
315
+ "properties": {
316
+ "supportedActions": {
317
+ "not": {
318
+ "contains": {
319
+ "const": "request"
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ ]
327
+ },
328
+ "PermissionDeclarationError": {
329
+ "type": "object",
330
+ "additionalProperties": false,
331
+ "required": [
332
+ "componentId",
333
+ "message"
334
+ ],
335
+ "properties": {
336
+ "componentId": {
337
+ "type": "string",
338
+ "minLength": 1
339
+ },
340
+ "message": {
341
+ "type": "string",
342
+ "minLength": 1
343
+ }
344
+ }
345
+ },
346
+ "HostPermissions": {
347
+ "type": "object",
348
+ "additionalProperties": false,
349
+ "required": [
350
+ "hostId",
351
+ "hostVersion",
352
+ "platform",
353
+ "observedAtMs",
354
+ "permissions",
355
+ "declarationErrors"
356
+ ],
357
+ "properties": {
358
+ "hostId": {
359
+ "type": "string",
360
+ "minLength": 1
361
+ },
362
+ "hostVersion": {
363
+ "type": "string",
364
+ "minLength": 1
365
+ },
366
+ "platform": {
367
+ "type": "string",
368
+ "minLength": 1
369
+ },
370
+ "observedAtMs": {
371
+ "type": "integer",
372
+ "minimum": 0
373
+ },
374
+ "permissions": {
375
+ "type": "array",
376
+ "items": {
377
+ "$ref": "#/$defs/HostPermission"
378
+ }
379
+ },
380
+ "declarationErrors": {
381
+ "type": "array",
382
+ "items": {
383
+ "$ref": "#/$defs/PermissionDeclarationError"
384
+ }
385
+ }
386
+ }
387
+ },
388
+ "HostPermissionRequest": {
389
+ "type": "object",
390
+ "additionalProperties": false,
391
+ "required": [
392
+ "hostId",
393
+ "componentId",
394
+ "permission"
395
+ ],
396
+ "properties": {
397
+ "hostId": {
398
+ "type": "string",
399
+ "minLength": 1
400
+ },
401
+ "componentId": {
402
+ "type": "string",
403
+ "minLength": 1
404
+ },
405
+ "permission": {
406
+ "$ref": "#/$defs/PermissionKey"
407
+ }
408
+ }
409
+ },
410
+ "ServicePermissions": {
411
+ "type": "object",
412
+ "additionalProperties": false,
413
+ "required": [
414
+ "processId",
415
+ "permissions"
416
+ ],
417
+ "properties": {
418
+ "processId": {
419
+ "type": "integer",
420
+ "minimum": 1
421
+ },
422
+ "permissions": {
423
+ "type": "array",
424
+ "items": {
425
+ "$ref": "#/$defs/PermissionObservation"
426
+ }
427
+ }
428
+ }
429
+ }
430
+ },
431
+ "oneOf": [
432
+ {
433
+ "$ref": "#/$defs/HostPermissions"
434
+ },
435
+ {
436
+ "$ref": "#/$defs/HostPermissionRequest"
437
+ },
438
+ {
439
+ "$ref": "#/$defs/ServicePermissions"
440
+ }
441
+ ]
442
+ }