@myrobotaxi/contracts 0.1.0 → 0.2.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/dist/index.cjs CHANGED
@@ -561,7 +561,7 @@ var ws_messages_schema_default = {
561
561
  properties: {
562
562
  code: {
563
563
  type: "string",
564
- description: "Stable typed error code from the catalog in websocket-protocol.md \xA76.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md \xA710. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md \xA76.1.1 and \xA77.1.",
564
+ description: "Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md \xA74.1.1, \xA74.1.1.a; ws catalog websocket-protocol.md \xA76.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md \xA710). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md \xA74.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports \u2014 they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) \u2014 the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md \xA76.1.1 / \xA77.1.",
565
565
  enum: [
566
566
  "auth_failed",
567
567
  "auth_timeout",
@@ -569,7 +569,9 @@ var ws_messages_schema_default = {
569
569
  "vehicle_not_owned",
570
570
  "rate_limited",
571
571
  "internal_error",
572
- "snapshot_required"
572
+ "snapshot_required",
573
+ "not_found",
574
+ "invalid_request"
573
575
  ],
574
576
  "x-classification": "P0"
575
577
  },
@@ -580,8 +582,8 @@ var ws_messages_schema_default = {
580
582
  },
581
583
  subCode: {
582
584
  type: "string",
583
- description: "Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: \"device_cap\"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md \xA76.1.1 for the full device_cap UX contract.",
584
- enum: ["device_cap"],
585
+ description: "Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: \"device_cap\"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code \u2014 see websocket-protocol.md \xA76.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md \xA77.6 / \xA77.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window \u2014 the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.",
586
+ enum: ["device_cap", "reauth_required"],
585
587
  "x-classification": "P0"
586
588
  }
587
589
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../schemas/vehicle-state.schema.json","../schemas/ws-messages.schema.json","../schemas/ws-envelope.schema.json","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/vehicle-state.schema.json\",\n \"title\": \"VehicleState\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\n \"vehicleId\",\n \"name\",\n \"model\",\n \"year\",\n \"color\",\n \"status\",\n \"chargeLevel\",\n \"estimatedRange\",\n \"speed\",\n \"heading\",\n \"latitude\",\n \"longitude\",\n \"locationName\",\n \"locationAddress\",\n \"interiorTemp\",\n \"exteriorTemp\",\n \"odometerMiles\",\n \"fsdMilesSinceReset\",\n \"lastUpdated\"\n ],\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database identifier (cuid). NOT the VIN. All SDK API calls use this ID (FR-4.2).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"clxyz1234567890abcdef\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Optimus\"]\n },\n \"model\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Model 3\"]\n },\n \"year\": {\n \"type\": \"integer\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [2024]\n },\n \"color\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Midnight Silver Metallic\"]\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"driving\", \"parked\", \"charging\", \"offline\", \"in_service\"],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"speed\": {\n \"type\": \"integer\",\n \"description\": \"Current vehicle speed. Rounded to nearest integer from Tesla's float emission.\",\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"heading\": {\n \"type\": \"integer\",\n \"description\": \"Compass heading. 0 = North, 90 = East, 180 = South, 270 = West.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 359,\n \"x-atomic-group\": \"gps\"\n },\n \"latitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"gps\"\n },\n \"longitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"gps\"\n },\n \"locationName\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"locationAddress\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"gearPosition\": {\n \"type\": [\"string\", \"null\"],\n \"enum\": [\"P\", \"D\", \"R\", \"N\", null],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"chargeLevel\": {\n \"type\": \"integer\",\n \"description\": \"Battery state of charge. Rounded to nearest integer from Tesla's float emission. Maps from Tesla SOC or BatteryLevel fields.\",\n \"x-unit\": \"percent\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 100,\n \"x-atomic-group\": \"charge\"\n },\n \"chargeState\": {\n \"type\": [\"string\", \"null\"],\n \"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`).\",\n \"enum\": [\"Unknown\", \"Disconnected\", \"NoPower\", \"Starting\", \"Charging\", \"Complete\", \"Stopped\", null],\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 179\n },\n \"estimatedRange\": {\n \"type\": \"integer\",\n \"description\": \"Estimated remaining driving range based on current charge. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\"\n },\n \"timeToFull\": {\n \"type\": [\"number\", \"null\"],\n \"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.\",\n \"x-unit\": \"hours\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 43\n },\n \"interiorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Cabin interior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"exteriorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Ambient exterior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"odometerMiles\": {\n \"type\": \"integer\",\n \"description\": \"Total odometer reading. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"fsdMilesSinceReset\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"destinationName\": {\n \"type\": [\"string\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\",\n \"examples\": [\"Whole Foods Market\"]\n },\n \"destinationAddress\": {\n \"type\": [\"string\", \"null\"],\n \"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).\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"etaMinutes\": {\n \"type\": [\"integer\", \"null\"],\n \"description\": \"Estimated time of arrival in minutes remaining. Null when no navigation is active. Rounded to nearest integer.\",\n \"x-unit\": \"minutes\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"tripDistanceRemaining\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Remaining distance to navigation destination. Null when no navigation is active.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"navRouteCoordinates\": {\n \"type\": [\"array\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"x-atomic-group\": \"navigation\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2,\n \"description\": \"A coordinate pair: [longitude, latitude].\"\n }\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"ISO 8601 timestamp of the most recent telemetry update applied to this vehicle state.\",\n \"x-classification\": \"P0\"\n }\n },\n \"x-atomic-groups\": {\n \"navigation\": {\n \"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).\",\n \"fields\": [\n \"destinationName\",\n \"destinationAddress\",\n \"destinationLatitude\",\n \"destinationLongitude\",\n \"originLatitude\",\n \"originLongitude\",\n \"etaMinutes\",\n \"tripDistanceRemaining\",\n \"navRouteCoordinates\"\n ],\n \"consistency-predicates\": [\n \"If destinationLatitude is non-null, then destinationLongitude MUST also be non-null, and vice versa.\",\n \"If originLatitude is non-null, then originLongitude MUST also be non-null, and vice versa.\",\n \"If destinationName is non-null, then destinationLatitude, destinationLongitude, and navRouteCoordinates MUST also be non-null (NFR-3.3).\",\n \"If any navigation field is null, ALL navigation fields MUST be null (atomic clear on nav cancel, FR-2.3).\",\n \"etaMinutes and tripDistanceRemaining MAY be null independently during the 500ms accumulation window, but the DB snapshot MUST have all-or-nothing consistency.\"\n ],\n \"nullability\": \"All fields nullable. Null = no active navigation.\"\n },\n \"charge\": {\n \"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).\",\n \"fields\": [\n \"chargeLevel\",\n \"chargeState\",\n \"estimatedRange\",\n \"timeToFull\"\n ],\n \"consistency-predicates\": [\n \"chargeLevel and estimatedRange MUST both be present in the DB snapshot (NFR-3.5). They are non-nullable with default 0.\",\n \"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.\",\n \"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.\"\n ],\n \"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.\"\n },\n \"gps\": {\n \"description\": \"GPS position and orientation delivered together. On initial connect before the first GPS fix, latitude and longitude may be 0 (DB default).\",\n \"fields\": [\n \"latitude\",\n \"longitude\",\n \"heading\"\n ],\n \"consistency-predicates\": [\n \"latitude and longitude MUST always be present together (NFR-3.3).\",\n \"heading MUST be present whenever latitude/longitude are present.\",\n \"Values of 0,0 for latitude/longitude represent the DB default (no GPS fix yet), not a valid location.\"\n ],\n \"nullability\": \"Non-nullable in DB (default 0). SDK consumers SHOULD treat 0,0 as 'no fix'.\"\n },\n \"gear\": {\n \"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.\",\n \"fields\": [\n \"gearPosition\",\n \"status\"\n ],\n \"consistency-predicates\": [\n \"When gearPosition is 'D' or 'R', status MUST be 'driving'.\",\n \"When gearPosition is 'P' or 'N', status MUST be 'parked' (unless overridden by charging/offline/in_service logic).\",\n \"status is always present; gearPosition is nullable (null = vehicle asleep or gear not yet reported).\"\n ],\n \"nullability\": \"gearPosition is nullable. status is non-nullable (default 'offline').\"\n }\n },\n \"dependentRequired\": {\n \"destinationLatitude\": [\"destinationLongitude\"],\n \"destinationLongitude\": [\"destinationLatitude\"],\n \"originLatitude\": [\"originLongitude\"],\n \"originLongitude\": [\"originLatitude\"]\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-messages.schema.json\",\n \"title\": \"WebSocketMessages\",\n \"description\": \"JSON Schemas for every WebSocket payload exchanged between the telemetry server and SDK clients. The vehicle_update payload's `fields` map is a subset of the canonical VehicleState (see vehicle-state.schema.json); per-field types/units/classification live there and are NOT duplicated here. This file holds the wire-only shapes (envelope payloads, control messages, errors).\",\n \"$defs\": {\n \"AuthPayload\": {\n \"type\": \"object\",\n \"description\": \"Client->server authentication payload. The first frame on every WebSocket connection MUST be {type: 'auth', payload: {token: '...'}}. The token is opaque to the server's transport layer and is validated by the configured Authenticator (JWT in production, NoopAuthenticator in dev). Anchored by FR-6.1, NFR-3.21.\",\n \"required\": [\"token\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"token\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Bearer-style session token resolved by the consumer's getToken() callback (FR-6.1). Server validates via Authenticator.ValidateToken. P1 — never log this value (data-classification.md §1.2).\",\n \"x-classification\": \"P1\"\n }\n }\n },\n \"AuthOkPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client positive authentication acknowledgement. v1-required. Emitted as the FIRST frame after successful `Authenticator.ValidateToken` + `GetUserVehicles` + `Hub.Register`. Triggers the SDK `connectionState` transition `connecting -> connected` (state-machine C-3). Without this frame, SDKs on idle vehicles would sit in `connecting` up to one heartbeat interval waiting for telemetry traffic — an unacceptable regression on cold watchOS wakes. See websocket-protocol.md §2.3 for the full handshake contract. Note: `auth_ok` has been pulled OUT of divergence DV-07 and is v1-required; the rest of DV-07 (subscribe/unsubscribe/ping/pong + typed permission_denied) remains deferred. `state-machine.md` §1.3 C-3 trigger alignment is tracked as DV-15 (requires a follow-up MYR-10 amendment PR). Anchored by FR-6.1, FR-8.1, NFR-3.21.\",\n \"required\": [\"userId\", \"vehicleCount\", \"issuedAt\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"userId\": {\n \"type\": \"string\",\n \"description\": \"Opaque cuid of the authenticated user. Echoed back from the resolved token so the SDK can sanity-check ownership set on reconnect (e.g., compare against a cached userId and clear caches if they disagree, defending against consumer token-mixup bugs). P1 per data-classification.md §1.1 User.id.\",\n \"x-classification\": \"P1\"\n },\n \"vehicleCount\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"Size of the user's vehicle set at handshake time (length of `Authenticator.GetUserVehicles(userId)`). Informational integrity check against the SDK's cached snapshot; the authoritative ownership set is populated via the REST snapshot fetch on reconnect (NFR-3.11). P1 because vehicle count leaks the shape of a user's fleet.\",\n \"x-classification\": \"P1\"\n },\n \"issuedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which Hub.Register returned success. Debug/telemetry only — the SDK MUST NOT use this value for any behavioral decision. P2 because it reveals server-side timing to an observer.\",\n \"x-classification\": \"P2\"\n }\n }\n },\n \"VehicleUpdatePayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client telemetry update for a single vehicle. The `fields` map carries one or more atomic-group members from VehicleState (see vehicle-state.schema.json). Each call to broadcast carries fields from at most ONE atomic group plus optionally non-grouped fields delivered alongside (NFR-3.1). Anchored by FR-1.1, FR-1.2, FR-1.3, FR-2.1, FR-2.2, FR-2.3, NFR-3.1, NFR-3.2.\",\n \"required\": [\"vehicleId\", \"fields\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database cuid for the vehicle (NOT the VIN). All SDK API calls use this ID per FR-4.2. VINs are P0 with mandatory last-4 redaction in logs (data-classification.md §2.1) and MUST NOT appear in this payload under any circumstance.\",\n \"x-classification\": \"P0\"\n },\n \"fields\": {\n \"type\": \"object\",\n \"description\": \"Map of field name -> value. Field names and per-field classifications are defined in vehicle-state.schema.json. A single vehicle_update message contains the members of at most ONE atomic group plus any individually-delivered fields (speed, odometerMiles, interiorTemp, exteriorTemp, fsdMilesSinceReset, locationName, locationAddress, lastUpdated). v1 atomic groups: navigation = {destinationName, destinationAddress, destinationLatitude, destinationLongitude, originLatitude, originLongitude, etaMinutes, tripDistanceRemaining, navRouteCoordinates}; charge = {chargeLevel, chargeState, estimatedRange, timeToFull}; gps = {latitude, longitude, heading}; gear = {gearPosition, status}. The `drive` atomic group's single member `startedAt` is delivered via the `drive_started` lifecycle message, not via `vehicle_update.fields` — see DV-13 in websocket-protocol.md §10. Server explicitly clears a navigation field by sending it as JSON null; the SDK MUST atomically null all members of the affected group per NFR-3.9. The optional `driveTrailCoordinates` field (array of [lng, lat] pairs) carries an active drive's accumulated GPS trail (\\\"where the car has been\\\") and is not part of a normal atomic group — it is always paired with an active drive and routed to dataState.gps. It is semantically distinct from the navigation group's `navRouteCoordinates` (Tesla's planned route polyline; \\\"where the car is going\\\") and the two MUST NOT be conflated.\",\n \"additionalProperties\": true,\n \"x-classification\": \"mixed (per-field; see vehicle-state.schema.json)\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp for this update. Not the Tesla telemetry emission time — this is the server's `time.Now().UTC()` at broadcast (or the event's CreatedAt for non-nav fields). Drives the SDK's lastUpdated state and feeds NFR-3.11 snapshot resume.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveStartedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing the start of a new drive. Also carries the drive atomic group's `startedAt` field, which is the v1 home of `tripStartTime` (relocated from the navigation group per DV-13 — see websocket-protocol.md §4.2). The drive group is NOT delivered via vehicle_update.fields; it is delivered via this message. Anchored by FR-3.1, NFR-3.1.\",\n \"required\": [\"vehicleId\", \"driveId\", \"startLocation\", \"startedAt\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid. Matches the eventual Drive row's id when the drive completes and is persisted.\",\n \"x-classification\": \"P0\"\n },\n \"startLocation\": {\n \"type\": \"object\",\n \"description\": \"GPS coordinates where the drive began. Encrypted at rest in the eventual Drive row but transported plaintext over WSS (NFR-3.22, NFR-3.25).\",\n \"required\": [\"latitude\", \"longitude\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"latitude\": {\n \"type\": \"number\",\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n },\n \"longitude\": {\n \"type\": \"number\",\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n }\n }\n },\n \"startedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"v1 home of `tripStartTime` (per DV-13). Server-authoritative ISO 8601 UTC timestamp at which the drive detector's `started_at` was recorded. SDKs MUST surface this as the authoritative tripStartTime for consumers. Value is always equal to `timestamp` for drive_started; the two fields are separate so `timestamp` can remain the uniform envelope-event time across every server->client message while `startedAt` carries the semantic drive-lifecycle meaning.\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector (`internal/drives/`) declared the drive started. Equal to `startedAt` for this message type.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveEndedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing a completed drive that passed the micro-drive filter (state-machine.md §3.5). The wire payload is intentionally a SUMMARY — it carries only the handful of fields the SDK needs for an immediate 'drive finished' toast or card. The full FR-3.4 drive record (energy used, FSD miles, intervention count, start/end charge level, start/end addresses, full route polyline) is fetched on demand via REST `GET /drives/{id}` (see rest-api.md) using the SDK's `fetchDrive(driveId)` helper. Neither the TypeScript nor the Swift SDK auto-fetches the full record on drive_ended; doing so would burn cellular bandwidth on idle consumers (per NFR-3.36, this is especially bad for watchOS). Tracked as DV-11 (RESOLVED) in websocket-protocol.md §10. Anchored by FR-3.1, FR-3.4.\",\n \"required\": [\"vehicleId\", \"driveId\", \"distance\", \"durationSeconds\", \"avgSpeed\", \"maxSpeed\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid (matches the persisted Drive row id). Input to `fetchDrive(driveId)` for the full FR-3.4 record.\",\n \"x-classification\": \"P0\"\n },\n \"distance\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Total distance driven in miles. Computed as the haversine sum of route points at drive completion.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\"\n },\n \"durationSeconds\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Drive duration in seconds, as a JSON number (double). Server-side value is `DriveStats.Duration.Seconds()`. This replaces the earlier `duration` string field (Go `time.Duration` format like '12m34s'), which was dropped before v1 ship — there are no pre-v1 consumers, so there is no deprecation tail. TypeScript consumers construct `Temporal.Duration` or a Date delta from this value; Swift consumers map directly to `Duration(secondsComponent:attosecondsComponent:)` or `TimeInterval`. Tracked as DV-12 (RESOLVED) in websocket-protocol.md §10.\",\n \"x-unit\": \"seconds\",\n \"x-classification\": \"P0\"\n },\n \"avgSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"maxSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector declared the drive ended.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ConnectivityPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client signal that a vehicle's mTLS connection to the telemetry server has changed state. This is vehicle<->server connectivity, NOT client<->server connectivity (the latter is implicit in the WebSocket connection itself). Anchored by FR-1.3 (extensibility) and FR-8.1 (state surface).\",\n \"required\": [\"vehicleId\", \"online\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"online\": {\n \"type\": \"boolean\",\n \"description\": \"True when the vehicle has an active mTLS Tesla Fleet Telemetry stream into the server. False on disconnect (e.g., vehicle asleep, network drop).\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"HeartbeatPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client keepalive frame. The current server emits a bare envelope `{\\\"type\\\":\\\"heartbeat\\\"}` with NO payload (heartbeat.go computes the message once at init). The optional `timestamp` field is reserved for future use. Anchored by NFR-3.10 (reconnect cadence).\",\n \"additionalProperties\": false,\n \"properties\": {\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"OPTIONAL and NOT currently emitted. Reserved for future use to expose server clock to clients (would also enable client clock-skew detection). Tracked by follow-up issue.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ErrorPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client error frame. The server sends this for transport-level failures the client must surface (auth rejection, auth timeout). The Code field is a stable enum that consumers branch on (FR-7.1) — never string-match the Message field. Anchored by FR-7.1, FR-7.3.\",\n \"required\": [\"code\", \"message\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable typed error code from the catalog in websocket-protocol.md §6.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md §6.1.1 and §7.1.\",\n \"enum\": [\n \"auth_failed\",\n \"auth_timeout\",\n \"permission_denied\",\n \"vehicle_not_owned\",\n \"rate_limited\",\n \"internal_error\",\n \"snapshot_required\"\n ],\n \"x-classification\": \"P0\"\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human-readable description for logs and developer tooling. P0 BUT MUST NOT contain P1 values (no GPS, no addresses, no tokens, no emails, no VINs unless redacted to ***XXXX). Per data-classification.md §2.1-2.2, error message construction sites MUST use opaque IDs only.\",\n \"x-classification\": \"P0\"\n },\n \"subCode\": {\n \"type\": \"string\",\n \"description\": \"Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: \\\"device_cap\\\"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md §6.1.1 for the full device_cap UX contract.\",\n \"enum\": [\"device_cap\"],\n \"x-classification\": \"P0\"\n }\n }\n },\n \"SubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to begin or resume streaming a specific vehicle. Today the server implicitly subscribes the client to ALL vehicles owned by the authenticated user as part of the auth handshake (handler.go authenticateClient -> Authenticator.GetUserVehicles). Adding explicit subscribe/unsubscribe is tracked as divergence DV-07 in websocket-protocol.md §10. `sinceSeq` snapshot-resume depends on divergence DV-02 (envelope `seq`) landing first.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"sinceSeq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"PLANNED. Last envelope `seq` the client successfully processed for this vehicle. The server uses this to choose between snapshot-resume (replay missed frames) and full-refresh (REST snapshot fetch + live stream from current seq).\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"UnsubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to stop receiving updates for a specific vehicle without closing the underlying WebSocket. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PingPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server ping. Today, browser clients rely on the server's outbound `heartbeat` frames (§7.4) and the underlying WebSocket protocol's PING/PONG control frames (handled transparently by coder/websocket) to detect liveness. An app-level `ping` is reserved for platforms where the WebSocket library does not expose RFC 6455 PING/PONG — specifically watchOS extended-runtime sessions and iOS background sockets, per NFR-3.36 / NFR-3.36a-d. The TypeScript SDK runs on browser `WebSocket` and Node `ws`, both of which expose transport-level PING/PONG, so this is a Swift-SDK forward-compatibility concern only. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"description\": \"Optional opaque value echoed back in the corresponding `pong` for round-trip latency measurement.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PongPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT emitted by the server today. Server->client response to a client-initiated `ping`. Echoes the nonce so the client can compute round-trip latency. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n }\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-envelope.schema.json\",\n \"title\": \"WebSocketEnvelope\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\"type\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"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).\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ],\n \"x-classification\": \"P0\"\n },\n \"payload\": {\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"seq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"ts\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"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.\",\n \"x-classification\": \"P0\"\n }\n },\n \"$defs\": {\n \"MessageType\": {\n \"type\": \"string\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ]\n }\n }\n}\n","// Canonical wire-protocol surface for MyRoboTaxi consumers.\n//\n// Two entrypoints:\n// - `import { VehicleState } from '@myrobotaxi/contracts'` — pre-generated\n// TypeScript types from the schemas/ folder. Tree-shakes (no runtime\n// payload).\n// - `import { schemas } from '@myrobotaxi/contracts'` — parsed JSON Schema\n// objects, suitable for Ajv / runtime validation. Pulls the JSON into the\n// consumer bundle; use the `./types` subpath if you only need types.\n//\n// The Go telemetry server and the future Swift SDK consume the raw JSON files\n// under `./schemas/*.json` directly via the package's `exports` map.\n\nexport * from './generated/index.js';\n\nimport vehicleState from '../schemas/vehicle-state.schema.json' with { type: 'json' };\nimport wsMessages from '../schemas/ws-messages.schema.json' with { type: 'json' };\nimport wsEnvelope from '../schemas/ws-envelope.schema.json' with { type: 'json' };\n\nexport const schemas = {\n vehicleState,\n wsMessages,\n wsEnvelope,\n} as const;\n"]}
1
+ {"version":3,"sources":["../schemas/vehicle-state.schema.json","../schemas/ws-messages.schema.json","../schemas/ws-envelope.schema.json","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/vehicle-state.schema.json\",\n \"title\": \"VehicleState\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\n \"vehicleId\",\n \"name\",\n \"model\",\n \"year\",\n \"color\",\n \"status\",\n \"chargeLevel\",\n \"estimatedRange\",\n \"speed\",\n \"heading\",\n \"latitude\",\n \"longitude\",\n \"locationName\",\n \"locationAddress\",\n \"interiorTemp\",\n \"exteriorTemp\",\n \"odometerMiles\",\n \"fsdMilesSinceReset\",\n \"lastUpdated\"\n ],\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database identifier (cuid). NOT the VIN. All SDK API calls use this ID (FR-4.2).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"clxyz1234567890abcdef\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Optimus\"]\n },\n \"model\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Model 3\"]\n },\n \"year\": {\n \"type\": \"integer\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [2024]\n },\n \"color\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Midnight Silver Metallic\"]\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"driving\", \"parked\", \"charging\", \"offline\", \"in_service\"],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"speed\": {\n \"type\": \"integer\",\n \"description\": \"Current vehicle speed. Rounded to nearest integer from Tesla's float emission.\",\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"heading\": {\n \"type\": \"integer\",\n \"description\": \"Compass heading. 0 = North, 90 = East, 180 = South, 270 = West.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 359,\n \"x-atomic-group\": \"gps\"\n },\n \"latitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"gps\"\n },\n \"longitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"gps\"\n },\n \"locationName\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"locationAddress\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"gearPosition\": {\n \"type\": [\"string\", \"null\"],\n \"enum\": [\"P\", \"D\", \"R\", \"N\", null],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"chargeLevel\": {\n \"type\": \"integer\",\n \"description\": \"Battery state of charge. Rounded to nearest integer from Tesla's float emission. Maps from Tesla SOC or BatteryLevel fields.\",\n \"x-unit\": \"percent\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 100,\n \"x-atomic-group\": \"charge\"\n },\n \"chargeState\": {\n \"type\": [\"string\", \"null\"],\n \"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`).\",\n \"enum\": [\"Unknown\", \"Disconnected\", \"NoPower\", \"Starting\", \"Charging\", \"Complete\", \"Stopped\", null],\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 179\n },\n \"estimatedRange\": {\n \"type\": \"integer\",\n \"description\": \"Estimated remaining driving range based on current charge. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\"\n },\n \"timeToFull\": {\n \"type\": [\"number\", \"null\"],\n \"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.\",\n \"x-unit\": \"hours\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 43\n },\n \"interiorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Cabin interior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"exteriorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Ambient exterior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"odometerMiles\": {\n \"type\": \"integer\",\n \"description\": \"Total odometer reading. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"fsdMilesSinceReset\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"destinationName\": {\n \"type\": [\"string\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\",\n \"examples\": [\"Whole Foods Market\"]\n },\n \"destinationAddress\": {\n \"type\": [\"string\", \"null\"],\n \"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).\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"etaMinutes\": {\n \"type\": [\"integer\", \"null\"],\n \"description\": \"Estimated time of arrival in minutes remaining. Null when no navigation is active. Rounded to nearest integer.\",\n \"x-unit\": \"minutes\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"tripDistanceRemaining\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Remaining distance to navigation destination. Null when no navigation is active.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"navRouteCoordinates\": {\n \"type\": [\"array\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"x-atomic-group\": \"navigation\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2,\n \"description\": \"A coordinate pair: [longitude, latitude].\"\n }\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"ISO 8601 timestamp of the most recent telemetry update applied to this vehicle state.\",\n \"x-classification\": \"P0\"\n }\n },\n \"x-atomic-groups\": {\n \"navigation\": {\n \"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).\",\n \"fields\": [\n \"destinationName\",\n \"destinationAddress\",\n \"destinationLatitude\",\n \"destinationLongitude\",\n \"originLatitude\",\n \"originLongitude\",\n \"etaMinutes\",\n \"tripDistanceRemaining\",\n \"navRouteCoordinates\"\n ],\n \"consistency-predicates\": [\n \"If destinationLatitude is non-null, then destinationLongitude MUST also be non-null, and vice versa.\",\n \"If originLatitude is non-null, then originLongitude MUST also be non-null, and vice versa.\",\n \"If destinationName is non-null, then destinationLatitude, destinationLongitude, and navRouteCoordinates MUST also be non-null (NFR-3.3).\",\n \"If any navigation field is null, ALL navigation fields MUST be null (atomic clear on nav cancel, FR-2.3).\",\n \"etaMinutes and tripDistanceRemaining MAY be null independently during the 500ms accumulation window, but the DB snapshot MUST have all-or-nothing consistency.\"\n ],\n \"nullability\": \"All fields nullable. Null = no active navigation.\"\n },\n \"charge\": {\n \"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).\",\n \"fields\": [\n \"chargeLevel\",\n \"chargeState\",\n \"estimatedRange\",\n \"timeToFull\"\n ],\n \"consistency-predicates\": [\n \"chargeLevel and estimatedRange MUST both be present in the DB snapshot (NFR-3.5). They are non-nullable with default 0.\",\n \"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.\",\n \"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.\"\n ],\n \"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.\"\n },\n \"gps\": {\n \"description\": \"GPS position and orientation delivered together. On initial connect before the first GPS fix, latitude and longitude may be 0 (DB default).\",\n \"fields\": [\n \"latitude\",\n \"longitude\",\n \"heading\"\n ],\n \"consistency-predicates\": [\n \"latitude and longitude MUST always be present together (NFR-3.3).\",\n \"heading MUST be present whenever latitude/longitude are present.\",\n \"Values of 0,0 for latitude/longitude represent the DB default (no GPS fix yet), not a valid location.\"\n ],\n \"nullability\": \"Non-nullable in DB (default 0). SDK consumers SHOULD treat 0,0 as 'no fix'.\"\n },\n \"gear\": {\n \"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.\",\n \"fields\": [\n \"gearPosition\",\n \"status\"\n ],\n \"consistency-predicates\": [\n \"When gearPosition is 'D' or 'R', status MUST be 'driving'.\",\n \"When gearPosition is 'P' or 'N', status MUST be 'parked' (unless overridden by charging/offline/in_service logic).\",\n \"status is always present; gearPosition is nullable (null = vehicle asleep or gear not yet reported).\"\n ],\n \"nullability\": \"gearPosition is nullable. status is non-nullable (default 'offline').\"\n }\n },\n \"dependentRequired\": {\n \"destinationLatitude\": [\"destinationLongitude\"],\n \"destinationLongitude\": [\"destinationLatitude\"],\n \"originLatitude\": [\"originLongitude\"],\n \"originLongitude\": [\"originLatitude\"]\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-messages.schema.json\",\n \"title\": \"WebSocketMessages\",\n \"description\": \"JSON Schemas for every WebSocket payload exchanged between the telemetry server and SDK clients. The vehicle_update payload's `fields` map is a subset of the canonical VehicleState (see vehicle-state.schema.json); per-field types/units/classification live there and are NOT duplicated here. This file holds the wire-only shapes (envelope payloads, control messages, errors).\",\n \"$defs\": {\n \"AuthPayload\": {\n \"type\": \"object\",\n \"description\": \"Client->server authentication payload. The first frame on every WebSocket connection MUST be {type: 'auth', payload: {token: '...'}}. The token is opaque to the server's transport layer and is validated by the configured Authenticator (JWT in production, NoopAuthenticator in dev). Anchored by FR-6.1, NFR-3.21.\",\n \"required\": [\"token\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"token\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Bearer-style session token resolved by the consumer's getToken() callback (FR-6.1). Server validates via Authenticator.ValidateToken. P1 — never log this value (data-classification.md §1.2).\",\n \"x-classification\": \"P1\"\n }\n }\n },\n \"AuthOkPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client positive authentication acknowledgement. v1-required. Emitted as the FIRST frame after successful `Authenticator.ValidateToken` + `GetUserVehicles` + `Hub.Register`. Triggers the SDK `connectionState` transition `connecting -> connected` (state-machine C-3). Without this frame, SDKs on idle vehicles would sit in `connecting` up to one heartbeat interval waiting for telemetry traffic — an unacceptable regression on cold watchOS wakes. See websocket-protocol.md §2.3 for the full handshake contract. Note: `auth_ok` has been pulled OUT of divergence DV-07 and is v1-required; the rest of DV-07 (subscribe/unsubscribe/ping/pong + typed permission_denied) remains deferred. `state-machine.md` §1.3 C-3 trigger alignment is tracked as DV-15 (requires a follow-up MYR-10 amendment PR). Anchored by FR-6.1, FR-8.1, NFR-3.21.\",\n \"required\": [\"userId\", \"vehicleCount\", \"issuedAt\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"userId\": {\n \"type\": \"string\",\n \"description\": \"Opaque cuid of the authenticated user. Echoed back from the resolved token so the SDK can sanity-check ownership set on reconnect (e.g., compare against a cached userId and clear caches if they disagree, defending against consumer token-mixup bugs). P1 per data-classification.md §1.1 User.id.\",\n \"x-classification\": \"P1\"\n },\n \"vehicleCount\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"Size of the user's vehicle set at handshake time (length of `Authenticator.GetUserVehicles(userId)`). Informational integrity check against the SDK's cached snapshot; the authoritative ownership set is populated via the REST snapshot fetch on reconnect (NFR-3.11). P1 because vehicle count leaks the shape of a user's fleet.\",\n \"x-classification\": \"P1\"\n },\n \"issuedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which Hub.Register returned success. Debug/telemetry only — the SDK MUST NOT use this value for any behavioral decision. P2 because it reveals server-side timing to an observer.\",\n \"x-classification\": \"P2\"\n }\n }\n },\n \"VehicleUpdatePayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client telemetry update for a single vehicle. The `fields` map carries one or more atomic-group members from VehicleState (see vehicle-state.schema.json). Each call to broadcast carries fields from at most ONE atomic group plus optionally non-grouped fields delivered alongside (NFR-3.1). Anchored by FR-1.1, FR-1.2, FR-1.3, FR-2.1, FR-2.2, FR-2.3, NFR-3.1, NFR-3.2.\",\n \"required\": [\"vehicleId\", \"fields\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database cuid for the vehicle (NOT the VIN). All SDK API calls use this ID per FR-4.2. VINs are P0 with mandatory last-4 redaction in logs (data-classification.md §2.1) and MUST NOT appear in this payload under any circumstance.\",\n \"x-classification\": \"P0\"\n },\n \"fields\": {\n \"type\": \"object\",\n \"description\": \"Map of field name -> value. Field names and per-field classifications are defined in vehicle-state.schema.json. A single vehicle_update message contains the members of at most ONE atomic group plus any individually-delivered fields (speed, odometerMiles, interiorTemp, exteriorTemp, fsdMilesSinceReset, locationName, locationAddress, lastUpdated). v1 atomic groups: navigation = {destinationName, destinationAddress, destinationLatitude, destinationLongitude, originLatitude, originLongitude, etaMinutes, tripDistanceRemaining, navRouteCoordinates}; charge = {chargeLevel, chargeState, estimatedRange, timeToFull}; gps = {latitude, longitude, heading}; gear = {gearPosition, status}. The `drive` atomic group's single member `startedAt` is delivered via the `drive_started` lifecycle message, not via `vehicle_update.fields` — see DV-13 in websocket-protocol.md §10. Server explicitly clears a navigation field by sending it as JSON null; the SDK MUST atomically null all members of the affected group per NFR-3.9. The optional `driveTrailCoordinates` field (array of [lng, lat] pairs) carries an active drive's accumulated GPS trail (\\\"where the car has been\\\") and is not part of a normal atomic group — it is always paired with an active drive and routed to dataState.gps. It is semantically distinct from the navigation group's `navRouteCoordinates` (Tesla's planned route polyline; \\\"where the car is going\\\") and the two MUST NOT be conflated.\",\n \"additionalProperties\": true,\n \"x-classification\": \"mixed (per-field; see vehicle-state.schema.json)\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp for this update. Not the Tesla telemetry emission time — this is the server's `time.Now().UTC()` at broadcast (or the event's CreatedAt for non-nav fields). Drives the SDK's lastUpdated state and feeds NFR-3.11 snapshot resume.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveStartedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing the start of a new drive. Also carries the drive atomic group's `startedAt` field, which is the v1 home of `tripStartTime` (relocated from the navigation group per DV-13 — see websocket-protocol.md §4.2). The drive group is NOT delivered via vehicle_update.fields; it is delivered via this message. Anchored by FR-3.1, NFR-3.1.\",\n \"required\": [\"vehicleId\", \"driveId\", \"startLocation\", \"startedAt\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid. Matches the eventual Drive row's id when the drive completes and is persisted.\",\n \"x-classification\": \"P0\"\n },\n \"startLocation\": {\n \"type\": \"object\",\n \"description\": \"GPS coordinates where the drive began. Encrypted at rest in the eventual Drive row but transported plaintext over WSS (NFR-3.22, NFR-3.25).\",\n \"required\": [\"latitude\", \"longitude\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"latitude\": {\n \"type\": \"number\",\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n },\n \"longitude\": {\n \"type\": \"number\",\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n }\n }\n },\n \"startedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"v1 home of `tripStartTime` (per DV-13). Server-authoritative ISO 8601 UTC timestamp at which the drive detector's `started_at` was recorded. SDKs MUST surface this as the authoritative tripStartTime for consumers. Value is always equal to `timestamp` for drive_started; the two fields are separate so `timestamp` can remain the uniform envelope-event time across every server->client message while `startedAt` carries the semantic drive-lifecycle meaning.\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector (`internal/drives/`) declared the drive started. Equal to `startedAt` for this message type.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveEndedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing a completed drive that passed the micro-drive filter (state-machine.md §3.5). The wire payload is intentionally a SUMMARY — it carries only the handful of fields the SDK needs for an immediate 'drive finished' toast or card. The full FR-3.4 drive record (energy used, FSD miles, intervention count, start/end charge level, start/end addresses, full route polyline) is fetched on demand via REST `GET /drives/{id}` (see rest-api.md) using the SDK's `fetchDrive(driveId)` helper. Neither the TypeScript nor the Swift SDK auto-fetches the full record on drive_ended; doing so would burn cellular bandwidth on idle consumers (per NFR-3.36, this is especially bad for watchOS). Tracked as DV-11 (RESOLVED) in websocket-protocol.md §10. Anchored by FR-3.1, FR-3.4.\",\n \"required\": [\"vehicleId\", \"driveId\", \"distance\", \"durationSeconds\", \"avgSpeed\", \"maxSpeed\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid (matches the persisted Drive row id). Input to `fetchDrive(driveId)` for the full FR-3.4 record.\",\n \"x-classification\": \"P0\"\n },\n \"distance\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Total distance driven in miles. Computed as the haversine sum of route points at drive completion.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\"\n },\n \"durationSeconds\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Drive duration in seconds, as a JSON number (double). Server-side value is `DriveStats.Duration.Seconds()`. This replaces the earlier `duration` string field (Go `time.Duration` format like '12m34s'), which was dropped before v1 ship — there are no pre-v1 consumers, so there is no deprecation tail. TypeScript consumers construct `Temporal.Duration` or a Date delta from this value; Swift consumers map directly to `Duration(secondsComponent:attosecondsComponent:)` or `TimeInterval`. Tracked as DV-12 (RESOLVED) in websocket-protocol.md §10.\",\n \"x-unit\": \"seconds\",\n \"x-classification\": \"P0\"\n },\n \"avgSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"maxSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector declared the drive ended.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ConnectivityPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client signal that a vehicle's mTLS connection to the telemetry server has changed state. This is vehicle<->server connectivity, NOT client<->server connectivity (the latter is implicit in the WebSocket connection itself). Anchored by FR-1.3 (extensibility) and FR-8.1 (state surface).\",\n \"required\": [\"vehicleId\", \"online\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"online\": {\n \"type\": \"boolean\",\n \"description\": \"True when the vehicle has an active mTLS Tesla Fleet Telemetry stream into the server. False on disconnect (e.g., vehicle asleep, network drop).\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"HeartbeatPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client keepalive frame. The current server emits a bare envelope `{\\\"type\\\":\\\"heartbeat\\\"}` with NO payload (heartbeat.go computes the message once at init). The optional `timestamp` field is reserved for future use. Anchored by NFR-3.10 (reconnect cadence).\",\n \"additionalProperties\": false,\n \"properties\": {\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"OPTIONAL and NOT currently emitted. Reserved for future use to expose server clock to clients (would also enable client clock-skew detection). Tracked by follow-up issue.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ErrorPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client error frame. The server sends this for transport-level failures the client must surface (auth rejection, auth timeout). The Code field is a stable enum that consumers branch on (FR-7.1) — never string-match the Message field. Anchored by FR-7.1, FR-7.3.\",\n \"required\": [\"code\", \"message\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md §4.1.1, §4.1.1.a; ws catalog websocket-protocol.md §6.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md §4.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports — they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) — the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md §6.1.1 / §7.1.\",\n \"enum\": [\n \"auth_failed\",\n \"auth_timeout\",\n \"permission_denied\",\n \"vehicle_not_owned\",\n \"rate_limited\",\n \"internal_error\",\n \"snapshot_required\",\n \"not_found\",\n \"invalid_request\"\n ],\n \"x-classification\": \"P0\"\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human-readable description for logs and developer tooling. P0 BUT MUST NOT contain P1 values (no GPS, no addresses, no tokens, no emails, no VINs unless redacted to ***XXXX). Per data-classification.md §2.1-2.2, error message construction sites MUST use opaque IDs only.\",\n \"x-classification\": \"P0\"\n },\n \"subCode\": {\n \"type\": \"string\",\n \"description\": \"Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: \\\"device_cap\\\"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code — see websocket-protocol.md §6.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md §7.6 / §7.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window — the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.\",\n \"enum\": [\"device_cap\", \"reauth_required\"],\n \"x-classification\": \"P0\"\n }\n }\n },\n \"SubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to begin or resume streaming a specific vehicle. Today the server implicitly subscribes the client to ALL vehicles owned by the authenticated user as part of the auth handshake (handler.go authenticateClient -> Authenticator.GetUserVehicles). Adding explicit subscribe/unsubscribe is tracked as divergence DV-07 in websocket-protocol.md §10. `sinceSeq` snapshot-resume depends on divergence DV-02 (envelope `seq`) landing first.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"sinceSeq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"PLANNED. Last envelope `seq` the client successfully processed for this vehicle. The server uses this to choose between snapshot-resume (replay missed frames) and full-refresh (REST snapshot fetch + live stream from current seq).\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"UnsubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to stop receiving updates for a specific vehicle without closing the underlying WebSocket. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PingPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server ping. Today, browser clients rely on the server's outbound `heartbeat` frames (§7.4) and the underlying WebSocket protocol's PING/PONG control frames (handled transparently by coder/websocket) to detect liveness. An app-level `ping` is reserved for platforms where the WebSocket library does not expose RFC 6455 PING/PONG — specifically watchOS extended-runtime sessions and iOS background sockets, per NFR-3.36 / NFR-3.36a-d. The TypeScript SDK runs on browser `WebSocket` and Node `ws`, both of which expose transport-level PING/PONG, so this is a Swift-SDK forward-compatibility concern only. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"description\": \"Optional opaque value echoed back in the corresponding `pong` for round-trip latency measurement.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PongPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT emitted by the server today. Server->client response to a client-initiated `ping`. Echoes the nonce so the client can compute round-trip latency. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n }\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-envelope.schema.json\",\n \"title\": \"WebSocketEnvelope\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\"type\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"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).\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ],\n \"x-classification\": \"P0\"\n },\n \"payload\": {\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"seq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"ts\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"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.\",\n \"x-classification\": \"P0\"\n }\n },\n \"$defs\": {\n \"MessageType\": {\n \"type\": \"string\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ]\n }\n }\n}\n","// Canonical wire-protocol surface for MyRoboTaxi consumers.\n//\n// Two entrypoints:\n// - `import { VehicleState } from '@myrobotaxi/contracts'` — pre-generated\n// TypeScript types from the schemas/ folder. Tree-shakes (no runtime\n// payload).\n// - `import { schemas } from '@myrobotaxi/contracts'` — parsed JSON Schema\n// objects, suitable for Ajv / runtime validation. Pulls the JSON into the\n// consumer bundle; use the `./types` subpath if you only need types.\n//\n// The Go telemetry server and the future Swift SDK consume the raw JSON files\n// under `./schemas/*.json` directly via the package's `exports` map.\n\nexport * from './generated/index.js';\n\nimport vehicleState from '../schemas/vehicle-state.schema.json' with { type: 'json' };\nimport wsMessages from '../schemas/ws-messages.schema.json' with { type: 'json' };\nimport wsEnvelope from '../schemas/ws-envelope.schema.json' with { type: 'json' };\n\nexport const schemas = {\n vehicleState,\n wsMessages,\n wsEnvelope,\n} as const;\n"]}
package/dist/index.js CHANGED
@@ -559,7 +559,7 @@ var ws_messages_schema_default = {
559
559
  properties: {
560
560
  code: {
561
561
  type: "string",
562
- description: "Stable typed error code from the catalog in websocket-protocol.md \xA76.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md \xA710. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md \xA76.1.1 and \xA77.1.",
562
+ description: "Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md \xA74.1.1, \xA74.1.1.a; ws catalog websocket-protocol.md \xA76.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md \xA710). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md \xA74.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports \u2014 they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) \u2014 the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md \xA76.1.1 / \xA77.1.",
563
563
  enum: [
564
564
  "auth_failed",
565
565
  "auth_timeout",
@@ -567,7 +567,9 @@ var ws_messages_schema_default = {
567
567
  "vehicle_not_owned",
568
568
  "rate_limited",
569
569
  "internal_error",
570
- "snapshot_required"
570
+ "snapshot_required",
571
+ "not_found",
572
+ "invalid_request"
571
573
  ],
572
574
  "x-classification": "P0"
573
575
  },
@@ -578,8 +580,8 @@ var ws_messages_schema_default = {
578
580
  },
579
581
  subCode: {
580
582
  type: "string",
581
- description: "Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: \"device_cap\"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md \xA76.1.1 for the full device_cap UX contract.",
582
- enum: ["device_cap"],
583
+ description: "Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: \"device_cap\"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code \u2014 see websocket-protocol.md \xA76.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md \xA77.6 / \xA77.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window \u2014 the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.",
584
+ enum: ["device_cap", "reauth_required"],
583
585
  "x-classification": "P0"
584
586
  }
585
587
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../schemas/vehicle-state.schema.json","../schemas/ws-messages.schema.json","../schemas/ws-envelope.schema.json","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/vehicle-state.schema.json\",\n \"title\": \"VehicleState\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\n \"vehicleId\",\n \"name\",\n \"model\",\n \"year\",\n \"color\",\n \"status\",\n \"chargeLevel\",\n \"estimatedRange\",\n \"speed\",\n \"heading\",\n \"latitude\",\n \"longitude\",\n \"locationName\",\n \"locationAddress\",\n \"interiorTemp\",\n \"exteriorTemp\",\n \"odometerMiles\",\n \"fsdMilesSinceReset\",\n \"lastUpdated\"\n ],\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database identifier (cuid). NOT the VIN. All SDK API calls use this ID (FR-4.2).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"clxyz1234567890abcdef\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Optimus\"]\n },\n \"model\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Model 3\"]\n },\n \"year\": {\n \"type\": \"integer\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [2024]\n },\n \"color\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Midnight Silver Metallic\"]\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"driving\", \"parked\", \"charging\", \"offline\", \"in_service\"],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"speed\": {\n \"type\": \"integer\",\n \"description\": \"Current vehicle speed. Rounded to nearest integer from Tesla's float emission.\",\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"heading\": {\n \"type\": \"integer\",\n \"description\": \"Compass heading. 0 = North, 90 = East, 180 = South, 270 = West.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 359,\n \"x-atomic-group\": \"gps\"\n },\n \"latitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"gps\"\n },\n \"longitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"gps\"\n },\n \"locationName\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"locationAddress\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"gearPosition\": {\n \"type\": [\"string\", \"null\"],\n \"enum\": [\"P\", \"D\", \"R\", \"N\", null],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"chargeLevel\": {\n \"type\": \"integer\",\n \"description\": \"Battery state of charge. Rounded to nearest integer from Tesla's float emission. Maps from Tesla SOC or BatteryLevel fields.\",\n \"x-unit\": \"percent\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 100,\n \"x-atomic-group\": \"charge\"\n },\n \"chargeState\": {\n \"type\": [\"string\", \"null\"],\n \"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`).\",\n \"enum\": [\"Unknown\", \"Disconnected\", \"NoPower\", \"Starting\", \"Charging\", \"Complete\", \"Stopped\", null],\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 179\n },\n \"estimatedRange\": {\n \"type\": \"integer\",\n \"description\": \"Estimated remaining driving range based on current charge. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\"\n },\n \"timeToFull\": {\n \"type\": [\"number\", \"null\"],\n \"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.\",\n \"x-unit\": \"hours\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 43\n },\n \"interiorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Cabin interior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"exteriorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Ambient exterior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"odometerMiles\": {\n \"type\": \"integer\",\n \"description\": \"Total odometer reading. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"fsdMilesSinceReset\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"destinationName\": {\n \"type\": [\"string\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\",\n \"examples\": [\"Whole Foods Market\"]\n },\n \"destinationAddress\": {\n \"type\": [\"string\", \"null\"],\n \"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).\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"etaMinutes\": {\n \"type\": [\"integer\", \"null\"],\n \"description\": \"Estimated time of arrival in minutes remaining. Null when no navigation is active. Rounded to nearest integer.\",\n \"x-unit\": \"minutes\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"tripDistanceRemaining\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Remaining distance to navigation destination. Null when no navigation is active.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"navRouteCoordinates\": {\n \"type\": [\"array\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"x-atomic-group\": \"navigation\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2,\n \"description\": \"A coordinate pair: [longitude, latitude].\"\n }\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"ISO 8601 timestamp of the most recent telemetry update applied to this vehicle state.\",\n \"x-classification\": \"P0\"\n }\n },\n \"x-atomic-groups\": {\n \"navigation\": {\n \"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).\",\n \"fields\": [\n \"destinationName\",\n \"destinationAddress\",\n \"destinationLatitude\",\n \"destinationLongitude\",\n \"originLatitude\",\n \"originLongitude\",\n \"etaMinutes\",\n \"tripDistanceRemaining\",\n \"navRouteCoordinates\"\n ],\n \"consistency-predicates\": [\n \"If destinationLatitude is non-null, then destinationLongitude MUST also be non-null, and vice versa.\",\n \"If originLatitude is non-null, then originLongitude MUST also be non-null, and vice versa.\",\n \"If destinationName is non-null, then destinationLatitude, destinationLongitude, and navRouteCoordinates MUST also be non-null (NFR-3.3).\",\n \"If any navigation field is null, ALL navigation fields MUST be null (atomic clear on nav cancel, FR-2.3).\",\n \"etaMinutes and tripDistanceRemaining MAY be null independently during the 500ms accumulation window, but the DB snapshot MUST have all-or-nothing consistency.\"\n ],\n \"nullability\": \"All fields nullable. Null = no active navigation.\"\n },\n \"charge\": {\n \"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).\",\n \"fields\": [\n \"chargeLevel\",\n \"chargeState\",\n \"estimatedRange\",\n \"timeToFull\"\n ],\n \"consistency-predicates\": [\n \"chargeLevel and estimatedRange MUST both be present in the DB snapshot (NFR-3.5). They are non-nullable with default 0.\",\n \"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.\",\n \"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.\"\n ],\n \"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.\"\n },\n \"gps\": {\n \"description\": \"GPS position and orientation delivered together. On initial connect before the first GPS fix, latitude and longitude may be 0 (DB default).\",\n \"fields\": [\n \"latitude\",\n \"longitude\",\n \"heading\"\n ],\n \"consistency-predicates\": [\n \"latitude and longitude MUST always be present together (NFR-3.3).\",\n \"heading MUST be present whenever latitude/longitude are present.\",\n \"Values of 0,0 for latitude/longitude represent the DB default (no GPS fix yet), not a valid location.\"\n ],\n \"nullability\": \"Non-nullable in DB (default 0). SDK consumers SHOULD treat 0,0 as 'no fix'.\"\n },\n \"gear\": {\n \"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.\",\n \"fields\": [\n \"gearPosition\",\n \"status\"\n ],\n \"consistency-predicates\": [\n \"When gearPosition is 'D' or 'R', status MUST be 'driving'.\",\n \"When gearPosition is 'P' or 'N', status MUST be 'parked' (unless overridden by charging/offline/in_service logic).\",\n \"status is always present; gearPosition is nullable (null = vehicle asleep or gear not yet reported).\"\n ],\n \"nullability\": \"gearPosition is nullable. status is non-nullable (default 'offline').\"\n }\n },\n \"dependentRequired\": {\n \"destinationLatitude\": [\"destinationLongitude\"],\n \"destinationLongitude\": [\"destinationLatitude\"],\n \"originLatitude\": [\"originLongitude\"],\n \"originLongitude\": [\"originLatitude\"]\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-messages.schema.json\",\n \"title\": \"WebSocketMessages\",\n \"description\": \"JSON Schemas for every WebSocket payload exchanged between the telemetry server and SDK clients. The vehicle_update payload's `fields` map is a subset of the canonical VehicleState (see vehicle-state.schema.json); per-field types/units/classification live there and are NOT duplicated here. This file holds the wire-only shapes (envelope payloads, control messages, errors).\",\n \"$defs\": {\n \"AuthPayload\": {\n \"type\": \"object\",\n \"description\": \"Client->server authentication payload. The first frame on every WebSocket connection MUST be {type: 'auth', payload: {token: '...'}}. The token is opaque to the server's transport layer and is validated by the configured Authenticator (JWT in production, NoopAuthenticator in dev). Anchored by FR-6.1, NFR-3.21.\",\n \"required\": [\"token\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"token\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Bearer-style session token resolved by the consumer's getToken() callback (FR-6.1). Server validates via Authenticator.ValidateToken. P1 — never log this value (data-classification.md §1.2).\",\n \"x-classification\": \"P1\"\n }\n }\n },\n \"AuthOkPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client positive authentication acknowledgement. v1-required. Emitted as the FIRST frame after successful `Authenticator.ValidateToken` + `GetUserVehicles` + `Hub.Register`. Triggers the SDK `connectionState` transition `connecting -> connected` (state-machine C-3). Without this frame, SDKs on idle vehicles would sit in `connecting` up to one heartbeat interval waiting for telemetry traffic — an unacceptable regression on cold watchOS wakes. See websocket-protocol.md §2.3 for the full handshake contract. Note: `auth_ok` has been pulled OUT of divergence DV-07 and is v1-required; the rest of DV-07 (subscribe/unsubscribe/ping/pong + typed permission_denied) remains deferred. `state-machine.md` §1.3 C-3 trigger alignment is tracked as DV-15 (requires a follow-up MYR-10 amendment PR). Anchored by FR-6.1, FR-8.1, NFR-3.21.\",\n \"required\": [\"userId\", \"vehicleCount\", \"issuedAt\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"userId\": {\n \"type\": \"string\",\n \"description\": \"Opaque cuid of the authenticated user. Echoed back from the resolved token so the SDK can sanity-check ownership set on reconnect (e.g., compare against a cached userId and clear caches if they disagree, defending against consumer token-mixup bugs). P1 per data-classification.md §1.1 User.id.\",\n \"x-classification\": \"P1\"\n },\n \"vehicleCount\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"Size of the user's vehicle set at handshake time (length of `Authenticator.GetUserVehicles(userId)`). Informational integrity check against the SDK's cached snapshot; the authoritative ownership set is populated via the REST snapshot fetch on reconnect (NFR-3.11). P1 because vehicle count leaks the shape of a user's fleet.\",\n \"x-classification\": \"P1\"\n },\n \"issuedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which Hub.Register returned success. Debug/telemetry only — the SDK MUST NOT use this value for any behavioral decision. P2 because it reveals server-side timing to an observer.\",\n \"x-classification\": \"P2\"\n }\n }\n },\n \"VehicleUpdatePayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client telemetry update for a single vehicle. The `fields` map carries one or more atomic-group members from VehicleState (see vehicle-state.schema.json). Each call to broadcast carries fields from at most ONE atomic group plus optionally non-grouped fields delivered alongside (NFR-3.1). Anchored by FR-1.1, FR-1.2, FR-1.3, FR-2.1, FR-2.2, FR-2.3, NFR-3.1, NFR-3.2.\",\n \"required\": [\"vehicleId\", \"fields\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database cuid for the vehicle (NOT the VIN). All SDK API calls use this ID per FR-4.2. VINs are P0 with mandatory last-4 redaction in logs (data-classification.md §2.1) and MUST NOT appear in this payload under any circumstance.\",\n \"x-classification\": \"P0\"\n },\n \"fields\": {\n \"type\": \"object\",\n \"description\": \"Map of field name -> value. Field names and per-field classifications are defined in vehicle-state.schema.json. A single vehicle_update message contains the members of at most ONE atomic group plus any individually-delivered fields (speed, odometerMiles, interiorTemp, exteriorTemp, fsdMilesSinceReset, locationName, locationAddress, lastUpdated). v1 atomic groups: navigation = {destinationName, destinationAddress, destinationLatitude, destinationLongitude, originLatitude, originLongitude, etaMinutes, tripDistanceRemaining, navRouteCoordinates}; charge = {chargeLevel, chargeState, estimatedRange, timeToFull}; gps = {latitude, longitude, heading}; gear = {gearPosition, status}. The `drive` atomic group's single member `startedAt` is delivered via the `drive_started` lifecycle message, not via `vehicle_update.fields` — see DV-13 in websocket-protocol.md §10. Server explicitly clears a navigation field by sending it as JSON null; the SDK MUST atomically null all members of the affected group per NFR-3.9. The optional `driveTrailCoordinates` field (array of [lng, lat] pairs) carries an active drive's accumulated GPS trail (\\\"where the car has been\\\") and is not part of a normal atomic group — it is always paired with an active drive and routed to dataState.gps. It is semantically distinct from the navigation group's `navRouteCoordinates` (Tesla's planned route polyline; \\\"where the car is going\\\") and the two MUST NOT be conflated.\",\n \"additionalProperties\": true,\n \"x-classification\": \"mixed (per-field; see vehicle-state.schema.json)\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp for this update. Not the Tesla telemetry emission time — this is the server's `time.Now().UTC()` at broadcast (or the event's CreatedAt for non-nav fields). Drives the SDK's lastUpdated state and feeds NFR-3.11 snapshot resume.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveStartedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing the start of a new drive. Also carries the drive atomic group's `startedAt` field, which is the v1 home of `tripStartTime` (relocated from the navigation group per DV-13 — see websocket-protocol.md §4.2). The drive group is NOT delivered via vehicle_update.fields; it is delivered via this message. Anchored by FR-3.1, NFR-3.1.\",\n \"required\": [\"vehicleId\", \"driveId\", \"startLocation\", \"startedAt\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid. Matches the eventual Drive row's id when the drive completes and is persisted.\",\n \"x-classification\": \"P0\"\n },\n \"startLocation\": {\n \"type\": \"object\",\n \"description\": \"GPS coordinates where the drive began. Encrypted at rest in the eventual Drive row but transported plaintext over WSS (NFR-3.22, NFR-3.25).\",\n \"required\": [\"latitude\", \"longitude\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"latitude\": {\n \"type\": \"number\",\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n },\n \"longitude\": {\n \"type\": \"number\",\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n }\n }\n },\n \"startedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"v1 home of `tripStartTime` (per DV-13). Server-authoritative ISO 8601 UTC timestamp at which the drive detector's `started_at` was recorded. SDKs MUST surface this as the authoritative tripStartTime for consumers. Value is always equal to `timestamp` for drive_started; the two fields are separate so `timestamp` can remain the uniform envelope-event time across every server->client message while `startedAt` carries the semantic drive-lifecycle meaning.\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector (`internal/drives/`) declared the drive started. Equal to `startedAt` for this message type.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveEndedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing a completed drive that passed the micro-drive filter (state-machine.md §3.5). The wire payload is intentionally a SUMMARY — it carries only the handful of fields the SDK needs for an immediate 'drive finished' toast or card. The full FR-3.4 drive record (energy used, FSD miles, intervention count, start/end charge level, start/end addresses, full route polyline) is fetched on demand via REST `GET /drives/{id}` (see rest-api.md) using the SDK's `fetchDrive(driveId)` helper. Neither the TypeScript nor the Swift SDK auto-fetches the full record on drive_ended; doing so would burn cellular bandwidth on idle consumers (per NFR-3.36, this is especially bad for watchOS). Tracked as DV-11 (RESOLVED) in websocket-protocol.md §10. Anchored by FR-3.1, FR-3.4.\",\n \"required\": [\"vehicleId\", \"driveId\", \"distance\", \"durationSeconds\", \"avgSpeed\", \"maxSpeed\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid (matches the persisted Drive row id). Input to `fetchDrive(driveId)` for the full FR-3.4 record.\",\n \"x-classification\": \"P0\"\n },\n \"distance\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Total distance driven in miles. Computed as the haversine sum of route points at drive completion.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\"\n },\n \"durationSeconds\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Drive duration in seconds, as a JSON number (double). Server-side value is `DriveStats.Duration.Seconds()`. This replaces the earlier `duration` string field (Go `time.Duration` format like '12m34s'), which was dropped before v1 ship — there are no pre-v1 consumers, so there is no deprecation tail. TypeScript consumers construct `Temporal.Duration` or a Date delta from this value; Swift consumers map directly to `Duration(secondsComponent:attosecondsComponent:)` or `TimeInterval`. Tracked as DV-12 (RESOLVED) in websocket-protocol.md §10.\",\n \"x-unit\": \"seconds\",\n \"x-classification\": \"P0\"\n },\n \"avgSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"maxSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector declared the drive ended.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ConnectivityPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client signal that a vehicle's mTLS connection to the telemetry server has changed state. This is vehicle<->server connectivity, NOT client<->server connectivity (the latter is implicit in the WebSocket connection itself). Anchored by FR-1.3 (extensibility) and FR-8.1 (state surface).\",\n \"required\": [\"vehicleId\", \"online\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"online\": {\n \"type\": \"boolean\",\n \"description\": \"True when the vehicle has an active mTLS Tesla Fleet Telemetry stream into the server. False on disconnect (e.g., vehicle asleep, network drop).\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"HeartbeatPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client keepalive frame. The current server emits a bare envelope `{\\\"type\\\":\\\"heartbeat\\\"}` with NO payload (heartbeat.go computes the message once at init). The optional `timestamp` field is reserved for future use. Anchored by NFR-3.10 (reconnect cadence).\",\n \"additionalProperties\": false,\n \"properties\": {\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"OPTIONAL and NOT currently emitted. Reserved for future use to expose server clock to clients (would also enable client clock-skew detection). Tracked by follow-up issue.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ErrorPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client error frame. The server sends this for transport-level failures the client must surface (auth rejection, auth timeout). The Code field is a stable enum that consumers branch on (FR-7.1) — never string-match the Message field. Anchored by FR-7.1, FR-7.3.\",\n \"required\": [\"code\", \"message\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable typed error code from the catalog in websocket-protocol.md §6.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md §6.1.1 and §7.1.\",\n \"enum\": [\n \"auth_failed\",\n \"auth_timeout\",\n \"permission_denied\",\n \"vehicle_not_owned\",\n \"rate_limited\",\n \"internal_error\",\n \"snapshot_required\"\n ],\n \"x-classification\": \"P0\"\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human-readable description for logs and developer tooling. P0 BUT MUST NOT contain P1 values (no GPS, no addresses, no tokens, no emails, no VINs unless redacted to ***XXXX). Per data-classification.md §2.1-2.2, error message construction sites MUST use opaque IDs only.\",\n \"x-classification\": \"P0\"\n },\n \"subCode\": {\n \"type\": \"string\",\n \"description\": \"Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: \\\"device_cap\\\"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md §6.1.1 for the full device_cap UX contract.\",\n \"enum\": [\"device_cap\"],\n \"x-classification\": \"P0\"\n }\n }\n },\n \"SubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to begin or resume streaming a specific vehicle. Today the server implicitly subscribes the client to ALL vehicles owned by the authenticated user as part of the auth handshake (handler.go authenticateClient -> Authenticator.GetUserVehicles). Adding explicit subscribe/unsubscribe is tracked as divergence DV-07 in websocket-protocol.md §10. `sinceSeq` snapshot-resume depends on divergence DV-02 (envelope `seq`) landing first.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"sinceSeq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"PLANNED. Last envelope `seq` the client successfully processed for this vehicle. The server uses this to choose between snapshot-resume (replay missed frames) and full-refresh (REST snapshot fetch + live stream from current seq).\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"UnsubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to stop receiving updates for a specific vehicle without closing the underlying WebSocket. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PingPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server ping. Today, browser clients rely on the server's outbound `heartbeat` frames (§7.4) and the underlying WebSocket protocol's PING/PONG control frames (handled transparently by coder/websocket) to detect liveness. An app-level `ping` is reserved for platforms where the WebSocket library does not expose RFC 6455 PING/PONG — specifically watchOS extended-runtime sessions and iOS background sockets, per NFR-3.36 / NFR-3.36a-d. The TypeScript SDK runs on browser `WebSocket` and Node `ws`, both of which expose transport-level PING/PONG, so this is a Swift-SDK forward-compatibility concern only. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"description\": \"Optional opaque value echoed back in the corresponding `pong` for round-trip latency measurement.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PongPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT emitted by the server today. Server->client response to a client-initiated `ping`. Echoes the nonce so the client can compute round-trip latency. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n }\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-envelope.schema.json\",\n \"title\": \"WebSocketEnvelope\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\"type\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"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).\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ],\n \"x-classification\": \"P0\"\n },\n \"payload\": {\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"seq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"ts\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"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.\",\n \"x-classification\": \"P0\"\n }\n },\n \"$defs\": {\n \"MessageType\": {\n \"type\": \"string\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ]\n }\n }\n}\n","// Canonical wire-protocol surface for MyRoboTaxi consumers.\n//\n// Two entrypoints:\n// - `import { VehicleState } from '@myrobotaxi/contracts'` — pre-generated\n// TypeScript types from the schemas/ folder. Tree-shakes (no runtime\n// payload).\n// - `import { schemas } from '@myrobotaxi/contracts'` — parsed JSON Schema\n// objects, suitable for Ajv / runtime validation. Pulls the JSON into the\n// consumer bundle; use the `./types` subpath if you only need types.\n//\n// The Go telemetry server and the future Swift SDK consume the raw JSON files\n// under `./schemas/*.json` directly via the package's `exports` map.\n\nexport * from './generated/index.js';\n\nimport vehicleState from '../schemas/vehicle-state.schema.json' with { type: 'json' };\nimport wsMessages from '../schemas/ws-messages.schema.json' with { type: 'json' };\nimport wsEnvelope from '../schemas/ws-envelope.schema.json' with { type: 'json' };\n\nexport const schemas = {\n vehicleState,\n wsMessages,\n wsEnvelope,\n} as const;\n"]}
1
+ {"version":3,"sources":["../schemas/vehicle-state.schema.json","../schemas/ws-messages.schema.json","../schemas/ws-envelope.schema.json","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/vehicle-state.schema.json\",\n \"title\": \"VehicleState\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\n \"vehicleId\",\n \"name\",\n \"model\",\n \"year\",\n \"color\",\n \"status\",\n \"chargeLevel\",\n \"estimatedRange\",\n \"speed\",\n \"heading\",\n \"latitude\",\n \"longitude\",\n \"locationName\",\n \"locationAddress\",\n \"interiorTemp\",\n \"exteriorTemp\",\n \"odometerMiles\",\n \"fsdMilesSinceReset\",\n \"lastUpdated\"\n ],\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database identifier (cuid). NOT the VIN. All SDK API calls use this ID (FR-4.2).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"clxyz1234567890abcdef\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Optimus\"]\n },\n \"model\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Model 3\"]\n },\n \"year\": {\n \"type\": \"integer\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [2024]\n },\n \"color\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P0\",\n \"examples\": [\"Midnight Silver Metallic\"]\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"driving\", \"parked\", \"charging\", \"offline\", \"in_service\"],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"speed\": {\n \"type\": \"integer\",\n \"description\": \"Current vehicle speed. Rounded to nearest integer from Tesla's float emission.\",\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"heading\": {\n \"type\": \"integer\",\n \"description\": \"Compass heading. 0 = North, 90 = East, 180 = South, 270 = West.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 359,\n \"x-atomic-group\": \"gps\"\n },\n \"latitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"gps\"\n },\n \"longitude\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"gps\"\n },\n \"locationName\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"locationAddress\": {\n \"type\": \"string\",\n \"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).\",\n \"x-classification\": \"P1\"\n },\n \"gearPosition\": {\n \"type\": [\"string\", \"null\"],\n \"enum\": [\"P\", \"D\", \"R\", \"N\", null],\n \"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.\",\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"gear\"\n },\n \"chargeLevel\": {\n \"type\": \"integer\",\n \"description\": \"Battery state of charge. Rounded to nearest integer from Tesla's float emission. Maps from Tesla SOC or BatteryLevel fields.\",\n \"x-unit\": \"percent\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"maximum\": 100,\n \"x-atomic-group\": \"charge\"\n },\n \"chargeState\": {\n \"type\": [\"string\", \"null\"],\n \"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`).\",\n \"enum\": [\"Unknown\", \"Disconnected\", \"NoPower\", \"Starting\", \"Charging\", \"Complete\", \"Stopped\", null],\n \"x-classification\": \"P0\",\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 179\n },\n \"estimatedRange\": {\n \"type\": \"integer\",\n \"description\": \"Estimated remaining driving range based on current charge. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\"\n },\n \"timeToFull\": {\n \"type\": [\"number\", \"null\"],\n \"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.\",\n \"x-unit\": \"hours\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"charge\",\n \"x-tesla-proto-field\": 43\n },\n \"interiorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Cabin interior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"exteriorTemp\": {\n \"type\": \"integer\",\n \"description\": \"Ambient exterior temperature. Rounded to nearest integer.\",\n \"x-unit\": \"fahrenheit\",\n \"x-classification\": \"P0\"\n },\n \"odometerMiles\": {\n \"type\": \"integer\",\n \"description\": \"Total odometer reading. Rounded to nearest integer.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"fsdMilesSinceReset\": {\n \"type\": \"number\",\n \"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.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0\n },\n \"destinationName\": {\n \"type\": [\"string\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\",\n \"examples\": [\"Whole Foods Market\"]\n },\n \"destinationAddress\": {\n \"type\": [\"string\", \"null\"],\n \"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).\",\n \"x-classification\": \"P1\",\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"destinationLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation destination. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLatitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Latitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-atomic-group\": \"navigation\"\n },\n \"originLongitude\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Longitude of the navigation trip origin. Encrypted at rest (AES-256-GCM). Null when no navigation is active.\",\n \"x-unit\": \"degrees\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-atomic-group\": \"navigation\"\n },\n \"etaMinutes\": {\n \"type\": [\"integer\", \"null\"],\n \"description\": \"Estimated time of arrival in minutes remaining. Null when no navigation is active. Rounded to nearest integer.\",\n \"x-unit\": \"minutes\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"tripDistanceRemaining\": {\n \"type\": [\"number\", \"null\"],\n \"description\": \"Remaining distance to navigation destination. Null when no navigation is active.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\",\n \"minimum\": 0,\n \"x-atomic-group\": \"navigation\"\n },\n \"navRouteCoordinates\": {\n \"type\": [\"array\", \"null\"],\n \"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.\",\n \"x-classification\": \"P1\",\n \"x-encrypted\": true,\n \"x-atomic-group\": \"navigation\",\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"number\"\n },\n \"minItems\": 2,\n \"maxItems\": 2,\n \"description\": \"A coordinate pair: [longitude, latitude].\"\n }\n },\n \"lastUpdated\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"ISO 8601 timestamp of the most recent telemetry update applied to this vehicle state.\",\n \"x-classification\": \"P0\"\n }\n },\n \"x-atomic-groups\": {\n \"navigation\": {\n \"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).\",\n \"fields\": [\n \"destinationName\",\n \"destinationAddress\",\n \"destinationLatitude\",\n \"destinationLongitude\",\n \"originLatitude\",\n \"originLongitude\",\n \"etaMinutes\",\n \"tripDistanceRemaining\",\n \"navRouteCoordinates\"\n ],\n \"consistency-predicates\": [\n \"If destinationLatitude is non-null, then destinationLongitude MUST also be non-null, and vice versa.\",\n \"If originLatitude is non-null, then originLongitude MUST also be non-null, and vice versa.\",\n \"If destinationName is non-null, then destinationLatitude, destinationLongitude, and navRouteCoordinates MUST also be non-null (NFR-3.3).\",\n \"If any navigation field is null, ALL navigation fields MUST be null (atomic clear on nav cancel, FR-2.3).\",\n \"etaMinutes and tripDistanceRemaining MAY be null independently during the 500ms accumulation window, but the DB snapshot MUST have all-or-nothing consistency.\"\n ],\n \"nullability\": \"All fields nullable. Null = no active navigation.\"\n },\n \"charge\": {\n \"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).\",\n \"fields\": [\n \"chargeLevel\",\n \"chargeState\",\n \"estimatedRange\",\n \"timeToFull\"\n ],\n \"consistency-predicates\": [\n \"chargeLevel and estimatedRange MUST both be present in the DB snapshot (NFR-3.5). They are non-nullable with default 0.\",\n \"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.\",\n \"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.\"\n ],\n \"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.\"\n },\n \"gps\": {\n \"description\": \"GPS position and orientation delivered together. On initial connect before the first GPS fix, latitude and longitude may be 0 (DB default).\",\n \"fields\": [\n \"latitude\",\n \"longitude\",\n \"heading\"\n ],\n \"consistency-predicates\": [\n \"latitude and longitude MUST always be present together (NFR-3.3).\",\n \"heading MUST be present whenever latitude/longitude are present.\",\n \"Values of 0,0 for latitude/longitude represent the DB default (no GPS fix yet), not a valid location.\"\n ],\n \"nullability\": \"Non-nullable in DB (default 0). SDK consumers SHOULD treat 0,0 as 'no fix'.\"\n },\n \"gear\": {\n \"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.\",\n \"fields\": [\n \"gearPosition\",\n \"status\"\n ],\n \"consistency-predicates\": [\n \"When gearPosition is 'D' or 'R', status MUST be 'driving'.\",\n \"When gearPosition is 'P' or 'N', status MUST be 'parked' (unless overridden by charging/offline/in_service logic).\",\n \"status is always present; gearPosition is nullable (null = vehicle asleep or gear not yet reported).\"\n ],\n \"nullability\": \"gearPosition is nullable. status is non-nullable (default 'offline').\"\n }\n },\n \"dependentRequired\": {\n \"destinationLatitude\": [\"destinationLongitude\"],\n \"destinationLongitude\": [\"destinationLatitude\"],\n \"originLatitude\": [\"originLongitude\"],\n \"originLongitude\": [\"originLatitude\"]\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-messages.schema.json\",\n \"title\": \"WebSocketMessages\",\n \"description\": \"JSON Schemas for every WebSocket payload exchanged between the telemetry server and SDK clients. The vehicle_update payload's `fields` map is a subset of the canonical VehicleState (see vehicle-state.schema.json); per-field types/units/classification live there and are NOT duplicated here. This file holds the wire-only shapes (envelope payloads, control messages, errors).\",\n \"$defs\": {\n \"AuthPayload\": {\n \"type\": \"object\",\n \"description\": \"Client->server authentication payload. The first frame on every WebSocket connection MUST be {type: 'auth', payload: {token: '...'}}. The token is opaque to the server's transport layer and is validated by the configured Authenticator (JWT in production, NoopAuthenticator in dev). Anchored by FR-6.1, NFR-3.21.\",\n \"required\": [\"token\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"token\": {\n \"type\": \"string\",\n \"minLength\": 1,\n \"description\": \"Bearer-style session token resolved by the consumer's getToken() callback (FR-6.1). Server validates via Authenticator.ValidateToken. P1 — never log this value (data-classification.md §1.2).\",\n \"x-classification\": \"P1\"\n }\n }\n },\n \"AuthOkPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client positive authentication acknowledgement. v1-required. Emitted as the FIRST frame after successful `Authenticator.ValidateToken` + `GetUserVehicles` + `Hub.Register`. Triggers the SDK `connectionState` transition `connecting -> connected` (state-machine C-3). Without this frame, SDKs on idle vehicles would sit in `connecting` up to one heartbeat interval waiting for telemetry traffic — an unacceptable regression on cold watchOS wakes. See websocket-protocol.md §2.3 for the full handshake contract. Note: `auth_ok` has been pulled OUT of divergence DV-07 and is v1-required; the rest of DV-07 (subscribe/unsubscribe/ping/pong + typed permission_denied) remains deferred. `state-machine.md` §1.3 C-3 trigger alignment is tracked as DV-15 (requires a follow-up MYR-10 amendment PR). Anchored by FR-6.1, FR-8.1, NFR-3.21.\",\n \"required\": [\"userId\", \"vehicleCount\", \"issuedAt\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"userId\": {\n \"type\": \"string\",\n \"description\": \"Opaque cuid of the authenticated user. Echoed back from the resolved token so the SDK can sanity-check ownership set on reconnect (e.g., compare against a cached userId and clear caches if they disagree, defending against consumer token-mixup bugs). P1 per data-classification.md §1.1 User.id.\",\n \"x-classification\": \"P1\"\n },\n \"vehicleCount\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"Size of the user's vehicle set at handshake time (length of `Authenticator.GetUserVehicles(userId)`). Informational integrity check against the SDK's cached snapshot; the authoritative ownership set is populated via the REST snapshot fetch on reconnect (NFR-3.11). P1 because vehicle count leaks the shape of a user's fleet.\",\n \"x-classification\": \"P1\"\n },\n \"issuedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which Hub.Register returned success. Debug/telemetry only — the SDK MUST NOT use this value for any behavioral decision. P2 because it reveals server-side timing to an observer.\",\n \"x-classification\": \"P2\"\n }\n }\n },\n \"VehicleUpdatePayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client telemetry update for a single vehicle. The `fields` map carries one or more atomic-group members from VehicleState (see vehicle-state.schema.json). Each call to broadcast carries fields from at most ONE atomic group plus optionally non-grouped fields delivered alongside (NFR-3.1). Anchored by FR-1.1, FR-1.2, FR-1.3, FR-2.1, FR-2.2, FR-2.3, NFR-3.1, NFR-3.2.\",\n \"required\": [\"vehicleId\", \"fields\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque database cuid for the vehicle (NOT the VIN). All SDK API calls use this ID per FR-4.2. VINs are P0 with mandatory last-4 redaction in logs (data-classification.md §2.1) and MUST NOT appear in this payload under any circumstance.\",\n \"x-classification\": \"P0\"\n },\n \"fields\": {\n \"type\": \"object\",\n \"description\": \"Map of field name -> value. Field names and per-field classifications are defined in vehicle-state.schema.json. A single vehicle_update message contains the members of at most ONE atomic group plus any individually-delivered fields (speed, odometerMiles, interiorTemp, exteriorTemp, fsdMilesSinceReset, locationName, locationAddress, lastUpdated). v1 atomic groups: navigation = {destinationName, destinationAddress, destinationLatitude, destinationLongitude, originLatitude, originLongitude, etaMinutes, tripDistanceRemaining, navRouteCoordinates}; charge = {chargeLevel, chargeState, estimatedRange, timeToFull}; gps = {latitude, longitude, heading}; gear = {gearPosition, status}. The `drive` atomic group's single member `startedAt` is delivered via the `drive_started` lifecycle message, not via `vehicle_update.fields` — see DV-13 in websocket-protocol.md §10. Server explicitly clears a navigation field by sending it as JSON null; the SDK MUST atomically null all members of the affected group per NFR-3.9. The optional `driveTrailCoordinates` field (array of [lng, lat] pairs) carries an active drive's accumulated GPS trail (\\\"where the car has been\\\") and is not part of a normal atomic group — it is always paired with an active drive and routed to dataState.gps. It is semantically distinct from the navigation group's `navRouteCoordinates` (Tesla's planned route polyline; \\\"where the car is going\\\") and the two MUST NOT be conflated.\",\n \"additionalProperties\": true,\n \"x-classification\": \"mixed (per-field; see vehicle-state.schema.json)\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp for this update. Not the Tesla telemetry emission time — this is the server's `time.Now().UTC()` at broadcast (or the event's CreatedAt for non-nav fields). Drives the SDK's lastUpdated state and feeds NFR-3.11 snapshot resume.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveStartedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing the start of a new drive. Also carries the drive atomic group's `startedAt` field, which is the v1 home of `tripStartTime` (relocated from the navigation group per DV-13 — see websocket-protocol.md §4.2). The drive group is NOT delivered via vehicle_update.fields; it is delivered via this message. Anchored by FR-3.1, NFR-3.1.\",\n \"required\": [\"vehicleId\", \"driveId\", \"startLocation\", \"startedAt\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid. Matches the eventual Drive row's id when the drive completes and is persisted.\",\n \"x-classification\": \"P0\"\n },\n \"startLocation\": {\n \"type\": \"object\",\n \"description\": \"GPS coordinates where the drive began. Encrypted at rest in the eventual Drive row but transported plaintext over WSS (NFR-3.22, NFR-3.25).\",\n \"required\": [\"latitude\", \"longitude\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"latitude\": {\n \"type\": \"number\",\n \"minimum\": -90,\n \"maximum\": 90,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n },\n \"longitude\": {\n \"type\": \"number\",\n \"minimum\": -180,\n \"maximum\": 180,\n \"x-classification\": \"P1\",\n \"x-encrypted-at-rest\": true\n }\n }\n },\n \"startedAt\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"v1 home of `tripStartTime` (per DV-13). Server-authoritative ISO 8601 UTC timestamp at which the drive detector's `started_at` was recorded. SDKs MUST surface this as the authoritative tripStartTime for consumers. Value is always equal to `timestamp` for drive_started; the two fields are separate so `timestamp` can remain the uniform envelope-event time across every server->client message while `startedAt` carries the semantic drive-lifecycle meaning.\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector (`internal/drives/`) declared the drive started. Equal to `startedAt` for this message type.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"DriveEndedPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client message announcing a completed drive that passed the micro-drive filter (state-machine.md §3.5). The wire payload is intentionally a SUMMARY — it carries only the handful of fields the SDK needs for an immediate 'drive finished' toast or card. The full FR-3.4 drive record (energy used, FSD miles, intervention count, start/end charge level, start/end addresses, full route polyline) is fetched on demand via REST `GET /drives/{id}` (see rest-api.md) using the SDK's `fetchDrive(driveId)` helper. Neither the TypeScript nor the Swift SDK auto-fetches the full record on drive_ended; doing so would burn cellular bandwidth on idle consumers (per NFR-3.36, this is especially bad for watchOS). Tracked as DV-11 (RESOLVED) in websocket-protocol.md §10. Anchored by FR-3.1, FR-3.4.\",\n \"required\": [\"vehicleId\", \"driveId\", \"distance\", \"durationSeconds\", \"avgSpeed\", \"maxSpeed\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"description\": \"Opaque vehicle cuid.\",\n \"x-classification\": \"P0\"\n },\n \"driveId\": {\n \"type\": \"string\",\n \"description\": \"Opaque drive cuid (matches the persisted Drive row id). Input to `fetchDrive(driveId)` for the full FR-3.4 record.\",\n \"x-classification\": \"P0\"\n },\n \"distance\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Total distance driven in miles. Computed as the haversine sum of route points at drive completion.\",\n \"x-unit\": \"miles\",\n \"x-classification\": \"P0\"\n },\n \"durationSeconds\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"description\": \"Drive duration in seconds, as a JSON number (double). Server-side value is `DriveStats.Duration.Seconds()`. This replaces the earlier `duration` string field (Go `time.Duration` format like '12m34s'), which was dropped before v1 ship — there are no pre-v1 consumers, so there is no deprecation tail. TypeScript consumers construct `Temporal.Duration` or a Date delta from this value; Swift consumers map directly to `Duration(secondsComponent:attosecondsComponent:)` or `TimeInterval`. Tracked as DV-12 (RESOLVED) in websocket-protocol.md §10.\",\n \"x-unit\": \"seconds\",\n \"x-classification\": \"P0\"\n },\n \"avgSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"maxSpeed\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"x-unit\": \"mph\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"Server-authoritative ISO 8601 UTC timestamp at which the drive detector declared the drive ended.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ConnectivityPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client signal that a vehicle's mTLS connection to the telemetry server has changed state. This is vehicle<->server connectivity, NOT client<->server connectivity (the latter is implicit in the WebSocket connection itself). Anchored by FR-1.3 (extensibility) and FR-8.1 (state surface).\",\n \"required\": [\"vehicleId\", \"online\", \"timestamp\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"online\": {\n \"type\": \"boolean\",\n \"description\": \"True when the vehicle has an active mTLS Tesla Fleet Telemetry stream into the server. False on disconnect (e.g., vehicle asleep, network drop).\",\n \"x-classification\": \"P0\"\n },\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"HeartbeatPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client keepalive frame. The current server emits a bare envelope `{\\\"type\\\":\\\"heartbeat\\\"}` with NO payload (heartbeat.go computes the message once at init). The optional `timestamp` field is reserved for future use. Anchored by NFR-3.10 (reconnect cadence).\",\n \"additionalProperties\": false,\n \"properties\": {\n \"timestamp\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"description\": \"OPTIONAL and NOT currently emitted. Reserved for future use to expose server clock to clients (would also enable client clock-skew detection). Tracked by follow-up issue.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"ErrorPayload\": {\n \"type\": \"object\",\n \"description\": \"Server->client error frame. The server sends this for transport-level failures the client must surface (auth rejection, auth timeout). The Code field is a stable enum that consumers branch on (FR-7.1) — never string-match the Message field. Anchored by FR-7.1, FR-7.3.\",\n \"required\": [\"code\", \"message\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"code\": {\n \"type\": \"string\",\n \"description\": \"Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md §4.1.1, §4.1.1.a; ws catalog websocket-protocol.md §6.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md §4.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports — they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) — the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md §6.1.1 / §7.1.\",\n \"enum\": [\n \"auth_failed\",\n \"auth_timeout\",\n \"permission_denied\",\n \"vehicle_not_owned\",\n \"rate_limited\",\n \"internal_error\",\n \"snapshot_required\",\n \"not_found\",\n \"invalid_request\"\n ],\n \"x-classification\": \"P0\"\n },\n \"message\": {\n \"type\": \"string\",\n \"description\": \"Human-readable description for logs and developer tooling. P0 BUT MUST NOT contain P1 values (no GPS, no addresses, no tokens, no emails, no VINs unless redacted to ***XXXX). Per data-classification.md §2.1-2.2, error message construction sites MUST use opaque IDs only.\",\n \"x-classification\": \"P0\"\n },\n \"subCode\": {\n \"type\": \"string\",\n \"description\": \"Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: \\\"device_cap\\\"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code — see websocket-protocol.md §6.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md §7.6 / §7.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window — the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.\",\n \"enum\": [\"device_cap\", \"reauth_required\"],\n \"x-classification\": \"P0\"\n }\n }\n },\n \"SubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to begin or resume streaming a specific vehicle. Today the server implicitly subscribes the client to ALL vehicles owned by the authenticated user as part of the auth handshake (handler.go authenticateClient -> Authenticator.GetUserVehicles). Adding explicit subscribe/unsubscribe is tracked as divergence DV-07 in websocket-protocol.md §10. `sinceSeq` snapshot-resume depends on divergence DV-02 (envelope `seq`) landing first.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n },\n \"sinceSeq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"description\": \"PLANNED. Last envelope `seq` the client successfully processed for this vehicle. The server uses this to choose between snapshot-resume (replay missed frames) and full-refresh (REST snapshot fetch + live stream from current seq).\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"UnsubscribePayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server request to stop receiving updates for a specific vehicle without closing the underlying WebSocket. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"required\": [\"vehicleId\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"vehicleId\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PingPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT accepted by the server today. Client->server ping. Today, browser clients rely on the server's outbound `heartbeat` frames (§7.4) and the underlying WebSocket protocol's PING/PONG control frames (handled transparently by coder/websocket) to detect liveness. An app-level `ping` is reserved for platforms where the WebSocket library does not expose RFC 6455 PING/PONG — specifically watchOS extended-runtime sessions and iOS background sockets, per NFR-3.36 / NFR-3.36a-d. The TypeScript SDK runs on browser `WebSocket` and Node `ws`, both of which expose transport-level PING/PONG, so this is a Swift-SDK forward-compatibility concern only. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"description\": \"Optional opaque value echoed back in the corresponding `pong` for round-trip latency measurement.\",\n \"x-classification\": \"P0\"\n }\n }\n },\n \"PongPayload\": {\n \"type\": \"object\",\n \"description\": \"PLANNED — NOT emitted by the server today. Server->client response to a client-initiated `ping`. Echoes the nonce so the client can compute round-trip latency. Tracked as divergence DV-07 in websocket-protocol.md §10. Note: `auth_ok` has been pulled out of DV-07 and is v1-required (see AuthOkPayload); the rest of DV-07 remains deferred.\",\n \"additionalProperties\": false,\n \"properties\": {\n \"nonce\": {\n \"type\": \"string\",\n \"x-classification\": \"P0\"\n }\n }\n }\n }\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"https://myrobotaxi.com/schemas/ws-envelope.schema.json\",\n \"title\": \"WebSocketEnvelope\",\n \"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.\",\n \"type\": \"object\",\n \"required\": [\"type\"],\n \"additionalProperties\": false,\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"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).\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ],\n \"x-classification\": \"P0\"\n },\n \"payload\": {\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"seq\": {\n \"type\": \"integer\",\n \"minimum\": 0,\n \"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.\",\n \"x-classification\": \"P0\"\n },\n \"ts\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"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.\",\n \"x-classification\": \"P0\"\n }\n },\n \"$defs\": {\n \"MessageType\": {\n \"type\": \"string\",\n \"enum\": [\n \"auth\",\n \"auth_ok\",\n \"vehicle_update\",\n \"drive_started\",\n \"drive_ended\",\n \"connectivity\",\n \"heartbeat\",\n \"error\",\n \"subscribe\",\n \"unsubscribe\",\n \"ping\",\n \"pong\"\n ]\n }\n }\n}\n","// Canonical wire-protocol surface for MyRoboTaxi consumers.\n//\n// Two entrypoints:\n// - `import { VehicleState } from '@myrobotaxi/contracts'` — pre-generated\n// TypeScript types from the schemas/ folder. Tree-shakes (no runtime\n// payload).\n// - `import { schemas } from '@myrobotaxi/contracts'` — parsed JSON Schema\n// objects, suitable for Ajv / runtime validation. Pulls the JSON into the\n// consumer bundle; use the `./types` subpath if you only need types.\n//\n// The Go telemetry server and the future Swift SDK consume the raw JSON files\n// under `./schemas/*.json` directly via the package's `exports` map.\n\nexport * from './generated/index.js';\n\nimport vehicleState from '../schemas/vehicle-state.schema.json' with { type: 'json' };\nimport wsMessages from '../schemas/ws-messages.schema.json' with { type: 'json' };\nimport wsEnvelope from '../schemas/ws-envelope.schema.json' with { type: 'json' };\n\nexport const schemas = {\n vehicleState,\n wsMessages,\n wsEnvelope,\n} as const;\n"]}
package/dist/types.d.cts CHANGED
@@ -467,11 +467,11 @@ interface HeartbeatPayload {
467
467
  */
468
468
  interface ErrorPayload {
469
469
  /**
470
- * Stable typed error code from the catalog in websocket-protocol.md §6.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md §6.1.1 and §7.1.
470
+ * Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md §4.1.1, §4.1.1.a; ws catalog websocket-protocol.md §6.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md §4.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports — they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md §6.1.1 / §7.1.
471
471
  *
472
472
  * @classification "P0"
473
473
  */
474
- code: 'auth_failed' | 'auth_timeout' | 'permission_denied' | 'vehicle_not_owned' | 'rate_limited' | 'internal_error' | 'snapshot_required';
474
+ code: 'auth_failed' | 'auth_timeout' | 'permission_denied' | 'vehicle_not_owned' | 'rate_limited' | 'internal_error' | 'snapshot_required' | 'not_found' | 'invalid_request';
475
475
  /**
476
476
  * Human-readable description for logs and developer tooling. P0 BUT MUST NOT contain P1 values (no GPS, no addresses, no tokens, no emails, no VINs unless redacted to ***XXXX). Per data-classification.md §2.1-2.2, error message construction sites MUST use opaque IDs only.
477
477
  *
@@ -479,11 +479,11 @@ interface ErrorPayload {
479
479
  */
480
480
  message: string;
481
481
  /**
482
- * Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: "device_cap"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md §6.1.1 for the full device_cap UX contract.
482
+ * Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: "device_cap"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code see websocket-protocol.md §6.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md §7.6 / §7.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window — the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.
483
483
  *
484
484
  * @classification "P0"
485
485
  */
486
- subCode?: 'device_cap';
486
+ subCode?: 'device_cap' | 'reauth_required';
487
487
  }
488
488
  /**
489
489
  * PLANNED — NOT accepted by the server today. Client->server request to begin or resume streaming a specific vehicle. Today the server implicitly subscribes the client to ALL vehicles owned by the authenticated user as part of the auth handshake (handler.go authenticateClient -> Authenticator.GetUserVehicles). Adding explicit subscribe/unsubscribe is tracked as divergence DV-07 in websocket-protocol.md §10. `sinceSeq` snapshot-resume depends on divergence DV-02 (envelope `seq`) landing first.
package/dist/types.d.ts CHANGED
@@ -467,11 +467,11 @@ interface HeartbeatPayload {
467
467
  */
468
468
  interface ErrorPayload {
469
469
  /**
470
- * Stable typed error code from the catalog in websocket-protocol.md §6.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md §6.1.1 and §7.1.
470
+ * Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md §4.1.1, §4.1.1.a; ws catalog websocket-protocol.md §6.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md §4.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports — they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md §6.1.1 / §7.1.
471
471
  *
472
472
  * @classification "P0"
473
473
  */
474
- code: 'auth_failed' | 'auth_timeout' | 'permission_denied' | 'vehicle_not_owned' | 'rate_limited' | 'internal_error' | 'snapshot_required';
474
+ code: 'auth_failed' | 'auth_timeout' | 'permission_denied' | 'vehicle_not_owned' | 'rate_limited' | 'internal_error' | 'snapshot_required' | 'not_found' | 'invalid_request';
475
475
  /**
476
476
  * Human-readable description for logs and developer tooling. P0 BUT MUST NOT contain P1 values (no GPS, no addresses, no tokens, no emails, no VINs unless redacted to ***XXXX). Per data-classification.md §2.1-2.2, error message construction sites MUST use opaque IDs only.
477
477
  *
@@ -479,11 +479,11 @@ interface ErrorPayload {
479
479
  */
480
480
  message: string;
481
481
  /**
482
- * Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: "device_cap"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md §6.1.1 for the full device_cap UX contract.
482
+ * Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: "device_cap"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code see websocket-protocol.md §6.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md §7.6 / §7.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window — the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.
483
483
  *
484
484
  * @classification "P0"
485
485
  */
486
- subCode?: 'device_cap';
486
+ subCode?: 'device_cap' | 'reauth_required';
487
487
  }
488
488
  /**
489
489
  * PLANNED — NOT accepted by the server today. Client->server request to begin or resume streaming a specific vehicle. Today the server implicitly subscribes the client to ALL vehicles owned by the authenticated user as part of the auth handshake (handler.go authenticateClient -> Authenticator.GetUserVehicles). Adding explicit subscribe/unsubscribe is tracked as divergence DV-07 in websocket-protocol.md §10. `sinceSeq` snapshot-resume depends on divergence DV-02 (envelope `seq`) landing first.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myrobotaxi/contracts",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
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
5
  "license": "MIT",
6
6
  "repository": {
@@ -213,7 +213,7 @@
213
213
  "properties": {
214
214
  "code": {
215
215
  "type": "string",
216
- "description": "Stable typed error code from the catalog in websocket-protocol.md §6.1.1. Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. `auth_failed` and `auth_timeout` are emitted today; the remaining values are reserved for PLANNED server emission tracked by divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10. `rate_limited` has two carriers: (a) an HTTP 429 response on the WS upgrade (per-IP cap breach, pre-auth) and (b) an `error` frame paired with WebSocket close code 4003 Server Overload (per-user cap breach, post-auth). The SDK MUST treat both carriers as the same typed error and apply the extended reconnect backoff documented in websocket-protocol.md §6.1.1 and §7.1.",
216
+ "description": "Stable typed error code. Shared enum across the WebSocket and REST transports so the SDK's CoreError is a single union (rest-api.md §4.1.1, §4.1.1.a; ws catalog websocket-protocol.md §6.1.1). Consumer SDKs map these to typed errors per FR-7.1 and MUST NOT string-match on the sibling `message` field. WS emits today: `auth_failed`, `auth_timeout`; the remaining WS values are PLANNED (divergences DV-02 / DV-07 / DV-08 in websocket-protocol.md §10). `not_found` and `invalid_request` are REST-only on the wire (the WS path enforces ownership via silent filtering and has no structured request bodies, rest-api.md §4.1.1.a) but are members of this shared enum by mandate so the SDK union is one enum across transports — they are NOT a WS drift (DV-20 enum slice). `service_unavailable` is intentionally NOT in this enum: it is REST-only and the WS 503 analogue is a close code, not a typed frame (rest-api.md:258); the SDK declares it as a REST-only variant on its side. `rate_limited` has two carriers: (a) HTTP 429 on the WS upgrade (per-IP, pre-auth) and (b) an `error` frame paired with WS close 4003 (per-user, post-auth) the SDK treats both as the same typed error with the extended backoff in websocket-protocol.md §6.1.1 / §7.1.",
217
217
  "enum": [
218
218
  "auth_failed",
219
219
  "auth_timeout",
@@ -221,7 +221,9 @@
221
221
  "vehicle_not_owned",
222
222
  "rate_limited",
223
223
  "internal_error",
224
- "snapshot_required"
224
+ "snapshot_required",
225
+ "not_found",
226
+ "invalid_request"
225
227
  ],
226
228
  "x-classification": "P0"
227
229
  },
@@ -232,8 +234,8 @@
232
234
  },
233
235
  "subCode": {
234
236
  "type": "string",
235
- "description": "Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. Currently defined only for `rate_limited`: when the server rejects a connection because it breached the per-user cap (post-auth, close 4003), the error frame MUST carry `subCode: \"device_cap\"` so SDKs can surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast. Per-IP breaches (HTTP 429 on upgrade) do NOT carry a sub-code because the SDK cannot distinguish NAT-mate floods from user intent. See websocket-protocol.md §6.1.1 for the full device_cap UX contract.",
236
- "enum": ["device_cap"],
237
+ "description": "Optional typed sub-code for branching consumer-visible UI when the primary code is ambiguous across carriers. v1 enum: `device_cap` (WS-only) and `reauth_required` (REST-only). `device_cap`: WS server rejected a connection because it breached the per-user cap (post-auth, close 4003); the error frame carries `subCode: \"device_cap\"` so SDKs surface an actionable 'Too many devices signed in' message instead of a generic rate-limit toast (per-IP HTTP 429 breaches carry no sub-code see websocket-protocol.md §6.1.1). `reauth_required`: REST-only, emitted with `code: auth_failed` by rest-api.md §7.6 / §7.7 when the bearer token is valid but the most recent fresh OAuth sign-in is older than the recent-auth window — the SDK MUST surface this to the consumer's auth layer to trigger a fresh interactive sign-in and MUST NOT silently retry via getToken() (a silent refresh cannot advance the `auth_time` claim). Declared on this shared enum for single-union SDK typing; the WS transport never emits `reauth_required`.",
238
+ "enum": ["device_cap", "reauth_required"],
237
239
  "x-classification": "P0"
238
240
  }
239
241
  }