{
  "_comment": "Canonical facts for the /plugin emulator. SOURCE OF TRUTH: the securaCV firmware + docs/design/usb_onboard.md + docs/hardware/grove_vision_ai_v2_guide.md. This object is mirrored VERBATIM as `var SPEC` in js/plugin.js; any drift between the two — or against the firmware values cited in `provenance` — fails tests/plugin-facts.test.mjs in CI. Do not edit one copy without the other.",
  "helpUrlBase": "https://securacv.com/canary",
  "reason": "onboard",
  "exampleDeviceId": "canary-7fA3",
  "interfaces": ["read-only drive", "keyboard", "serial console"],
  "drive": { "name": "CANARY-EVIDENCE", "readOnly": true, "folders": ["WITNESS", "HEALTH", "CHAIN", "VAULT"] },
  "startHere": { "any": "START-HERE.html", "windows": "Open-Canary-Help.url", "macos": "Open-Canary-Help.webloc" },
  "defaultLaunchMethod": "manual",
  "os": {
    "windows": { "browser": "Microsoft Edge", "hotkey": "Win + R", "launcher": "Run", "serial": "a COM port (e.g. COM7)", "monitor": "Device Manager / a serial monitor" },
    "macos": { "browser": "Safari", "hotkey": "⌘ Space", "launcher": "Spotlight", "serial": "/dev/tty.usbmodem…", "monitor": "Terminal (screen) or a serial app" },
    "linux": { "browser": "Firefox", "hotkey": "Super", "launcher": "Activities search", "serial": "/dev/ttyACM0", "monitor": "a serial monitor (screen / minicom)" },
    "android": { "browser": "Chrome", "hotkey": "a USB keyboard", "launcher": "the address bar", "serial": "a USB serial device", "monitor": "a USB-serial terminal app" },
    "iphone": { "browser": "Safari", "hotkey": "a USB keyboard", "launcher": "the address bar", "serial": "—", "monitor": "limited — use a computer" }
  },
  "boards": {
    "canary-s3": { "name": "Canary · ESP32-S3", "emoji": "🐤", "mcu": "XIAO ESP32-S3 Sense", "usb": "otg", "flow": "onboard", "hasSD": true, "ports": ["controller"], "blurb": "The base witness board — full USB onboarding." },
    "canary-wap": { "name": "Canary WAP", "emoji": "📡", "mcu": "XIAO ESP32-S3 Sense", "usb": "otg", "flow": "evidence", "hasSD": true, "ports": ["controller"], "blurb": "Same S3 silicon; ships the read-only evidence drive." },
    "canary-vision": { "name": "Canary Vision", "emoji": "👁️", "mcu": "ESP32-C3 host + Grove Vision AI V2", "usb": "dual", "flow": "vision", "hasSD": false, "ports": ["firmware", "model"], "blurb": "Two boards, two USB-C ports — controller vs camera AI." },
    "canary-sense": { "name": "Canary Sense", "emoji": "📶", "mcu": "XIAO ESP32-C6 · MR60BHA2 radar", "usb": "serial", "flow": "serial", "hasSD": false, "ports": ["controller"], "blurb": "Radar presence board — USB-Serial console only." }
  },
  "flows": {
    "onboard": "Read-only drive + keyboard: START-HERE opens the help page, or a BOOT tap types it.",
    "evidence": "Read-only evidence drive over USB — browse your sealed records. No keyboard / START-HERE.",
    "serial": "USB-Serial/JTAG console only. No drive, no keyboard. Open a serial monitor at 115200; visit the help page yourself.",
    "vision-model": "Talks to the Grove Vision AI camera co-processor — load or preview the model with SenseCraft AI."
  },
  "vision": {
    "coprocessor": "Grove Vision AI V2 (Himax WiseEye2 HX6538 + Ethos-U55 NPU)",
    "link": "I2C 0x62 over the Grove/XIAO connector — only boxes and scores cross the wire, never pixels",
    "model": "person-detection",
    "tool": "SenseCraft AI",
    "rule": "Model work → module port. Firmware work → XIAO port. Neither port can do the other's job — the module port can't see the ESP32, and the XIAO port can't reach the Himax flash.",
    "orientation": "When a XIAO is stacked, both USB-C connectors must face the same way — a backwards seat feeds power into GPIO and can damage a board.",
    "ports": {
      "firmware": { "label": "XIAO / host port", "role": "FIRMWARE PORT", "chip": "ESP32-C3", "connects": "the SecuraCV witness controller (the ESP32)", "usb": "native USB-Serial — no driver", "flow": "serial", "does": "flash the canary-vision firmware and open its serial console" },
      "model": { "label": "module port", "role": "MODEL PORT", "chip": "Himax WiseEye2 HX6538", "connects": "the Grove Vision AI V2 camera co-processor — NOT the ESP32", "usb": "CH343 USB-serial — needs the CH343 driver on Windows/macOS", "flow": "vision-model", "does": "load / preview the AI model with SenseCraft AI (SSCMA)" }
    }
  },
  "selftest": {
    "bootRunsIt": true,
    "healthPerProbe": 10,
    "probes": [
      { "id": "nvs_rw", "label": "NVS read/write", "checks": "settings store round-trips" },
      { "id": "heap_ok", "label": "Free heap", "checks": "> 50 KB free" },
      { "id": "psram_ok", "label": "PSRAM", "checks": "external RAM present" },
      { "id": "crypto_ok", "label": "Device keys", "checks": "identity initialized" },
      { "id": "sd_card", "label": "SD card", "checks": "card present (or n/a)" },
      { "id": "wifi_ok", "label": "Wi-Fi radio", "checks": "radio not off" },
      { "id": "temp_range", "label": "Die temp", "checks": "−20…85 °C" },
      { "id": "uptime_ok", "label": "Uptime clock", "checks": "millis() advances" },
      { "id": "watchdog", "label": "Watchdog", "checks": "configured" },
      { "id": "chain_ok", "label": "Witness chain", "checks": "crypto-healthy" }
    ]
  },
  "featureTests": [
    { "key": "peek", "emoji": "📷", "name": "Camera PEEK", "how": "GET /api/peek/snapshot", "blurb": "Capture one frame on demand — the fastest camera test." },
    { "key": "mic", "emoji": "🎤", "name": "Mic self-test", "how": "POST /api/audio/test/start", "blurb": "Beep + level meter; suppressed from Home Assistant so it won't trip automations." },
    { "key": "csi", "emoji": "〰️", "name": "Motion & breathing", "how": "GET /api/sensing", "blurb": "Wi-Fi CSI presence / micro-motion." },
    { "key": "gps", "emoji": "📍", "name": "GPS fix", "how": "console: g", "blurb": "Coarsened position + satellite count." },
    { "key": "thermal", "emoji": "🌡️", "name": "Thermal", "how": "GET /api/thermal", "blurb": "Die temperature + passive watchdog history." },
    { "key": "battery", "emoji": "🔋", "name": "Battery", "how": "console: b", "blurb": "State of charge + charge state." },
    { "key": "mesh", "emoji": "📡", "name": "Fleet mesh", "how": "GET /api/mesh", "blurb": "Peers + identify pulse across your Canaries." }
  ],
  "bluetooth": {
    "fullBuildOnly": true,
    "tool": "nRF Connect",
    "why": "BLE ships only in the [env:full] build — the NimBLE image (~2.7 MB) doesn't fit the dev/release OTA A/B slots, and the stack needs ~96 KB free internal RAM (a ~30 KB contiguous block PSRAM can't host).",
    "stages": [
      { "id": "not-built", "label": "Not compiled in", "hint": "BLE is in the [env:full] build only — too big for the shipping OTA slots.", "terminal": true },
      { "id": "stack", "label": "NimBLE stack up", "hint": "NimBLEDevice::init() succeeded — needs ~96 KB free internal RAM or it fails/panics." },
      { "id": "service", "label": "GATT service", "hint": "The SecuraCV status service (battery, health, chain) registered." },
      { "id": "advertising", "label": "Advertising", "hint": "Broadcasting — connect a phone (nRF Connect) to confirm the link." },
      { "id": "connected", "label": "Phone connected", "hint": "A central connected; read the health/battery characteristic." },
      { "id": "verified", "label": "Verified", "hint": "Advertised, connected, and a characteristic exchanged — end to end." }
    ]
  },
  "builds": {
    "_comment": "Mirror of firmware/build_matrix.json (securaCV repo), which is itself pinned to canary/platformio.ini + canary_config.h by scripts/lint_build_matrix.py. Drives the /checkup firmware-type selector: pick a build product (+ level, where the S3 canary has dev/release/full) and the self-test / feature tiles / BLE ladder show exactly what that image ships. Only the `canary` product has levels; the others are single-level board builds.",
    "downloadBase": "https://github.com/kmay89/securaCV/releases/latest",
    "flasherUrl": "https://kmay89.github.io/securaCV/canary-local/flash.html",
    "flasherProductPrefix": "securacv-",
    "recommended": {
      "product": "canary",
      "level": "release",
      "why": "The all-rounder witness — full sensing plus the Home Assistant bridge — on the verified S3, shipping channel. If you're not sure, this is the one."
    },
    "featureCatalog": {
      "selftest":  "Boot self-test",
      "peek":      "Camera PEEK",
      "mic":       "Mic self-test",
      "csi":       "Motion & breathing",
      "gps":       "GPS fix",
      "thermal":   "Thermal",
      "battery":   "Battery",
      "mesh":      "Fleet mesh",
      "bluetooth": "Bluetooth (BLE)"
    },
    "levels": {
      "dev":     "Debug channel — verbose logs, all sensing, BLE off. Official core, 1.9 MB OTA slots.",
      "release": "Shipping channel — quiet, sensing on, BLE off. What release devices pull.",
      "full":    "Superset — adds BLE + mesh. Needs core 3.x + the 8 MB partition, so it's its own image."
    },
    "products": [
      {
        "id": "canary", "name": "Canary · ESP32-S3", "emoji": "🐤", "mcu": "XIAO ESP32-S3 Sense",
        "tier": "verified", "flow": "onboard", "hasLevels": true,
        "manifest": "manifest-canary.json",
        "levels": {
          "dev":     { "env": "dev",        "features": ["selftest", "peek", "mic", "csi", "gps", "thermal", "battery"] },
          "release": { "env": "release_ha", "features": ["selftest", "peek", "mic", "csi", "gps", "thermal", "battery"] },
          "full":    { "env": "full",       "features": ["selftest", "peek", "mic", "csi", "gps", "thermal", "battery", "mesh", "bluetooth"] }
        }
      },
      {
        "id": "canary-wap", "name": "Canary WAP", "emoji": "📡", "mcu": "XIAO ESP32-S3 Sense",
        "tier": "verified", "flow": "evidence", "hasLevels": false,
        "manifest": "manifest-canary-wap.json",
        "build": { "env": "canary-wap-default", "features": ["selftest", "peek", "mic", "csi", "gps", "thermal", "battery", "mesh", "bluetooth"] }
      },
      {
        "id": "canary-vision", "name": "Canary Vision", "emoji": "👁️", "mcu": "ESP32-C3 + Grove Vision AI V2",
        "tier": "verified", "flow": "vision", "hasLevels": false,
        "manifest": "manifest-canary-vision.json",
        "build": { "env": "canary-vision-xiao-c3", "features": ["selftest"] },
        "note": "Camera lives on the Himax co-processor (SenseCraft), not the ESP — the canary camera/SD/CSI tiles don't apply. Serial console only."
      },
      {
        "id": "canary-sense", "name": "Canary Sense", "emoji": "📶", "mcu": "XIAO ESP32-C6 · MR60BHA2 radar",
        "tier": "compile-tested", "flow": "serial", "hasLevels": false,
        "manifest": "manifest-canary-sense.json",
        "build": { "env": "canary-sense-default", "features": ["selftest", "battery", "thermal"] },
        "note": "60 GHz radar presence. No camera / SD / CSI / BLE. Serial console only; bench validation pending."
      }
    ]
  },
  "serialCommands": {
    "_comment": "Mirror of the firmware serial console help menu (firmware/canary/src/main.cpp handle_serial_commands()). `plain` is for a regular person, `dev` is the technical detail; `audience` groups them (everyday / dev / delightful). Drives the /canary tool explainer so the on-device commands can't drift from what the site claims.",
    "baud": 115200,
    "list": [
      { "key": "l", "emoji": "🎨", "name": "Its ID card, as art", "audience": "delightful",
        "plain": "It draws its own ID card — with its secret identity turned into a little picture you can recognise at a glance.",
        "dev": "Trust card: device id, firmware+git, chain head, self-test score, and the Ed25519 public key rendered as drunken-bishop randomart." },
      { "key": "a", "emoji": "✨", "name": "Watch it wake up", "audience": "delightful",
        "plain": "Watch it come alive and check itself — each test lights up green as it passes, live in front of you.",
        "dev": "Animated reveal of the 10-probe self-test ([..]→[OK]/[!!]) that settles into the identity card. ANSI in-place repaint; ASCII-safe fallback." },
      { "key": "t", "emoji": "✅", "name": "Run every check", "audience": "everyday",
        "plain": "One key runs every self-check and grades the device's health from 0 to 100%.",
        "dev": "diag_run_selftest() + per-feature health + the Bluetooth bring-up ladder." },
      { "key": "i", "emoji": "🪪", "name": "Who are you?", "audience": "everyday",
        "plain": "Ask the device who it is — its name and its public key.",
        "dev": "Device ID + 32-byte Ed25519 public key (hex)." },
      { "key": "s", "emoji": "📟", "name": "How are you doing?", "audience": "everyday",
        "plain": "A quick check-in: what it's up to right now.",
        "dev": "Uptime, network/AP, storage, sensors, state machine." },
      { "key": "w", "emoji": "🛡️", "name": "Has anyone touched it?", "audience": "everyday",
        "plain": "Read-only tamper log — has anything tried to mess with it?",
        "dev": "Passive tamper counters + last-seen per detector (enclosure/touch, temp-drift, vision)." },
      { "key": "b", "emoji": "🔋", "name": "Battery", "audience": "everyday",
        "plain": "Charge level and whether it's plugged in.",
        "dev": "State of charge + charge state (FEATURE_POWER_MONITOR)." },
      { "key": "g", "emoji": "📍", "name": "Where am I?", "audience": "everyday",
        "plain": "Roughly where it thinks it is (deliberately coarsened — never precise).",
        "dev": "Coarsened GNSS fix + satellite count." },
      { "key": "v", "emoji": "🩹", "name": "Recovery guide", "audience": "everyday",
        "plain": "Step-by-step help to get the device back to healthy.",
        "dev": "Prints the SD-log recovery walkthrough." },
      { "key": "k", "emoji": "🔓", "name": "Unseal guide", "audience": "everyday",
        "plain": "How to open your sealed evidence on your own computer.",
        "dev": "Prints the .svlt unseal walkthrough (your private key, off-device)." },
      { "key": "u", "emoji": "🌐", "name": "Open this page", "audience": "everyday",
        "plain": "Re-open this help page (announces a preview, then waits for a BOOT press).",
        "dev": "Consented HID help-launch; the only URL it can ever type." },
      { "key": "c", "emoji": "🔏", "name": "Prove it's really this device", "audience": "dev",
        "plain": "Cryptographic proof that this is your device, at this exact point in its history.",
        "dev": "Signs SECURACV-ATTEST-v1 || nonce || chain_head || seq || boot with the device key — domain-separated, verifiable offline. `c <nonce>`." },
      { "key": "f", "emoji": "🔎", "name": "Provenance / fingerprint", "audience": "dev",
        "plain": "Everything about the build it's running and how it's locked down.",
        "dev": "Firmware version + git, build time, key fingerprint, OTA partition, secure-boot / flash-encryption, console-policy verdict." },
      { "key": "e", "emoji": "🧭", "name": "Why did you restart?", "audience": "dev",
        "plain": "Explains the last boot and any recent faults.",
        "dev": "esp_reset_reason() (brownout/panic/watchdog) + degrade level + recent health-ring events." },
      { "key": "d", "emoji": "🧪", "name": "Diagnostics", "audience": "dev",
        "plain": "The deep internals — memory, storage wear, self-test details.",
        "dev": "Heap snapshot, SD health/wear, self-test report (FEATURE_DIAGNOSTICS)." },
      { "key": "o", "emoji": "🔌", "name": "Onboarding status", "audience": "dev",
        "plain": "How the plug-in behaves; change the launch method.",
        "dev": "USB onboarding status; cycle LaunchMethod (manual / macOS / Windows / Linux)." },
      { "key": "h", "emoji": "❔", "name": "Help menu", "audience": "everyday",
        "plain": "Lists every key you can press.",
        "dev": "Prints the full command menu." }
    ]
  },
  "tools": {
    "_comment": "The three web tools the Canary points you to. Links are relative (same site) or the single-sourced builds.flasherUrl.",
    "list": [
      { "id": "plugin", "emoji": "🔌", "name": "Plug-in emulator", "href": "/plugin", "audience": "everyday",
        "plain": "See exactly what happens when you plug a Canary into any computer or phone — no hardware needed.",
        "dev": "Board × OS × launch-path simulation of the mount + START-HERE flow." },
      { "id": "checkup", "emoji": "🩺", "name": "Canary Doctor", "href": "/checkup", "audience": "everyday",
        "plain": "Watch the device run its 10-point self-test and try every feature, live.",
        "dev": "Emulated self-test + feature tiles + BLE ladder + firmware-build selector." },
      { "id": "flasher", "emoji": "⚡", "name": "Browser flasher", "href": "flasherUrl", "audience": "dev",
        "plain": "Install or update the firmware straight from your browser — verified, backed up, and it can't brick the board.",
        "dev": "WebSerial + vendored esptool-js; signed image, SHA-256 before / chip-MD5 after, auto safety backup." }
    ]
  },
  "provenance": {
    "helpUrlBase": "firmware/canary/include/canary_config.h · SECURACV_HELP_URL_BASE",
    "serialCommands": "firmware/canary/src/main.cpp · handle_serial_commands() help menu (h/i/s/g/b/t/l/a/c/f/e/w/… + FEATURE_* gates). plain/dev copy is website-authored; keys + intent mirror the firmware.",
    "tools": "/plugin + /checkup (this site) + builds.flasherUrl (canary-local/flash.html)",
    "builds": "firmware/build_matrix.json (securaCV) · products + per-level feature lists; pinned to canary/platformio.ini + canary_config.h by scripts/lint_build_matrix.py; binaries + manifests at releases/latest/download/ per firmware-release.yml",
    "bluetooth": "firmware/common/health/test_console.h · ble_stage()/ble_hint() ladder; firmware/canary/lib/securacv_ble_status (NimBLE GATT service, ble_status_is_connected); [env:full]-only per PARTITIONS.md/LESSONS_LEARNED.md",
    "selftest": "firmware/canary/lib/securacv_diagnostics/src/securacv_diagnostics.cpp · TESTS[] (10 probes) + diag_run_selftest(); health_score = passed*100/10. Runs at boot (main.cpp). Re-run via GET /api/selftest.",
    "featureTests": "firmware/canary/lib/securacv_network/src/securacv_network.cpp · registerHttpHandlers() endpoints (/api/peek/snapshot, /api/audio/test/*, /api/sensing, /api/thermal, /api/mesh) + serial handle_serial_commands() (g, b); per-feature *_is_running()/*_get_stats() accessors",
    "reason": "firmware/canary/lib/securacv_usb_onboard/src/securacv_usb_onboard.cpp · build_help_url(..., \"onboard\")",
    "startHere": "…/securacv_usb_onboard.cpp · write_start_here_files() writes /START-HERE.html, /Open-Canary-Help.url, /Open-Canary-Help.webloc",
    "defaultLaunchMethod": "firmware/common/usb/usb_onboard_logic.h · LaunchMethod::MANUAL (= 0, the default)",
    "interfaces": "firmware/common/usb/usb_onboard_logic.h + securacv_usb_onboard.h — CDC console + HID keyboard + read-only MSC",
    "drive": "docs/design/usb_evidence_drive.md · CANARY-EVIDENCE (read-only MSC); SD layout /WITNESS /HEALTH /CHAIN /VAULT",
    "boards": "firmware/boards/boards.json + firmware/flavors.json + firmware/boards/<id>/pins/pins.h (HAS_SD_CARD)",
    "usb_capability": "firmware/envs/platformio/common.ini (S3 OTG vs C3 USB-Serial/JTAG) + canary-sense.ini (C6 USB-Serial/JTAG); base Canary [env:usb-onboard] flips ARDUINO_USB_MODE 1→0",
    "evidence": "firmware/projects/canary-wap/arduino/canary_wap/usb_evidence_drive.h (read-only MSC, opt-in, no HID)",
    "vision": "firmware/projects/canary-vision/README.md + firmware/docs/hardware/grove_vision_ai_v2_guide.md (two USB-C ports, CH343 module port, SenseCraft/SSCMA, I2C 0x62) + include/canary/config.h (Grove Vision AI V2)",
    "browsers": "Illustrative default browser per OS — the firmware opens whatever the host's default browser is, it does not choose one.",
    "doc": "docs/design/usb_onboard.md"
  }
}
