ENGINEERING / RAW DOCS

Architecture.
Plans & contrats.

Les documents du dépôt, directement. Schémas texte, couches du système, câblage et conditions de validation.

01 / DATA FLOW

┌──────────────┐   NDJSON    ┌──────────────┐   frames   ┌──────────────┐
 │ HARDWARE DOG │ ──────────> │  TRANSPORT   │ ─────────> │    SYSTEM    │
 │   firmware   │ <────────── │ web serial / │ <───────── │ state + rules│
 └──────────────┘  commands   │  simulator / │  commands  └──────┬───────┘
                              │  replay      │                   │
                              └──────▲───────┘                   │ every frame,
                                     │ .hdlog                    │ reject, cmd,
                              ┌──────┴───────┐                   │ mark
                              │   SESSION    │ <─────────────────┤
                              │   ARCHIVE    │   recorder        │
                              │  IndexedDB   │                   │
                              └──────────────┘                   │
                                                  ┌──────────────┼──────────────┐
                                                  v              v              v
                                            ┌──────────┐  ┌─────────────┐ ┌──────────┐
                                            │  TRACE   │  │   SCREENS   │ │ COMMANDS │
                                            │ timeline │  │ GUI panels  │ │  CTRL+K  │
                                            └────┬─────┘  └─────────────┘ └──────────┘
                                                 v
                                            ┌──────────┐
                                            │  REPORT  │  txt / json, local only
                                            └──────────┘

02 / WIRING — ESP32-S3 / INA226

Prototype : câblage par défaut. Bring-up et calibration sur carte encore à valider. GPIO 3.3 V; UART 5 V avec adaptation de niveau.

ESP32-S3            INA226 breakout
3V3        ──────── VCC
GND        ──────── GND
GPIO 8     ──────── SDA
GPIO 9     ──────── SCL
                    VIN+  ── supply side (e.g. 5 V from the charger)
                    VIN-  ── target side (the device under test)

ESP32-S3            TARGET
GPIO 18 (RX) ────── target TX      (3.3 V logic only)
GPIO 17 (TX) ────── target RX
GPIO 4  (SDA) ───── target I2C SDA (target pull-ups)
GPIO 5  (SCL) ───── target I2C SCL
GND        ──────── target GND     (always)
ARCHITECTURE.md
# HARDWARE DOG / ARCHITECTURE

```text
STATUS
------

[ OK ] web interface          web/
[ OK ] wire protocol v1       docs/PROTOCOL.md, protocol/hdp_v1.json (contract)
[ OK ] device simulator       web/src/core/simulator.ts
[ OK ] session recording      .hdlog v2 files, browser archive, replay
[ OK ] evidence integrity     SHA-256 seals, provenance, cases/
[ OK ] dogd                   local daemon, fourth transport (docs/DOGD.md)
[ .. ] firmware               firmware/: core verified on PC + CI, board pending
[ -- ] hardware / PCB         not started
[ -- ] enclosure              not started
```

## DATA FLOW

```text
 ┌──────────────┐   NDJSON    ┌──────────────┐   frames   ┌──────────────┐
 │ HARDWARE DOG │ ──────────> │  TRANSPORT   │ ─────────> │    SYSTEM    │
 │   firmware   │ <────────── │ web serial / │ <───────── │ state + rules│
 └──────────────┘  commands   │  simulator / │  commands  └──────┬───────┘
                              │  replay      │                   │
                              └──────▲───────┘                   │ every frame,
                                     │ .hdlog                    │ reject, cmd,
                              ┌──────┴───────┐                   │ mark
                              │   SESSION    │ <─────────────────┤
                              │   ARCHIVE    │   recorder        │
                              │  IndexedDB   │                   │
                              └──────────────┘                   │
                                                  ┌──────────────┼──────────────┐
                                                  v              v              v
                                            ┌──────────┐  ┌─────────────┐ ┌──────────┐
                                            │  TRACE   │  │   SCREENS   │ │ COMMANDS │
                                            │ timeline │  │ GUI panels  │ │  CTRL+K  │
                                            └────┬─────┘  └─────────────┘ └──────────┘
                                                 v
                                            ┌──────────┐
                                            │  REPORT  │  txt / json, local only
                                            └──────────┘
```

## LAYERS

```text
web/src/core/        no UI, no DOM rendering, fully unit tested
  protocol.ts        frame types, validating decoder, line splitter
  transport.ts       Transport interface, PackTransport (several Dogs)
  pack.ts            a pack: Dog clocks, one timeline, IN / OUT / UNKNOWN
  webserial.ts       real device over Web Serial (Chromium)
  simulator.ts       simulated device + simulated target, same protocol
  simpack.ts         one simulated incident watched by several simulated Dogs
  dogd.ts            transport to dogd on 127.0.0.1, local store
  session.ts         .hdlog writer (seals, footer), reader + verifier, replay
  sha256.ts          SHA-256, synchronous (works on plain HTTP too)
  cases.ts           recording -> regression case, case checker
  archive.ts         sessions streamed to IndexedDB, retention, export
  system.ts          single source of truth, diagnostic rules
  trace.ts           chronological timeline, pause without data loss
  diagnostics.ts     deterministic diagnostic engine, session facts
  scenarios.ts       physical fault scenarios for the simulator
  report.ts          observation / correlation / possible cause
  commands.ts        command layer, calls the same System methods as the GUI
  format.ts          units and timestamps
  ascii.ts           ASCII identity

web/src/ui/          Preact components, one file per screen
web/src/styles/      design tokens (spec 06, 07) and panel styles
```

## RULES OF THE CODEBASE

```text
ONE SOURCE OF TRUTH   The GUI and the command layer never keep their own
                      copy of device state. Both call System.

FRAMES ONLY           Device state changes only when a validated frame
                      arrives. Buttons send commands; they do not edit state.

NO FAKE CERTAINTY     Rules emit OBSERVED facts, CORRELATION in time, and
                      POSSIBLE CAUSE. Nothing is promoted to a diagnosis.

SIMULATOR IS LABELED  Simulated sessions say so in the header, the boot
                      sequence and every exported report. Switching source
                      starts a new session so data never mixes.

RECORDING IS REALITY  A replay goes through the same decoder and rules as a
                      live device, on the thresholds it was recorded with.
                      It is read-only, and a replayed simulator session is
                      still labeled SIMULATED. Format: PROTOCOL.md.

EVIDENCE STAYS        Every file is sealed (SHA-256 chain + footer). The
EVIDENCE              header is frozen when recording starts; origin is
                      never rewritten. A modified file is still shown, and
                      labeled MODIFIED with line numbers.

LOCAL FIRST           No network requests, no CDN, no account, no
                      telemetry. Fonts are bundled. Settings and recorded
                      sessions stay in the browser. Enforced by
                      web/test/laws.test.ts. Laws: LAWS.md.

SMALL                 The build must fit on the device flash and be served
                      by the firmware. Check the gzip size on every change.
```

## DIAGNOSTIC RULES

The deterministic engine (`web/src/core/diagnostics.ts`), its 13 rules,
confidence definitions, the 11 fault scenarios and the reliability matrix
are documented in [`DIAGNOSTICS.md`](DIAGNOSTICS.md).
ENGINEERING_PLAN.md
# HARDWARE DOG / ENGINEERING PLAN

```text
DOCUMENT      ENGINEERING PLAN
VERSION       0.7 / IMPLEMENTATION-ALIGNED
STATUS        ACTIVE IMPLEMENTATION
COMPANION TO  LAWS.md              the laws of the project
              DESIGN_SPEC.md       interface and industrial design (locked)
              BRAND.md             official mascot and assets (locked)
              VISION.md            product vision
              PROTOCOL.md          HDP v1, human-readable
              DIAGNOSTICS.md       rules, confidence, fault scenarios
              protocol/hdp_v1.json HDP v1, machine-readable contract
```

Hardware Dog is a **source-available** hardware diagnostic platform.

The repository is public so that individuals can study it, modify it, build
their own unit and use it for permitted non-commercial purposes. Commercial
manufacturing, distribution, integration and commercial service use require
a separate Hardware Dog commercial license (see [LICENSING](#licensing)).

The laws of the project ([`LAWS.md`](LAWS.md)) are architecture
boundaries, not preferences:

```text
ONE EVENT MODEL.
ONE CLOCK.
ONE TIMELINE.

EVERY REAL FAILURE SHOULD BE ABLE
TO BECOME A REPRODUCIBLE TEST.

LOCAL IS THE SOURCE OF TRUTH.
THE CLOUD IS NEVER REQUIRED.
```

A real Hardware Dog device and the simulator must produce the same HDP
event stream. The trace engine and the UI must not need to know which one
produced it.

```text
LAW OF THE PROJECT

The trace engine must not care whether reality or simulation
produced the event.
```

This law is enforced, not just stated: `web/test/protocol-contract.test.ts`
validates every simulator frame against `protocol/hdp_v1.json`, and the
firmware will pass the same contract before it ships.

---

## 1 — WHERE THE PROJECT IS

The first version of this plan (0.1, in git history) described a first work
session: create a skeleton and make a dashboard appear. That is done.

```text
[ OK ] repository skeleton
[ OK ] HDP v1 wire protocol + JSON Schema contract
[ OK ] transport abstraction
[ OK ] Web Serial link (real device side, awaiting firmware)
[ OK ] deterministic simulator
[ OK ] trace engine, one timeline
[ OK ] responsive diagnostic UI, desktop + mobile
[ OK ] power telemetry simulation
[ OK ] brand asset pipeline
[ OK ] design system (tokens, instrument panels)
[ OK ] automated tests + CI
```

### MILESTONE — SOFTWARE REFERENCE IMPLEMENTATION

```text
[ OK ]   HDP v1                             docs/PROTOCOL.md, protocol/hdp_v1.json
[ OK ]   simulator transport                web/src/core/simulator.ts
[ OK ]   Web Serial transport               web/src/core/webserial.ts
[ OK ]   trace engine                       web/src/core/trace.ts
[ OK ]   responsive UI                      web/src/ui
[ OK ]   fault scenarios                    11 scenarios, HD-T000 to HD-T010
[ OK ]   deterministic diagnostic rules     13 rules, reliability matrix
[ OK ]   report generation                  TXT + JSON, diagnosis-driven
[ OK ]   session recording                  .hdlog, browser archive, exact replay
[ OK ]   dogd integration                   Rust daemon, fourth transport
[ NEXT ] physical ESP32-S3 reference device firmware on a dev board
```

### MILESTONE — EVIDENCE INTEGRITY + CASES

A recorded fault must travel from one workshop to another and stay what
it was. Hardware Dog no longer only captures and diagnoses:

```text
CAPTURE -> PRESERVE -> SHARE -> REPLAY -> VERIFY -> LEARN
```

```text
[ OK ]   stable .hdlog header (v2)          recording id, origin, ruleset, app
[ OK ]   format / protocol / ruleset versions in every file
[ OK ]   sealed blocks + finalized hash     SHA-256 chain, footer with counts
[ OK ]   provenance preserved forever       PHYSICAL | SIMULATED, never rewritten
[ OK ]   replay cannot execute commands     recorded commands are documentation
[ OK ]   unclean stops recovered            closed: RECOVERED, sealed lines only
[ OK ]   live export is verifiable          closed: SNAPSHOT
[ OK ]   recording -> regression case       REPORT -> SAVE AS CASE, cases/
[ OK ]   cases run in the test suite        web/test/cases.test.ts
[ OK ]   report references the recording    id, origin, integrity, file SHA-256
[ LATER ] recordings signed by the device   key in the firmware
```

> **DON'T SEND A SCREENSHOT. SEND THE .HDLOG.**

**Not the PCB yet.** The order is deliberate: first a system that can
receive, record, correlate and explain the data perfectly; then the box
that produces it. Hardware built before the contract is stable has nothing
to be tested against.

---

## 2 — PRODUCT

> **Hardware Dog — a pocket diagnostic companion for hardware, USB, serial
> and network troubleshooting.**
> You plug it in. It sniffs. It tells you what is really going on.

Local-first. No account, no SaaS, no mandatory cloud. Useful without
Internet.

It observes several layers of a system:

```text
PHYSICAL -> ELECTRICAL -> PROTOCOL -> NETWORK -> SOFTWARE
```

It does not replace an oscilloscope or a Fluke multimeter. It answers:

> **"Why doesn't this thing work?"**

Diagnostics of hardware you control. No security exploitation, no bypass.

### The four modes

```text
SNIFF    passive observation: power, USB presence, UART, I2C, network, device state
PROBE    simple, safe active tests, always announced before running
TRACE    every event from every source on one clock
REPORT   exportable diagnosis: TXT + JSON (HTML / PDF later)
```

---

## 3 — ARCHITECTURE

```text
                     ┌──────────────────┐
                     │   REAL DEVICE    │
                     │ ESP32-S3 / HDP   │
                     └────────┬─────────┘
                              │
                              │ HDP EVENTS
                              │
                     ┌────────▼─────────┐
                     │ TRANSPORT LAYER  │
                     └────────┬─────────┘
                              │
              ┌───────────────┴───────────────┐
              │                               │
     ┌────────▼────────┐             ┌────────▼────────┐
     │ REAL TRANSPORT  │             │   SIMULATOR     │
     │ Web Serial      │             │ deterministic   │
     │ (dogd: planned) │             │ scenarios       │
     └────────┬────────┘             └────────┬────────┘
              └───────────────┬───────────────┘
                              │
                     ┌────────▼─────────┐
                     │   TRACE ENGINE   │
                     │ ONE TIMELINE     │
                     └────────┬─────────┘
                              │
                ┌─────────────┴─────────────┐
                │                           │
       ┌────────▼────────┐         ┌────────▼────────┐
       │ DIAGNOSTIC RULES│         │ SESSION STORE   │
       └────────┬────────┘         └────────┬────────┘
                │                           │
                └─────────────┬─────────────┘
                              │
                       ┌──────▼───────┐
                       │ UI / REPORTS │
                       └──────────────┘
```

```text
LAYER              STATUS    WHERE
transport          OK        web/src/core/transport.ts (Web Serial, simulator, replay, dogd)
HDP decoder        OK        web/src/core/protocol.ts
trace engine       OK        web/src/core/trace.ts
system state       OK        web/src/core/system.ts (single source of truth)
diagnostic rules   OK        web/src/core/diagnostics.ts
session store      OK        web/src/core/session.ts, archive.ts (.hdlog, IndexedDB)
UI / reports       OK        web/src/ui, web/src/core/report.ts
dogd               OK        dogd/, section 10, DOGD.md
firmware           PARTIAL   firmware/, section 11, FIRMWARE.md (board bring-up pending)
```

Invariants of the codebase (details in `ARCHITECTURE.md`):

```text
ONE SOURCE OF TRUTH   GUI and command layer both call System; no copies of state
FRAMES ONLY           device state changes only when a validated HDP frame arrives
SAME PATH             simulator frames are serialized and go through the real decoder
NO FAKE CERTAINTY     OBSERVED / CORRELATION / POSSIBLE CAUSE / NEXT CHECK kept apart
LOCAL FIRST           no network requests, no CDN, no account
```

---

## 4 — HDP, HARDWARE DOG PROTOCOL

```text
VERSION     1
FORMAT      NDJSON, UTF-8, one object per line
CLOCK       "t" = device uptime in ms, anchored to host time by "hello"
TRANSPORTS  USB CDC serial (now), WebSocket via dogd (planned), local TCP (planned)
CONTRACT    protocol/hdp_v1.json, enforced by tests
LATER       CBOR / protobuf only if bandwidth requires it; same event model
```

Device frames: `hello`, `power`, `usb.attach`, `usb.detach`,
`uart.config`, `uart.rx`, `uart.error`, `i2c.scan`, `net.status`,
`probe.result`, `probe.done`, `log`. Host commands: `hello`,
`usb.enumerate`, `uart.config`, `uart.tx`, `i2c.scan`, `net.refresh`,
`probe`. Full reference: `PROTOCOL.md`.

A breaking change means HDP v2, a new schema file, and a `proto` bump in
`hello`. Decoders reject what they do not understand; they never guess.

---

## 5 — SIMULATOR

The simulator is a fundamental component, not a demo feature. It is:

```text
THE DEMO          anyone can run Hardware Dog without owning one (npm run demo)
THE TEST RIG      every diagnostic rule is proven against it on each commit
THE SPEC PARTNER  it defines, in code, what the firmware must emit
```

Design:

```text
DETERMINISTIC     seeded PRNG: same seed, same session, bit for bit
MANUAL TIME       tests drive time with advance(ms); no timers, no flakiness
PHYSICAL MODEL    supply voltage, cable resistance, load bursts, brownout,
                  target boot log, UART baud, USB data link, network layers
HDP ONLY          it serializes frames to text and feeds the real decoder;
                  it never touches System state directly
BLIND ENGINE      the diagnostic engine never sees the scenario
```

Fault scenarios (`web/src/core/scenarios.ts`, details in `DIAGNOSTICS.md`):

```text
HD-T000 HEALTHY BASELINE           HD-T006 TARGET RESET LOOP
HD-T001 USB UNDERVOLTAGE           HD-T007 UNSTABLE NETWORK
HD-T002 DHCP FAILURE               HD-T008 UPSTREAM DOWN
HD-T003 DNS FAILURE                HD-T009 OVERCURRENT
HD-T004 SERIAL FRAMING MISMATCH    HD-T010 USB NOT ENUMERATED
HD-T005 INTERMITTENT USB DISCONNECT
```

Each scenario has an answer key. The reliability matrix requires the
engine to produce exactly that key, with no false positive on HD-T000.

Every new fault class starts here: scenario first, rule second, hardware
last.

---

## 6 — TRACE ENGINE

Probably the killer feature: **ONE TIMELINE.**

Diagnosing today means juggling a multimeter, a serial terminal,
Wireshark, system logs, ping, Device Manager and a browser console. Hardware
Dog puts all of it on one clock:

```text
12:02:01.011  POWER  WARN  voltage drop           4.61 V < 4.75 V
12:02:01.079  USB    WARN  device disconnected
12:02:01.079  RULE   WARN  disconnect 68 ms after voltage drop
12:02:02.310  UART   INFO  rst:0x1 (POWERON),boot:0x8
```

And suddenly: **cause -> consequence.**

Recording never stops; pausing freezes the view only. Events stay sorted by
device time even when frames arrive out of order. Capacity: 10 000 events
in memory, full timeline in the JSON export.

---

## 7 — DIAGNOSTIC ENGINE

Deterministic. **No mandatory AI.** 13 written rules, each with a
confidence level that has a written definition, all documented in
`DIAGNOSTICS.md` and proven by `web/test/scenarios.test.ts`:

```text
11 scenarios x 5 seeds      exact expected diagnoses, 0 false positive
HD-T000                     silent for 10 minutes
every fault                 HIGH confidence within 90 s, detected within 30 s
```

### Optional AI, later

AI may only **explain** diagnoses already produced by the rules, in plain
language. Detection never depends on it. This is essential for the product's
credibility.

---

## 8 — SESSION STORE

```text
DONE   every live session recorded as it happens (.hdlog, PROTOCOL.md):
       frames, rejected lines, commands, marks, threshold changes
DONE   browser archive (IndexedDB, memory fallback): sessions streamed in
       ordered chunks; the first failed write stops recording, no silent
       gap; simulator sessions pruned (latest 5), hardware sessions with
       data never deleted automatically
DONE   replay through the same decoder: same timeline, same facts, same
       diagnoses, on the recorded thresholds; read-only; a replayed
       simulator session stays labeled SIMULATED
DONE   save / open .hdlog files: a fault recorded by one person can be
       replayed by anyone, without the device
DONE   evidence integrity (.hdlog v2): sealed SHA-256 chain, footer,
       provenance, VERIFIED / RECOVERED / INCOMPLETE / MODIFIED
DONE   cases: a recording + the facts and diagnosis it must replay to
LATER  SQLite in dogd, same data model
LATER  recordings signed with a key held by the device
```

Everything is local. A session file leaves the machine only when the
operator saves it and gives it to someone.

Data model (dogd / SQLite, mirrored in IndexedDB):

```text
devices    id, serial_number, hardware_revision, firmware_version, first_seen, last_seen
sessions   id, device_id, source (DEVICE | SIMULATOR + scenario), started_at, ended_at, name, notes
events     id, session_id, timestamp, category, type, severity, payload_json
metrics    id, session_id, timestamp, metric, value, unit
reports    id, session_id, created_at, summary, diagnosis_json
```

A recorded session stores raw HDP frames. Replaying it through the decoder
must reproduce the same timeline and the same diagnoses: the recording
becomes a regression test. `cases/` holds such tests: each recording is
named by its SHA-256 and must replay to the facts and diagnosis its case
states (`web/test/cases.test.ts`, format in PROTOCOL.md).

---

## 9 — REPORTS

```text
FORMAT     TXT (readable with no software) + JSON (report + full timeline)
CONTENT    measurements per section, then one DIAGNOSIS block per finding:
           CONFIDENCE + basis, OBSERVED, CORRELATION, POSSIBLE CAUSE, NEXT CHECK
LABELING   simulated sessions say so in every export
LATER      HTML / PDF
```

---

## 10 — DOGD, LOCAL DAEMON

Rust, local only: `dogd/`, documented in [`DOGD.md`](DOGD.md).

```text
Rust, Tokio, Axum, Serde, SQLite

dogd/src/
├── main.rs        serve, status, devices, sessions
├── config.rs      local by default, --listen-lan explicit
├── api/           the local API, host + origin guard, HDP socket
├── transport/     the device link: bridge, reconnect
├── device/        port listing, HDP hello identification
├── sessions/      .hdlog integrity check (same algorithm as the reader)
├── storage/       SQLite index + files
└── protocol/      HDP constants, hello watcher
```

```text
DISCOVER  CONNECT  BRIDGE  STORE  SERVE
never: interpret differently, invent events, rewrite HDP, become SaaS
```

dogd is **the fourth transport**. `WS /v1/hdp` carries the device bytes
exactly as they arrive; the interface decodes them with the same decoder as
Web Serial. dogd owns no diagnostic rule: `dogd = transport + storage`,
`web = trace + diagnosis`. Headless diagnosis, if ever, will come from one
shared rule representation, not from a manual port to Rust.

---

## 11 — FIRMWARE

ESP32-S3, ESP-IDF, C11: `firmware/`, documented in [`FIRMWARE.md`](FIRMWARE.md).

```text
firmware/components/hdp   THE CORE: portable C, no ESP-IDF. HDP writer,
                          command parser, INA226 driver. Built and tested
                          on a PC too (firmware/host).
firmware/main             ESP-IDF glue: I2C, UART, native USB, Wi-Fi
```

Each subsystem emits HDP v1 frames; nothing else leaves the device (logs
go to UART0). The device declares what it observes in `hello.caps`.

Acceptance: the firmware's output validates against `protocol/hdp_v1.json`
and runs through the same engine as the simulator: on a PC and in CI today
(`web/test/firmware.test.ts`), captured on the board at bring-up
(FIRMWARE.md), then through the physical scenarios HD-P0xx (section 14).

---

## 12 — HARDWARE, REV A

### MCU

**ESP32-S3**: Wi-Fi, Bluetooth, USB OTG, plenty of GPIO, ADC, I2C, SPI,
UART, huge ecosystem, cheap, easy to prototype. An STM32 / RP2040 variant can
come later.

### USB: what Rev A does, precisely

```text
[X] measures VBUS voltage and current on the passthrough (INA226)
[X] detects attach / detach and correlates it with power on one clock
[X] enumerates a device plugged into its own HOST port and reads
    descriptors (VID, PID, class, strings)
[X] reports the speed the device enumerated at on that port
[ ] NOT a USB protocol analyzer: no packet capture, no traffic decoding
[ ] NOT 480 Mbps: the ESP32-S3 USB OTG controller is full-speed (12 Mbps);
    a high-speed device enumerates on the host port at full speed
[ ] the passthrough does not touch D+ / D-; it observes power only
```

```text
USB IN ──── INA226 current sense on VBUS ──── USB OUT     (data lines pass through)

USB HOST port (ESP32-S3 OTG, full speed): enumeration + descriptors
```

### Display

Small IPS display, 2.4–2.8". Field information only; the full interface
stays on the phone / laptop. Layout rules: `DESIGN_SPEC.md` sections 26–27.

### Connectors

```text
USB-C HOST      enumeration port
USB-C / USB-A   power passthrough (measured)
USB-C           Hardware Dog power + link to the host computer
RJ45            Ethernet (0.2)
UART header     GND TX RX 3V3
I2C header
GPIO
```

Rev A does not need everything to be bidirectional.

### Ethernet

**W5500 over SPI.** Not sexy, extremely well documented and stable. An
integrated PHY can come with Rev B.

### UART

3.3 V TTL only, with proper protection (series resistors, clamping). No
RS-232 on the MCU pins.

---

## 13 — PCB (AFTER THE REFERENCE DEVICE)

KiCad, public:

```text
hardware/
  rev-a/
    schematic/
    pcb/
    gerbers/
    bom/
    assembly/
```

Silkscreen and color rules: `DESIGN_SPEC.md` sections 28–29.

---

## 14 — TESTS

```text
UNIT         protocol decoder, trace, rules, commands, report, chart layout
CONTRACT     every simulator frame validates against protocol/hdp_v1.json
REPLAY       recording then replaying any scenario gives the same timeline,
             facts and diagnoses; tampered files are reported MODIFIED
CASES        every cases/<shelf>/*.hdlog is the file its case names and replays to
             the facts and diagnosis the case states
SIMULATION   reliability matrix: HD-T000 .. HD-T010 (DIAGNOSTICS.md)
PHYSICAL     HD-P001 real undervoltage (resistive cable, loaded target)
  (planned)  HD-P002 real DHCP failure (isolated switch, no server)
             HD-P003 real DNS failure (blackholed resolver)
             HD-P004 real framing mismatch (target at 9600)
             HD-P005 real intermittent USB (worn connector jig)
```

A physical scenario passes when the same engine produces the same
diagnosis as its simulated twin.

---

## 15 — REPOSITORY

```text
NOW                                   PLANNED
README.md                             CONTRIBUTING.md
LICENSE, COMMERCIAL.md                SECURITY.md
assets/brand/    brand pipeline       dogd/        Rust daemon
docs/            specs and plans      firmware/    ESP32-S3
protocol/        HDP v1 schema        hardware/    KiCad Rev A
web/             UI, core, simulator  enclosure/   3D printable case
cases/           recorded incidents
.github/         CI
```

The simulator lives in `web/src/core` today because the UI and the tests
use it directly. It moves to a shared package only when dogd needs it.

---

## 16 — CI

```text
NOW       typecheck, unit + contract + simulation tests, production build
NEXT      lint + format check, Rust build + tests (dogd), firmware build,
          contract test on captured firmware output, release artifacts
```

Badges appear in the README only when the job exists and passes.

---

## 17 — ROADMAP, BY LEVEL

```text
DESTINATION   Hardware Dog = the local-first incident recorder and
              diagnostic layer for physical hardware.

              Specialized tools measure one layer deeply.
              Hardware Dog preserves what happened across the system.
```

The project climbs one level at a time. A level is done when its **gate**
passes, not when its code exists. MAX LVL does not mean forty protocols: it
means a technician can trust Hardware Dog with a fault, share the proof,
reproduce it, and make a decision on it.

```text
LVL   NAME                                  STATUS
40    .hdlog becomes technical evidence      DONE
45    diagnostic engine v1                   DONE      16 rules, each with a scenario and a case
50    .hdlog becomes a CASE                  DONE      context in cases, hwdog test cases/ in CI
55    dogd, the local backbone               DONE      gate passed, real ESP32 at 60
60    first physical Hardware Dog            PARTIAL   core PASS on PC + CI, board bring-up pending
65    calibration + truthfulness             PARTIAL   software PASS, reference comparison on boards pending
70    real fault lab                         PARTIAL   procedures + pipeline PASS, bench recordings pending
75    professional reports                   DONE      TXT, JSON, HTML, PDF from the same lines
80    network + I2C on hardware              PARTIAL   rules, simulator, firmware core PASS; ESP32-S3 + W5500 builds in CI; silicon pending
85    PCB Rev A                              PLANNED
90    probe architecture                     PARTIAL   software PASS (PACK.md); two boards on one bench pending
95    community incident library             PARTIAL   library, anonymize, checks PASS (LIBRARY.md); first outside case pending
100   Hardware Dog 1.0                       PARTIAL   software ready (RELEASE.md); the hardware lines pending
MAX   incidents make Hardware Dog better     PLANNED
```

### LVL 40 — .hdlog BECOMES TECHNICAL EVIDENCE        DONE

```text
[ OK ] versions in every file: hdlog, HDP, ruleset, app (firmware from the
       hello frame, in the footer)
[ OK ] immutable provenance: origin PHYSICAL | SIMULATED, never rewritten;
       a replay is shown as REPLAY OF PHYSICAL | REPLAY OF SIMULATED
[ OK ] SHA-256 seal chain + footer hash of the final content
[ OK ] final counts: frames, rejects, commands, marks, thresholds, lost
[ OK ] unfinished files explicitly INCOMPLETE; RECOVERED after a crash
[ OK ] size and line limits, field validation, hostile-file tests
[ OK ] compatibility: every version is read forever, files are never
       rewritten; a newer file says it needs a newer Hardware Dog
[ OK ] report.json references the recording id, origin, integrity, hash
```

GATE: a file recorded today opens in two years without ambiguity.
`web/test/fixtures/hdlog-v1.hdlog` (written by an earlier build) and
`cases/*/*.hdlog` (v2, v3) must parse and replay to the same diagnosis on every
commit.

Why there is no REPLAYED_* origin in the file: replaying does not change
where the evidence came from. Exporting a replay gives back the same bytes,
so a file can never become "a replay of a replay". The replay state is
shown by the interface and the report, never written into the evidence.

### LVL 45 — DIAGNOSTIC ENGINE V1     DONE

Not 150 rules. Ten diagnostics that are extremely solid.

```text
WANTED                       TODAY (id kept: ids live in case files)
POWER_INSTABILITY            [ OK ] POWER_INSTABILITY
USB_RECONNECT_LOOP           [ OK ] USB_INTERMITTENT
USB_ENUMERATION_FAILURE      [ OK ] USB_NOT_ENUMERATED
PERIODIC_DEVICE_REBOOT       [ OK ] TARGET_RESET_LOOP
UART_CONFIGURATION_MISMATCH  [ OK ] SERIAL_CONFIGURATION_MISMATCH
DHCP_FAILURE                 [ OK ] DHCP_FAILURE
DNS_FAILURE                  [ OK ] DNS_FAILURE
UNSTABLE_NETWORK             [ OK ] NETWORK_UNSTABLE
I2C_DEVICE_DISAPPEARED       [ OK ] I2C_DEVICE_DISAPPEARED (i2c.watch, LVL 80)
I2C_BUS_INSTABILITY          [ OK ] I2C_BUS_INSTABILITY (i2c.error, LVL 80)
+ NETWORK_POWER_LOSS         [ OK ] the network goes down with the supply
```

Every diagnosis returns OBSERVED, CORRELATED, POSSIBLE CAUSE, CONFIDENCE,
RECOMMENDED CHECK and EVIDENCE REFERENCES (`[ OK ]`: the HDP frames it
rests on, by sequence number). Never "the problem is definitely X".

GATE: every diagnosis has a scenario, an .hdlog, an expected answer and a
regression test. **PASS for the 16 rules** (HD-T000 to HD-T016, HD-C001
to HD-C017, `cases.test.ts` fails if one is missing).

### LVL 50 — .hdlog BECOMES A CASE        DONE

```text
incident.hdlog -> CREATE CASE -> HD-Cxxx -> hwdog test cases/
```

```text
[ OK ] case = recording + SHA-256 + expected facts + diagnosis + confidence
[ OK ] SAVE AS CASE in the interface; every case runs on each commit
[ OK ] description, hardware context, notes in the case file
[ OK ] hwdog test cases/: a command line runner outside the test suite
       (web/src/cli/hwdog.ts, run in CI), and hwdog report <file.hdlog>
```

### LVL 55 — dogd, THE LOCAL BACKBONE        DONE

dogd is not a second brain, and not an Internet backend (LAWS.md).
Everything about it: [`DOGD.md`](DOGD.md), with the gate checklist.

```text
DEVICE DISCOVERY   SESSION STORAGE   HDP TRANSPORT
LOCAL API          WEBSOCKET         REPORT GENERATION

WebSerial ─┐
Simulator ─┼─> HDP v1        no special Rust event, same contract everywhere
dogd ──────┘
```

GATE: the interface connected over Web Serial or through dogd receives the
same frames and reaches the same results. Proven in CI against the real
binary (`web/test/dogd.test.ts`): simulator direct == simulator through
dogd, same facts, same diagnosis, same evidence references.

Locked before dogd: **evidence references**. Every HDP frame has a sequence
number in its session; every diagnosis cites the frames it rests on. The
numbers are the same live, replayed and through dogd, so dogd can index
sessions without a migration later.

### LVL 60 — FIRST PHYSICAL HARDWARE DOG     PARTIAL

```text
SIMULATOR                 PASS
FIRMWARE CORE (PC + CI)   PASS   every frame validates hdp_v1.json; the engine
                                 reads it like the simulator; through dogd ==
                                 direct (web/test/firmware.test.ts)
FIRMWARE BUILD esp32s3    CI     ESP-IDF v5.4
FIRMWARE ON SILICON       PENDING  bring-up checklist, FIRMWARE.md
REPLAY                    PASS
```

Found on the way, fixed for good: a device that does not watch USB was
diagnosed USB NOT ENUMERATED from silence. Devices now declare what they
observe (`hello.caps`), and the USB rules only run when USB is watched
(ruleset v2).

No custom PCB. ESP32-S3 dev board, INA226, UART, Wi-Fi.
`REAL HARDWARE -> HDP v1 -> existing software`, the frontend barely
changes.

```text
GATE   SIMULATOR   PASS
       FIRMWARE    PASS     same contract test
       REPLAY      PASS
```

### LVL 65 — CALIBRATION + TRUTHFULNESS     PARTIAL

Every measurement exposes its range, sample rate, expected accuracy,
resolution, calibration date, sensor identity, firmware and limitations,
and says: `DIAGNOSTIC MEASUREMENT, NOT CERTIFIED METROLOGY`. Several
INA226 boards are tested against a reference instrument: the goal is to
know exactly how far the numbers can be trusted.

```text
[ OK ] power.meter (PROTOCOL.md): sensor, shunt, range, resolution, rate,
       expected error and its basis (DATASHEET | CALIBRATION), calibration
       date and reference instrument
[ OK ] firmware: datasheet worst case from the INA226 figures and the
       shunt tolerance; meter.cal / meter.clear, stored in NVS, applied to
       every sample, refused out of range (C tests, firmware.test.ts)
[ OK ] interface: POWER -> MEASUREMENT, +- next to every live reading,
       "accuracy unknown" when a device declares nothing
[ OK ] calibration from the palette: meter point / meter cal, least-squares
       fit, largest residual declared (calibration.test.ts)
[ OK ] reports: MEASUREMENT section and the metrology line in every format
[ -- ] GATE: several INA226 boards against a reference instrument,
       declared error vs measured error. Needs the boards (FIRMWARE.md,
       bring-up step 4).
```

### LVL 70 — REAL FAULT LAB     PARTIAL

Faults created on purpose: cheap USB cable, undervoltage, brownout, loose
connector, wrong baud, missing I2C pull-up, intermittent I2C, DHCP loss,
DNS loss, network latency, boot loop, USB reconnect loop.

```text
PHYSICAL FAILURE -> HDLOG -> CASE -> REGRESSION TEST
```

The simulator gradually stops being made of imagined scenarios and starts
reproducing traces of real failures.

```text
[ OK ] FAULT_LAB.md: twelve faults, how to make each one safely, what to
       expect, what the reference firmware can see today
[ OK ] the pipeline: record -> SAVE AS CASE -> context -> hwdog test
[ -- ] GATE: the faults recorded on the bench as PHYSICAL cases, and the
       simulator re-tuned from them. Needs the board.
```

### LVL 75 — PROFESSIONAL REPORTS     DONE

A document a technician attaches to a ticket: device, session, provenance,
recording hash, observations, correlations, diagnoses, confidence,
recommended checks, timeline excerpt, measurement limitations.
Exports TXT, JSON, HTML, PDF. JSON stays the machine-readable truth.

```text
[ OK ] one source: reportLines() (core/report.ts). TXT, HTML and PDF are
       set from the same styled lines: no format says what another does not
[ OK ] timeline excerpt: the events the diagnoses cite (>) with two
       events of context each side, and how many were left out
[ OK ] measurement limitations (LVL 65) and the metrology line
[ OK ] HTML: one file, no script, no remote resource (CSP), everything
       from the device escaped, prints on A4
[ OK ] PDF: written by Hardware Dog (core/pdf.ts), no library, no server;
       standard Courier fonts, A4, the SIMULATED / MODIFIED warning on
       every page, page numbers, same report = same bytes
[ OK ] GATE: a real PDF reader (pdf.js, test only) opens it and finds
       every line of the TXT report (web/test/reports.test.ts)
```

### LVL 80 — NETWORK + I2C ON HARDWARE

W5500 Ethernet, I2C scanner, network probes, correlated with everything
else. Not "ping works", but the chain:

```text
12:01:22  POWER     sag
12:01:22  SYSTEM    reboot
12:01:23  LINK      down
12:01:26  LINK      up
12:01:27  DHCP      acquired
12:01:28  DNS       ready
```

```text
[ OK ] HDP: i2c.error, i2c.watch, net.watch; scans carry every_ms
[ OK ] rules (ruleset 3): I2C_DEVICE_DISAPPEARED, I2C_BUS_INSTABILITY,
       NETWORK_POWER_LOSS with the way back timed: link, DHCP, DNS
[ OK ] simulator HD-T014 to HD-T016, cases HD-C015 to HD-C017
[ OK ] firmware core: periodic scans, stuck-line and timeout faults, net
       checks, background probes, net.status sent on change (C tests,
       firmware.test.ts: the host build gives the same diagnoses)
[ OK ] ESP32-S3 glue: W5500 on SPI2 or Wi-Fi, ping / resolve / TCP 443
       watch task, PING / DNS / TCP / HTTP probe task; built in CI with
       and without the W5500
[ OK ] interface: BUS watch + presence per address, NET watch + OUTAGES
[ -- ] GATE: bring-up steps 9 to 12 on the board (FIRMWARE.md), the
       router chain recorded as a PHYSICAL case
```

### LVL 85 — PCB REV A

KiCad: USB power path, current sensing, ESP32-S3, Ethernet, UART
protection, I2C, display, ESD and power protection, test points,
programming / debug. Delivered with schematic, PCB, BOM, Gerbers, assembly
notes and a test procedure.

**No PCB before the physical prototype has many real hours of diagnosis
behind it.** Otherwise the mistakes get etched into copper.

### LVL 90 — PROBE ARCHITECTURE

Hardware Dog stops being a box and becomes a platform, without growing a
Frankenstein core: CAN DOG, USB DOG, POWER DOG, ENVIRONMENT, RS-485, GPIO
are probes, and every probe simply produces HDP.

Several Dogs watching one incident are a **pack** ([`PACK.md`](PACK.md)):
one timeline, one clock with a known error, one source of truth per
signal, and rules that never know which Dog spoke.

```text
[ OK ] HDP time: {"cmd":"time","id":n} -> {"type":"time","t":T,"id":n};
       schema, decoder, simulator, firmware core (C tests)
[ OK ] clocks: hello (unbounded), time round trips (+- half the round
       trip), 100 ppm drift, resampling every 10 s (core/pack.ts)
[ OK ] one timeline: a frame waits for the other Dogs (500 ms at most),
       released only on arrivals, so a replay is the session again
[ OK ] no fake certainty: across clocks a correlation is IN, OUT or
       UNKNOWN, never guessed; UNKNOWN is said in the diagnosis
[ OK ] one source per signal: capabilities claimed once, commands routed
       to the Dog that observes them, a lost Dog does not stop the pack
[ OK ] GATE (software): a simulated pack of three Dogs reaches the same
       diagnosis as one device on every scenario (pack-system.test.ts)
[ OK ] hdlog v3: a pack recorded and replayed to the same timeline,
       clocks and diagnosis; case HD-C018; dogd stores it        LVL 90.2
[ OK ] dogd with several sources, one stream per Dog; the firmware
       core says only what its caps declare; two firmware processes
       through one dogd give the diagnosis of one board        LVL 90.3
[ OK ] the interface shows the pack: built from dogd, from boards on
       USB or as a demo; every Dog, its link, what it observes, its
       clock; in the header, the report and the tour         LVL 90.4
[ -- ] GATE (hardware): two boards on one bench, a PHYSICAL case
```

### LVL 95 — COMMUNITY INCIDENT LIBRARY

Not a social network. A repository of anonymized, reproducible cases
(`cases/power`, `usb`, `uart`, `network`, `i2c`). Submissions are a pull
request: .hdlog, description, hardware, expected behavior; tests run
automatically, then review. A real library of hardware failures is worth
more than code. ([`LIBRARY.md`](LIBRARY.md))

```text
[ OK ] shelves: cases/baseline, power, usb, uart, network, i2c; a case
       sits on the shelf of its first diagnosis; cases/INDEX.md generated
[ OK ] anonymize: chip id, device id, USB serial, addresses, host names,
       MACs, e-mails, secrets, long hex, user names in paths, endpoint;
       consistent, so every case replays to the same facts, diagnosis and
       evidence (anonymize.test.ts, all 18 cases)
[ OK ] submission checks in CI: replay, shelf, name, words (description,
       hardware, expected), privacy, size, index (hwdog test cases)
[ OK ] REPORT -> SHARE WITH THE LIBRARY: the anonymized copy and its
       drafted case; hwdog anonymize from a terminal
[ OK ] contribution: docs/LIBRARY.md, the incident pull request template
[ -- ] the first case contributed from outside, recorded on real hardware
```

### LVL 100 — HARDWARE DOG 1.0

The checklist with its evidence, line by line: [`RELEASE.md`](RELEASE.md).
Software: every line `[ OK ]` or waiting for a board (security review,
reproducible builds, update and recovery procedures, trademark, documents
indexed and their links checked). Hardware: the bring-up comes first.

```text
[ ] stable HDP v1                  [ ] USB
[ ] stable HDLOG                   [ ] power
[ ] deterministic replay           [ ] UART
[ ] evidence integrity             [ ] I2C
[ ] diagnostic engine              [ ] network
[ ] physical reference hardware    [ ] custom PCB
[ ] documented measurement limits  [ ] enclosure
[ ] real fault regression library  [ ] reproducible build
[ ] dogd                           [ ] firmware update procedure
[ ] session archive                [ ] recovery / failed update path
[ ] professional reports           [ ] complete documentation
[ ] security review                [ ] license + trademark docs
```

Then, without exaggeration:

> Hardware Dog 1.0 is a source-available, local-first hardware diagnostic
> system that records electrical, protocol and network evidence on one
> synchronized timeline and turns real incidents into reproducible
> diagnostic cases.

### MAX LVL — AFTER 1.0

```text
LIVE INCIDENT -> .HDLOG -> ANONYMIZE -> CASE LIBRARY -> REGRESSION
              -> BETTER RULES -> BETTER HARDWARE DOG
```

No mandatory cloud: users choose to contribute an incident. Hardware Dog
learns in the engineering sense, more reproducible cases, more tests,
better diagnoses. AI, if ever, comes last and only explains:

```text
FACTS + CORRELATIONS + RULE RESULTS -> OPTIONAL LOCAL AI -> HUMAN EXPLANATION
```

Never `raw signals -> AI guess -> trust me bro`.

### Not now

```text
advanced Bluetooth diagnostics     CAN bus (until LVL 90 probes)
JTAG / SWD debugger                full USB-PD analyzer
oscilloscope                       16-channel logic analyzer
USB packet analyzer                AI everywhere
```

Otherwise Hardware Dog becomes a Frankenstein before it has an identity.

---

## 18 — DESIGN AND BRAND

Not duplicated here. Both are locked in their own documents:

```text
DESIGN_SPEC.md   interface, screens, colors, typography, industrial design
BRAND.md         official mascot, asset roles, favicon / icon / header rules
```

---

## LICENSING

Hardware Dog uses a dual-license model.

```text
PERSONAL / NON-COMMERCIAL    PolyForm Noncommercial 1.0.0
COMMERCIAL                   separate paid Hardware Dog commercial license
```

See [`LICENSE`](../LICENSE) and [`COMMERCIAL.md`](../COMMERCIAL.md).

The repository is **source-available**. It is not open source in the OSI
sense.

The Hardware Dog name, logo and official mascot are separate brand assets
and are not granted for unrestricted third-party branding or resale.

---

## CHANGELOG

```text
0.8   LVL 90 started: the pack (PACK.md), HDP time, one timeline and one
      clock for several Dogs, verified against every scenario
0.7   LVL 60 partial: firmware core verified on a PC and in CI, ESP32-S3
      build, bring-up checklist; hello.caps and ruleset v2
0.6   LVL 55 done: dogd (DOGD.md), evidence references locked before it
0.5   laws of the project (LAWS.md); roadmap by level, LVL 40 to MAX LVL;
      LVL 40 closed: hostile-file limits, compatibility rule, v1 fixture
0.4   milestone EVIDENCE INTEGRITY + CASES (section 1): .hdlog v2 with
      provenance, sealed hash chain and footer; recovery and snapshots;
      cases as regression tests
0.3   session recording implemented (section 8): .hdlog format, browser
      archive, replay, example sessions as regression tests
0.2   aligned with the implementation: source-available wording, single
      licensing section, simulator as a core component, precise USB
      capabilities, HDP schema contract, design and brand by reference,
      new milestone and roadmap
0.1   original plan (git history)
```
FIRMWARE.md
# HARDWARE DOG / FIRMWARE

```text
TARGET        ESP32-S3 dev board (ESP32-S3-DevKitC-1 or equivalent)
SENSOR        INA226 breakout (R100 shunt), I2C
SDK           ESP-IDF v5.4, C11
HDP LINK      native USB (USB-Serial-JTAG), 303A:1001
STATUS        LVL 60-80: core verified on a PC and in CI, ESP32-S3 builds
              in CI (Wi-Fi, and W5500 + I2C watch), SILICON BRING-UP
              PENDING (needs the board on a desk)
```

The firmware is a producer of HDP v1, like the simulator. Nothing else.
The interface, the trace engine and dogd do not change for it.

```text
REALITY -> firmware -> HDP v1 -> (Web Serial | dogd) -> same decoder
        -> same trace -> same diagnosis
```

---

## WHAT IS VERIFIED, AND WHERE

No claim without the test that backs it.

```text
                                       PC      CI      BOARD
core: HDP frames, commands, UART       PASS    PASS    pending
  lines, I2C scan, INA226 driver math
every frame validates hdp_v1.json      PASS    PASS    pending
engine reads it like the simulator     PASS    PASS    pending
firmware -> TCP -> dogd == direct      PASS    PASS    --
ESP-IDF build for esp32s3              --      CI      --
  (default, and W5500 + I2C watch)
I2C watch, bus faults, net checks,     PASS    PASS    pending
  background probes (host simulation)
W5500 link, real ping / DNS / TCP      --      --      pending
I2C timing, real INA226, UART pins,    --      --      pending
  USB link, Wi-Fi
measurement accuracy                   --      --      LVL 65
```

"PC" is the core built natively (`firmware/host`) against a board
simulated at the register level: the INA226 model computes the current
register the way the chip does (shunt register x calibration / 2048), so
the driver math is really exercised. It is a test fixture, not the device.

---

## WIRED NETWORK PORT (LVL 80, optional)

`idf.py menuconfig` -> Hardware Dog -> W5500 Ethernet. The wired port wins
over Wi-Fi when both are configured.

```text
ESP32-S3            W5500 module (3.3 V)
3V3        ──────── 3V3
GND        ──────── GND
GPIO 12    ──────── SCLK
GPIO 11    ──────── MOSI
GPIO 13    ──────── MISO
GPIO 10    ──────── CS
GPIO 14    ──────── INT     (-1 in menuconfig: not wired, polled)
GPIO 21    ──────── RST     (-1: not wired)
```

Plug the W5500 into the network the incident is about, e.g. into the
router the target powers: when the router reboots with the supply, the
link loss, DHCP and DNS are on the same timeline as the voltage drop.

---

## LAYOUT

```text
firmware/
├── components/hdp/        THE CORE, portable C11, no ESP-IDF
│   ├── include/hdp/hdp.h  HAL interface, config, capabilities
│   └── hdp.c              HDP writer, command parser, INA226 driver
├── main/main.c            ESP-IDF glue: I2C, UART, USB, Wi-Fi
├── main/Kconfig.projbuild pins, shunt, Wi-Fi (menuconfig)
├── sdkconfig.defaults     logs on UART0, never on the HDP link
└── host/                  the core on a PC
    ├── host_main.c        hwdog-host: simulated board, stdout or TCP
    └── tests.c            unit tests (AddressSanitizer + UBSan)
```

---

## WIRING (defaults, change in menuconfig)

```text
ESP32-S3            INA226 breakout
3V3        ──────── VCC
GND        ──────── GND
GPIO 8     ──────── SDA
GPIO 9     ──────── SCL
                    VIN+  ── supply side (e.g. 5 V from the charger)
                    VIN-  ── target side (the device under test)

ESP32-S3            TARGET
GPIO 18 (RX) ────── target TX      (3.3 V logic only)
GPIO 17 (TX) ────── target RX
GPIO 4  (SDA) ───── target I2C SDA (target pull-ups)
GPIO 5  (SCL) ───── target I2C SCL
GND        ──────── target GND     (always)
```

**The ESP32-S3 pins are 3.3 V.** A 5 V target UART needs a level shifter.
The INA226 measures the target rail up to 36 V; its own supply is 3.3 V.
Default full scale: 0.8 A with a 0.1 ohm shunt (81.92 mV max across it).
For more current, a smaller shunt and `HWDOG_SHUNT_MOHM` /
`HWDOG_MAX_CURRENT_MA`.

---

## WHAT THIS REVISION OBSERVES

The device says it in `hello.caps` (PROTOCOL.md):

```text
power   YES   INA226, identity verified (manufacturer 0x5449, die 0x226)
uart    YES   lines, framing / parity / overrun / break errors
i2c     YES   address scan 0x08-0x77 on request, or every N ms (i2c.watch,
              or HWDOG_I2C_WATCH_MS from boot); a line stuck low or a bus
              timeout is an i2c.error, never an empty scan; no identity
              claimed
net     WITH W5500 OR WI-FI CONFIGURED: link, address, DHCP, sent at once
              when they change. Gateway, DNS, Internet, latency, loss only
              from net.watch checks (ping, resolve, TCP 443), else UNKNOWN
usb     NO    the dev board has no USB host port wired for the target
probe   WITH A NETWORK: PING (ICMP x4), DNS (resolve), TCP (port 80),
              HTTP (GET /, status line), in a background task
```

A floating line (no pull-up at all) can read high as well as low: a stuck
low line is certain, a floating one is not always seen. The scan then
finds nothing, or a device that comes and goes.

Because `usb` is not declared, the engine makes **no** USB diagnosis from
silence (ruleset v2). A target drawing current is not "not enumerated" just
because nobody is watching its USB.

The INA226 is identified before any number is published. Not answering, or
answering with another identity, gives a `log` error and **no** power
frames: never a made-up value.

---

## BUILD AND FLASH

```sh
cd firmware
idf.py set-target esp32s3
idf.py menuconfig            # optional: Hardware Dog -> Wi-Fi, pins, shunt
idf.py build
idf.py -p /dev/ttyUSB0 flash # the UART port of the dev board
```

The Wi-Fi password, if set, stays in your local `sdkconfig` (ignored by
git). Never commit it.

The core on a PC, no board needed:

```sh
cmake -S firmware/host -B firmware/host/build && cmake --build firmware/host/build
ctest --test-dir firmware/host/build
firmware/host/build/hwdog-host --fast --scenario sag --seconds 30
```

`--chip 7cdfa13a1f2c` gives the simulated board a factory id for its
hello (a board knows its own; a simulation is given one or sends none).
With `--fast` and commands piped on stdin, every command is read at t=0
before device time runs: a scripted run never races its script.

`--caps power,uart,i2c` makes the process one Dog of a pack: it declares
and observes only those (the core sends no frame for anything else, and
rejects commands for it); `--device HD-P0WER` names it. Two processes
through one dogd are a pack on a PC (web/test/firmware.test.ts, LVL 90.3).
Each plays the same scripted incident from its own start: that proves the
links, the clocks and the merge, not shared physics, which takes two
boards on one bench.

---

## UPDATE

There is no over-the-air update: a Hardware Dog is updated on the bench,
by cable, by the person who owns it. Nothing reaches the board by itself.

```sh
cd firmware && git pull
idf.py build
idf.py -p PORT app-flash      # the application only: the calibration stays
```

`app-flash` writes the application partition only. The calibration lives
in NVS and survives it; `idf.py flash` also rewrites the bootloader and
the partition table, and survives it as well unless the partition table
changed. **`erase-flash` removes the calibration**: write the calibration
points down (METER in the interface) before using it.

After an update the interface shows the new version at the next hello
(`DEVICE` / `FIRMWARE`), and every recording names the firmware that
produced it, so no session ever mixes two versions without saying so.

The image is reproducible (`CONFIG_APP_REPRODUCIBLE_BUILD`): the same
sources give the same bytes, checked in CI by building twice. Anyone can
check that a published image is what the code builds:
`sha256sum build/hwdog.bin`.

## RECOVERY (a failed update)

An ESP32-S3 cannot be bricked by a bad application: the first bootloader
is in mask ROM and always answers in download mode.

```text
1  Hold BOOT (GPIO0), press and release RESET, release BOOT.
   The board is now in ROM download mode, whatever was flashed.
2  idf.py -p PORT flash          the known good build (git checkout <tag>)
3  If the board still does not boot: idf.py -p PORT erase-flash, then
   idf.py -p PORT flash. The calibration is lost: calibrate again.
4  Press RESET. The interface must show the hello of the version flashed.
```

The UART port of the dev board (CP2102 / CH343) is the most reliable for
recovery: it does not depend on the firmware's USB stack.

```text
[ -- ] update and recovery procedures, checked on the board    LVL 60 bring-up
```

These steps follow the ESP-IDF tools and the ESP32-S3 boot ROM. They are
**not yet verified on a Hardware Dog**: that is part of the bring-up.

---

## BRING-UP ON THE BOARD (the LVL 60 gate)

This is the part a computer cannot do for us. Each step has the result to
expect; a different result is a finding, not a failure to hide.

```text
[ ] 1  FLASH       idf.py flash, then plug the NATIVE USB port.
[ ] 2  IDENTIFY    dogd devices --identify
                   expect: HARDWARE DOG HD-xxxxxx rev DEVKIT-S3 fw 0.1.0 hdp v1
                   (the ROM may print its boot banner on USB at reset: those
                   2-3 lines are not HDP, the interface rejects and counts them)
[ ] 3  NO SENSOR   INA226 unplugged. Interface: SETUP -> CONNECT DOGD
                   (dogd --source serial:PORT) or CONNECT WEB SERIAL.
                   expect: timeline "INA226 not answering at 0x40", no power
[ ] 4  POWER       INA226 wired, target powered through it.
                   expect: power frames at 50 Hz, voltage within the declared
                   error (POWER -> MEASUREMENT) of a multimeter on VIN-.
                   3 points with `meter point` (no load, ~100 mA, ~500 mA),
                   then `meter cal "<multimeter>"`: CALIBRATION survives a
                   reboot. These points seed the LVL 65 gate.
[ ] 5  UART        target TX on GPIO 18. expect: its lines on SERIAL.
                   Set a wrong baud: expect framing errors, then
                   SERIAL CONFIGURATION MISMATCH.
[ ] 6  I2C         a known sensor on the target bus, PROBE -> I2C scan.
                   expect: its address listed, no identity claimed.
[ ] 7  RECORD      a 10 min session, SAVE .HDLOG, replay it.
                   expect: VERIFIED, origin PHYSICAL, same diagnosis replayed.
[ ] 8  HD-P001     a resistive cable (long, thin) between supply and target,
                   target under load. expect: undervoltage events, SUPPLY SAG.

LVL 80, on the same bench:

[ ] 9  I2C WATCH   i2c watch 5 in the palette, a sensor on the target bus.
                   Pull its SDA wire for 10 s, twice.
                   expect: "0x76 no longer answers" / "answers", then
                   I2C DEVICE DISAPPEARED, HIGH.
[ ] 10 PULL-UPS    remove the target bus pull-ups (keep the device).
                   expect: i2c.error SDA_LOW or SCL_LOW, I2C BUS FAULT; no
                   empty scan. If the lines float high instead, write it in
                   the notes: that is what the board does.
[ ] 11 NET WATCH   W5500 (or Wi-Fi) up, net watch 10 example.com in the
                   palette. expect: gateway, DNS PASS with latency.
                   Unplug the cable 20 s: link down, then link, DHCP, DNS
                   back, timed in NET -> OUTAGES.
[ ] 12 ROUTER      power a USB travel router through the INA226 and a thin
                   cable, W5500 plugged into it. Load the rail.
                   expect: NETWORK LOST WITH POWER, the chain drop > link
                   down > up > DHCP > DNS. The recording becomes a PHYSICAL
                   case.
```

When 1 to 7 pass, LVL 60 is done. The recording of step 8 becomes the first
**PHYSICAL** case (the next free `HD-Cxxx`, on its shelf: [`LIBRARY.md`](LIBRARY.md)): send the `.hdlog`. The
other faults to record are in [`FAULT_LAB.md`](FAULT_LAB.md).

---

## CALIBRATION (LVL 65)

The firmware declares what its numbers are worth (`power.meter`): the
INA226 datasheet worst case until the board is calibrated against a
reference instrument. Out of the box, with a 1 % R100 shunt:

```text
VOLTAGE   +-0.1 % + 8.75 mV     (5 V rail: +-13.8 mV)
CURRENT   +-1.1 % + 0.12 mA     (300 mA: +-3.4 mA)   the shunt tolerance dominates
```

Calibrating, from the command palette (CTRL+K), with a multimeter on the
same rail and the same load:

```text
meter                         what the numbers are worth now
meter point 5.012 2.1         no load: what the multimeter reads (V, mA)
meter point 4.981 101.4       ~100 mA load
meter point 4.874 498.0       ~500 mA load
meter cal "Fluke 87V"         fit, store on the device (NVS), declare
meter clear                   back to datasheet accuracy
```

Each point averages the last second of samples, taken before any stored
calibration. Voltage: one gain. Current: gain and offset when the points
span 50 mA or more. The device stores the largest residual seen and
declares it as its accuracy, with the date and the reference named.
Disagreement above 10 % is refused: that is a wiring fault, not a
calibration.

**What this is not:** traceable metrology. A calibration is worth the
reference it was made with, and its accuracy is not included.

## RULES OF THE FIRMWARE

```text
HDP ONLY ON THE LINK   logs on UART0; the native USB carries HDP and nothing else
NO MADE-UP VALUE       sensor missing or failing: a log, no number
NO FAKE IDENTITY       i2c.scan lists addresses; ident only when verified
SAY WHAT IS OBSERVED   hello.caps; UNKNOWN for what is not checked
TIME NEVER GOES BACK   t from esp_timer, clamped monotonic
NO SILENT LOSS         UART overrun -> uart.error "overrun"; long lines split,
                       never dropped; a frame too long is not sent half
```