@mentra/cloud-protocol 3.2.1 → 3.3.0-beta.954
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/package.json +1 -1
- package/src/__fixtures__/fleet/ack-invalid-reason-on-accepted.json +11 -0
- package/src/__fixtures__/fleet/ack-invalid-rejected-without-reason.json +10 -0
- package/src/__fixtures__/fleet/ack-invalid-unknown-error.json +3 -0
- package/src/__fixtures__/fleet/ack-valid-mixed.json +33 -0
- package/src/__fixtures__/fleet/ack-valid-not-installed.json +3 -0
- package/src/__fixtures__/fleet/ack-valid-unavailable.json +4 -0
- package/src/__fixtures__/fleet/report-invalid-battery-zero-when-unsupported.json +55 -0
- package/src/__fixtures__/fleet/report-invalid-email-identity.json +57 -0
- package/src/__fixtures__/fleet/report-invalid-empty-batch.json +4 -0
- package/src/__fixtures__/fleet/report-invalid-execution-without-package-name.json +32 -0
- package/src/__fixtures__/fleet/report-invalid-missing-record-id.json +55 -0
- package/src/__fixtures__/fleet/report-invalid-negative-elapsed.json +33 -0
- package/src/__fixtures__/fleet/report-invalid-offset-time.json +56 -0
- package/src/__fixtures__/fleet/report-invalid-photo-without-operation-id.json +30 -0
- package/src/__fixtures__/fleet/report-invalid-snapshot-glasses-without-binding.json +50 -0
- package/src/__fixtures__/fleet/report-valid-batch-mixed.json +182 -0
- package/src/__fixtures__/fleet/report-valid-coverage-gap.json +27 -0
- package/src/__fixtures__/fleet/report-valid-miniapp-launch.json +28 -0
- package/src/__fixtures__/fleet/report-valid-photo-operation.json +25 -0
- package/src/__fixtures__/fleet/report-valid-snapshot-provisional.json +53 -0
- package/src/__fixtures__/fleet/report-valid-snapshot.json +56 -0
- package/src/__fixtures__/fleet/report-valid-usage-checkpoint.json +33 -0
- package/src/__fixtures__/fleet/report-valid-usage-packed.json +62 -0
- package/src/audio.ts +11 -0
- package/src/fleet.ts +253 -0
- package/src/handshake.ts +5 -1
- package/src/index.ts +2 -0
- package/src/report-automation.ts +20 -0
package/package.json
CHANGED
|
@@ -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,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,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,50 @@
|
|
|
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
|
+
"payload": {
|
|
20
|
+
"phone": {
|
|
21
|
+
"model": "Pixel 8",
|
|
22
|
+
"os": "android",
|
|
23
|
+
"osVersion": "15",
|
|
24
|
+
"appVersion": "3.4.0",
|
|
25
|
+
"appBuild": "3400",
|
|
26
|
+
"engineVersion": "1.12.0",
|
|
27
|
+
"sdkVersion": "0.9.2"
|
|
28
|
+
},
|
|
29
|
+
"glasses": {
|
|
30
|
+
"model": "mentra_live",
|
|
31
|
+
"firmware": {
|
|
32
|
+
"apk": "1.7.3",
|
|
33
|
+
"mtk": "2.1.0",
|
|
34
|
+
"bes": "0.8.5"
|
|
35
|
+
},
|
|
36
|
+
"connection": {
|
|
37
|
+
"state": "connected",
|
|
38
|
+
"observedAt": "2026-10-13T11:00:00Z"
|
|
39
|
+
},
|
|
40
|
+
"battery": {
|
|
41
|
+
"state": "measured",
|
|
42
|
+
"observedAt": "2026-10-13T11:00:00Z",
|
|
43
|
+
"level": 76,
|
|
44
|
+
"charging": false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
"app_foreground",
|
|
17
|
+
"engine_active",
|
|
18
|
+
"photo_operations"
|
|
19
|
+
],
|
|
20
|
+
"kind": "snapshot",
|
|
21
|
+
"deviceBinding": {
|
|
22
|
+
"kind": "serial",
|
|
23
|
+
"manufacturer": "Mentra",
|
|
24
|
+
"product": "mentra_live",
|
|
25
|
+
"serial": "ML2A-0042-7731"
|
|
26
|
+
},
|
|
27
|
+
"payload": {
|
|
28
|
+
"phone": {
|
|
29
|
+
"model": "Pixel 8",
|
|
30
|
+
"os": "android",
|
|
31
|
+
"osVersion": "15",
|
|
32
|
+
"appVersion": "3.4.0",
|
|
33
|
+
"appBuild": "3400",
|
|
34
|
+
"engineVersion": "1.12.0",
|
|
35
|
+
"sdkVersion": "0.9.2"
|
|
36
|
+
},
|
|
37
|
+
"glasses": {
|
|
38
|
+
"model": "mentra_live",
|
|
39
|
+
"firmware": {
|
|
40
|
+
"apk": "1.7.3",
|
|
41
|
+
"mtk": "2.1.0",
|
|
42
|
+
"bes": "0.8.5"
|
|
43
|
+
},
|
|
44
|
+
"connection": {
|
|
45
|
+
"state": "connected",
|
|
46
|
+
"observedAt": "2026-10-13T11:00:00Z"
|
|
47
|
+
},
|
|
48
|
+
"battery": {
|
|
49
|
+
"state": "measured",
|
|
50
|
+
"observedAt": "2026-10-13T11:00:00Z",
|
|
51
|
+
"level": 76,
|
|
52
|
+
"charging": false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"schemaVersion": 1,
|
|
59
|
+
"recordId": "rec-mix-0002",
|
|
60
|
+
"phoneDeviceId": "phone-7f3a91",
|
|
61
|
+
"collectorSessionId": "cs-20261013-01",
|
|
62
|
+
"sequence": 11,
|
|
63
|
+
"observedAt": "2026-10-13T11:02:10Z",
|
|
64
|
+
"capabilities": [
|
|
65
|
+
"glasses_connection",
|
|
66
|
+
"glasses_battery",
|
|
67
|
+
"glasses_versions",
|
|
68
|
+
"miniapp_execution",
|
|
69
|
+
"app_foreground",
|
|
70
|
+
"engine_active",
|
|
71
|
+
"photo_operations"
|
|
72
|
+
],
|
|
73
|
+
"kind": "miniapp_launch",
|
|
74
|
+
"payload": {
|
|
75
|
+
"packageName": "com.mentra.notes",
|
|
76
|
+
"miniappName": "Mentra Notes",
|
|
77
|
+
"versionName": "2.0.1",
|
|
78
|
+
"start": "user",
|
|
79
|
+
"outcome": "started"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"schemaVersion": 1,
|
|
84
|
+
"recordId": "rec-mix-0003",
|
|
85
|
+
"phoneDeviceId": "phone-7f3a91",
|
|
86
|
+
"collectorSessionId": "cs-20261013-01",
|
|
87
|
+
"sequence": 12,
|
|
88
|
+
"observedAt": "2026-10-13T11:03:10Z",
|
|
89
|
+
"capabilities": [
|
|
90
|
+
"glasses_connection",
|
|
91
|
+
"glasses_battery",
|
|
92
|
+
"glasses_versions",
|
|
93
|
+
"miniapp_execution",
|
|
94
|
+
"app_foreground",
|
|
95
|
+
"engine_active",
|
|
96
|
+
"photo_operations"
|
|
97
|
+
],
|
|
98
|
+
"kind": "usage_checkpoint",
|
|
99
|
+
"payload": {
|
|
100
|
+
"sessions": [
|
|
101
|
+
{
|
|
102
|
+
"activity": "glasses_connection",
|
|
103
|
+
"activitySessionId": "act-conn-01",
|
|
104
|
+
"deviceBinding": {
|
|
105
|
+
"kind": "serial",
|
|
106
|
+
"manufacturer": "Mentra",
|
|
107
|
+
"product": "mentra_live",
|
|
108
|
+
"serial": "ML2A-0042-7731"
|
|
109
|
+
},
|
|
110
|
+
"startedAt": "2026-10-13T10:59:00Z",
|
|
111
|
+
"elapsedMs": 250000,
|
|
112
|
+
"ended": false
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"activity": "miniapp_execution",
|
|
116
|
+
"activitySessionId": "act-8d02",
|
|
117
|
+
"packageName": "com.mentra.notes",
|
|
118
|
+
"startedAt": "2026-10-13T11:02:10Z",
|
|
119
|
+
"elapsedMs": 60000,
|
|
120
|
+
"ended": false
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"schemaVersion": 1,
|
|
127
|
+
"recordId": "rec-mix-0004",
|
|
128
|
+
"phoneDeviceId": "phone-7f3a91",
|
|
129
|
+
"collectorSessionId": "cs-20261013-01",
|
|
130
|
+
"sequence": 13,
|
|
131
|
+
"observedAt": "2026-10-13T11:04:00Z",
|
|
132
|
+
"capabilities": [
|
|
133
|
+
"glasses_connection",
|
|
134
|
+
"glasses_battery",
|
|
135
|
+
"glasses_versions",
|
|
136
|
+
"miniapp_execution",
|
|
137
|
+
"app_foreground",
|
|
138
|
+
"engine_active",
|
|
139
|
+
"photo_operations"
|
|
140
|
+
],
|
|
141
|
+
"kind": "photo_operation",
|
|
142
|
+
"operationId": "op-photo-41c9",
|
|
143
|
+
"deviceBinding": {
|
|
144
|
+
"kind": "serial",
|
|
145
|
+
"manufacturer": "Mentra",
|
|
146
|
+
"product": "mentra_live",
|
|
147
|
+
"serial": "ML2A-0042-7731"
|
|
148
|
+
},
|
|
149
|
+
"payload": {
|
|
150
|
+
"stage": "requested"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"schemaVersion": 1,
|
|
155
|
+
"recordId": "rec-mix-0005",
|
|
156
|
+
"phoneDeviceId": "phone-7f3a91",
|
|
157
|
+
"collectorSessionId": "cs-20261013-01",
|
|
158
|
+
"sequence": 14,
|
|
159
|
+
"observedAt": "2026-10-13T11:04:02Z",
|
|
160
|
+
"capabilities": [
|
|
161
|
+
"glasses_connection",
|
|
162
|
+
"glasses_battery",
|
|
163
|
+
"glasses_versions",
|
|
164
|
+
"miniapp_execution",
|
|
165
|
+
"app_foreground",
|
|
166
|
+
"engine_active",
|
|
167
|
+
"photo_operations"
|
|
168
|
+
],
|
|
169
|
+
"kind": "photo_operation",
|
|
170
|
+
"operationId": "op-photo-41c9",
|
|
171
|
+
"deviceBinding": {
|
|
172
|
+
"kind": "serial",
|
|
173
|
+
"manufacturer": "Mentra",
|
|
174
|
+
"product": "mentra_live",
|
|
175
|
+
"serial": "ML2A-0042-7731"
|
|
176
|
+
},
|
|
177
|
+
"payload": {
|
|
178
|
+
"stage": "captured"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
@@ -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/audio.ts
CHANGED
|
@@ -56,6 +56,13 @@ export type UdpLivenessAckPayload = z.infer<typeof udpLivenessAckPayloadSchema>;
|
|
|
56
56
|
|
|
57
57
|
// --- Result types -----------------------------------------------------------
|
|
58
58
|
|
|
59
|
+
/** Position on the phone's submitted audio stream, independent of provider latency. */
|
|
60
|
+
export const audioPositionSchema = z.object({
|
|
61
|
+
sessionTag: z.number().int().nonnegative(),
|
|
62
|
+
offsetMs: z.number().nonnegative(),
|
|
63
|
+
});
|
|
64
|
+
export type AudioPosition = z.infer<typeof audioPositionSchema>;
|
|
65
|
+
|
|
59
66
|
export const transcriptionTokenSchema = z.object({
|
|
60
67
|
text: z.string(),
|
|
61
68
|
startMs: z.number(),
|
|
@@ -65,6 +72,7 @@ export const transcriptionTokenSchema = z.object({
|
|
|
65
72
|
speaker: z.string().optional(),
|
|
66
73
|
// per-token; mid-sentence language switches are possible
|
|
67
74
|
detectedLanguage: z.string().optional(),
|
|
75
|
+
audioPosition: audioPositionSchema.optional(),
|
|
68
76
|
});
|
|
69
77
|
export type TranscriptionToken = z.infer<typeof transcriptionTokenSchema>;
|
|
70
78
|
|
|
@@ -88,6 +96,9 @@ export const transcriptionDataSchema = z.object({
|
|
|
88
96
|
|
|
89
97
|
// Per-token detail for consumers that need it
|
|
90
98
|
tokens: z.array(transcriptionTokenSchema),
|
|
99
|
+
// Internal result capability: missing timing on a positioned session must
|
|
100
|
+
// be withheld, while an older/unnegotiated session keeps legacy delivery.
|
|
101
|
+
frameTimelineVersion: z.literal(1).optional(),
|
|
91
102
|
|
|
92
103
|
provider: z.string(),
|
|
93
104
|
timestamp: z.number(),
|
package/src/fleet.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
]).superRefine((value, ctx) => {
|
|
201
|
+
// A snapshot's glasses belong to the pair its binding names, so the two come together.
|
|
202
|
+
if (value.kind === "snapshot" && (value.payload.glasses !== undefined) !== (value.deviceBinding !== undefined))
|
|
203
|
+
ctx.addIssue({
|
|
204
|
+
code: "custom",
|
|
205
|
+
path: ["deviceBinding"],
|
|
206
|
+
message: "deviceBinding is required for, and only for, a snapshot that includes glasses",
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
export type FleetRecord = z.infer<typeof FleetRecordSchema>
|
|
210
|
+
export type FleetDeviceBinding = z.infer<typeof FleetDeviceBindingSchema>
|
|
211
|
+
|
|
212
|
+
/** The envelope. Records are validated one at a time, so one bad record cannot strand a batch. */
|
|
213
|
+
export const FleetReportSchema = z
|
|
214
|
+
.object({
|
|
215
|
+
schemaVersion: z.literal(FLEET_SCHEMA_VERSION),
|
|
216
|
+
records: z.array(z.unknown()).min(1).max(FLEET_LIMITS.maxRecordsPerBatch),
|
|
217
|
+
})
|
|
218
|
+
.strict()
|
|
219
|
+
export type FleetReport = {schemaVersion: 1; records: FleetRecord[]}
|
|
220
|
+
|
|
221
|
+
export const FLEET_REJECT_REASONS = [
|
|
222
|
+
"schema_invalid",
|
|
223
|
+
"unsupported_schema_version",
|
|
224
|
+
"content_mismatch",
|
|
225
|
+
"outside_replay_window",
|
|
226
|
+
"timestamp_out_of_bounds",
|
|
227
|
+
"account_deleted",
|
|
228
|
+
] as const
|
|
229
|
+
export const FleetRecordResultSchema = z
|
|
230
|
+
.object({
|
|
231
|
+
index: z.number().int().min(0),
|
|
232
|
+
recordId: FleetId.optional(),
|
|
233
|
+
status: z.enum(["accepted", "duplicate", "retryable", "rejected"]),
|
|
234
|
+
reasonCode: z.enum(FLEET_REJECT_REASONS).optional(),
|
|
235
|
+
retryAfterSeconds: z.number().int().min(0).optional(),
|
|
236
|
+
})
|
|
237
|
+
.strict()
|
|
238
|
+
.superRefine((value, ctx) => {
|
|
239
|
+
if ((value.status === "rejected") !== (value.reasonCode !== undefined))
|
|
240
|
+
ctx.addIssue({code: "custom", message: "reasonCode is required for, and only for, status rejected"})
|
|
241
|
+
})
|
|
242
|
+
export const FleetReportAckSchema = z
|
|
243
|
+
.object({
|
|
244
|
+
schemaVersion: z.literal(FLEET_SCHEMA_VERSION),
|
|
245
|
+
results: z.array(FleetRecordResultSchema),
|
|
246
|
+
})
|
|
247
|
+
.strict()
|
|
248
|
+
export type FleetRecordResult = z.infer<typeof FleetRecordResultSchema>
|
|
249
|
+
export type FleetReportAck = z.infer<typeof FleetReportAckSchema>
|
|
250
|
+
|
|
251
|
+
/** Whole-response error codes Core and MFM answer with. */
|
|
252
|
+
export const FLEET_NOT_INSTALLED = "fleet_not_installed"
|
|
253
|
+
export const FLEET_UNAVAILABLE = "fleet_unavailable"
|
package/src/handshake.ts
CHANGED
|
@@ -26,9 +26,12 @@ export const connectionInitPayloadSchema = z.object({
|
|
|
26
26
|
.object({
|
|
27
27
|
codec: z.enum(["lc3", "pcm"]),
|
|
28
28
|
sampleRate: z.number().int(), // e.g. 16000
|
|
29
|
+
frameTimelineVersion: z.literal(1).optional(),
|
|
29
30
|
// LC3 bitrate / frame size in bytes. Only meaningful when codec is
|
|
30
31
|
// "lc3"; omitted PCM sessions keep treating payload bytes as PCM.
|
|
31
|
-
frameSizeBytes: z
|
|
32
|
+
frameSizeBytes: z
|
|
33
|
+
.union([z.literal(20), z.literal(40), z.literal(60)])
|
|
34
|
+
.optional(),
|
|
32
35
|
// Optional initial subscription set, seeded atomically with the session
|
|
33
36
|
// so audio that starts before the first REST update is not transcribed
|
|
34
37
|
// with an empty set.
|
|
@@ -47,6 +50,7 @@ export const connectionAckPayloadSchema = z.object({
|
|
|
47
50
|
audio: z
|
|
48
51
|
.object({
|
|
49
52
|
sessionTag: z.number().int(), // u32 stamped into UDP audio frames
|
|
53
|
+
frameTimelineVersion: z.literal(1).optional(),
|
|
50
54
|
udp: z.object({ host: z.string(), port: z.number().int() }),
|
|
51
55
|
// Per-session key for encrypting UDP audio. Delivered here because the
|
|
52
56
|
// handshake is over the TLS WebSocket.
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Total automated incident submission and required device collection budget. */
|
|
2
|
+
export const AUTOMATED_INCIDENT_TIMEOUT_MS = 120_000;
|
|
3
|
+
|
|
4
|
+
/** Identity of the single device incident request saved by an automated run.
|
|
5
|
+
* This binds report creation, independently of transport or collection completion.
|
|
6
|
+
*/
|
|
7
|
+
export interface ReportAutomationCorrelation {
|
|
8
|
+
alertId: string;
|
|
9
|
+
testRunId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Preserve exact request IDs; never normalize or truncate correlation. */
|
|
13
|
+
export function reportAutomationCorrelation(value: unknown): ReportAutomationCorrelation | null {
|
|
14
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
15
|
+
const row = value as Record<string, unknown>;
|
|
16
|
+
if (Object.keys(row).length !== 2 || typeof row.alertId !== 'string'
|
|
17
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/.test(row.alertId)
|
|
18
|
+
|| typeof row.testRunId !== 'string' || !row.testRunId.trim() || row.testRunId.length > 160) return null;
|
|
19
|
+
return {alertId: row.alertId, testRunId: row.testRunId};
|
|
20
|
+
}
|