# ADR-v2-152 — Biosignal switches: artifact-grade EEG/EMG/EOG events under a measured false-positive budget

**Status:** Proposed (2026-10-29)
**Deciders:** Mohsen Seyedkazemi Ardebili
**Context links:** [[adr-v2-129-killer-features-10x]] (the "Rejected" paragraph this ADR narrows and
clarifies, and the activation-source seam it built), [[adr-v04-003-emg-serial]] (the serial EMG
source), [[adr-v2-135-face-gesture-switch]] and [[adr-v2-147-face-switch-intent]] (the same
"detector emits an abstract event, never an action" boundary), [[adr-005]] (`InputBackend`
Protocol), [[adr-011]] (zero telemetry), [[adr-019-egress-inventory-and-escalation]] (egress),
[[adr-021-invest-in-error-cost]] (rule 9: a guard is judged on how rarely it fires),
[[adr-v2-148-hands-free-composition]] (the one global pause/kill state this source must obey),
[[adr-v2-153-biosignal-false-positive-governor]] (the governor mechanism in detail), research:
`docs/research/muscle-brain-control.md`, spec: `design/specs/biosignal-pipeline.md`, field protocol:
`design/specs/biosignal-validation-protocol.md`

## Context

Three pieces of EMG/EEG plumbing already exist, and none of them is a pipeline:

- `EMGBackend` (YESP over serial) is a finished `HotkeyBackend` duck-type, but the device does the
  signal processing. The daemon sees only `HOLD_START` / `HOLD_END` and cannot measure, tune or
  gate the signal behind them (`design/emg-protocol.md`).
- `platform/emg/brainflow_source.py` reads BrainFlow boards (OpenBCI, Muse and others) and feeds
  graded squeeze logic (`platform/emg/pressure.py`). It is one source, one detector, and no
  signal-quality gate.
- The activation seam (ADR-v2-129) and the intent path (`activation/intent.py`) accept any
  non-keyboard trigger, but only a source that already speaks "start / end / label" can use them.

Raw electrodes have a harder problem than the keyboard they replace. The research page
(`docs/research/muscle-brain-control.md`) records it: the only reliable switches a consumer EEG
headset yields, blink and jaw clench, are **eye and muscle artifacts**, not brain signals; the best
measured blink switch is 99.5% accurate at 0.10 false positives per minute, which is about 50
phantom activations in a workday; and a jaw clench is confounded by chewing and by *talking*, which
is fatal in a dictation tool. A dedicated EMG electrode on the muscle measures the same activity
directly and strictly dominates on signal-to-noise, with a false-activation rate you can tune. The
same page also notes that Meta's *Nature* paper reports no false-activation rate at all, and that
the rate is the number that decides whether a trigger is livable.

So the question is not "can an electrode produce a trigger" (it can) but "can we ship one whose
false-activation rate is *measured, bounded and enforced* on the user's own machine".

ADR-v2-129's Rejected paragraph reads: consumer-EEG "thought" triggers are rejected, "artifact-grade
only; ~0.10 FP/min best case ≈ dozens of phantom mic-openings a day — strictly dominated by the EMG
electrode". That sentence is easy to over-read as "no EEG hardware, ever". This ADR keeps its
intent and states the boundary precisely.

## Decision

Add an opt-in `yazses.biosignal` package, `[biosignal] enabled = false`, that turns multichannel
electrode samples into a **small closed set of artifact-grade events**, behind the existing
activation seam, and that holds itself to an explicit, measured false-positive budget.

### 1. Scope: artifact-grade switches only

The package may report exactly these events (`EventKind`): squeeze on/off (graded light/hard),
blink, double blink, jaw clench. Each is a *large, physical, deliberate-or-detectable artifact*:
a muscle burst, an eye closure, a clenched jaw. It may read them from EMG, EOG or the frontal
channels of an EEG headset, and it says which signal kind a channel is (`SignalKind`).

**This ADR does not reverse ADR-v2-129.** It narrows and clarifies it:

| Still rejected | In scope here |
|---|---|
| Decoding thought or intent from brain activity | Detecting a muscle burst, a blink or a jaw clench |
| Motor imagery, SSVEP, P300, any evoked-potential or cortical decoder | Energy and timing of the artifact itself |
| Any classifier whose output depends on what the user is *thinking* | Thresholds on a signal the user *did* with a muscle |
| Silent-speech or open-vocabulary decoding from sEMG | (unchanged: not in this package) |

If a future change wants a decoder from the left column, it needs its own ADR and its own evidence,
and it does not get to cite this one.

### 2. The false-positive budget is part of the feature, not a metric beside it

Following ADR-021 rule 9 ("hold only on a specific, checkable signal, never a heuristic"), the
package ships four defences against its own mistakes:

1. **A signal-quality gate suppresses events.** Flatline, saturation, mains interference, baseline
   drift and sample dropout each mark a window "not trustworthy", and no event is emitted from an
   untrustworthy window. A detached electrode must produce silence, never a trigger.
2. **Detectors are conservative by construction.** Double-threshold onset/offset with a frozen
   baseline, a max-hold watchdog that ends a hold which never releases, and a refractory period.
   The jaw-clench detector has a **speech guard** because talking is the confound the research page
   names; the package does not pretend to solve it, it declines to fire when speech is present.
   The guard is a suppressor: it may miss a clench, never invent one, and a guard that fails
   (raises) is treated as "speaking". The guard is a callable the integrator supplies. The daemon
   passes the dictation state (no clench while a turn is being recorded); `speechguard.py`
   combines several such sources (recording, VAD, text-to-speech playback) with a hangover tail and
   a stuck-source watchdog, so a flag wedged at "speaking" cannot lock the switch out for ever. All
   of it reads *audio-side* state. A test on the electrode signal itself (spectral shape of the
   jaw channel) is not part of this decision (see "Known limits").
3. **An activation ledger measures the cost.** Every activation is recorded locally. An activation
   the user explicitly cancels or undoes within a short grace window (design default 3 s)
   counts as *cancelled*; an activation that is merely not followed by anything is **not** counted,
   because silence is just as likely a correct, uneventful activation. The cancelled fraction is a
   proxy for false positives and is reported with a Wilson confidence interval so a small sample
   is not read as a result. It is a **proxy**, not ground truth; the docs say so wherever it is
   shown. Rates are per *active* exposure time, so a paused, gated or suspended period never
   dilutes them.
4. **A budget governor acts on the ledger.** `max_cancelled_per_hour` bounds cancelled activations.
   Over budget (judged on a conservative lower bound of the cancelled *rate*, with a minimum
   amount of evidence, not on the displayed fraction), the governor first *desensitizes* (raises
   thresholds), and only if that is not enough *suspends* the source and says so. Suspension
   recovers by itself after a timer that grows on repeats, into the desensitized state, and ends
   at once on a single user action. A switch that keeps misfiring ends up off, loudly, rather than
   quietly annoying, and never off for good. The mechanism, states and alternatives are in
   [[adr-v2-153-biosignal-false-positive-governor]]; the wording "desensitize, then suspend" always
   means that escalation order.

The numeric thresholds, budget default and desensitization steps are **design choices to be set at
implementation and then measured**; this ADR fixes the structure, not the numbers, and no value may
be called recommended until the field measurement the research page asks for exists. Values in
code (for example the default budget of 6 cancelled activations an hour, which is the best
published blink switch, 0.10 false positives a minute, expressed per hour) are placeholders of
that kind.

### 3. The detector emits events, never actions

Same boundary as ADR-v2-147: a `StreamDetector` consumes `SampleBlock`s and returns `BioEvent`s. It
imports no daemon, injector, pointer or window code. A thin runner maps events onto the *existing*
seams: `on_mode_start` / `on_mode_end` (the callbacks `EMGBackend` and `BrainFlowSource` already
feed) and **labels-only** intents through `activation/intent.py`. Because intents go through the
ordinary command grammar and its confidence-times-consequence gate, nothing here can run an
irreversible action without the confirmation a spoken command would need. Unmapped events do
nothing.

### 4. Everything is local, optional and lazily imported

`numpy` is the only hard requirement of the pure modules (filters, detectors, quality gate,
ledger). BrainFlow stays behind the existing `emg-band` extra and is imported only inside the
source that needs it, so a base install imports and runs unchanged (AGENTS.md rule 3). Pure logic
sits beside injected backends (rule 5): detectors are tested on synthetic and replayed signals with
no hardware. Hardware sources degrade to a logged no-op when the device or library is absent, as
the serial and BrainFlow paths do today.

### 5. Per-person calibration drives the detectors; it is not optional

Fixed microvolt thresholds are meaningless across people, placements and days. The calibration
session (`calibration.py`) derives reference levels from the user's own relaxed baseline and
contractions and writes a local profile with no raw samples. The EMG switch is **off** until a
profile with a maximum-contraction reference exists: it is refused rather than guessed. Blink and
jaw-clench detectors adapt to a per-person reference only through hooks that the calibration
exposes (a constructor from calibration values, and a runtime sensitivity scale that the governor
turns up and back down). Where a detector does not yet take calibration values, it runs on its own
conservative, self-estimated baseline and the docs say so.

### 6. The source obeys the global pause, and the governor's release is one action

Hands-free composition (ADR-v2-148) has one global ACTIVE / PAUSED / FAULTED state. A biosignal
source is one more hands-free input: while the global state is not ACTIVE the daemon ignores the
source's squeezes and intents, and a squeeze already held when a pause arrives is released at once
and latched off until the signal relaxes, so the pause cannot be followed by a stale hold. The
source reports a heartbeat to the gate while blocks arrive, so a device that goes quiet ages out
of the global state like a camera that goes blind. This is enforced at the daemon seam, in the
adapter callbacks; the package stays free of daemon imports. The runner exposes a small public
API (`ledger`, `pipeline`, `note_cancelled`, `governor_release`, `save_ledger`, `status`) so the
daemon never reaches into private attributes. An explicit Resume of the global state also releases
the governor: the governor's release is one
action (ADR-v2-153). The pipeline keeps running while the global state is paused (only the
daemon-side effects are gated), but the daemon pauses the runner's exposure accounting
(`pause_accounting()` / `resume_accounting()`) on the gate's transitions, so a paused period does not
accrue as ledger exposure and cannot dilute the measured rate.

### 7. Measurement is built in: the ledger survives a crash, and an evaluation harness scores the detectors

- **The ledger is persisted as it goes**, not only on a clean shutdown (`[biosignal]
  ledger_autosave_seconds`), as aggregate counters only (sessions, active seconds, activations and
  cancellations per event kind; no raw signal, no per-event record). Each save adds only what
  changed since the previous save to what the file holds at that moment, so repeating it neither
  double counts nor overwrites totals written by another process. A daemon that is killed loses at
  most one interval of the measurement the feature exists to produce.
- **An evaluation harness** (`evaluate.py`) scores a detector or pipeline against a recording with
  scripted events (recall, precision, latency) and counts phantom activations per hour on a stream
  with nothing happening, with Wilson and Poisson intervals. Its report states on every run that
  synthetic results prove the logic and not real-world accuracy.
- **Commands to record, evaluate, field-test, inspect profiles and boards** sit beside the first five
  (`doctor`, `calibrate`, `monitor`, `replay`, `report`); see the spec for the current command set.
  A suspended switch is released by the global `yazses handsfree resume`; there is no
  `yazses biosignal resume`. A table of known boards (`boards.py`) is
  advice for choosing a `source` string, built only from ids read from the installed BrainFlow
  package; it opens nothing.

## Alternatives rejected

- **Reverse ADR-v2-129 and ship "EEG control".** No measured number in the repository supports a
  livable false-activation rate for thought decoding on consumer hardware; BCI inefficiency (some
  users never produce usable patterns) makes it a feature that silently does not work for part of
  its audience. Rejected again, on the same evidence.
- **Keep one detector per source and no shared pipeline.** That is what exists. It cannot gate on
  signal quality, share calibration, or measure a false-positive rate across sources, and each new
  board would re-implement a threshold.
- **Do the processing on the microcontroller (YESP only).** Right for the DIY serial device and
  still supported, but it leaves the daemon blind: no quality gate, no ledger, no budget. The new
  pipeline covers the raw-sample case; YESP is unchanged.
- **A trained classifier for blink/clench.** More accuracy on a lab set, but it needs a training
  corpus we do not have, hides the failure mode, and cannot be explained to a user whose switch
  misfired. Thresholds on interpretable features can be.
- **Fire first, report the false-positive rate later.** ADR-021's lesson: a guard that fires too
  often teaches users to dismiss it. The budget must act on the user's machine, not sit in a paper.
- **Upload recordings to tune detectors centrally.** Excluded by ADR-011 and ADR-019; see below.

## Consequences

- EMG/EOG/EEG-artifact switches become measurable on the user's own machine: the proxy rate, its
  interval and the budget state are visible (`yazses biosignal report`, `monitor`).
- A new optional section, a new `biosignal` command group and a new package. All dormant by
  default; an existing install is unchanged (AGENTS.md rule 2). Config keys and the CLI surface are
  public interfaces and so are L3 work: the generated reference docs are regenerated with them.
- **Biometric-adjacent data.** Raw recordings and calibration profiles describe a person's
  physiology. They are classified accordingly in the spec: stored locally, profile file mode 0600,
  never transmitted, never part of a diagnostics bundle by default.
- **Honest limitation, carried into every user-facing page:** blink and jaw clench are artifacts.
  Talking confounds the jaw clench. Thresholds are per person and per electrode placement, so
  calibration is mandatory and a re-seated electrode needs a new one. The shipped structure is
  reasoned; the numbers are unmeasured until community reports exist.
- The package gives the research page's open question 1 ("measured false-activation rates") a way
  to be answered with real data rather than anecdotes.
- Cost: a polling loop and a few filters while the source runs. Off means zero cost.

## Egress

**No new egress path exists.** Nothing the package reads is sent anywhere. Sources are a local
serial port (BITalino, NeuroSky ThinkGear), a local BrainFlow device (USB/Bluetooth to the machine,
not the network), a Myo armband over Bluetooth LE, a Lab Streaming Layer stream published on this
machine, a file the user names, or a synthetic generator. Recordings, profiles and the ledger are
written to local disk only. Nothing is added to the nine registered egress paths of ADR-019.

Two entries in ADR-019 are about this package, and neither is an egress path. `biosignal/myo.py`
imports `asyncio` to drive a Bluetooth LE radio, recorded as `LOCAL_BOUND` beside the existing
BLE backend. `biosignal/lsl_source.py` opens no socket itself, but liblsl under it does: discovery
is pinned to this machine (`ResolveScope = machine`, loopback only, TTL 0) before the first liblsl
call, a stream from another host is refused, and a test fails if the pin is removed (ADR-019,
"Reached through a dependency's own sockets"). If a board that streams over a network socket is
ever supported (OpenBCI Galea, the Wi-Fi shields and BioListener in BrainFlow are UDP/Wi-Fi), that
is a new egress decision under ADR-019 and needs its own review. This is enforced, not assumed:
`brainflow:<id>` opens only the ids listed in `biosignal/boards.py`, and refuses every other id with
an error, so an unlisted Bluetooth or USB board needs an entry there too. The LSL pin holds only
if YazSes makes the process's first liblsl call. Nothing else in YazSes loads liblsl, but a
third-party plugin that did so first would have fixed the configuration. (BrainFlow's synthetic board and the in-package synthetic
source need no hardware.)

## Open questions / known limits

What is still unmeasured or unproven, stated plainly so no page overstates it:

- **Thresholds are untuned on real recordings.** Every number in the detectors, the quality gate,
  the calibration and the governor is a design choice validated on synthetic signals. None is a
  measured operating point, and none may be called recommended.
- **Validation is synthetic only.** The test and evaluation signals are generated. They show that
  the logic does what it says; they do not show real-world sensitivity, false-positive rate or
  latency on a person with a real board in a real room.
- **The cancelled fraction is a proxy.** It undercounts false positives the user does not notice
  and overcounts cancellations that have nothing to do with the switch. It is shown with an
  interval and a caveat; it is not an error rate.
- **The governor needs the integrator to report cancels.** If no undo or cancel is reported, the
  budget never acts. That fails safe but is a no-op that must be visible.
- **Speech suppression reads audio-side state only.** The speech guard fires on the dictation
  state, a voice-activity flag and playback, not on the jaw electrode itself. If those sources are
  absent or late, a clench during speech can still pass.
- **Per-person adaptation is only as good as the calibration the person could produce.** The blink
  and jaw-clench detectors take calibration values and a sensitivity scale, and the pipeline refuses
  a stale or mismatched profile, but all of that is validated on synthetic signal only; whether
  the intended users can perform the calibration gestures is unmeasured. A blink-detector warm-up
  defect found in review is fixed (see the spec).
- **Hardware coverage.** Only the synthetic source runs without hardware. BrainFlow boards are
  reached through BrainFlow's own support and are not individually verified here. The LSL, ThinkGear,
  BITalino and Myo sources and the EDF/BDF/XDF readers were built from published protocol documents
  and tested with byte-level fixtures, fakes and two independent implementations (pyedflib, pyxdf);
  none has run against a real device. Myo and the gForce boards report counts, not microvolts, so the
  quality gate's microvolt limits do not hold there.
- **Time-of-day, fatigue and electrode drift** over a workday are not modelled; a re-seated
  electrode needs a new calibration.

## Evidence and what it does not show

This ADR rests on reasoning and on a small amount of published literature, all of it recorded on the
research page (`docs/research/muscle-brain-control.md`, which grades each reference *measured*, *vendor*
or *secondary*). The figures below are quoted as that page records them; this ADR did not re-derive them
from the primary papers. What each one supports, and what it does not:

| What is cited | What it supports | What it does **not** show |
|---|---|---|
| An EOG blink switch at 99.5% accuracy, 1.3 s response and **0.10 false positives per minute** (Li et al., *Neurocomputing* 2018; *measured*) | That an artifact-grade switch can be accurate, and that the **best published** false-positive rate for a blink switch is of this order, about 50 phantom activations in a workday | That our detector reaches it; that it holds on a headband rather than the study's electrodes; that it holds over a workday of talking and typing; that 0.10/min is *livable* (the research page calls it a fatal problem without extra safeguards). It is a ceiling to beat, not a result we have. |
| A breath-hold PPG toggle at **0.02 false operations per minute** (Han et al., *Sensors* 2020; *measured*) | That a different physiological channel can be five times quieter than a blink switch | Anything about EMG or EOG. It motivates choosing the channel where the action is loudest; it supplies no number for ours. |
| Meta's *Nature* 2025 sEMG wristband (Kaifosh et al.; *measured*): large-scale offline gesture decoding on held-out users | That surface EMG gestures generalise across many people in an offline evaluation | A false-activation rate. As the research page records, the paper reports **no false-activation rate**, which is the number that decides whether a trigger is livable. Its hardware is also not available to this project (no raw EMG, no Linux, ADR-v2-129). |
| "Jaw clench about 90%, sub-second possible" (a row of the research page's table) | Nothing citable: that row carries no reference on the page | Do not quote it as evidence. The same table names the real problem, chewing and talking, and no cited source measures the rate under them. |
| BCI inefficiency (Tibrewal et al.; *measured*) | Why thought decoding stays out of scope: some users never produce usable patterns | Anything about artifact switches. |
| **This repository's own results** | That the code does what the spec says: the quality gate suppresses, the watchdog closes holds, the governor escalates in order, the ledger counts correctly. Synthetic and replayed signals only. | Anything about a human body: no phantom-activation rate, no hit rate, no latency, no calibration time, and no behaviour on any board has been measured on a real person. However large the synthetic test suite is, it is evidence about the logic, not about physiology. |

Taken together: **the literature establishes that the idea is not absurd and that the false-positive rate
is the number that matters; it does not establish that this implementation meets any rate, and the
repository has no real-signal evidence at all.** The structure this ADR decides (gate, budget, ledger,
measurement) is the response to that gap. It is not a claim that the gap is closed.

Further unknowns that no cited source settles: how the cancelled-activation proxy relates to labelled
phantom activations (never compared); how far a person's rate varies day to day and electrode re-seat to
re-seat; whether the speech guard, which reads audio-side state, is enough in real dictation; and whether
the intended users, who matter most, can produce the gesture the calibration asks for as reliably as the
volunteers any first study will be able to find.

## Falsifiable acceptance criteria

The status stays **Proposed** until a real-hardware field test, run under
`design/specs/biosignal-validation-protocol.md`, meets the gates below. They are **design choices**, set
before any data exists so they cannot be bent to fit data; they are not derived from a clinical or
usability standard, and they can be changed only by a recorded amendment made *before* the run they
govern. Moving the status is a maintainer decision recorded as a dated amendment to this ADR; passing the
gates makes the move permitted, not automatic.

Common conditions for every gate: thresholds, budget and mapping frozen before the evaluation round (no
tuning on the data being judged); each participant calibrated for their own configuration; bounds
computed as the protocol states (a one-sided 95% Poisson upper bound for rates per usable hour, a
two-sided 95% Wilson interval for proportions, read at the **conservative** end); results reported per
configuration and per participant beside the pool.

**Tier 1: Proposed to Accepted.**

| # | Gate | Pass condition |
|---|---|---|
| A0 | Sample | At least 5 participants, using at least 2 distinct board or electrode-site configurations, each configuration with at least 2 participants. |
| A1 | Phantom activations | In each of the phases `rest`, `hands_busy` (typing), `speech` and `chew` of the field test, the pooled one-sided 95% upper bound on phantom activations per usable hour, over all trigger kinds the configuration enabled, is at most **6 per hour**. With no phantom seen that needs about half an hour of pooled usable signal in the phase (rule of three), more if any were seen; the report prints the extra hours needed. The 6 per hour is the best published blink switch (0.10 per minute) expressed per hour, which makes the gate "no worse than the best published artifact switch", a floor and not a statement that 6 an hour is pleasant. The per-kind counts are reported beside it. |
| A2 | Hit rate | The two-sided 95% Wilson **lower** bound on deliberate-gesture hits (pooled scored cues of the `gesture_*` phases) is at least **90% for the squeeze** and at least **80% for the double blink**, which with no misses needs about 35 and 16 scored cues respectively (a default session scores 8 per switch). This is stricter than the tool's own advisory `usable` verdict, which judges the hit rate on the point estimate. |
| A3 | Calibration time | At least 80% of participants reach a written profile within **5 minutes of wall-clock time and 3 attempts** from the start of `yazses biosignal calibrate`, electrode seating included. (The guided phases themselves are about half a minute of recorded data at the default timings in `CalibrationConfig`; the ceiling is for the person, not the code.) Participant counts are small, so the proportion is reported with its interval and read on the point estimate. |
| A4 | Safety | **Zero stuck-mic events** in the full stress list of the protocol on real hardware (cable pull, USB unplug mid-squeeze, daemon restart mid-hold, pause mid-hold, governor suspension mid-hold) and in all of the participants' live use (the field-test command itself drives nothing, so it cannot see a stuck microphone). One event fails the gate; the fix restarts the list. |
| A5 | Governor behaviour | Judged in live use, because the field test acts on nothing and reports no cancels, so it cannot move the governor: in the participants' own days with the daemon and ledger on, the governor does not leave `OK` for those who meet A1; in one deliberately noisy live session it escalates in order (desensitize, then suspend); and a single release returns it to `OK` from each state, every time it is tried. Details: ADR-v2-153, "Falsifiable acceptance criteria". |
| A6 | Privacy | The shared field-test JSON contains no signal, no recording, no profile and no per-event timestamps, confirmed by reading the schema and a sample file. |

**Jaw clench (separate, optional).** It is off by default and stays off unless it meets A1 and A2 in
`speech` and `chew`, with the speech guard on. Chewing is itself a clench, so failing is a plausible
outcome and is not a failure of the rest of the feature: the squeeze can reach Accepted while the jaw
clench is documented as unsupported.

**Tier 2: leaving "experimental".** The feature stays labelled experimental on every page until all of
Tier 1 holds **and**: at least 10 participants on at least 3 configurations, including intended users of
the feature (people for whom a key press is difficult), who take part voluntarily and are asked for no
health information; a phantom upper bound of at most **2 per usable hour** in `rest` and `hands_busy`; at
least 5 days of each of several participants' own use with the live ledger on (the cancelled-activation
proxy, shown against the labelled field-test counts); and no gate regression after the last code change
to a detector, the quality gate or the governor. Leaving "experimental" removes a label. It does not make
the feature default-on: AGENTS.md rule 2 (new features ship off by default) is not relaxed by a field
test.

## Kill criteria

A feature that cannot meet its own budget should stop, not drift. These are decisions recorded in
advance; each is a design choice and applies to the **tested hardware and code revision**, not to
electrodes in general.

1. **Budget unreachable on a board.** If a board or electrode-site configuration still exceeds A1 after
   **2 tuning rounds** (each judged on a fresh round after its configuration was frozen), it is recorded
   as failing and removed from the documented hardware. If **every** configuration tested fails A1 for
   the squeeze, the biosignal switch is withdrawn as a user feature: the package is reduced to the
   evaluation harness and the replay tools, and the user pages are rewritten to say so. Blink and jaw
   events are withdrawn first (the likelier to fail), and the squeeze on a dedicated muscle electrode is
   the last thing standing, because it is the case the research page argues for.
2. **Calibration is the barrier.** If fewer than 60% of participants reach a profile within the A3
   ceiling after 2 rounds of calibration-procedure changes, the per-person calibration design is wrong
   and needs a new ADR before any other work; the feature does not proceed on a calibration most people
   cannot complete.
3. **A stuck-mic event that cannot be designed out.** If a stuck-mic event recurs after its fix and the
   cause is a property of the hardware path rather than a defect (for example a driver that reports a
   healthy stream while delivering stale data), the affected source type is removed, not guarded
   further.
4. **The governor's evidence is unusable.** If the field-test shows the cancelled-activation proxy does
   not move with labelled phantom activations (silent when phantoms are frequent, or loud when there are
   none), the proxy-driven governor is retired or replaced with a different mechanism by a new ADR. The
   measurement and the one-action release stay.
5. **No evidence.** If no real-hardware field result exists after **two minor releases** from the first
   release containing the feature, a maintainer reviews the status and either extends with a stated
   reason or withdraws the user-facing pages. A feature nobody can measure is not kept alive on the
   strength of its test suite.
6. **Scope creep.** Any request to turn the feature into a decoder of thought, intent, emotion or health
   is out of scope under section 1 and is not a "next step" of this ADR (it needs its own ADR and
   evidence, and may not cite this one).

## Implementation status

Verified against the tree when this ADR was last updated. The status line stays "Proposed" until
field measurement exists; an item here can be present in code and still be unmeasured.

| Item | State |
|---|---|
| Package, quality gate, detectors, calibration session and profile, sources, pipeline, runner, daemon wiring, ledger, governor, `doctor` / `calibrate` / `monitor` / `replay` / `report` | present |
| Speech guard combiner (`speechguard.py`, used by the daemon, config `speech_guard`), evaluation harness (`evaluate.py`), global-pause handling in the daemon adapter, public runner API, periodic ledger autosave (`ledger_autosave_seconds`), board table (`boards.py`), commands `record` / `evaluate` / `boards` / `profile` / `profile-clear` | present |
| Calibrated blink/clench detectors with a runtime sensitivity scale, and the blink warm-up fix | present (synthetic-signal validation only); the spec's "Round-2 status" table is the authority |
| Round-3 hardening: squeeze artifact blanking and channel agreement; block-size-independent jaw timing and an opt-in chew hold-off; per-hop quality evaluation with lead-off, common-mode and contact score; calibration provenance enforcement (`calibration_max_age_days`); `reset(keep_governor_state=...)`; release survives one evaluation; accounting pause/resume; richer `status()`; `watchdog_seconds` and `min_event_confidence`; daemon status block and ordered shutdown | present (synthetic-signal validation only); the spec's "Round-3 status" table is the authority |
| `fieldtest.py` and `yazses biosignal field-test`, the doctor `Verdict:` line, and the validation protocol | present as tooling and a written protocol. **No field-test result exists.** The gates above are unmet; the status stays Proposed. |

## Related work in the repository

The HCI × Hugging Face research track (epic #692, draft PR #711, `design/hci-huggingface/EEG_EOG_FEASIBILITY_PROTOCOL.md`
on branch `feat/hci-research-metrics`, not yet on `main`) specifies a staged feasibility study of the same
kind of switch: synthetic baseline, injected artifacts, then a gated human pilot. This ADR and that
protocol agree on the measure that matters (false activations per hour over scored exposure) and on the
limits (artifact-grade activation only; no thought, emotion or attention inference; no flicker stimuli).
The mapping, and the one definition still to be aligned (the minimum exposure before an hourly rate is
shown), is in `design/specs/biosignal-validation-protocol.md`. That protocol states that a runtime
adapter needs its own approved ADR and real-user evidence; this ADR is that ADR and it is **Proposed**,
with the adapter present in code but dormant and off by default. The acceptance gates above are the
evidence still owed, and the "human pilot" gate there applies to any pooled multi-participant use of the
field test.

## Not decided here

- Default thresholds, budget and desensitization schedule (measure first).
- Whether a graded third squeeze level is learnable (research page, open question 2).
- Additional boards beyond what BrainFlow already supports.
- Any silent-speech or label-emitting decoder; the intent path carries labels, this package emits
  only the fixed event set above.
