Commit Graph
13 Commits
Author SHA1 Message Date
Edison Jwa 01a4a9ed28 docs: add README files to 9 crates and update verification plan (TODO-030,036)
Add purpose, architecture, and public API summary to each crate
README following chanora_resolver pattern. Update verification master
plan with new evidence sources and entry/exit criteria.
2026-06-11 11:09:12 +09:00
Edison Jwa 8c4f85ee70 refactor: reuse built-ins and shared helpers 2026-06-08 20:19:17 +09:00
Edison Jwa 2c3c3873dc fix(diag): record bridge and audio interruption diagnostics (#18)
* fix(diag): record bridge events in diagnostics page

Connection lost/reconnecting/disconnected and iOS audio interruption
events now appear in the diagnostics dialog alongside existing error
snackbar entries.

* fix(diag): reduce audio callback sample verbosity to debug

The render callback diagnostic sample logged every 100 callbacks
(~2s) at INFO level, flooding the 256-entry release log buffer and
pushing out useful events. Changed to DEBUG so it only appears in
debug builds with the larger 4096-entry buffer.

* fix(diag): add timestamps to Rust diagnostic log entries
2026-06-03 18:09:25 +09:00
Edison Jwa fe6e07353e chore: restore product scaffold to rollback baseline 2026-05-29 14:02:04 +09:00
Edison Jwa 7d5d8c2c90 feat: integrate chat voice and diagnostics client 2026-05-23 06:51:55 +09:00
Edison Jwa 6af4ecab0f feat(voice): add iOS VAD runtime support 2026-05-21 20:51:45 +09:00
Edison Jwa 26b06ce786 fix(android): expose voice diagnostics safely 2026-05-20 14:52:33 +09:00
EdisonJwa 4c19410556 test+diag: SWE.4 unit tests for Rust paths, Dart service tests, diagnostics audio.android section
Verification + diagnostics:

- apps/chanora_flutter/test/services/back_intent_policy_test.dart:
  8-case truth table for the BackIntentPolicy pure function
  (SWE4-UV-042).
- apps/chanora_flutter/test/services/back_intent_service_test.dart:
  5 channel-routing tests (SWE4-UV-042, SWE5-IV-019 unit slice).
- apps/chanora_flutter/test/services/android_permissions_service_test.dart:
  12 tests covering inbound channel events, outbound requests,
  state-machine transitions, and non-Android short-circuit
  (SWE4-UV-041).
- SDD/SRS trace headers added to alpha_e2e_test.dart,
  beta_e2e_test.dart, widget_test.dart so the existing test ↔ ID
  mapping is discoverable by grep.
- chanora_diagnostics: extends DiagnosticExport with android_audio:
  Option<String> for the SDD-116 evidence schema (requested /
  achieved performance mode + sharing mode + input preset + sample
  rate + frames per burst, per-effect engagement, latency tier).
  The bridge's export_diagnostics() now embeds the Android section
  when current_android_audio_diagnostics() returns Some.

All 93 workspace Rust tests and 26 Dart test/services tests pass.

Trace: SDD-090, SDD-112, SDD-113, SDD-116, SWE4-UV-041, SWE4-UV-042,
SWE4-UV-045, SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051,
SWE4-UV-052, SWE5-IV-019.
2026-05-18 12:48:28 +08:00
Edison Jwa 7a59f5b9a1 feat(ios,p0): iOS P0 platform, audio fixes, channel UX 2026-05-17 22:00:00 +09:00
EdisonJwa 9831624079 feat(ptt): close P0 unit-boundary gaps (SDD-088 / SDD-090 / SDD-091)
The P0 audit on v0.9.4-docs found three SDD items whose specified
software units were inlined into other types rather than packaged as
named units at the SDD-defined boundary:

* SDD-088 PttController — backend ownership + binding mutex +
  capability watch lived split between AudioEngine and
  ChanoraSession. Extracted into chanora_core::ptt::PttController.
  AudioEngine now owns only the cpal streams and the missed-key-up
  watchdog (SDD-092); the platform input backend, the active
  PttBinding, and the capability watch::Sender live in the
  controller. ChanoraSession::start_audio constructs the controller
  against the engine's gate; disconnect/reconnect/restart paths
  tear it down through stop().await before the engine.

* SDD-090 PttSanitizer — banned-field check was inlined as
  PttBanCheckVisitor inside RedactingLogLayer::on_event. Extracted
  into a generic PttSanitizer<L> tracing_subscriber::Layer that
  decorates an inner Layer (canonical pairing:
  RedactingLogLayer::with_sanitizer). The inner layer keeps its
  own structural ban check as defence-in-depth for bare-install
  callers.

* SDD-091 PttCapabilityBadge — Voice Bar badge was anonymous
  Padding/Tooltip/Row inside _AudioControlsState.build. Extracted
  into a public PttCapabilityBadge widget and added the
  SDD-091-specified per-platform explanation sheet that opens on
  the info-icon tap when the resolved capability is L0Focused.
  New l10n strings (en + zh) cover the sheet copy.

Tests:
  * 2 new unit tests for PttController (arm + descriptor watch)
  * 1 new unit test for PttSanitizer (end-to-end through a real
    tracing subscriber proving banned drop + safe forward)
  cargo test --workspace: 55 passed / 0 failed / 3 ignored
  cargo deny check: advisories ok, bans ok, licenses ok, sources ok
  flutter analyze: no issues
  tools/validate_docs.py: zero undefined refs, zero direct-layer
    violations (pre-existing 35 old-package-name warning unchanged)

No SDD/SAD/SRS doc changes — the contracts already named these
units; this commit aligns code unit boundaries with those contracts.
2026-05-15 17:45:03 +08:00
EdisonJwa 7b21916049 feat(ptt): code-side initial split — transmit_active / capability badge / sanitizer
Implements the gen2 v0.9.3 doc baseline's first slice of code work:

  * SRS-201: split the audio engine's `ptt` AtomicBool into the
    authoritative `transmit_active` flag. The legacy `set_ptt` /
    `ptt` accessors are retained as `#[doc(hidden)]` thin wrappers
    so the existing bridge command and the existing Flutter
    hold-to-talk UI keep compiling.
  * SAD-075 / SDD-089 acknowledged at the type level: only
    `AudioEngine::set_transmit_active` (or its legacy alias)
    mutates the flag; the encoder feed reads it once per outbound
    frame and never writes.
  * SDD-082: new `chanora_audio::ptt` module ships the
    `PttCapabilityLevel` enum (`L0Focused`, `L1GlobalShortcut`,
    `L2GlobalHoldToTalk`, `L3GlobalWithMouseButtons`,
    `L4DeviceAware` reserved) with a stable `as_str` mapping and
    an `is_global` classifier.
  * SDD-087: `PttBackendDescriptor::focused()` constant value for
    the universal Focused-PTT fallback. The struct shape carries
    only privacy-safe fields (`level`, `backend_id`,
    `bound_input_class`) — a key code cannot fit through this
    surface by construction (DEC-027).
  * SAD-077 / SDD-090: `RedactingLogLayer` now hosts the
    `PttBanCheckVisitor` and the `PTT_BANNED_FIELDS` constant
    (`key_code`, `scan_code`, `virtual_key`, `vk`, `keysym`,
    `keysym_string`, `key_sequence`, `key_press_history`,
    `key_timing`). Any record whose field set names a banned key
    is dropped before reaching the in-memory log sink or the
    user-initiated diagnostic export. The check is structural and
    runs ahead of formatting / redaction.
  * `SessionEvent::PttCapability` carries the diagnostics-safe
    descriptor through the broadcast event stream;
    `chanora_core::ChanoraSession::start_audio` publishes the
    Focused-PTT descriptor when the audio engine starts (SRS-196
    / SDD-091).
  * `BridgeEvent::PttCapability` mirrors the event across the
    FFI boundary. flutter_rust_bridge codegen regenerated.
  * Flutter `_AudioControls` renders a capability badge above the
    PTT button: a globe icon for Global levels, a focus-frame
    icon for `L0Focused`, plus a Tooltip exposing the bound input
    class. New ARB key `pttCapabilityBadge(level, backend)` in
    `app_en.arb` and `app_zh.arb`.

Per-platform global PTT backends (`WindowsRawInputBackend`,
`MacOSEventTapBackend`, `LinuxGnomeWaylandBackend`) and the
`MissedKeyUpWatchdog` task land in a separate follow-up commit;
this milestone ships only PTT-L0 universally so the application's
runtime capability reporting is honest from day one.

Tests
-----

* `chanora_audio` rises from 1 to 4 unit tests covering
  `PttCapabilityLevel::as_str`, `is_global`, and the
  `PttBackendDescriptor::focused()` shape contract.
* `chanora_diagnostics` rises from 9 to 11 unit tests covering
  the new `PttBanCheckVisitor` over every banned field name and
  the `PTT_BANNED_FIELDS` stability assertion.
* Workspace total: 53 unit + integration tests, all green with
  `CHANORA_DISABLE_KEYRING=1` (was 49 at v1.0.0-rc.2).
* `flutter analyze`: clean.
* `cargo deny check`: advisories ok, bans ok, licenses ok,
  sources ok.
* `cargo about generate`: zero warnings (license inventory
  regenerated).
* `tools/dump_flutter_licenses.sh`: 94 packages, zero without
  LICENSE.
* Linux x86_64 release bundle builds clean.

No Android live verification in this commit per the user's note
that the test device was removed. Android arm64-v8a continues to
build via the same `cargo ndk` path; runtime reporting on Android
is `L0Focused` for the foreseeable future.
2026-05-15 15:02:03 +08:00
EdisonJwa d2d9ba0a5b feat(diagnostics): A.3 — redacted in-memory log sink + user-initiated export
Replaces the diagnostics scaffold with the production redaction
policy + a user-initiated export path that satisfies DEC-016 (no
automatic uploads).

* `chanora_diagnostics::Redactor` applies the six policy rules to
  every captured log line: `$HOME` paths → `[home]`; IPv4 + IPv6
  literals → `[ip]`; email-shaped strings → `[email]`; long
  base64-ish tokens → `[token]`; substrings registered with
  `KnownSecretRegistry` → `[REDACTED]`. The registry implements
  SS-AUD-003 defence-in-depth: storage adapters can register
  secrets as they cross out of the keyring so an accidental
  `Debug` print is still scrubbed at write time.
* `InMemoryLogSink` is a bounded ring buffer (cap 500 lines in the
  bridge) that always passes lines through the redactor before
  storing them. `RedactingLogLayer` plugs it into `tracing-
  subscriber` alongside the existing logcat / fmt layers.
* `DiagnosticExport::from_sink` builds a plaintext blob — already
  redacted — combining free-form metadata (crate version, target
  os/arch) with the retained log tail. `bridge::api::
  export_diagnostics()` is the Flutter-facing entrypoint
  (`#[frb(sync)]`).
* `bridge_init` now installs the redaction layer on both Android
  and desktop hosts, switching from the global `fmt::init()`
  shortcut to a layered `Registry` so the in-memory sink can sit
  side-by-side with the platform sink.
* Flutter adds a bug-report icon to the AppBar; tapping it opens a
  scrollable monospace dialog with Copy and Close actions. New
  `diagnosticsAction` / `copyAction` / `closeAction` strings land
  in `app_en.arb` + `app_zh.arb`.

Tests cover the redaction matrix (IPv4, IPv6, email, long tokens,
known secret), the ring buffer capacity, and the full
`DiagnosticExport::to_text()` round-trip — 9/9 green.

Live-verified on Moto G: the dialog rendered a multi-line transcript
with `[ip]`, `[token]`, `[home]` substitutions, the metadata block
showed `target_os=android` `target_arch=aarch64`, and Copy placed
the same text on the clipboard.
2026-05-15 01:26:49 +08:00
EdisonJwa 974dda9601 feat(scaffold): create product workspace + Rust crates + Flutter app
Implements the canonical implementation directory layout adopted by
DEC-022 (register v0.9.5). Closes the scaffolding phase; no PoC code
has been promoted in yet (per proof-of-concept-plan.md §4 a PoC is
not product code unless explicitly promoted).

Rust workspace
==============

Top-level Cargo.toml declares seven workspace members:

  core/chanora_core           top-level Rust API + orchestration
  crates/chanora_protocol     tsclientlib isolation (SAD-067, SysDes-011/029)
  crates/chanora_state        state sync, reducers, deltas
  crates/chanora_audio        capture, DSP, Opus, jitter, mixer, playback
  crates/chanora_storage      non-secret DB + platform secure store
  crates/chanora_diagnostics  logs, redaction, export
  crates/chanora_bridge       typed Flutter/Rust DTOs

Workspace-wide pins:
  license      = "MIT OR Apache-2.0"   (DEC-020)
  rust-version = "1.95"
  edition      = "2021"

The Flutter app (apps/chanora_flutter) is NOT a Cargo workspace
member; it is owned by the Flutter / Gradle toolchain and is in the
workspace exclude array along with every poc/* spike.

Each crate ships:
  * a Cargo.toml referring to workspace.dependencies pins;
  * a lib.rs with #![forbid(unsafe_code)] + #![warn(missing_docs)],
    a typed Error enum, and the public types relevant to the
    subsystem's role per SAD §7.2;
  * minimal unit tests so
running 1 test
test tests::defaults_match_decisions ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test tests::it_compiles ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test tests::session_can_be_constructed ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test tests::marker_matches_poc ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test tests::it_compiles ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test tests::state_transitions_compile ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 1 test
test tests::it_compiles ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s is non-empty.

chanora_core's CoreError type-wraps every subsystem error via
#[from] so callers can match on origin without parsing strings.

chanora_audio's AudioEffects struct defaults all four effects to
true, matching DEC-007 (AEC), DEC-008 (AGC), DEC-009 (NS),
DEC-010 (HPF). A unit test pins this so a future regression that
flips a default fails immediately.

chanora_diagnostics exports REDACTION_MARKER = "[REDACTED]",
identical to the PoC's marker so audit grep patterns survive the
promotion.

chanora_bridge's BridgeError is Serialize + Deserialize so it can
flow across the FRB 2.x boundary (DEC-014).

Empirical verification: cargo check --workspace clean, cargo test
--workspace clean (7 unit tests + 7 doc-test runners, all passing)
against Rust 1.95.0 stable.

Flutter app
===========

apps/chanora_flutter created with .

DEC-004 applied: minSdk overridden to 28 in
android/app/build.gradle.kts with a comment that points back at the
decision register and forbids lowering it without re-opening DEC-004.

DEC-015 applied: shipped English + Simplified Chinese at MVP.
  - pubspec.yaml gains flutter_localizations + intl + generate:true.
  - l10n.yaml emits lib/l10n/generated/AppL10n (no synthetic package
    — that was removed in Flutter 3.41+).
  - lib/l10n/app_en.arb is the source of truth; lib/l10n/app_zh.arb
    mirrors the key set in zh-Hans. ARB metadata explicitly
    reaffirms ADR-008: server-provided content (channel names,
    nicknames, welcome banners) is preserved verbatim and never
    translated.

lib/main.dart and test/widget_test.dart were rewritten from the
template counter into a minimal localized scaffold that proves
both locales render correctly.

Empirical verification:  reports no issues;
 runs the two locale smoke tests and both pass.
2026-05-14 21:01:59 +08:00