Commit Graph
10 Commits
Author SHA1 Message Date
EdisonJwa 50768a8f48 feat(mvp): v1.0.0-rc.1 — keyring-backed DEK, encrypted bookmarks, MVP release-gate docs
Closes the v0.4 dual-file weakness in identity-at-rest and turns the
release into an MVP public release candidate. The remaining work
before `v1.0.0` is DEC-012 legal sign-off — see
`docs/governance/legal-review-readiness.md` — and the staged
platform promotions in `docs/governance/staged-release-plan.md`.
No decision rows in `product-decision-register.md` change; the
register's change-history advances to 0.9.8.

`chanora_storage`
-----------------

* New public `Crypto` trait + `IdentityFileStore::crypto()` give
  callers an encrypt / decrypt pair anchored on the per-install
  32-byte DEK without exposing the key material.
* `IdentityFileStore` keyring-first DEK retrieval (Linux Secret
  Service via D-Bus, macOS Keychain, Windows Credential Manager,
  iOS Keychain via the `keyring` crate). Pre-existing
  `identity.dek` files are opportunistically migrated into the
  keyring on first run; the on-disk DEK copy is removed once the
  keyring acknowledges. `CHANORA_DISABLE_KEYRING=1` forces the
  file-fallback path for tests and headless / CI hosts where a
  real keyring call would prompt the user or block on a missing
  D-Bus session.
* `BookmarkRepository::with_crypto(dir, crypto)` encrypts the
  server password into a new `password_blob` BLOB column under
  the same per-install DEK. Schema v2 migration is idempotent —
  legacy v0.4 rows with a plain `password TEXT` are read
  transparently and lifted into `password_blob` on the next
  `update()`. `BookmarkRepository::new` (no crypto) is preserved
  for tests and as a documented fallback when the DEK is
  unreachable.
* Storage tests rise from 8 to 10: encrypted bookmark password
  round-trip + legacy-plaintext-bookmark upgrade.

`chanora_core`
--------------

* `ChanoraSession::init_storage(dir)` wires the bookmark
  repository with crypto by default. On any crypto-derivation
  failure it falls back to the plain-password repository and
  logs the gap — better than hard-failing init.
* `supervisor_loop` now tracks a 64-bit `snapshot_signature` over
  channels (id + parent + order + name) and clients (id + channel
  + name) instead of the old `(channel_count, client_count)`
  tuple. Any in-channel client move, channel rename, or reorder
  now fires `SessionEvent::SnapshotChanged`. The signature sorts
  by id before hashing so it's stable under input-vector
  reordering.
* Two new unit tests cover the signature behaviour; new
  `tests/mvp_storage.rs` integration test drives
  `ChanoraSession::init_storage` end-to-end and verifies the
  bookmark `password_blob` does not contain the plaintext.
* Re-export `ChannelId` + `ClientId` from `chanora_protocol` so
  downstream callers and tests can construct DTOs directly.

Flutter
-------

* New About dialog (info icon in the AppBar) surfaces DEC-018
  (public name "Chanora"), DEC-019 (non-affiliation statement),
  and DEC-020 (Apache-2.0 OR MIT dual license). New ARB keys in
  `app_en.arb` and `app_zh.arb`: `aboutAction`, `aboutVersion`,
  `aboutNonAffiliation`, `aboutLicenseHeading`, `aboutLicenseBody`,
  `aboutThirdPartyHeading`, `aboutThirdPartyBody`.
* `pubspec.yaml` version bumps to `1.0.0-rc.1+5`.

Governance
----------

* `docs/governance/legal-review-readiness.md` — DEC-012 handoff
  package. Enumerates trademark / non-affiliation / license-text
  / third-party-attribution / `tsclientlib`-posture / crypto-
  export / data-handling items the legal reviewer must confirm,
  and lists the concrete engineering deliverables they block on
  (`cargo about generate`, `cargo deny check licenses`,
  Flutter `LicenseRegistry` dump).
* `docs/governance/staged-release-plan.md` — DEC-002 channel
  schedule. Linux + Android sideload promote to GA on DEC-012
  sign-off; Play Store / Windows / macOS / iOS gate on per-
  platform signed-build availability. Rollback policy included.
* `product-decision-register.md` change-history advances to
  0.9.8 with a single entry summarising v0.3, v0.4, and v1.0-rc.1
  progress against DEC-001. No decision rows mutate.

Build + ops
-----------

* `NOTICE` refreshed for the MVP product-code dependency set:
  adds `chacha20poly1305`, `rand`, `zeroize`, `base64`,
  `keyring`, `connectivity_plus`, `path_provider`,
  `freezed_annotation`; drops PoC-only entries.
* `CHANGELOG.md` restructured: explicit version sections for
  v0.3.0-beta.1, v0.4.0-beta.2, v1.0.0-rc.1. Previous "Unreleased"
  contents migrated into their respective milestone sections.
* `.github/workflows/ci.yml` exports `CHANORA_DISABLE_KEYRING=1`
  for the cargo-test job — CI runners have no D-Bus session and
  the keyring crate would otherwise block.
* `run-chanora.sh` reads `CHANORA_BUNDLE_FLAVOUR` (default
  `release`) and self-copies the latest cdylib into the bundle's
  `lib/` if missing.

Verification
------------

* `cargo test --workspace` with `CHANORA_DISABLE_KEYRING=1`: all
  green (49 unit tests across the workspace; up from 36 at
  v0.4.0-beta.2).
* `cargo test -p chanora_core --release -- --ignored alpha_smoke`
  passes against the live `cn.teamspeak.app` (DNS → connect →
  snapshot → disconnect in ~2.5 s).
* `flutter analyze`: clean.
* `cargo build -p chanora_bridge --release` + `flutter build
  linux --release` produce a working Linux x86_64 bundle.

No Android live test in this commit per the user's note that the
physical device was removed; the Android arm64-v8a build path is
mechanically identical to v0.4.0-beta.2.
2026-05-15 02:24:42 +08:00
EdisonJwa 9790005c3e feat(beta): wire voice in/out end-to-end with push-to-talk (v0.2.0-beta.1)
Reaches the Internal Beta milestone of DEC-001's release sequence the
same day as Alpha. Adds voice capture and playback through the full
Flutter UI → FRB → Rust core → tsclientlib → server path.

Promotions from PoC:
  poc/audio-capture-playback-spike  →  crates/chanora_audio/

New product code:
  crates/chanora_audio/src/engine.rs — cpal capture and playback,
    audiopus Opus VoIP encoder (48 kHz mono 20 ms frames), tsclientlib
    AudioHandler for decode + jitter buffer + mix on playback,
    push-to-talk gate, graceful playback-only fallback when capture
    is unavailable.
  crates/chanora_protocol/src/adapter.rs — extended with
    voice_out_tx (clonable mpsc::Sender<OutPacket>) and
    take_voice_in() (one-shot mpsc::Receiver<InboundVoice>); main
    loop now interleaves outbound voice drain, event pumping, and
    control-request handling.
  crates/chanora_protocol/src/lib.rs — re-exports the few
    tsproto_packets types (OutAudio, OutPacket, InAudioBuf,
    AudioData, CodecType, Direction) that chanora_audio
    legitimately needs. Documented as the single deliberate
    cross-crate type re-export per SAD-067, justified by the
    performance cost of a parallel type hierarchy on the 20 ms
    voice frame.
  core/chanora_core/src/lib.rs — ChanoraSession::start_audio,
    set_ptt, audio_stats; disconnect now stops the engine first.
  crates/chanora_bridge/src/api.rs — startAudio, setPtt,
    audioStats commands and BridgeAudioStats DTO.
  apps/chanora_flutter/lib/main.dart — "Start audio" button +
    hold-to-talk PTT button with pressed/released visual state +
    live stats line (TX/RX/PTT). Stats polled every 500 ms.

ARB:
  Both en and zh-Hans gain startAudioAction, pttHoldToTalk,
  pttTransmitting, audioStatsLine. Banner updated to
  "Beta build — voice in/out wired; not production ready."

FRB config:
  flutter_rust_bridge.yaml gains local: true so codegen resolves
  the workspace member's library stem to "chanora_bridge" instead
  of falling back to "UNKNOWN".

Empirical verification (2026-05-14, against cn.teamspeak.app):
  cargo check + cargo test --workspace: all green.
  flutter analyze: 0 issues.
  flutter test: 4/4 passing including:
    - test/alpha_e2e_test.dart (regression: Alpha still works)
    - test/beta_e2e_test.dart (Beta: connect → startAudio →
      PTT cycle → disconnect against cn.teamspeak.app).
  Live smoke (cargo test alpha_smoke -- --ignored): 49 channels,
  37 clients retrieved.
  Capture stream open against the host PipeWire auto_null source
  refused (snd_pcm_hw_params); engine correctly logged the warning
  and continued in playback-only mode. TX=0 frames, RX=0 frames
  reflects the headless null-source environment; on a real mic
  host the encoder produces ~50 frames/second while PTT is held.

Honest Beta scope (NOT in this release):
  - AEC / AGC / NS / HPF DSP (DEC-007..010): AudioEffects exists
    as a struct but the filters are no-ops. Beta+ work.
  - Production-quality resampler: current code is linear
    interpolation. Beta+ work.
  - Identity persistence via chanora_storage: still ephemeral.
  - Push-to-Dart event stream: UI polls instead.
  - chanora_diagnostics tracing-layer wiring: still scaffold.
  - Mobile (Android) cdylib + UI: PoC-proven, not yet in product.
  - Reconnect / network-loss recovery for the voice path.

Docs updates:
  - docs/governance/product-decision-register.md bumped to v0.9.7
    (Beta-milestone change-history entry; no row changes).
  - docs/governance/poc-results-summary.md bumped to v0.6.0
    (RISK-PoC-005 updated with Beta progress).
2026-05-14 22:43:57 +08:00
EdisonJwa 4915ec0a1b feat(alpha): wire connect→snapshot→disconnect end-to-end (v0.1.0-alpha.1)
First Internal Alpha build per DEC-001. Closes the milestone of
'Flutter UI calls Rust via the typed bridge, Rust connects to a
TeamSpeak-compatible server through tsclientlib, returns a typed
snapshot, and disconnects cleanly.' Audio remains Beta scope.

Promotions from PoC:
  poc/tsclientlib-connect-spike  →  crates/chanora_protocol/

New product code:
  crates/chanora_protocol/src/{dto.rs,adapter.rs} — typed boundary
    over tsclientlib. Tokio task owns the Connection; public
    handle communicates via mpsc/oneshot. No tsclientlib types
    cross out of the crate (SAD-067 / SysDes-011 / SysDes-029).
  core/chanora_core/src/lib.rs — ChanoraSession composes the
    protocol crate, enforces the DEC-006 single-connection
    invariant.
  crates/chanora_bridge/src/{api.rs,frb_generated.rs} — FRB 2.12.0
    bridge per DEC-014. cdylib + staticlib + rlib. Typed
    BridgeSnapshot / BridgeChannel / BridgeClient / BridgeError
    DTOs. Process-wide OnceLock<Runtime> + OnceLock<ChanoraSession>.
  flutter_rust_bridge.yaml at repo root.
  apps/chanora_flutter/lib/main.dart — Alpha UI: server form,
    connect button, channel tree, disconnect.
  apps/chanora_flutter/lib/l10n/app_{en,zh}.arb expanded with the
    Alpha key set; ARB metadata reaffirms ADR-008 for
    server-provided content.
  Generated Dart bindings under apps/chanora_flutter/lib/src/rust/.

Empirical verification (2026-05-14):
  Workspace: cargo check + cargo test clean
    (workspace tests: all green).
  Bridge cdylib: target/release/libchanora_bridge.so produced
    (~15 MB).
  Flutter: flutter analyze clean; flutter test runs 3/3 green
    including the alpha_e2e_test that drives the full
      Dart → FRB → chanora_bridge → chanora_core → chanora_protocol
        → tsclientlib → UDP → cn.teamspeak.app
    path. The captured logcat/stdout shows the tsproto resender
    transitioning Connected → Disconnecting → Disconnected on
    clean teardown.

Architecture changes:
  - Removed the chanora_core ↔ chanora_bridge cyclic dependency.
    chanora_core no longer knows the bridge exists; the bridge
    maps from CoreError.
  - chanora_bridge crate's #![forbid(unsafe_code)] lint relaxed
    because FRB-generated glue legitimately uses unsafe at the
    FFI boundary. Hand-written code remains unsafe-free.

Open follow-ups (NOT in this Alpha):
  - Audio capture/playback wiring into chanora_audio
    (Beta scope per DEC-001).
  - Identity persistence via chanora_storage
    (currently regenerated on every connect).
  - Per-message diagnostics + redaction
    (chanora_diagnostics still scaffold).
  - Reconnect / network-loss recovery.
  - Mobile (Android) build of the bridge cdylib + UI verification.
2026-05-14 21:37:06 +08:00
EdisonJwa e0f34009d9 docs(governance): record product scaffolding paths (DEC-022)
Updates two documents to reflect the workspace + Flutter app
scaffold landed in the previous commit.

path-migration-map.md v0.9.2 -> v0.9.3:
  Adds §3 Implementation Path Layout. Lists each subsystem's
  canonical crate path alongside its SAD / SysDes / DEC authority.
  Notes that the Flutter app is owned by Flutter tooling and is
  not a Cargo workspace member.

CHANGELOG entry under [Unreleased]:
  - Documents the seven new Cargo workspace members and the
    invariants pinned at the workspace level.
  - Documents the Flutter app scaffold, the DEC-004 minSdk = 28
    override, and the DEC-015 English + Simplified Chinese ARB
    catalogue (with the ADR-008 server-content reaffirmation).
  - Records the empirical verification (cargo check + cargo test
    + flutter analyze + flutter test all clean).
2026-05-14 21:02:16 +08:00
EdisonJwa 0f418f1d7e feat(legal): resolve DEC-020 — dual-license under Apache-2.0 OR MIT
Closes the only previously-open decision in the register. Chanora is
now dual-licensed under either:

  * Apache License, Version 2.0 (LICENSE-APACHE), OR
  * MIT License (LICENSE-MIT)

at the recipient's option. This is the standard Rust-ecosystem
permissive model and is compatible with every direct dependency
in the PoC tree:

  tsclientlib          MIT OR Apache-2.0
  flutter_rust_bridge  MIT
  cpal                 Apache-2.0
  rusqlite             MIT
  keyring              MIT OR Apache-2.0
  hound                Apache-2.0
  ndk-context, jni, android_logger, regex, serde, tokio,
  tracing, thiserror, zeroize, etc.   MIT OR Apache-2.0

and with the Flutter framework's BSD-3-Clause.

Files added:
  - LICENSE-APACHE  Apache 2.0 license text.
  - LICENSE-MIT     MIT license text with the standard 2026 copyright
                    line.

Files updated:
  - LICENSE     Now the dual-license aggregator. Includes the standard
                Apache-2.0 inbound-contribution clause ("Unless you
                explicitly state otherwise, any contribution
                intentionally submitted for inclusion in Chanora by you,
                as defined in the Apache-2.0 license, shall be
                dual-licensed as above, without any additional terms or
                conditions.").
  - NOTICE      Rewritten with the dual-license declaration and an
                inventory of direct dependencies with their upstream
                licenses. Transitive deps remain to be enumerated by
                build tooling (cargo about, Flutter LicenseRegistry).
  - README.md   §License section rewritten to point at LICENSE-APACHE
                and LICENSE-MIT.
  - docs/governance/product-decision-register.md v0.9.5 → v0.9.6:
    DEC-020 status: Open → Accepted. §4 license row updated. §6
    collapsed: every previously-Proposed or Open decision in the
    register is now resolved. DEC-012 legal review remains as a
    release-gating *work* item, but is not an open decision.
  - docs/governance/poc-results-summary.md v0.3.0 → v0.4.0:
    RISK-PoC-003 closed. DEC-020 row moved out of 'Still open'.

This is a license-model commitment, not a substitute for the
DEC-012 legal review. Per DEC-012 the actual legal review work
(transitive-dep OSS obligations, trademark registrability, final
sign-off on the non-affiliation wording) must still be completed
before any public/store release; that is sign-off work, not an
architectural decision.

Decision register state after this commit:
  Accepted:       23 of 23 unique decisions
  Open/Deferred:  0
  Proposed:       0
2026-05-14 20:53:04 +08:00
EdisonJwa a0d1c35461 docs(governance): owner confirmation on remaining 17 decisions (register v0.9.5)
Closes the 'Proposed / Owner Confirmation Required' state for every
decision in the register except DEC-020 (license, explicitly deferred
and now the only public-release-gating decision outstanding).

Accepted as recommended:
  DEC-001 (Alpha → Beta → Public release sequence),
  DEC-002 (all five platforms as MVP target, staged release allowed),
  DEC-003 (iOS minimum: iOS 13),
  DEC-005 (Android target SDK: Play-required API on upload date),
  DEC-006 (single active server connection in MVP),
  DEC-007/008/009/010 (AEC + AGC + NS + HPF defaults),
  DEC-011 (platform-native audio first),
  DEC-012 (legal/trademark/licensing review as a release gate),
  DEC-013 (SQLite or equivalent for non-secret state),
  DEC-016 (no automatic diagnostics upload),
  DEC-017 (crash reporting disabled for MVP),
  DEC-018 (product name: Chanora),
  DEC-019 (drafted non-affiliation wording),
  DEC-021 (Apple App Store SDK gate: Xcode 26+ / iOS 26 SDK+ on/after 2026-04-28).

Modified from the original recommendation by explicit owner ruling:
  - DEC-004: Android minimum raised to API 28 (Android 9.0) from the
    recommended API 24. Rationale: simpler audio path (AAudio stable
    from API 28), narrower compatibility / privacy / scoped-storage
    surface. Affects the Android spike's minSdk=24 in product code:
    apps/chanora_flutter will need minSdk=28.
  - DEC-015: MVP product language expanded to English + Chinese
    (Simplified) from the recommended English-only. Rationale: the
    demonstrated TS3-compatible-server audience (verified live against
    cn.teamspeak.app) and broader TS3 audience include substantial
    Chinese-speaking users. Adds zh-Hans translation, font, and
    text-length-budget work to MVP. Server-provided content is still
    preserved verbatim per ADR-008.

Still Open / Deferred:
  - DEC-020 license model. The only remaining release-gating decision.

Documentation updates:
  - product-decision-register.md → v0.9.5. §3 statuses updated, §4
    renamed Recommended → Accepted with MODIFIED rows annotated,
    §6 collapsed to DEC-020 only, §7 dated and statused for every
    decision, change-history entry added.
  - poc-results-summary.md → v0.3.0. §4 expanded with the
    2026-05-14 owner-confirmation pass table. RISK-PoC-004 closed.
    New RISK-PoC-006 (Android minSdk move 24 → 28) and RISK-PoC-007
    (MVP language expansion to en + zh-Hans) added.
  - CHANGELOG entry under [Unreleased].
2026-05-14 20:46:29 +08:00
EdisonJwa 4c64517e45 docs(governance): promote audio PoC to PASS; close mobile-Android half
Documentation update following the Android audio spike pass.

Decision register (v0.9.3 → v0.9.4):
  - DEC-011.1 promoted from
      'Accepted (desktop: cpal) / Deferred (mobile)'
    to
      'Accepted (desktop: cpal; Android: cpal-on-Oboe) / Deferred (iOS)'.
  - Evidence pointer added: poc/audio-capture-playback-android-spike/
    VERIFICATION.md.

PoC plan (v0.2.0 → v0.3.0):
  - Audio row promoted from PARTIAL PASS to PASS.
  - All six PoC plan entries are now PASS.

PoC results summary (v0.1.0 → v0.2.0):
  - Audio row collapsed into one PASS spanning both spikes.
  - RISK-PoC-001 narrowed from 'mobile audio' to 'iOS audio only'.
  - Toolchain table expanded with Android NDK, cargo-ndk, AGP/
    Gradle/Kotlin, jni/ndk-context/android_logger, and the test
    device.

Cross-spike pointers updated:
  - poc/audio-capture-playback-spike/VERIFICATION.md result and
    follow-up sections updated to reference the Android spike.
  - poc/README.md status table lists both audio spike directories.

CHANGELOG updated under [Unreleased].
2026-05-14 18:25:03 +08:00
EdisonJwa 271d23faf7 docs(governance): record PoC outcomes, owner decisions, and audit evidence
Closes Phases A and D of the post-PoC sequencing.

Decision register (v0.9.2 → v0.9.3):
  - DEC-014 Accepted: flutter_rust_bridge 2.x pinned (closed by
    poc/flutter_rust_bridge_hello).
  - DEC-013.1 Accepted: rusqlite (bundled) (closed by
    poc/sqlite-storage-spike).
  - DEC-013.2 Accepted: Linux secure-storage backend policy —
    Secret Service preferred, keyutils fallback (closed by
    poc/secure-storage-spike; resolves SysRS-053 / SysRS-162
    ambiguity).
  - DEC-011.1 Accepted (desktop: cpal) / Deferred (mobile)
    (closed by poc/audio-capture-playback-spike desktop half only).
  - DEC-022 Accepted: canonical implementation directory layout per
    the README sketch and SAD §7.2.
  - DEC-020 explicitly Deferred by owner; remains a public-release
    blocker.

Audit reports updated with empirical evidence:
  - docs/security/secure-storage-audit-report.md v0.9.3:
    SS-AUD-001/002/003/005/006 = PoC Pass with evidence pointers;
    SS-TC-003 (Linux) Actual Result populated and Status = PoC Pass;
    SS-AUD-004 cross-referenced to diagnostics-redaction PoC;
    findings SS-FIND-001 (closed by DEC-013.2), SS-FIND-002 (keyutils
    session caveat), SS-FIND-003 (non-Linux adapters still open).
  - docs/security/diagnostic-redaction-audit-report.md v0.9.3:
    REDACT-TC-001..010 = PoC Pass with evidence pointers; export
    bundle policy §5 populated for every row; findings
    REDACT-FIND-001 (regex coverage), REDACT-FIND-002
    (tracing-layer integration), REDACT-FIND-003 (cross-spike
    KnownSecretRegistry contract).

PoC plan (v0.1.0 → v0.2.0):
  - Status column added to §2; outcomes recorded.

New doc:
  - docs/governance/poc-results-summary.md v0.1.0 — single-page
    reviewer-facing summary listing each spike's status, the
    toolchain exercised, the owner decisions taken, the audit
    coverage table, and open risks RISK-PoC-001..005 (mobile audio,
    non-Linux secure-storage adapters, license, remaining
    Proposed decisions, no product code yet).

This completes the post-PoC documentation work. Repo is at a clean
pause point: PoC code is committed, owner decisions are recorded,
audit reports carry empirical evidence, and the residual risks are
named in the summary doc.
2026-05-14 12:34:13 +08:00
EdisonJwa 181b3d329d docs(poc): add PoC index and record PoC outcomes in CHANGELOG
Adds poc/README.md as the top-level index across all six PoC spikes,
recording status (5 PASS, 1 PARTIAL PASS), authority, and the
non-promotion rule from proof-of-concept-plan.md §4.

Updates CHANGELOG.md to enumerate the six spikes with their
verification dates and to reference each spike's VERIFICATION.md.

This completes Phase E of the post-bootstrap sequencing:
  E.1  git init + baseline import
  E.2  justfile
  E.3..E.8  six PoC spikes
  E.9  PoC index + CHANGELOG  ← this commit
2026-05-14 12:27:13 +08:00
EdisonJwa f1bc9a6c85 chore(repo): initial baseline import (docs v0.9.2 + bootstrap)
Imports the v0.9.2 documentation baseline and the bootstrap files
required by docs/governance/repository-bootstrap-plan.md v0.1.0 §3,
minus the justfile (added in the next commit).

This commit establishes the git history for the project. All previous
work lived only as filesystem state with no version control.
2026-05-14 12:25:33 +08:00