@myrobotaxi/contracts 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONTRIBUTING.md CHANGED
@@ -48,7 +48,10 @@ If you add a new `x-*` keyword to a schema, add it to the `ANNOTATIONS` array in
48
48
 
49
49
  The repo doubles as a Swift package (`Package.swift` at the root, product `MyRobotaxiContracts`). Swift types are generated from the same `schemas/*.json` source of truth:
50
50
 
51
- - **Generator**: `scripts/codegen-swift.mjs` — a small custom Node script (zero npm dependencies; runs without `npm ci`). One `Codable`/`Equatable`/`Sendable` struct per root schema object and per `$defs` entry (websocket-protocol.md §9.2); string enums become Swift `enum ...: String` with wire raw values.
51
+ - **Generator**: `scripts/codegen-swift.mjs` — a small custom Node script (zero npm dependencies; runs without `npm ci`). One `Codable`/`Equatable`/`Sendable` struct per root schema object and per `$defs` entry (websocket-protocol.md §9.2); string enums become **decode-tolerant** Swift enums — a `RawRepresentable` enum with the known cases plus a catch-all `case unrecognized(String)` (MYR-195). Unknown wire values decode to `.unrecognized(raw)` instead of throwing (so a newer contracts version adding an enum member never hard-fails an older installed client's whole-frame decode) and re-encode byte-identically. The generator emits a small suite of unit tests (`src/codegen-swift.test.ts`) alongside the Swift `swift test` round-trip tests.
52
+ - **Loud codegen errors**: to prevent silent wire drift, `scripts/codegen-swift.mjs` hard-errors (naming the schema path) rather than degrading when it hits a construct it can't faithfully map:
53
+ - a property enum whose members match a `$defs` enum but in a **different order** (would otherwise silently mint a near-duplicate nested type) — reorder the property enum to match the `$defs` order, or replace it with a `$ref`;
54
+ - schema composition (`oneOf` / `anyOf` / `allOf`) and **non-string enums** (would otherwise silently degrade to `JSONValue` / `Int`) — model the position as a concrete typed object, a string enum, or an explicit open `JSONValue`.
52
55
  - **Output**: `Sources/MyRobotaxiContracts/Generated/*.swift` — **committed**, never hand-edited (same rule as `src/generated/`). Regeneration is deterministic: emission order follows the schema JSON, so re-running on an unchanged schema is a no-op diff.
53
56
  - **Hand-written support code**: `Sources/MyRobotaxiContracts/JSONValue.swift` (arbitrary-JSON enum used for `WebSocketEnvelope.payload` and `VehicleUpdatePayload.fields`) is NOT generated and may be edited.
54
57
  - **Regenerate**: `npm run codegen:swift` after any schema change, and commit the result alongside the regenerated TypeScript.
package/README.md CHANGED
@@ -64,7 +64,27 @@ let envelope = try JSONDecoder().decode(WebSocketEnvelope.self, from: frameData)
64
64
  if envelope.type == .vehicleUpdate { /* decode VehicleUpdatePayload from envelope.payload */ }
65
65
  ```
66
66
 
67
- The same custom annotations are folded into the generated SwiftDoc comments, so `grep '@classification "P1"'` works inside the checked-out package too. Wire enums (`status`, `chargeState`, `MessageType`, error codes, …) generate as Swift `enum ...: String` with wire raw values; nullable / non-required schema fields are Swift optionals. The intentionally-open wire positions — `WebSocketEnvelope.payload` and `VehicleUpdatePayload.fields` — are typed via the hand-written `JSONValue` enum, which preserves explicit JSON `null`s (required for atomic-group clears per NFR-3.9).
67
+ The same custom annotations are folded into the generated SwiftDoc comments, so `grep '@classification "P1"'` works inside the checked-out package too. Wire enums (`status`, `chargeState`, `MessageType`, error codes, …) generate as **decode-tolerant** Swift enums: a `RawRepresentable` enum with the known cases plus a catch-all `case unrecognized(String)` (MYR-195). Nullable / non-required schema fields are Swift optionals. The intentionally-open wire positions — `WebSocketEnvelope.payload` and `VehicleUpdatePayload.fields` — are typed via the hand-written `JSONValue` enum, which preserves explicit JSON `null`s (required for atomic-group clears per NFR-3.9).
68
+
69
+ ### Decode-tolerant wire enums (forward-compat for installed clients)
70
+
71
+ A plain `enum: String` (Swift's synthesized `Codable`) throws on the first unrecognized value, which would make **one** new enum member shipped in a newer contracts version hard-fail the *entire* frame/snapshot decode on a not-yet-updated iOS app. To keep installed clients forward-compatible, each string wire enum is emitted as:
72
+
73
+ ```swift
74
+ public enum Status: RawRepresentable, Codable, Hashable, Sendable {
75
+ case driving
76
+ case parked
77
+ // …known cases…
78
+ /// A wire value not known to this build of the contracts package.
79
+ case unrecognized(String)
80
+ // init(rawValue:) / rawValue / init(from:) / encode(to:) map every string,
81
+ // falling back to .unrecognized(raw) instead of throwing.
82
+ }
83
+ ```
84
+
85
+ - **Unknown values never throw** — they decode to `.unrecognized(raw)`, so the surrounding `VehicleState` / `WebSocketEnvelope` still decodes.
86
+ - **Round-trips byte-identically** — the raw string is preserved and re-encoded verbatim, so a relaying client never corrupts a value it doesn't understand.
87
+ - **Ergonomic for consumers** — `switch status { case .driving: …; case .unrecognized(let raw): … }` stays exhaustive and compiler-checked; `== .parked` and `.rawValue` work as before. The catch-all is named `unrecognized` (not `unknown`) because `ChargeState` already has a known member whose wire value is `"Unknown"`.
68
88
 
69
89
  ## Tool choice (Swift): custom generator
70
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myrobotaxi/contracts",
3
- "version": "0.4.0",
3
+ "version": "0.5.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": {