@mentra/cloud-protocol 3.3.0-dev.906 → 3.3.0-dev.909

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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/src/__fixtures__/fleet/ack-invalid-reason-on-accepted.json +11 -0
  3. package/src/__fixtures__/fleet/ack-invalid-rejected-without-reason.json +10 -0
  4. package/src/__fixtures__/fleet/ack-invalid-unknown-error.json +3 -0
  5. package/src/__fixtures__/fleet/ack-valid-mixed.json +33 -0
  6. package/src/__fixtures__/fleet/ack-valid-not-installed.json +3 -0
  7. package/src/__fixtures__/fleet/ack-valid-unavailable.json +4 -0
  8. package/src/__fixtures__/fleet/report-invalid-battery-zero-when-unsupported.json +55 -0
  9. package/src/__fixtures__/fleet/report-invalid-email-identity.json +57 -0
  10. package/src/__fixtures__/fleet/report-invalid-empty-batch.json +4 -0
  11. package/src/__fixtures__/fleet/report-invalid-execution-without-package-name.json +32 -0
  12. package/src/__fixtures__/fleet/report-invalid-missing-record-id.json +55 -0
  13. package/src/__fixtures__/fleet/report-invalid-negative-elapsed.json +33 -0
  14. package/src/__fixtures__/fleet/report-invalid-offset-time.json +56 -0
  15. package/src/__fixtures__/fleet/report-invalid-photo-without-operation-id.json +30 -0
  16. package/src/__fixtures__/fleet/report-valid-batch-mixed.json +172 -0
  17. package/src/__fixtures__/fleet/report-valid-coverage-gap.json +27 -0
  18. package/src/__fixtures__/fleet/report-valid-miniapp-launch.json +28 -0
  19. package/src/__fixtures__/fleet/report-valid-photo-operation.json +25 -0
  20. package/src/__fixtures__/fleet/report-valid-snapshot-provisional.json +53 -0
  21. package/src/__fixtures__/fleet/report-valid-snapshot.json +56 -0
  22. package/src/__fixtures__/fleet/report-valid-usage-checkpoint.json +33 -0
  23. package/src/__fixtures__/fleet/report-valid-usage-packed.json +62 -0
  24. package/src/fleet.ts +245 -0
  25. package/src/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/cloud-protocol",
3
- "version": "3.3.0-dev.906",
3
+ "version": "3.3.0-dev.909",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "results": [
4
+ {
5
+ "index": 0,
6
+ "recordId": "rec-x-0002",
7
+ "status": "accepted",
8
+ "reasonCode": "schema_invalid"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "results": [
4
+ {
5
+ "index": 0,
6
+ "recordId": "rec-x-0001",
7
+ "status": "rejected"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "error": "fleet_broken"
3
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "results": [
4
+ {
5
+ "index": 0,
6
+ "recordId": "rec-mix-0001",
7
+ "status": "accepted"
8
+ },
9
+ {
10
+ "index": 1,
11
+ "recordId": "rec-mix-0002",
12
+ "status": "duplicate"
13
+ },
14
+ {
15
+ "index": 2,
16
+ "recordId": "rec-mix-0003",
17
+ "status": "retryable",
18
+ "retryAfterSeconds": 30
19
+ },
20
+ {
21
+ "index": 3,
22
+ "recordId": "rec-mix-0004",
23
+ "status": "rejected",
24
+ "reasonCode": "content_mismatch"
25
+ },
26
+ {
27
+ "index": 4,
28
+ "recordId": "rec-mix-0005",
29
+ "status": "rejected",
30
+ "reasonCode": "outside_replay_window"
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "error": "fleet_not_installed"
3
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "error": "fleet_unavailable",
3
+ "retryAfterSeconds": 60
4
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-bad-0007",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 0,
10
+ "observedAt": "2026-10-13T11:00:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "snapshot",
19
+ "deviceBinding": {
20
+ "kind": "serial",
21
+ "manufacturer": "Mentra",
22
+ "product": "mentra_live",
23
+ "serial": "ML2A-0042-7731"
24
+ },
25
+ "payload": {
26
+ "phone": {
27
+ "model": "Pixel 8",
28
+ "os": "android",
29
+ "osVersion": "15",
30
+ "appVersion": "3.4.0",
31
+ "appBuild": "3400",
32
+ "engineVersion": "1.12.0",
33
+ "sdkVersion": "0.9.2"
34
+ },
35
+ "glasses": {
36
+ "model": "mentra_live",
37
+ "firmware": {
38
+ "apk": "1.7.3",
39
+ "mtk": "2.1.0",
40
+ "bes": "0.8.5"
41
+ },
42
+ "connection": {
43
+ "state": "connected",
44
+ "observedAt": "2026-10-13T11:00:00Z"
45
+ },
46
+ "battery": {
47
+ "state": "unsupported",
48
+ "observedAt": "2026-10-13T11:00:00Z",
49
+ "level": 0
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ]
55
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-bad-0002",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 0,
10
+ "observedAt": "2026-10-13T11:00:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "snapshot",
19
+ "deviceBinding": {
20
+ "kind": "serial",
21
+ "manufacturer": "Mentra",
22
+ "product": "mentra_live",
23
+ "serial": "ML2A-0042-7731"
24
+ },
25
+ "payload": {
26
+ "phone": {
27
+ "model": "Pixel 8",
28
+ "os": "android",
29
+ "osVersion": "15",
30
+ "appVersion": "3.4.0",
31
+ "appBuild": "3400",
32
+ "engineVersion": "1.12.0",
33
+ "sdkVersion": "0.9.2"
34
+ },
35
+ "glasses": {
36
+ "model": "mentra_live",
37
+ "firmware": {
38
+ "apk": "1.7.3",
39
+ "mtk": "2.1.0",
40
+ "bes": "0.8.5"
41
+ },
42
+ "connection": {
43
+ "state": "connected",
44
+ "observedAt": "2026-10-13T11:00:00Z"
45
+ },
46
+ "battery": {
47
+ "state": "measured",
48
+ "observedAt": "2026-10-13T11:00:00Z",
49
+ "level": 76,
50
+ "charging": false
51
+ }
52
+ }
53
+ },
54
+ "email": "someone@example.com"
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": []
4
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-bad-0005",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 1,
10
+ "observedAt": "2026-10-13T11:03:10Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "usage_checkpoint",
19
+ "payload": {
20
+ "sessions": [
21
+ {
22
+ "activity": "miniapp_execution",
23
+ "activitySessionId": "act-8d02",
24
+ "startedAt": "2026-10-13T11:02:10Z",
25
+ "elapsedMs": 60000,
26
+ "ended": false
27
+ }
28
+ ]
29
+ }
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "phoneDeviceId": "phone-7f3a91",
7
+ "collectorSessionId": "cs-20261013-01",
8
+ "sequence": 1,
9
+ "observedAt": "2026-10-13T11:00:00Z",
10
+ "capabilities": [
11
+ "glasses_connection",
12
+ "glasses_battery",
13
+ "glasses_versions",
14
+ "miniapp_execution",
15
+ "engine_active"
16
+ ],
17
+ "kind": "snapshot",
18
+ "deviceBinding": {
19
+ "kind": "serial",
20
+ "manufacturer": "Mentra",
21
+ "product": "mentra_live",
22
+ "serial": "ML2A-0042-7731"
23
+ },
24
+ "payload": {
25
+ "phone": {
26
+ "model": "Pixel 8",
27
+ "os": "android",
28
+ "osVersion": "15",
29
+ "appVersion": "3.4.0",
30
+ "appBuild": "3400",
31
+ "engineVersion": "1.12.0",
32
+ "sdkVersion": "0.9.2"
33
+ },
34
+ "glasses": {
35
+ "model": "mentra_live",
36
+ "firmware": {
37
+ "apk": "1.7.3",
38
+ "mtk": "2.1.0",
39
+ "bes": "0.8.5"
40
+ },
41
+ "connection": {
42
+ "state": "connected",
43
+ "observedAt": "2026-10-13T11:00:00Z"
44
+ },
45
+ "battery": {
46
+ "state": "measured",
47
+ "observedAt": "2026-10-13T11:00:00Z",
48
+ "level": 76,
49
+ "charging": false
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ]
55
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-bad-0003",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 1,
10
+ "observedAt": "2026-10-13T11:03:10Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "usage_checkpoint",
19
+ "payload": {
20
+ "sessions": [
21
+ {
22
+ "activity": "miniapp_execution",
23
+ "activitySessionId": "act-8d02",
24
+ "packageName": "com.mentra.notes",
25
+ "startedAt": "2026-10-13T11:02:10Z",
26
+ "elapsedMs": -5,
27
+ "ended": false
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-bad-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 0,
10
+ "observedAt": "2026-10-13T19:00:00+08:00",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "snapshot",
19
+ "deviceBinding": {
20
+ "kind": "serial",
21
+ "manufacturer": "Mentra",
22
+ "product": "mentra_live",
23
+ "serial": "ML2A-0042-7731"
24
+ },
25
+ "payload": {
26
+ "phone": {
27
+ "model": "Pixel 8",
28
+ "os": "android",
29
+ "osVersion": "15",
30
+ "appVersion": "3.4.0",
31
+ "appBuild": "3400",
32
+ "engineVersion": "1.12.0",
33
+ "sdkVersion": "0.9.2"
34
+ },
35
+ "glasses": {
36
+ "model": "mentra_live",
37
+ "firmware": {
38
+ "apk": "1.7.3",
39
+ "mtk": "2.1.0",
40
+ "bes": "0.8.5"
41
+ },
42
+ "connection": {
43
+ "state": "connected",
44
+ "observedAt": "2026-10-13T11:00:00Z"
45
+ },
46
+ "battery": {
47
+ "state": "measured",
48
+ "observedAt": "2026-10-13T11:00:00Z",
49
+ "level": 76,
50
+ "charging": false
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-bad-0006",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 3,
10
+ "observedAt": "2026-10-13T11:04:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "photo_operation",
19
+ "deviceBinding": {
20
+ "kind": "serial",
21
+ "manufacturer": "Mentra",
22
+ "product": "mentra_live",
23
+ "serial": "ML2A-0042-7731"
24
+ },
25
+ "payload": {
26
+ "stage": "captured"
27
+ }
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,172 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-mix-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 10,
10
+ "observedAt": "2026-10-13T11:00:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "snapshot",
19
+ "deviceBinding": {
20
+ "kind": "serial",
21
+ "manufacturer": "Mentra",
22
+ "product": "mentra_live",
23
+ "serial": "ML2A-0042-7731"
24
+ },
25
+ "payload": {
26
+ "phone": {
27
+ "model": "Pixel 8",
28
+ "os": "android",
29
+ "osVersion": "15",
30
+ "appVersion": "3.4.0",
31
+ "appBuild": "3400",
32
+ "engineVersion": "1.12.0",
33
+ "sdkVersion": "0.9.2"
34
+ },
35
+ "glasses": {
36
+ "model": "mentra_live",
37
+ "firmware": {
38
+ "apk": "1.7.3",
39
+ "mtk": "2.1.0",
40
+ "bes": "0.8.5"
41
+ },
42
+ "connection": {
43
+ "state": "connected",
44
+ "observedAt": "2026-10-13T11:00:00Z"
45
+ },
46
+ "battery": {
47
+ "state": "measured",
48
+ "observedAt": "2026-10-13T11:00:00Z",
49
+ "level": 76,
50
+ "charging": false
51
+ }
52
+ }
53
+ }
54
+ },
55
+ {
56
+ "schemaVersion": 1,
57
+ "recordId": "rec-mix-0002",
58
+ "phoneDeviceId": "phone-7f3a91",
59
+ "collectorSessionId": "cs-20261013-01",
60
+ "sequence": 11,
61
+ "observedAt": "2026-10-13T11:02:10Z",
62
+ "capabilities": [
63
+ "glasses_connection",
64
+ "glasses_battery",
65
+ "glasses_versions",
66
+ "miniapp_execution",
67
+ "engine_active"
68
+ ],
69
+ "kind": "miniapp_launch",
70
+ "payload": {
71
+ "packageName": "com.mentra.notes",
72
+ "miniappName": "Mentra Notes",
73
+ "versionName": "2.0.1",
74
+ "start": "user",
75
+ "outcome": "started"
76
+ }
77
+ },
78
+ {
79
+ "schemaVersion": 1,
80
+ "recordId": "rec-mix-0003",
81
+ "phoneDeviceId": "phone-7f3a91",
82
+ "collectorSessionId": "cs-20261013-01",
83
+ "sequence": 12,
84
+ "observedAt": "2026-10-13T11:03:10Z",
85
+ "capabilities": [
86
+ "glasses_connection",
87
+ "glasses_battery",
88
+ "glasses_versions",
89
+ "miniapp_execution",
90
+ "engine_active"
91
+ ],
92
+ "kind": "usage_checkpoint",
93
+ "payload": {
94
+ "sessions": [
95
+ {
96
+ "activity": "glasses_connection",
97
+ "activitySessionId": "act-conn-01",
98
+ "deviceBinding": {
99
+ "kind": "serial",
100
+ "manufacturer": "Mentra",
101
+ "product": "mentra_live",
102
+ "serial": "ML2A-0042-7731"
103
+ },
104
+ "startedAt": "2026-10-13T10:59:00Z",
105
+ "elapsedMs": 250000,
106
+ "ended": false
107
+ },
108
+ {
109
+ "activity": "miniapp_execution",
110
+ "activitySessionId": "act-8d02",
111
+ "packageName": "com.mentra.notes",
112
+ "startedAt": "2026-10-13T11:02:10Z",
113
+ "elapsedMs": 60000,
114
+ "ended": false
115
+ }
116
+ ]
117
+ }
118
+ },
119
+ {
120
+ "schemaVersion": 1,
121
+ "recordId": "rec-mix-0004",
122
+ "phoneDeviceId": "phone-7f3a91",
123
+ "collectorSessionId": "cs-20261013-01",
124
+ "sequence": 13,
125
+ "observedAt": "2026-10-13T11:04:00Z",
126
+ "capabilities": [
127
+ "glasses_connection",
128
+ "glasses_battery",
129
+ "glasses_versions",
130
+ "miniapp_execution",
131
+ "engine_active"
132
+ ],
133
+ "kind": "photo_operation",
134
+ "operationId": "op-photo-41c9",
135
+ "deviceBinding": {
136
+ "kind": "serial",
137
+ "manufacturer": "Mentra",
138
+ "product": "mentra_live",
139
+ "serial": "ML2A-0042-7731"
140
+ },
141
+ "payload": {
142
+ "stage": "requested"
143
+ }
144
+ },
145
+ {
146
+ "schemaVersion": 1,
147
+ "recordId": "rec-mix-0005",
148
+ "phoneDeviceId": "phone-7f3a91",
149
+ "collectorSessionId": "cs-20261013-01",
150
+ "sequence": 14,
151
+ "observedAt": "2026-10-13T11:04:02Z",
152
+ "capabilities": [
153
+ "glasses_connection",
154
+ "glasses_battery",
155
+ "glasses_versions",
156
+ "miniapp_execution",
157
+ "engine_active"
158
+ ],
159
+ "kind": "photo_operation",
160
+ "operationId": "op-photo-41c9",
161
+ "deviceBinding": {
162
+ "kind": "serial",
163
+ "manufacturer": "Mentra",
164
+ "product": "mentra_live",
165
+ "serial": "ML2A-0042-7731"
166
+ },
167
+ "payload": {
168
+ "stage": "captured"
169
+ }
170
+ }
171
+ ]
172
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-gap-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 9,
10
+ "observedAt": "2026-10-13T12:00:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "coverage_gap",
19
+ "payload": {
20
+ "reason": "queue_dropped",
21
+ "from": "2026-10-13T11:20:00Z",
22
+ "to": "2026-10-13T11:55:00Z",
23
+ "droppedRecordCount": 31
24
+ }
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-launch-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 1,
10
+ "observedAt": "2026-10-13T11:02:10Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "miniapp_launch",
19
+ "payload": {
20
+ "packageName": "com.acme.inventory",
21
+ "miniappName": "Acme Inventory",
22
+ "versionName": "2.0.1",
23
+ "start": "user",
24
+ "outcome": "started"
25
+ }
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-photo-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 3,
10
+ "observedAt": "2026-10-13T11:04:00Z",
11
+ "capabilities": ["glasses_connection", "photo_operations"],
12
+ "kind": "photo_operation",
13
+ "operationId": "op-photo-41c9",
14
+ "deviceBinding": {
15
+ "kind": "serial",
16
+ "manufacturer": "Mentra",
17
+ "product": "mentra_live",
18
+ "serial": "ML2A-0042-7731"
19
+ },
20
+ "payload": {
21
+ "stage": "captured"
22
+ }
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-snap-0002",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 0,
10
+ "observedAt": "2026-10-13T11:00:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "snapshot",
19
+ "deviceBinding": {
20
+ "kind": "provisional",
21
+ "bindingId": "bind-5c21e0",
22
+ "product": "mentra_live"
23
+ },
24
+ "payload": {
25
+ "phone": {
26
+ "model": "Pixel 8",
27
+ "os": "android",
28
+ "osVersion": "15",
29
+ "appVersion": "3.4.0",
30
+ "appBuild": "3400",
31
+ "engineVersion": "1.12.0",
32
+ "sdkVersion": "0.9.2"
33
+ },
34
+ "glasses": {
35
+ "model": "mentra_live",
36
+ "firmware": {
37
+ "apk": "1.7.3",
38
+ "mtk": "2.1.0",
39
+ "bes": "0.8.5"
40
+ },
41
+ "connection": {
42
+ "state": "connected",
43
+ "observedAt": "2026-10-13T11:00:00Z"
44
+ },
45
+ "battery": {
46
+ "state": "unsupported",
47
+ "observedAt": "2026-10-13T11:00:00Z"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ]
53
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-snap-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 0,
10
+ "observedAt": "2026-10-13T11:00:00Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "snapshot",
19
+ "deviceBinding": {
20
+ "kind": "serial",
21
+ "manufacturer": "Mentra",
22
+ "product": "mentra_live",
23
+ "serial": "ML2A-0042-7731"
24
+ },
25
+ "payload": {
26
+ "phone": {
27
+ "model": "Pixel 8",
28
+ "os": "android",
29
+ "osVersion": "15",
30
+ "appVersion": "3.4.0",
31
+ "appBuild": "3400",
32
+ "engineVersion": "1.12.0",
33
+ "sdkVersion": "0.9.2"
34
+ },
35
+ "glasses": {
36
+ "model": "mentra_live",
37
+ "firmware": {
38
+ "apk": "1.7.3",
39
+ "mtk": "2.1.0",
40
+ "bes": "0.8.5"
41
+ },
42
+ "connection": {
43
+ "state": "connected",
44
+ "observedAt": "2026-10-13T11:00:00Z"
45
+ },
46
+ "battery": {
47
+ "state": "measured",
48
+ "observedAt": "2026-10-13T11:00:00Z",
49
+ "level": 76,
50
+ "charging": false
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ]
56
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-cp-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 2,
10
+ "observedAt": "2026-10-13T11:03:10Z",
11
+ "capabilities": [
12
+ "glasses_connection",
13
+ "glasses_battery",
14
+ "glasses_versions",
15
+ "miniapp_execution",
16
+ "engine_active"
17
+ ],
18
+ "kind": "usage_checkpoint",
19
+ "payload": {
20
+ "sessions": [
21
+ {
22
+ "activity": "miniapp_execution",
23
+ "activitySessionId": "act-8d02",
24
+ "packageName": "com.mentra.notes",
25
+ "startedAt": "2026-10-13T11:02:10Z",
26
+ "elapsedMs": 60000,
27
+ "ended": false
28
+ }
29
+ ]
30
+ }
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "records": [
4
+ {
5
+ "schemaVersion": 1,
6
+ "recordId": "rec-pack-0001",
7
+ "phoneDeviceId": "phone-7f3a91",
8
+ "collectorSessionId": "cs-20261013-01",
9
+ "sequence": 4,
10
+ "observedAt": "2026-10-13T11:03:10Z",
11
+ "capabilities": ["glasses_connection", "miniapp_execution", "app_foreground", "engine_active"],
12
+ "kind": "usage_checkpoint",
13
+ "payload": {
14
+ "sessions": [
15
+ {
16
+ "activity": "glasses_connection",
17
+ "activitySessionId": "act-conn-01",
18
+ "deviceBinding": {
19
+ "kind": "serial",
20
+ "manufacturer": "Mentra",
21
+ "product": "mentra_live",
22
+ "serial": "ML2A-0042-7731"
23
+ },
24
+ "startedAt": "2026-10-13T10:59:00Z",
25
+ "elapsedMs": 250000,
26
+ "ended": false
27
+ },
28
+ {
29
+ "activity": "miniapp_execution",
30
+ "activitySessionId": "act-8d02",
31
+ "packageName": "com.mentra.notes",
32
+ "startedAt": "2026-10-13T11:02:10Z",
33
+ "elapsedMs": 60000,
34
+ "ended": false
35
+ },
36
+ {
37
+ "activity": "miniapp_execution",
38
+ "activitySessionId": "act-8c91",
39
+ "packageName": "com.acme.inventory",
40
+ "startedAt": "2026-10-13T11:00:30Z",
41
+ "elapsedMs": 120000,
42
+ "ended": true
43
+ },
44
+ {
45
+ "activity": "app_foreground",
46
+ "activitySessionId": "act-fg-01",
47
+ "startedAt": "2026-10-13T10:58:30Z",
48
+ "elapsedMs": 280000,
49
+ "ended": false
50
+ },
51
+ {
52
+ "activity": "engine_active",
53
+ "activitySessionId": "act-eng-01",
54
+ "startedAt": "2026-10-13T08:00:00Z",
55
+ "elapsedMs": 10990000,
56
+ "ended": false
57
+ }
58
+ ]
59
+ }
60
+ }
61
+ ]
62
+ }
package/src/fleet.ts ADDED
@@ -0,0 +1,245 @@
1
+ /**
2
+ * @fileoverview The Fleet report and acknowledgement contract.
3
+ *
4
+ * The Mentra App reports what a phone and its glasses did to the organization's Fleet service (MFM), through the
5
+ * phone's own Core. The reporter (via `@mentra/cloud-client`) and MFM both parse with these schemas, so this file is
6
+ * the one definition of what may cross that wire.
7
+ *
8
+ * Records are validated one at a time. The envelope accepts any record shape so that one bad record is rejected on
9
+ * its own, and never strands the rest of its batch.
10
+ */
11
+ import {z} from "zod"
12
+
13
+ export const FLEET_SCHEMA_VERSION = 1 as const
14
+
15
+ export const FLEET_LIMITS = {
16
+ maxRecordsPerBatch: 500,
17
+ maxBatchBytes: 1_048_576,
18
+ maxSessionsPerCheckpoint: 32,
19
+ maxSessionElapsedMs: 7 * 24 * 60 * 60 * 1000,
20
+ phoneQueueMaxRecords: 10_000,
21
+ phoneQueueMaxAgeMs: 7 * 24 * 60 * 60 * 1000,
22
+ replayWindowMs: 14 * 24 * 60 * 60 * 1000,
23
+ maxFutureSkewMs: 5 * 60 * 1000,
24
+ } as const
25
+
26
+ const FleetId = z
27
+ .string()
28
+ .min(1)
29
+ .max(128)
30
+ .regex(/^[A-Za-z0-9._:-]+$/)
31
+ const FLEET_UTC_TIME = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,3}))?Z$/
32
+
33
+ /**
34
+ * Whether `value` names a real instant. `Date.parse` alone is not enough: it normalizes a calendar-invalid date (such
35
+ * as `2025-02-30`) to a later instant, and accepts `24:00:00`. So the parsed instant is formatted back and must
36
+ * reproduce every field of the input, with a missing or short fraction read as milliseconds.
37
+ */
38
+ function isRoundTripUtcTime(value: string): boolean {
39
+ const match = FLEET_UTC_TIME.exec(value)
40
+ if (!match) return false
41
+ const instant = Date.parse(value)
42
+ if (Number.isNaN(instant)) return false
43
+ return new Date(instant).toISOString() === `${match[1]}.${(match[2] ?? "").padEnd(3, "0")}Z`
44
+ }
45
+
46
+ /** RFC 3339 UTC with a `Z`; a numeric offset, a calendar-invalid date, `24:00:00` and a leap second are refused. */
47
+ const FleetUtcTime = z.string().regex(FLEET_UTC_TIME).refine(isRoundTripUtcTime, "not a date")
48
+ const FleetText = z.string().min(1).max(128)
49
+
50
+ export const FleetDeviceBindingSchema = z.discriminatedUnion("kind", [
51
+ z.object({kind: z.literal("serial"), manufacturer: FleetText, product: FleetText, serial: FleetText}).strict(),
52
+ z.object({kind: z.literal("provisional"), bindingId: FleetId, product: FleetText}).strict(),
53
+ ])
54
+
55
+ export const FLEET_CAPABILITIES = [
56
+ "glasses_connection",
57
+ "glasses_battery",
58
+ "glasses_versions",
59
+ "miniapp_execution",
60
+ "app_foreground",
61
+ "engine_active",
62
+ "photo_operations",
63
+ ] as const
64
+
65
+ const Battery = z.discriminatedUnion("state", [
66
+ z
67
+ .object({
68
+ state: z.literal("measured"),
69
+ observedAt: FleetUtcTime,
70
+ level: z.number().int().min(0).max(100),
71
+ charging: z.boolean().optional(),
72
+ })
73
+ .strict(),
74
+ z.object({state: z.literal("unsupported"), observedAt: FleetUtcTime}).strict(),
75
+ z.object({state: z.literal("unknown"), observedAt: FleetUtcTime}).strict(),
76
+ ])
77
+
78
+ export const FleetSnapshotPayloadSchema = z
79
+ .object({
80
+ phone: z
81
+ .object({
82
+ model: FleetText.optional(),
83
+ os: z.enum(["ios", "android"]),
84
+ osVersion: FleetText,
85
+ appVersion: FleetText,
86
+ appBuild: FleetText,
87
+ engineVersion: FleetText.optional(),
88
+ sdkVersion: FleetText.optional(),
89
+ })
90
+ .strict(),
91
+ glasses: z
92
+ .object({
93
+ model: FleetText,
94
+ firmware: z
95
+ .object({
96
+ apk: FleetText.optional(),
97
+ mtk: FleetText.optional(),
98
+ bes: FleetText.optional(),
99
+ os: FleetText.optional(),
100
+ })
101
+ .strict()
102
+ .optional(),
103
+ connection: z
104
+ .object({state: z.enum(["connected", "disconnected", "connecting", "unknown"]), observedAt: FleetUtcTime})
105
+ .strict(),
106
+ battery: Battery.optional(),
107
+ })
108
+ .strict()
109
+ .optional(),
110
+ })
111
+ .strict()
112
+
113
+ export const FleetLaunchPayloadSchema = z
114
+ .object({
115
+ packageName: FleetText,
116
+ miniappName: FleetText.optional(),
117
+ versionName: FleetText.optional(),
118
+ start: z.enum(["user", "automatic"]),
119
+ outcome: z.enum(["started", "failed"]),
120
+ })
121
+ .strict()
122
+
123
+ export const FLEET_ACTIVITIES = ["glasses_connection", "miniapp_execution", "app_foreground", "engine_active"] as const
124
+ export const FleetActivityCheckpointSchema = z
125
+ .object({
126
+ activity: z.enum(FLEET_ACTIVITIES),
127
+ activitySessionId: FleetId,
128
+ packageName: FleetText.optional(),
129
+ deviceBinding: FleetDeviceBindingSchema.optional(),
130
+ startedAt: FleetUtcTime,
131
+ /** Cumulative monotonic elapsed time of this session, never wall-clock subtraction. */
132
+ elapsedMs: z.number().int().min(0).max(FLEET_LIMITS.maxSessionElapsedMs),
133
+ ended: z.boolean(),
134
+ })
135
+ .strict()
136
+ .superRefine((value, ctx) => {
137
+ if ((value.activity === "miniapp_execution") !== (value.packageName !== undefined))
138
+ ctx.addIssue({code: "custom", message: "packageName is required for, and only for, miniapp_execution"})
139
+ if ((value.activity === "glasses_connection") !== (value.deviceBinding !== undefined))
140
+ ctx.addIssue({code: "custom", message: "deviceBinding is required for, and only for, glasses_connection"})
141
+ })
142
+ export type FleetActivity = (typeof FLEET_ACTIVITIES)[number]
143
+ export const FleetUsagePayloadSchema = z
144
+ .object({
145
+ sessions: z.array(FleetActivityCheckpointSchema).min(1).max(FLEET_LIMITS.maxSessionsPerCheckpoint),
146
+ })
147
+ .strict()
148
+
149
+ export const FleetPhotoPayloadSchema = z
150
+ .object({
151
+ stage: z.enum(["requested", "captured", "delivered", "failed"]),
152
+ failedStage: z.enum(["requested", "captured", "delivered"]).optional(),
153
+ })
154
+ .strict()
155
+ .refine((v) => (v.stage === "failed") === (v.failedStage !== undefined), "failedStage goes with stage failed")
156
+
157
+ export const FleetCoverageGapPayloadSchema = z
158
+ .object({
159
+ reason: z.enum(["queue_dropped", "queue_expired", "clock_untrusted", "reporter_stopped", "record_rejected"]),
160
+ from: FleetUtcTime,
161
+ to: FleetUtcTime,
162
+ droppedRecordCount: z.number().int().min(0).optional(),
163
+ })
164
+ .strict()
165
+
166
+ const base = {
167
+ schemaVersion: z.literal(FLEET_SCHEMA_VERSION),
168
+ recordId: FleetId,
169
+ phoneDeviceId: FleetId,
170
+ collectorSessionId: FleetId,
171
+ sequence: z.number().int().min(0),
172
+ observedAt: FleetUtcTime,
173
+ management: z
174
+ .object({associationId: FleetId, generation: z.number().int().min(0)})
175
+ .strict()
176
+ .optional(),
177
+ capabilities: z.array(z.enum(FLEET_CAPABILITIES)).max(FLEET_CAPABILITIES.length),
178
+ }
179
+ export const FleetRecordSchema = z.discriminatedUnion("kind", [
180
+ z
181
+ .object({
182
+ ...base,
183
+ kind: z.literal("snapshot"),
184
+ deviceBinding: FleetDeviceBindingSchema.optional(),
185
+ payload: FleetSnapshotPayloadSchema,
186
+ })
187
+ .strict(),
188
+ z.object({...base, kind: z.literal("miniapp_launch"), payload: FleetLaunchPayloadSchema}).strict(),
189
+ z.object({...base, kind: z.literal("usage_checkpoint"), payload: FleetUsagePayloadSchema}).strict(),
190
+ z
191
+ .object({
192
+ ...base,
193
+ kind: z.literal("photo_operation"),
194
+ operationId: FleetId,
195
+ deviceBinding: FleetDeviceBindingSchema,
196
+ payload: FleetPhotoPayloadSchema,
197
+ })
198
+ .strict(),
199
+ z.object({...base, kind: z.literal("coverage_gap"), payload: FleetCoverageGapPayloadSchema}).strict(),
200
+ ])
201
+ export type FleetRecord = z.infer<typeof FleetRecordSchema>
202
+ export type FleetDeviceBinding = z.infer<typeof FleetDeviceBindingSchema>
203
+
204
+ /** The envelope. Records are validated one at a time, so one bad record cannot strand a batch. */
205
+ export const FleetReportSchema = z
206
+ .object({
207
+ schemaVersion: z.literal(FLEET_SCHEMA_VERSION),
208
+ records: z.array(z.unknown()).min(1).max(FLEET_LIMITS.maxRecordsPerBatch),
209
+ })
210
+ .strict()
211
+ export type FleetReport = {schemaVersion: 1; records: FleetRecord[]}
212
+
213
+ export const FLEET_REJECT_REASONS = [
214
+ "schema_invalid",
215
+ "unsupported_schema_version",
216
+ "content_mismatch",
217
+ "outside_replay_window",
218
+ "timestamp_out_of_bounds",
219
+ "account_deleted",
220
+ ] as const
221
+ export const FleetRecordResultSchema = z
222
+ .object({
223
+ index: z.number().int().min(0),
224
+ recordId: FleetId.optional(),
225
+ status: z.enum(["accepted", "duplicate", "retryable", "rejected"]),
226
+ reasonCode: z.enum(FLEET_REJECT_REASONS).optional(),
227
+ retryAfterSeconds: z.number().int().min(0).optional(),
228
+ })
229
+ .strict()
230
+ .superRefine((value, ctx) => {
231
+ if ((value.status === "rejected") !== (value.reasonCode !== undefined))
232
+ ctx.addIssue({code: "custom", message: "reasonCode is required for, and only for, status rejected"})
233
+ })
234
+ export const FleetReportAckSchema = z
235
+ .object({
236
+ schemaVersion: z.literal(FLEET_SCHEMA_VERSION),
237
+ results: z.array(FleetRecordResultSchema),
238
+ })
239
+ .strict()
240
+ export type FleetRecordResult = z.infer<typeof FleetRecordResultSchema>
241
+ export type FleetReportAck = z.infer<typeof FleetReportAckSchema>
242
+
243
+ /** Whole-response error codes Core and MFM answer with. */
244
+ export const FLEET_NOT_INSTALLED = "fleet_not_installed"
245
+ export const FLEET_UNAVAILABLE = "fleet_unavailable"
package/src/index.ts CHANGED
@@ -17,3 +17,4 @@ export * from "./languages";
17
17
  export * from "./camera";
18
18
  export * from "./maps";
19
19
  export * from "./report-automation";
20
+ export * from "./fleet";