@myrobotaxi/contracts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +67 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.cjs +721 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +615 -0
- package/dist/index.d.ts +615 -0
- package/dist/index.js +719 -0
- package/dist/index.js.map +1 -0
- package/dist/types.cjs +4 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +590 -0
- package/dist/types.d.ts +590 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +77 -0
- package/schemas/vehicle-state.schema.json +343 -0
- package/schemas/ws-envelope.schema.json +65 -0
- package/schemas/ws-messages.schema.json +295 -0
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@myrobotaxi/contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Canonical wire-protocol schemas for the MyRoboTaxi platform. JSON Schema (draft-2020-12) + pre-generated TypeScript types. Consumed by SDKs (TS, Swift) and the Go telemetry server.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/myrobotaxi/contracts.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/myrobotaxi/contracts#readme",
|
|
11
|
+
"bugs": "https://github.com/myrobotaxi/contracts/issues",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
27
|
+
"default": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"./types": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/types.d.ts",
|
|
33
|
+
"default": "./dist/types.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/types.d.cts",
|
|
37
|
+
"default": "./dist/types.cjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"./schemas/vehicle-state.json": "./schemas/vehicle-state.schema.json",
|
|
41
|
+
"./schemas/ws-messages.json": "./schemas/ws-messages.schema.json",
|
|
42
|
+
"./schemas/ws-envelope.json": "./schemas/ws-envelope.schema.json"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"schemas",
|
|
48
|
+
"README.md",
|
|
49
|
+
"CONTRIBUTING.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"codegen": "node scripts/codegen.mjs",
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"clean": "rm -rf dist coverage",
|
|
56
|
+
"lint": "eslint .",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"prepare": "[ -d schemas ] && npm run codegen && npm run build || true"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.10.0",
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
|
65
|
+
"@typescript-eslint/parser": "^8.18.0",
|
|
66
|
+
"eslint": "^9.39.3",
|
|
67
|
+
"json-schema-to-typescript": "^15.0.4",
|
|
68
|
+
"tsup": "^8.5.0",
|
|
69
|
+
"typescript": "^5.7.0",
|
|
70
|
+
"typescript-eslint": "^8.18.0",
|
|
71
|
+
"vitest": "^4.0.18"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"provenance": true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://myrobotaxi.com/schemas/vehicle-state.schema.json",
|
|
4
|
+
"title": "VehicleState",
|
|
5
|
+
"description": "Canonical vehicle state as consumed by MyRoboTaxi SDKs. Defines every field name, type, unit, nullability, atomic group membership, and data classification. Both the WebSocket vehicle_update messages and the REST snapshot endpoint return subsets of this schema.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"vehicleId",
|
|
9
|
+
"name",
|
|
10
|
+
"model",
|
|
11
|
+
"year",
|
|
12
|
+
"color",
|
|
13
|
+
"status",
|
|
14
|
+
"chargeLevel",
|
|
15
|
+
"estimatedRange",
|
|
16
|
+
"speed",
|
|
17
|
+
"heading",
|
|
18
|
+
"latitude",
|
|
19
|
+
"longitude",
|
|
20
|
+
"locationName",
|
|
21
|
+
"locationAddress",
|
|
22
|
+
"interiorTemp",
|
|
23
|
+
"exteriorTemp",
|
|
24
|
+
"odometerMiles",
|
|
25
|
+
"fsdMilesSinceReset",
|
|
26
|
+
"lastUpdated"
|
|
27
|
+
],
|
|
28
|
+
"properties": {
|
|
29
|
+
"vehicleId": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Opaque database identifier (cuid). NOT the VIN. All SDK API calls use this ID (FR-4.2).",
|
|
32
|
+
"x-classification": "P0",
|
|
33
|
+
"examples": ["clxyz1234567890abcdef"]
|
|
34
|
+
},
|
|
35
|
+
"name": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "User-assigned vehicle name from DB Vehicle.name (managed by the Next.js settings UI). NOT the Tesla-streamed VehicleName proto field — see vehicle-state-schema.md §1.2 for the disambiguation rationale (MYR-30).",
|
|
38
|
+
"x-classification": "P0",
|
|
39
|
+
"examples": ["Optimus"]
|
|
40
|
+
},
|
|
41
|
+
"model": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Vehicle model (e.g., 'Model 3', 'Model Y'). Sourced from DB Vehicle.model (Prisma NOT NULL). Loaded by the Go Vehicle struct and populated on snapshot as of MYR-24 (2026-04-23).",
|
|
44
|
+
"x-classification": "P0",
|
|
45
|
+
"examples": ["Model 3"]
|
|
46
|
+
},
|
|
47
|
+
"year": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"description": "Model year. Sourced from DB Vehicle.year (Prisma NOT NULL). Loaded by the Go Vehicle struct and populated on snapshot as of MYR-24 (2026-04-23).",
|
|
50
|
+
"x-classification": "P0",
|
|
51
|
+
"examples": [2024]
|
|
52
|
+
},
|
|
53
|
+
"color": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "Vehicle exterior color. Sourced from DB Vehicle.color (Prisma NOT NULL). Loaded by the Go Vehicle struct and populated on snapshot as of MYR-24 (2026-04-23).",
|
|
56
|
+
"x-classification": "P0",
|
|
57
|
+
"examples": ["Midnight Silver Metallic"]
|
|
58
|
+
},
|
|
59
|
+
"status": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"enum": ["driving", "parked", "charging", "offline", "in_service"],
|
|
62
|
+
"description": "Derived vehicle status. Part of the gear atomic group -- updated atomically with gearPosition. When gearPosition is D or R, status is 'driving'; otherwise 'parked'. The 'charging', 'offline', and 'in_service' states are set by server-side logic.",
|
|
63
|
+
"x-classification": "P0",
|
|
64
|
+
"x-atomic-group": "gear"
|
|
65
|
+
},
|
|
66
|
+
"speed": {
|
|
67
|
+
"type": "integer",
|
|
68
|
+
"description": "Current vehicle speed. Rounded to nearest integer from Tesla's float emission.",
|
|
69
|
+
"x-unit": "mph",
|
|
70
|
+
"x-classification": "P0",
|
|
71
|
+
"minimum": 0
|
|
72
|
+
},
|
|
73
|
+
"heading": {
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"description": "Compass heading. 0 = North, 90 = East, 180 = South, 270 = West.",
|
|
76
|
+
"x-unit": "degrees",
|
|
77
|
+
"x-classification": "P0",
|
|
78
|
+
"minimum": 0,
|
|
79
|
+
"maximum": 359,
|
|
80
|
+
"x-atomic-group": "gps"
|
|
81
|
+
},
|
|
82
|
+
"latitude": {
|
|
83
|
+
"type": "number",
|
|
84
|
+
"description": "Current GPS latitude. Encrypted at rest (AES-256-GCM). Non-nullable: on initial connect before the first GPS fix, latitude/longitude default to 0. Per the '0,0 = no fix' convention (§2.3), SDK consumers MUST treat latitude == 0 && longitude == 0 as 'no position available' rather than a valid location in the Gulf of Guinea.",
|
|
85
|
+
"x-unit": "degrees",
|
|
86
|
+
"x-classification": "P1",
|
|
87
|
+
"x-encrypted": true,
|
|
88
|
+
"minimum": -90,
|
|
89
|
+
"maximum": 90,
|
|
90
|
+
"x-atomic-group": "gps"
|
|
91
|
+
},
|
|
92
|
+
"longitude": {
|
|
93
|
+
"type": "number",
|
|
94
|
+
"description": "Current GPS longitude. Encrypted at rest (AES-256-GCM). Non-nullable: on initial connect before the first GPS fix, latitude/longitude default to 0. Per the '0,0 = no fix' convention (§2.3), SDK consumers MUST treat latitude == 0 && longitude == 0 as 'no position available' rather than a valid location in the Gulf of Guinea.",
|
|
95
|
+
"x-unit": "degrees",
|
|
96
|
+
"x-classification": "P1",
|
|
97
|
+
"x-encrypted": true,
|
|
98
|
+
"minimum": -180,
|
|
99
|
+
"maximum": 180,
|
|
100
|
+
"x-atomic-group": "gps"
|
|
101
|
+
},
|
|
102
|
+
"locationName": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Reverse-geocoded place name for current location (e.g., 'Home', 'Whole Foods Market'). Derived from GPS coordinates server-side. Non-nullable per DB Vehicle.locationName (NOT NULL DEFAULT ''); an empty string means no geocode is yet available. Loaded by the Go Vehicle struct and populated on snapshot as of MYR-24 (2026-04-23).",
|
|
105
|
+
"x-classification": "P1"
|
|
106
|
+
},
|
|
107
|
+
"locationAddress": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "Reverse-geocoded street address for current location. Derived from GPS coordinates server-side. Non-nullable per DB Vehicle.locationAddress (NOT NULL DEFAULT ''); an empty string means no geocode is yet available. Loaded by the Go Vehicle struct and populated on snapshot as of MYR-24 (2026-04-23).",
|
|
110
|
+
"x-classification": "P1"
|
|
111
|
+
},
|
|
112
|
+
"gearPosition": {
|
|
113
|
+
"type": ["string", "null"],
|
|
114
|
+
"enum": ["P", "D", "R", "N", null],
|
|
115
|
+
"description": "Transmission gear position. Null when vehicle is asleep or gear has not yet been reported. When gearPosition changes, status is re-derived and both are delivered together.",
|
|
116
|
+
"x-classification": "P0",
|
|
117
|
+
"x-atomic-group": "gear"
|
|
118
|
+
},
|
|
119
|
+
"chargeLevel": {
|
|
120
|
+
"type": "integer",
|
|
121
|
+
"description": "Battery state of charge. Rounded to nearest integer from Tesla's float emission. Maps from Tesla SOC or BatteryLevel fields.",
|
|
122
|
+
"x-unit": "percent",
|
|
123
|
+
"x-classification": "P0",
|
|
124
|
+
"minimum": 0,
|
|
125
|
+
"maximum": 100,
|
|
126
|
+
"x-atomic-group": "charge"
|
|
127
|
+
},
|
|
128
|
+
"chargeState": {
|
|
129
|
+
"type": ["string", "null"],
|
|
130
|
+
"description": "Charge state enum sourced from Tesla proto field 179 (DetailedChargeState) as of MYR-42 (2026-04-23). Added as a v1 charge atomic group member by MYR-11; live WS wiring completed by MYR-40 on 2026-04-22 using proto 2 (ChargeState); re-sourced to proto 179 by MYR-42 after empirical capture showed Tesla firmware ≥ 2024.44.25 accepts proto 2 in fleet_telemetry_config but never actually emits it. Proto 179 fires on the same transitions with identical enum string values (see websocket-protocol.md §4.1.4 and §10 DV-03 + DV-19). DB persistence on the Prisma-owned Vehicle table landed in MYR-41 on 2026-04-25 (Vehicle.chargeState String? + Go writer pipeline applier + store SELECT/UPDATE wiring). Nullable in steady state — a vehicle that has never charged surfaces null on both wire and REST /snapshot. SDK consumers MUST tolerate null and render a neutral placeholder (not a spinner). Nullability is expressed via the union `type: [\"string\", \"null\"]`; the `enum` list includes `null` explicitly alongside the string values for maximum validator compatibility (some Go and Python JSON Schema libraries require `null` in the enum to accept null values even when `type` includes `null`).",
|
|
131
|
+
"enum": ["Unknown", "Disconnected", "NoPower", "Starting", "Charging", "Complete", "Stopped", null],
|
|
132
|
+
"x-classification": "P0",
|
|
133
|
+
"x-atomic-group": "charge",
|
|
134
|
+
"x-tesla-proto-field": 179
|
|
135
|
+
},
|
|
136
|
+
"estimatedRange": {
|
|
137
|
+
"type": "integer",
|
|
138
|
+
"description": "Estimated remaining driving range based on current charge. Rounded to nearest integer.",
|
|
139
|
+
"x-unit": "miles",
|
|
140
|
+
"x-classification": "P0",
|
|
141
|
+
"minimum": 0,
|
|
142
|
+
"x-atomic-group": "charge"
|
|
143
|
+
},
|
|
144
|
+
"timeToFull": {
|
|
145
|
+
"type": ["number", "null"],
|
|
146
|
+
"description": "Estimated time to full charge at the current charging rate. Added as a v1 charge atomic group member by MYR-11; live WS wiring completed by MYR-40 on 2026-04-22 (see websocket-protocol.md §4.1.4 and §10 DV-04). Tesla proto field 43 (TimeToFullCharge, double). Unit is HOURS (decimal, fractional values supported — e.g. 1.5 for 90 minutes) per the tesla-fleet-telemetry-sme skill and the legacy Tesla REST API; empirically verified as 1.066666841506958 hours during a home charging session on 2026-04-22 (DV-17 resolved — see MYR-25 comment and TestDecoder_DecodePayload_TimeToFull). DB persistence on the Prisma-owned Vehicle table landed in MYR-41 on 2026-04-25 (Vehicle.timeToFull Float? + Go writer pipeline applier + store SELECT/UPDATE wiring). Nullable in steady state — null means no active charging session or no charge frame has arrived yet. SDK consumers MUST tolerate null and render a neutral placeholder (not a spinner). 0 (or null when disconnected) means no active charging session.",
|
|
147
|
+
"x-unit": "hours",
|
|
148
|
+
"x-classification": "P0",
|
|
149
|
+
"minimum": 0,
|
|
150
|
+
"x-atomic-group": "charge",
|
|
151
|
+
"x-tesla-proto-field": 43
|
|
152
|
+
},
|
|
153
|
+
"interiorTemp": {
|
|
154
|
+
"type": "integer",
|
|
155
|
+
"description": "Cabin interior temperature. Rounded to nearest integer.",
|
|
156
|
+
"x-unit": "fahrenheit",
|
|
157
|
+
"x-classification": "P0"
|
|
158
|
+
},
|
|
159
|
+
"exteriorTemp": {
|
|
160
|
+
"type": "integer",
|
|
161
|
+
"description": "Ambient exterior temperature. Rounded to nearest integer.",
|
|
162
|
+
"x-unit": "fahrenheit",
|
|
163
|
+
"x-classification": "P0"
|
|
164
|
+
},
|
|
165
|
+
"odometerMiles": {
|
|
166
|
+
"type": "integer",
|
|
167
|
+
"description": "Total odometer reading. Rounded to nearest integer.",
|
|
168
|
+
"x-unit": "miles",
|
|
169
|
+
"x-classification": "P0",
|
|
170
|
+
"minimum": 0
|
|
171
|
+
},
|
|
172
|
+
"fsdMilesSinceReset": {
|
|
173
|
+
"type": "number",
|
|
174
|
+
"description": "Miles driven using Full Self-Driving since last reset. The counter resets on OTA updates and factory resets, NOT daily. Maps from Tesla SelfDrivingMilesSinceReset. Non-nullable per DB Vehicle.fsdMilesSinceReset (NOT NULL DEFAULT 0) — DB column renamed from the legacy fsdMilesToday by MYR-24 (2026-04-23, cross-repo Prisma migration). Loaded by the Go Vehicle struct and populated on snapshot.",
|
|
175
|
+
"x-unit": "miles",
|
|
176
|
+
"x-classification": "P0",
|
|
177
|
+
"minimum": 0
|
|
178
|
+
},
|
|
179
|
+
"destinationName": {
|
|
180
|
+
"type": ["string", "null"],
|
|
181
|
+
"description": "Name of the active navigation destination. Null when no navigation is active. When navigation is cancelled, this field and all other navigation group fields are atomically cleared to null.",
|
|
182
|
+
"x-classification": "P1",
|
|
183
|
+
"x-atomic-group": "navigation",
|
|
184
|
+
"examples": ["Whole Foods Market"]
|
|
185
|
+
},
|
|
186
|
+
"destinationAddress": {
|
|
187
|
+
"type": ["string", "null"],
|
|
188
|
+
"description": "Street address of the active navigation destination. Null when no navigation is active. Sourced from DB Vehicle.destinationAddress (Prisma String?, nullable). Loaded by the Go Vehicle struct and populated on snapshot as of MYR-24 (2026-04-23).",
|
|
189
|
+
"x-classification": "P1",
|
|
190
|
+
"x-atomic-group": "navigation"
|
|
191
|
+
},
|
|
192
|
+
"destinationLatitude": {
|
|
193
|
+
"type": ["number", "null"],
|
|
194
|
+
"description": "Latitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.",
|
|
195
|
+
"x-unit": "degrees",
|
|
196
|
+
"x-classification": "P1",
|
|
197
|
+
"x-encrypted": true,
|
|
198
|
+
"minimum": -90,
|
|
199
|
+
"maximum": 90,
|
|
200
|
+
"x-atomic-group": "navigation"
|
|
201
|
+
},
|
|
202
|
+
"destinationLongitude": {
|
|
203
|
+
"type": ["number", "null"],
|
|
204
|
+
"description": "Longitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.",
|
|
205
|
+
"x-unit": "degrees",
|
|
206
|
+
"x-classification": "P1",
|
|
207
|
+
"x-encrypted": true,
|
|
208
|
+
"minimum": -180,
|
|
209
|
+
"maximum": 180,
|
|
210
|
+
"x-atomic-group": "navigation"
|
|
211
|
+
},
|
|
212
|
+
"originLatitude": {
|
|
213
|
+
"type": ["number", "null"],
|
|
214
|
+
"description": "Latitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.",
|
|
215
|
+
"x-unit": "degrees",
|
|
216
|
+
"x-classification": "P1",
|
|
217
|
+
"x-encrypted": true,
|
|
218
|
+
"minimum": -90,
|
|
219
|
+
"maximum": 90,
|
|
220
|
+
"x-atomic-group": "navigation"
|
|
221
|
+
},
|
|
222
|
+
"originLongitude": {
|
|
223
|
+
"type": ["number", "null"],
|
|
224
|
+
"description": "Longitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.",
|
|
225
|
+
"x-unit": "degrees",
|
|
226
|
+
"x-classification": "P1",
|
|
227
|
+
"x-encrypted": true,
|
|
228
|
+
"minimum": -180,
|
|
229
|
+
"maximum": 180,
|
|
230
|
+
"x-atomic-group": "navigation"
|
|
231
|
+
},
|
|
232
|
+
"etaMinutes": {
|
|
233
|
+
"type": ["integer", "null"],
|
|
234
|
+
"description": "Estimated time of arrival in minutes remaining. Null when no navigation is active. Rounded to nearest integer.",
|
|
235
|
+
"x-unit": "minutes",
|
|
236
|
+
"x-classification": "P0",
|
|
237
|
+
"minimum": 0,
|
|
238
|
+
"x-atomic-group": "navigation"
|
|
239
|
+
},
|
|
240
|
+
"tripDistanceRemaining": {
|
|
241
|
+
"type": ["number", "null"],
|
|
242
|
+
"description": "Remaining distance to navigation destination. Null when no navigation is active.",
|
|
243
|
+
"x-unit": "miles",
|
|
244
|
+
"x-classification": "P0",
|
|
245
|
+
"minimum": 0,
|
|
246
|
+
"x-atomic-group": "navigation"
|
|
247
|
+
},
|
|
248
|
+
"navRouteCoordinates": {
|
|
249
|
+
"type": ["array", "null"],
|
|
250
|
+
"description": "Active navigation route as an array of [longitude, latitude] coordinate pairs (GeoJSON/Mapbox order). Decoded from Tesla's RouteLine (Base64-encoded protobuf wrapping a Google Encoded Polyline at 1e6 precision). Encrypted at rest (AES-256-GCM). Null when no navigation is active.",
|
|
251
|
+
"x-classification": "P1",
|
|
252
|
+
"x-encrypted": true,
|
|
253
|
+
"x-atomic-group": "navigation",
|
|
254
|
+
"items": {
|
|
255
|
+
"type": "array",
|
|
256
|
+
"items": {
|
|
257
|
+
"type": "number"
|
|
258
|
+
},
|
|
259
|
+
"minItems": 2,
|
|
260
|
+
"maxItems": 2,
|
|
261
|
+
"description": "A coordinate pair: [longitude, latitude]."
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"lastUpdated": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"format": "date-time",
|
|
267
|
+
"description": "ISO 8601 timestamp of the most recent telemetry update applied to this vehicle state.",
|
|
268
|
+
"x-classification": "P0"
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
"x-atomic-groups": {
|
|
272
|
+
"navigation": {
|
|
273
|
+
"description": "Navigation state delivered as a single atomic unit. When any navigation field changes, the server accumulates all sibling nav fields within a 500ms debounce window (NFR-3.2) and delivers them together. When navigation is cancelled, ALL fields in this group are atomically cleared to null (FR-2.3).",
|
|
274
|
+
"fields": [
|
|
275
|
+
"destinationName",
|
|
276
|
+
"destinationAddress",
|
|
277
|
+
"destinationLatitude",
|
|
278
|
+
"destinationLongitude",
|
|
279
|
+
"originLatitude",
|
|
280
|
+
"originLongitude",
|
|
281
|
+
"etaMinutes",
|
|
282
|
+
"tripDistanceRemaining",
|
|
283
|
+
"navRouteCoordinates"
|
|
284
|
+
],
|
|
285
|
+
"consistency-predicates": [
|
|
286
|
+
"If destinationLatitude is non-null, then destinationLongitude MUST also be non-null, and vice versa.",
|
|
287
|
+
"If originLatitude is non-null, then originLongitude MUST also be non-null, and vice versa.",
|
|
288
|
+
"If destinationName is non-null, then destinationLatitude, destinationLongitude, and navRouteCoordinates MUST also be non-null (NFR-3.3).",
|
|
289
|
+
"If any navigation field is null, ALL navigation fields MUST be null (atomic clear on nav cancel, FR-2.3).",
|
|
290
|
+
"etaMinutes and tripDistanceRemaining MAY be null independently during the 500ms accumulation window, but the DB snapshot MUST have all-or-nothing consistency."
|
|
291
|
+
],
|
|
292
|
+
"nullability": "All fields nullable. Null = no active navigation."
|
|
293
|
+
},
|
|
294
|
+
"charge": {
|
|
295
|
+
"description": "Battery and charge state delivered together. When any charge field changes within the same Tesla 500ms vehicle-side bucket, sibling fields are included in the same vehicle_update message. v1 expands this group to four fields per MYR-11; live WS wiring for chargeState and timeToFull completed by MYR-40 on 2026-04-22; DB persistence for both completed by MYR-41 on 2026-04-25 (Prisma columns + Go writer applier + SELECT/UPDATE wiring).",
|
|
296
|
+
"fields": [
|
|
297
|
+
"chargeLevel",
|
|
298
|
+
"chargeState",
|
|
299
|
+
"estimatedRange",
|
|
300
|
+
"timeToFull"
|
|
301
|
+
],
|
|
302
|
+
"consistency-predicates": [
|
|
303
|
+
"chargeLevel and estimatedRange MUST both be present in the DB snapshot (NFR-3.5). They are non-nullable with default 0.",
|
|
304
|
+
"chargeState and timeToFull are nullable in steady state — null surfaces when the vehicle has never charged or when no charge frame has arrived yet. Consumers MUST tolerate null on both wire and REST /snapshot responses.",
|
|
305
|
+
"chargeLevel: 0 is context-dependent. With status == 'offline', the UI SHOULD interpret chargeLevel: 0 as 'unknown' (no telemetry received yet, DB default). With status == 'driving' or 'parked' (i.e., the vehicle is online), chargeLevel: 0 SHOULD be interpreted as 'critical' or 'empty battery'. See §2.2 of vehicle-state-schema.md."
|
|
306
|
+
],
|
|
307
|
+
"nullability": "chargeLevel and estimatedRange are non-nullable (default 0 on initial vehicle creation). chargeState and timeToFull are nullable on both wire and REST /snapshot — the Prisma-owned Vehicle columns are String?/Float? (post-MYR-41) and steady-state null is the expected shape for vehicles that have never charged."
|
|
308
|
+
},
|
|
309
|
+
"gps": {
|
|
310
|
+
"description": "GPS position and orientation delivered together. On initial connect before the first GPS fix, latitude and longitude may be 0 (DB default).",
|
|
311
|
+
"fields": [
|
|
312
|
+
"latitude",
|
|
313
|
+
"longitude",
|
|
314
|
+
"heading"
|
|
315
|
+
],
|
|
316
|
+
"consistency-predicates": [
|
|
317
|
+
"latitude and longitude MUST always be present together (NFR-3.3).",
|
|
318
|
+
"heading MUST be present whenever latitude/longitude are present.",
|
|
319
|
+
"Values of 0,0 for latitude/longitude represent the DB default (no GPS fix yet), not a valid location."
|
|
320
|
+
],
|
|
321
|
+
"nullability": "Non-nullable in DB (default 0). SDK consumers SHOULD treat 0,0 as 'no fix'."
|
|
322
|
+
},
|
|
323
|
+
"gear": {
|
|
324
|
+
"description": "Gear position and derived vehicle status delivered together. When gearPosition changes, the server derives status (driving/parked) and includes both fields in the same message.",
|
|
325
|
+
"fields": [
|
|
326
|
+
"gearPosition",
|
|
327
|
+
"status"
|
|
328
|
+
],
|
|
329
|
+
"consistency-predicates": [
|
|
330
|
+
"When gearPosition is 'D' or 'R', status MUST be 'driving'.",
|
|
331
|
+
"When gearPosition is 'P' or 'N', status MUST be 'parked' (unless overridden by charging/offline/in_service logic).",
|
|
332
|
+
"status is always present; gearPosition is nullable (null = vehicle asleep or gear not yet reported)."
|
|
333
|
+
],
|
|
334
|
+
"nullability": "gearPosition is nullable. status is non-nullable (default 'offline')."
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"dependentRequired": {
|
|
338
|
+
"destinationLatitude": ["destinationLongitude"],
|
|
339
|
+
"destinationLongitude": ["destinationLatitude"],
|
|
340
|
+
"originLatitude": ["originLongitude"],
|
|
341
|
+
"originLongitude": ["originLatitude"]
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://myrobotaxi.com/schemas/ws-envelope.schema.json",
|
|
4
|
+
"title": "WebSocketEnvelope",
|
|
5
|
+
"description": "Top-level envelope for every WebSocket frame exchanged between the telemetry server and SDK clients. Both directions (server->client and client->server) use this envelope. The `type` discriminator selects the payload schema; `seq` and `ts` are server-authoritative fields populated only on server->client frames to support snapshot resume per NFR-3.11.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["type"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"type": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Discriminator naming the message type. Server->client values: 'auth_ok' (v1-required positive auth acknowledgement, triggers state-machine C-3 — see websocket-protocol.md §2.3), 'vehicle_update', 'drive_started', 'drive_ended', 'connectivity', 'heartbeat', 'error'. Client->server values: 'auth' (only message currently accepted; 'subscribe', 'unsubscribe', 'ping' are reserved for follow-up — see websocket-protocol.md §10 DV-07).",
|
|
13
|
+
"enum": [
|
|
14
|
+
"auth",
|
|
15
|
+
"auth_ok",
|
|
16
|
+
"vehicle_update",
|
|
17
|
+
"drive_started",
|
|
18
|
+
"drive_ended",
|
|
19
|
+
"connectivity",
|
|
20
|
+
"heartbeat",
|
|
21
|
+
"error",
|
|
22
|
+
"subscribe",
|
|
23
|
+
"unsubscribe",
|
|
24
|
+
"ping",
|
|
25
|
+
"pong"
|
|
26
|
+
],
|
|
27
|
+
"x-classification": "P0"
|
|
28
|
+
},
|
|
29
|
+
"payload": {
|
|
30
|
+
"description": "Type-specific payload object. Schema is selected by the `type` discriminator. Omitted on bare control frames such as 'heartbeat'. See websocket-protocol.md §4 (server->client) and §5 (client->server) for the catalog.",
|
|
31
|
+
"x-classification": "P0"
|
|
32
|
+
},
|
|
33
|
+
"seq": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"minimum": 0,
|
|
36
|
+
"description": "PLANNED — NOT emitted by the server today. Monotonic per-connection sequence number assigned by the server, starting at 0 on each successful authenticated session. Used by clients on reconnect to request snapshot-resume from sequence N (NFR-3.11) via the planned `subscribe.sinceSeq` field. Until the server is extended to emit `seq`, clients MUST tolerate its absence and treat every frame as gap-free. Tracked as divergence DV-02 in websocket-protocol.md §10.",
|
|
37
|
+
"x-classification": "P0"
|
|
38
|
+
},
|
|
39
|
+
"ts": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"format": "date-time",
|
|
42
|
+
"description": "PLANNED — NOT emitted by the server today. Server-authoritative ISO 8601 UTC timestamp for the envelope itself, distinct from any payload-internal `timestamp` field. Clients MUST tolerate its absence today; the per-payload `timestamp` field (e.g., `vehicle_update.timestamp`, `drive_started.timestamp`) carries the same information in v1.0. Tracked as divergence DV-02 in websocket-protocol.md §10.",
|
|
43
|
+
"x-classification": "P0"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"$defs": {
|
|
47
|
+
"MessageType": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": [
|
|
50
|
+
"auth",
|
|
51
|
+
"auth_ok",
|
|
52
|
+
"vehicle_update",
|
|
53
|
+
"drive_started",
|
|
54
|
+
"drive_ended",
|
|
55
|
+
"connectivity",
|
|
56
|
+
"heartbeat",
|
|
57
|
+
"error",
|
|
58
|
+
"subscribe",
|
|
59
|
+
"unsubscribe",
|
|
60
|
+
"ping",
|
|
61
|
+
"pong"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|