Applies the gen2 desktop-PTT review summary
(`gen2/chanora-desktop-ptt-review-summary-v0.9.2.md`) to our doc set
with the owner rulings PTT-OPEN-001 through PTT-OPEN-006 resolved as
accepted decisions DEC-023 through DEC-028:
* DEC-023 Windows Global PTT P0 / MVP
* DEC-024 macOS Global PTT P0 / MVP with permission UX
* DEC-025 Linux officially-tested env: GNOME on Wayland only
* DEC-026 Mouse side buttons supported (Win + macOS; Linux portal)
* DEC-027 PTT diagnostics: capability + availability only, no
raw key codes ever
* DEC-028 Missed-key-up watchdog: P0
Requirements (SysRS / SRS) and architecture (SysDes / SAD / SDD)
gain the desktop-PTT ID set the gen2 summary describes:
SysRS-296..302 -> SysDes-142..148
-> SRS-195..203
-> SAD-071..079
-> SDD-081..092
ID totals advance from 295 / 141 / 194 / 70 / 80 to 302 / 148 / 203
/ 79 / 92. The strict layered sourcing rule (`SRS -> SysDes` only,
`SAD -> SRS` only, `SDD -> SAD` only) is preserved; the
`tools/validate_docs.py` validator reports zero undefined refs and
zero direct-layer-rule violations.
New document:
* `docs/architecture/desktop-ptt-architecture.md` — capability
ladder (L0Focused, L1GlobalShortcut, L2GlobalHoldToTalk,
L3GlobalWithMouseButtons, L4DeviceAware reserved), Windows /
macOS / Linux strategies, privacy rule, audio-gate rule,
missed-key-up watchdog, release-readiness evidence requirement,
traceability summary.
Doc addenda (Baseline Candidate 0.9.3):
* `privacy/privacy-policy.md` — no raw key history, capability-
dependent Global PTT, UI reflects actual runtime capability
* `security/threat-model.md` — THREAT-PTT-001..006
* `security/diagnostic-redaction-audit-report.md` —
REDACT-PTT-001..006 banned field list enforced by `PttSanitizer`
* `release/platform-release-policy.md` — per-platform evidence
fields, no over-claim on untested Linux compositors
* `release/release-readiness-go-nogo-record.md` — RR-PTT-001..008
release-readiness items
* `verification/swe4-unit-verification-plan.md` —
SWE4-UV-035..039
* `verification/swe5-software-integration-verification-plan.md` —
SWE5-IV-015
* `verification/swe6-software-verification-plan.md` — SWE6-SV-017
* `verification/sys4-system-integration-verification-plan.md` —
SYS4-SIV-016
* `governance/traceability-matrix.md` — full PTT trace rows +
verification map
* `governance/decision-impact-assessment.md` — DEC-023..028
impact matrix
* `governance/product-decision-register.md` v0.9.9 entry
recording DEC-023..028 in the decision table and the status
table at §7
* `governance/document-index.md` — adds
`desktop-ptt-architecture.md` to the controlled set
* `architecture/proof-of-concept-plan.md` —
PoC-PTT-001..005 platform items
* `references/external-references.md` — Windows Raw Input,
macOS event-tap, Linux GlobalShortcuts portal references
* Both validation reports
(`baseline-candidate-validation-report.md`,
`repo-format-validation-report.md`) bumped to v0.9.3 with the
new ID totals (302 / 148 / 203 / 79 / 92).
README §"Desktop Push-to-Talk" added between Architecture Overview
and Repository Layout: capability levels, per-platform strategy,
privacy posture, missed-key-up watchdog.
Tooling:
* `tools/validate_docs.py` copied from the gen2 zip into the
repo tree (was previously available only inside the zip).
Reports zero undefined refs, zero direct-layer-rule violations,
English-only CJK check passes. The 35 "old package-style
filename" hits are pre-existing and identical to the gen2
baseline (they live in `path-migration-map.md` and config-ID
headers of governance docs and are intentional per the path
migration policy).
* `.gitignore` adds `/gen2/` so the externally-provided review
package does not enter the repo.
No code changes in this commit; B (the implementation split into
`transmit_active` / `capture_active`, `PttCapabilityLevel`
reporting, `PttSanitizer` diagnostics rule, and the UI capability
badge) follows in a separate commit.
10 KiB
Desktop Push-to-Talk Architecture
| Field | Value |
|---|---|
| Document type | Architecture |
| Version | 0.9.3 |
| Status | Baseline Candidate |
| Language | English |
| Product | Chanora |
| Date | 2026-05-15 |
Owner-resolved review questions from gen2/chanora-desktop-ptt-review-summary-v0.9.2.md are recorded as DEC-023 through DEC-028 in docs/governance/product-decision-register.md.
1. Purpose
Desktop Push-to-Talk (PTT) is the operation by which a user holds a bound keyboard key or mouse button to enable voice transmission and releases it to disable transmission. This document fixes the architecture so the product can describe, implement, verify, and release desktop PTT honestly — every capability claim made to the user is auditable against a live runtime measurement.
The previous architecture treated PTT as a single global behaviour. That over-promised on platforms where the operating system, the user-granted permission set, the display server, or the compositor does not permit unconditional global key capture. This document replaces that with a capability-based model.
2. Architectural Decisions
| ID | Decision | Source |
|---|---|---|
| DEC-023 | Windows Global PTT is P0 / MVP. | Owner ruling 2026-05-15 |
| DEC-024 | macOS Global PTT is P0 / MVP with explicit permission UX flow. | Owner ruling 2026-05-15 |
| DEC-025 | The officially-tested Linux environment for the first public release is GNOME on Wayland (only). | Owner ruling 2026-05-15 |
| DEC-026 | Mouse side buttons are supported in the first desktop PTT release on Windows and macOS; Linux support follows whatever the GlobalShortcuts portal exposes. | Owner ruling 2026-05-15 |
| DEC-027 | The diagnostic export carries capability and availability state only — no raw key codes, scan codes, or virtual-key values ever leave the device. | Owner ruling 2026-05-15 |
| DEC-028 | The missed-key-up watchdog is P0: the audio engine shall self-release transmit_active if the OS suppresses a key-up event. |
Owner ruling 2026-05-15 |
3. Capability Ladder
| Level | Name | Meaning |
|---|---|---|
L0Focused |
Focused PTT | PTT works only while the Chanora window has input focus. Mandatory on every desktop platform per SysRS-296. |
L1GlobalShortcut |
Global shortcut activation | The OS recognises a global accelerator and notifies the application, but hold-to-talk semantics may be approximated rather than guaranteed. |
L2GlobalHoldToTalk |
Global hold-to-talk | Press and release events are delivered while the application is not focused. The common Windows / macOS / GNOME-Wayland-portal MVP target. |
L3GlobalWithMouseButtons |
Global hold-to-talk plus mouse buttons | Includes mouse side buttons (Mouse4 / Mouse5). |
L4DeviceAware |
Device-aware PTT | Backend can distinguish specific input devices. Reserved. No MVP implementation produces L4DeviceAware. |
The reported capability shall match runtime behaviour. A backend that could deliver L2GlobalHoldToTalk but lacks the user-granted permission shall report L0Focused until permission is granted.
4. Component Allocation
+------------------------------------+
| Flutter Voice UI |
| PTT binding-capture sheet |
| VoiceBar PttCapabilityBadge |
+---------------+--------------------+
|
| set_binding(...)
| events_stream() -> BridgeEvent::PttCapability
v
+---------------+--------------------+
| chanora_bridge |
| typed DTOs, no key data crosses |
+---------------+--------------------+
|
v
+------------------------------------+
| chanora_core |
| PttController |
| owns Box<dyn DesktopPttBackend>|
| owns Arc<AudioTransmitGate> |
| publishes PttCapabilityLevel |
| MissedKeyUpWatchdog (tokio task) |
+---------------+--------------------+
|
v
+------------------------------------+
| chanora_audio |
| DesktopPttBackend trait |
| WindowsRawInputBackend |
| WindowsHookBackend |
| MacOSEventTapBackend |
| LinuxGnomeWaylandBackend |
| FocusedPttBackend |
| AudioTransmitGate |
| capture_active / transmit_active |
+------------------------------------+
|
v
+------------------------------------+
| chanora_diagnostics |
| RedactingLogLayer |
| PttSanitizer (drops banned keys) |
+------------------------------------+
The audio engine reads transmit_active once per outbound Opus frame. No code path other than AudioTransmitGate::set flips the value.
5. Per-Platform Strategy
5.1 Windows
Three-level ladder evaluated once at audio-engine start:
WindowsRawInputBackend— preferred. UsesRegisterRawInputDeviceswithRIDEV_INPUTSINKto receive keyboard and mouse events even when Chanora is not focused. Runs a message-only window on its own OS thread so the WndProc is non-blocking. ReportsL2GlobalHoldToTalk(keyboard) orL3GlobalWithMouseButtons(when a mouse side button is bound).WindowsHookBackend— fallback. InstallsWH_KEYBOARD_LLandWH_MOUSE_LLhooks on its own thread. Used when Raw Input registration fails (some constrained environments). ReportsL2GlobalHoldToTalk/L3GlobalWithMouseButtons.FocusedPttBackend— final fallback. ReportsL0Focused.
The chosen rung is fixed for the lifetime of the audio engine; restart of the engine re-evaluates the ladder.
5.2 macOS
Two-level ladder with explicit permission gating:
MacOSEventTapBackend— preferred when Input Monitoring (or Accessibility, depending on macOS version) permission isGranted. Creates aCGEventTapon the main run loop, filtered to keyboard and mouse-button events. ReportsL2GlobalHoldToTalk/L3GlobalWithMouseButtons.FocusedPttBackend— fallback when permission isDenied,Undetermined, or revoked at runtime. ReportsL0Focused.
The audio engine starts immediately on user request; the permission state is queried in parallel and the capability level is upgraded asynchronously through BridgeEvent::PttCapability if the user grants the permission. This avoids blocking voice functionality on a permission prompt.
5.3 Linux
Two-level ladder restricted to the officially-tested environment per DEC-025:
LinuxGnomeWaylandBackend— used whenXDG_SESSION_TYPE=waylandand the desktop environment is GNOME, and theorg.freedesktop.portal.GlobalShortcutsD-Bus interface is reachable. CallsCreateSession,BindShortcuts(delegates binding capture to the portal's own dialog), and listens forActivated/Deactivatedsignals. ReportsL2GlobalHoldToTalk; mouse-button support follows whatever the portal exposes for the current session.FocusedPttBackend— fallback on any other Linux environment (X11, sway, KDE, untested compositor, missing portal). ReportsL0Focused.
Per DEC-025 the application does not claim Global PTT support on an untested Linux environment. The UI capability badge explicitly notes "Focused PTT — untested compositor for Global PTT" when the user runs Chanora outside GNOME-on-Wayland.
6. Privacy Rule
Per SysRS-302 and SRS-202:
- Raw key codes, scan codes, virtual-key values, keysyms, and key-press timing sequences shall not be logged, persisted, or included in any user-initiated diagnostic export.
- The
PttSanitizerlog-sink decorator enforces this at write time by inspecting field names and dropping records whose field names match a banned list (key_code,scan_code,virtual_key,vk,keysym,keysym_string,key_sequence). The check is structural — it does not rely on a content scan. - The diagnostic export shall name only the capability level (
PttCapabilityLevel::as_str()), the backend identifier (a fixed&'static strper implementation), and the bound input class ("keyboard","mouse-side-button").
7. Audio Gate Rule
Per SRS-201:
capture_active: AtomicBoolis set by the audio engine on input-stream lifecycle transitions (stream opened or closed) and by the platform input-permission state. The PTT subsystem does not writecapture_active.transmit_active: AtomicBoollives insideAudioTransmitGate. The Opus encoder feed reads it once per outbound frame; the gate is the only mutator path.- A muted self-input (per
set_input_muted) forcestransmit_activeto false regardless of the PTT subsystem's wish; this preserves the existing self-mute semantics.
8. Missed-Key-Up Watchdog
Per SRS-203 / DEC-028:
- The audio engine spawns one
MissedKeyUpWatchdogtask per audio session. - It subscribes to
AudioTransmitGate'swatch::Receiver<bool>and notes the timestamp of eachfalse -> truetransition. - On each
true -> falsetransition it clears the timestamp. - If a
truelifetime exceeds the configured ceiling (default 30 s, owner-tunable through a future setting), the watchdog callsAudioTransmitGate::set(false)and emits a sanitised diagnostic line naming only the capability level and backend identifier.
9. Release Readiness
Per SysDes-148 and the release-readiness record:
- Every release artefact carries a per-platform capability-evidence row listing the detected
PttCapabilityLevel, the active backend identifier, and whether the Focused fallback was exercised during verification. - Release notes shall mirror that evidence and shall not over-claim Global PTT support.
10. Traceability
SysRS-296..302
-> SysDes-142..148
-> SRS-195..203
-> SAD-071..079
-> SDD-081..092
Verification coverage:
SDD-081..092 -> SWE4-UV-035..039
SAD-071..079 + SDD-081..092 -> SWE5-IV-015
SRS-195..203 -> SWE6-SV-017
SysDes-142..148 -> SYS4-SIV-016
11. Change History
| Version | Date | Description |
|---|---|---|
| 0.9.3 | 2026-05-15 | Initial baseline-candidate architecture for capability-based desktop Push-to-Talk. Codifies the owner rulings for PTT-OPEN-001 through PTT-OPEN-006 as DEC-023 through DEC-028. |