# Data Walker — Firmware Manifest API Contract

**Purpose:** Stable interface between the ESP32 firmware OTA client and the Vercel-hosted web app. Lock this down early so both sides can be built in parallel.

**Base URL:** `https://datawalker.app` *(replace with actual production domain)*

**Format:** JSON over HTTPS. UTF-8. No authentication required for read endpoints (firmware downloads are public).

---

## Endpoints

### `GET /api/firmware/latest`

Returns the manifest for the latest non-deprecated firmware release.

**Response 200 (application/json):**
```json
{
  "version": "v202605221130",
  "releaseDate": "2026-05-22T11:30:00-04:00",
  "binaryUrl": "https://datawalker.app/firmware/data-walker-v202605221130.bin",
  "binarySize": 1287456,
  "binarySha256": "3a7f9c8e2b1d4a5f6c0e9d8b7a6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c",
  "minSourceVersion": "v202510020734",
  "mandatory": false,
  "title": "Daily Calibration Feature",
  "summary": "Adds boot-time daily calibration walk with bidirectional 3 m measurement and three-gate validation.",
  "releaseNotesUrl": "https://datawalker.app/releases/v202605221130",
  "deprecated": false
}
```

**Headers:**
```
Content-Type: application/json
Cache-Control: public, max-age=300
Access-Control-Allow-Origin: *
```

---

### `GET /api/firmware/check?current=v202510020734`

Lightweight "is there an update?" call. Preferred over `latest` when the device just wants to know if it needs to do anything.

**Query parameters:**
- `current` (required) — the version string currently running on the device, in `vYYYYMMDDHHMM` format.

**Response 200, update available:**
```json
{
  "hasUpdate": true,
  "manifest": { ...same shape as /latest response... }
}
```

**Response 200, no update available:**
```json
{
  "hasUpdate": false
}
```

**Response 400 (malformed `current`):**
```json
{
  "error": "invalid_version",
  "message": "Expected format vYYYYMMDDHHMM"
}
```

---

### `GET /api/firmware/[version]`

Returns the manifest for a specific historical version. Useful for rollback or pinning.

**Path parameter:** `version` — version string like `v202510020734`.

**Response 200:** same shape as `/latest`.

**Response 404:**
```json
{
  "error": "not_found",
  "message": "No firmware manifest for version v202401010000"
}
```

---

### `GET /firmware/data-walker-vYYYYMMDDHHMM.bin`

Direct binary download. Served as static asset from Vercel's CDN.

**Response 200:**
- Content-Type: `application/octet-stream`
- Content-Length: bytes (matches `binarySize` in manifest)
- ETag and Last-Modified headers for caching

The firmware client MUST verify `binarySha256` from the manifest against the SHA-256 of the downloaded bytes before flashing. Mismatch = abort and keep current firmware.

---

## Field reference

| Field | Type | Notes |
|---|---|---|
| `version` | string | `vYYYYMMDDHHMM`. Lexically sortable — `"v202605221130" > "v202510020734"` works as a string compare, no parsing needed. |
| `releaseDate` | string | ISO 8601 with timezone. Apollo Beach local time recommended for consistency. |
| `binaryUrl` | string | Absolute HTTPS URL. Must be on same origin or CORS-allowed. |
| `binarySize` | integer | Bytes. Used for download progress display on device. Sanity-check against actual Content-Length. |
| `binarySha256` | string | 64-char lowercase hex. MUST verify before flashing. |
| `minSourceVersion` | string or null | If non-null, devices on older versions should chain through this version first. Null = upgradeable from any version. |
| `mandatory` | boolean | If true, device UI should not present a Skip button. Reserve for security-critical fixes only. |
| `title` | string | Max 60 chars. Shown on device update prompt. |
| `summary` | string | Max 200 chars. Shown on device update prompt below title. |
| `releaseNotesUrl` | string | Full release notes web page. Shown as QR code on device for clinician to scan. |
| `deprecated` | boolean | If true, this version is no longer recommended. Devices running a deprecated version should see a persistent warning. |

---

## Firmware-side flow (reference)

```
On boot, after WiFi connection succeeds:
  GET /api/firmware/check?current={VERSION_NUMBER}
  
  if response.hasUpdate == false:
    proceed to normal operation
    
  if response.hasUpdate == true:
    show update prompt screen:
      - Title and summary from manifest
      - "Update Now" / "Later" / "Never" buttons
      - QR code for releaseNotesUrl
      - If mandatory: no Skip option
    
    on "Update Now":
      - Connect HTTPClient to binaryUrl (verify HTTPS cert)
      - Stream to Update.h, computing SHA-256 incrementally
      - On EOF: verify SHA-256 matches manifest.binarySha256
      - If match: Update.end() and ESP.restart()
      - If mismatch: Update.abort(), display error, keep current firmware
    
    on "Later":
      - Skip for this session, prompt again next boot
    
    on "Never":
      - Store dismissed version to NVS
      - Don't prompt again for this specific version
      - Still prompt when an even newer version drops
```

---

## Versioning rules

- **Lexical comparison works** as long as you stick to `vYYYYMMDDHHMM`. `"v202605221130" > "v202510020734"` is true via simple string compare. No need for `semver`.
- **Never reuse a version string.** Even for hotfixes — bump the minute.
- **`minSourceVersion` is for the rare case** where intermediate state migration is required (e.g., NVS schema change that requires running an interim version first). Use sparingly.
- **`deprecated: true` is a soft signal.** It does not force a downgrade or block boot; it warns the user. Reserve for versions with known clinical-data-affecting bugs (like the pre-v202510020734 double-trigger bug).

---

## Security considerations

**Current threat model is low-to-moderate.** Devices are in clinical settings, behind clinic WiFi. The realistic threats are accidental — bad binary, MITM via misconfigured router, downgrade attack.

Built-in protections in this design:
1. **HTTPS only.** Vercel forces it. Firmware must validate cert (no `setInsecure()` in production).
2. **SHA-256 verification.** Manifest carries the hash; firmware verifies bytes before flashing. Defeats both bad-binary and basic MITM.
3. **ESP32 dual-partition rollback.** If a flashed binary fails to boot, ESP32 reverts to previous partition automatically. Built-in to `Update.h`.
4. **`mandatory: false` by default.** Clinician has the option to defer. Important for clinics with maintenance windows.

**Not in this design (defer to Phase D+):**
- Firmware signing (requires secure boot configuration on ESP32 — non-trivial)
- Per-device authentication (devices currently anonymous)
- Encrypted firmware payload (HTTPS in transit is sufficient for current threat model)

If the device ever moves from wellness-monitoring to FDA-regulated medical device territory, firmware signing becomes mandatory and this contract needs to add a `binarySignature` field with ECDSA over the binary.

---

## Example: seed `firmware-manifest.json`

This file lives in the Next.js project root and is read by the API routes. Replace the SHA-256 hashes with actual values after building each binary.

```json
{
  "manifests": [
    {
      "version": "v202605221130",
      "releaseDate": "2026-05-22T11:30:00-04:00",
      "binaryUrl": "https://datawalker.app/firmware/data-walker-v202605221130.bin",
      "binarySize": 1287456,
      "binarySha256": "PLACEHOLDER_HASH_REPLACE_AFTER_BUILD_64_HEX_CHARS_REQUIRED_HERE_XXXX",
      "minSourceVersion": "v202510020734",
      "mandatory": false,
      "title": "Daily Calibration Feature",
      "summary": "Adds boot-time daily calibration walk with bidirectional 3 m measurement.",
      "releaseNotesUrl": "https://datawalker.app/releases/v202605221130",
      "deprecated": false
    },
    {
      "version": "v202510020734",
      "releaseDate": "2025-10-02T07:34:00-04:00",
      "binaryUrl": "https://datawalker.app/firmware/data-walker-v202510020734.bin",
      "binarySize": 1245312,
      "binarySha256": "PLACEHOLDER_HASH_REPLACE_AFTER_BUILD_64_HEX_CHARS_REQUIRED_HERE_YYYY",
      "minSourceVersion": null,
      "mandatory": false,
      "title": "Hall Sensor Edge-Detection Fix",
      "summary": "Resolves intermittent double-triggering causing ~47% distance over-reading.",
      "releaseNotesUrl": "https://datawalker.app/releases/v202510020734",
      "deprecated": false
    }
  ]
}
```

To generate SHA-256 of a built binary:
```bash
sha256sum data-walker-v202605221130.bin
# or on macOS:
shasum -a 256 data-walker-v202605221130.bin
```

---

*End of contract. Treat this as the source of truth for both sides of the OTA system. If something needs to change, change this doc first, then bump both implementations.*
