Code architecture

Two layers. The protocol is device-independent and meant for adoption by others. Goldfish is one profile of it, plus the bindings to its hardware.

                ┌──────────────────────────── protocol/ (Resolution Protocol) ────────────────────────────┐
 speech ──────► │ grammar: locale JSON → intents ──► session: voices · records · levels ──► retention    │
 (any port)     │   (en, es, …)                       (rules §6, events §11)              (schedule §5)  │
                │ reference: python/resolution_protocol  ·  js/resolution.js  ·  vectors/ (conformance)  │
                └───────────────▲──────────────────────────────▲─────────────────────────────▲──────────┘
                                │ profile (parameters + locales)│ events / applied levels     │ max_level, unreadable_after
                ┌───────────────┴──────────────── v01/ (Goldfish profile + bindings) ─────────┴──────────┐
                │ profile/goldfish.profile.json   — default 3, invocation "goldfish", en+es, 3 d/30 d/1 y   │
                │ firmware/goldfish/device.py     — loads the profile and grammars, owns the session        │
                │   display.py  ring frames (indicator)   keys.py + store.py  key chains (store, §5.3)       │
                │   handoff.py + st25dv.py  NFC note (hand-off)          RTC (clock)   mics/ASR (audio in)   │
                │ firmware/js/goldfish.js         — the same bindings for the workbench site                │
                └──────────────────────────────────────────────────────────────────────────────────────────┘
                                │
                site/ (workbench) — runs protocol/js + goldfish.js in the browser; Rules tab runs the vectors

Where things live

Concern Protocol (shared) Goldfish (profile + bindings)
Levels, rules, voices, records protocol/SPEC.md §3–6, session.py / resolution.js —
Phrases → intents protocol/locales/*.json invocation: ["goldfish"], locales: ["en", "es"]
Parameters schema profile.schema.json v01/profile/goldfish.profile.json
Retention schedule retention.py (readable_level, unreadable_after) device.key_curve() → key chains in keys.py
Enforcement §5.3 (requirement) keys.py + store.py (ATECC608 chains; per-voice keys: backlog B-26)
Indicator §10 (requirement) display.py / goldfish.js ring, who-is-speaking and the room-off glow; mic-rail eye light (hardware)
UI intents §7.1 (source: "ui") device.press_eye / goldfish.js pressEye: the eye button lowers the room or resumes it; a hold arms the hand-off
Hand-off §10.3 (binding) handoff.py g1 notes over NFC
Conformance vectors/, conformance.py, conformance.js v01/firmware/tests/test_device.py + test_scenario.py

Rules of the split

  1. Nothing in protocol/ may import from v01/ or mention Goldfish hardware.
  2. Goldfish never re-implements a rule. It configures one (profile) or binds a port (display, store, hand-off).
  3. A behaviour change to the rules starts as a vector in protocol/vectors/, then both reference implementations change together; v01/validate.py runs both.
  4. Languages are data: a new locale is a JSON grammar plus its phrase vectors.

Decisions: ADR-0010, ADR-0011, ADR-0012, ADR-0013 (the eye button: device.press_eye, display.ring_frame(room_off=True)).

Parity: v01/validate.py runs both reference implementations against the vectors and a differential fuzz (protocol/tools/fuzz_parity.py). Light-ring parity between display.py and goldfish.js is checked the same way.

# Code architecture

Two layers. The **protocol** is device-independent and meant for adoption by others. **Goldfish** is one profile of it, plus the bindings to its hardware.

```
                ┌──────────────────────────── protocol/ (Resolution Protocol) ────────────────────────────┐
 speech ──────► │ grammar: locale JSON → intents ──► session: voices · records · levels ──► retention    │
 (any port)     │   (en, es, …)                       (rules §6, events §11)              (schedule §5)  │
                │ reference: python/resolution_protocol  ·  js/resolution.js  ·  vectors/ (conformance)  │
                └───────────────▲──────────────────────────────▲─────────────────────────────▲──────────┘
                                │ profile (parameters + locales)│ events / applied levels     │ max_level, unreadable_after
                ┌───────────────┴──────────────── v01/ (Goldfish profile + bindings) ─────────┴──────────┐
                │ profile/goldfish.profile.json   — default 3, invocation "goldfish", en+es, 3 d/30 d/1 y   │
                │ firmware/goldfish/device.py     — loads the profile and grammars, owns the session        │
                │   display.py  ring frames (indicator)   keys.py + store.py  key chains (store, §5.3)       │
                │   handoff.py + st25dv.py  NFC note (hand-off)          RTC (clock)   mics/ASR (audio in)   │
                │ firmware/js/goldfish.js         — the same bindings for the workbench site                │
                └──────────────────────────────────────────────────────────────────────────────────────────┘
                                │
                site/ (workbench) — runs protocol/js + goldfish.js in the browser; Rules tab runs the vectors
```

## Where things live

| Concern | Protocol (shared) | Goldfish (profile + bindings) |
|---|---|---|
| Levels, rules, voices, records | `protocol/SPEC.md` §3–6, `session.py` / `resolution.js` | — |
| Phrases → intents | `protocol/locales/*.json` | `invocation: ["goldfish"]`, `locales: ["en", "es"]` |
| Parameters | schema `profile.schema.json` | `v01/profile/goldfish.profile.json` |
| Retention schedule | `retention.py` (`readable_level`, `unreadable_after`) | `device.key_curve()` → key chains in `keys.py` |
| Enforcement | §5.3 (requirement) | `keys.py` + `store.py` (ATECC608 chains; per-voice keys: backlog B-26) |
| Indicator | §10 (requirement) | `display.py` / `goldfish.js` ring, who-is-speaking and the room-off glow; mic-rail eye light (hardware) |
| UI intents | §7.1 (`source: "ui"`) | `device.press_eye` / `goldfish.js pressEye`: the eye button lowers the room or resumes it; a hold arms the hand-off |
| Hand-off | §10.3 (binding) | `handoff.py` g1 notes over NFC |
| Conformance | `vectors/`, `conformance.py`, `conformance.js` | `v01/firmware/tests/test_device.py` + `test_scenario.py` |

## Rules of the split

1. Nothing in `protocol/` may import from `v01/` or mention Goldfish hardware.
2. Goldfish never re-implements a rule. It configures one (profile) or binds a port (display, store, hand-off).
3. A behaviour change to the rules starts as a vector in `protocol/vectors/`, then both reference implementations change together; `v01/validate.py` runs both.
4. Languages are data: a new locale is a JSON grammar plus its phrase vectors.

Decisions: [ADR-0010](decisions/0010-each-voice-owns-its-record.md), [ADR-0011](decisions/0011-protocol-and-profile.md), [ADR-0012](decisions/0012-rp-0-2.md), [ADR-0013](decisions/0013-eye-button-and-kit-build.md) (the eye button: `device.press_eye`, `display.ring_frame(room_off=True)`).

**Parity:** `v01/validate.py` runs both reference implementations against the vectors and a differential fuzz (`protocol/tools/fuzz_parity.py`). Light-ring parity between `display.py` and `goldfish.js` is checked the same way.