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.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_audio"
|
||||
description = "Chanora audio subsystem — capture, DSP (HPF/NS/AEC/AGC per DEC-007..010), Opus encode/decode, jitter buffer, mixer, playback. Crate selection: cpal for desktop and Android per DEC-011.1."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,83 @@
|
||||
//! # `chanora_audio`
|
||||
//!
|
||||
//! Audio subsystem. Per SAD §7.2:
|
||||
//!
|
||||
//! * capture from the platform default input device
|
||||
//! * DSP chain: high-pass filter → noise suppression → echo
|
||||
//! cancellation → automatic gain control → gate
|
||||
//! (per-effect defaults: DEC-007..010)
|
||||
//! * Opus encode/decode
|
||||
//! * jitter buffer, mixer
|
||||
//! * playback to the platform default output device
|
||||
//!
|
||||
//! Crate selection per DEC-011.1: `cpal` on desktop and Android.
|
||||
//! iOS audio crate remains deferred.
|
||||
//!
|
||||
//! Empirical evidence from PoC:
|
||||
//! `poc/audio-capture-playback-spike` (Linux/PipeWire) +
|
||||
//! `poc/audio-capture-playback-android-spike` (Android 14 arm64-v8a,
|
||||
//! physical device).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only. PoC code is not promoted here yet.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised by the audio subsystem.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AudioError {
|
||||
/// Platform did not expose a usable default input device.
|
||||
#[error("no default input device")]
|
||||
NoInputDevice,
|
||||
/// Platform did not expose a usable default output device.
|
||||
#[error("no default output device")]
|
||||
NoOutputDevice,
|
||||
/// The audio backend rejected a stream configuration.
|
||||
#[error("stream config rejected: {0}")]
|
||||
StreamConfig(String),
|
||||
/// A backend-specific failure surfaced without a typed mapping.
|
||||
/// Production code must narrow this further as failure modes
|
||||
/// are catalogued.
|
||||
#[error("audio backend: {0}")]
|
||||
Backend(String),
|
||||
}
|
||||
|
||||
/// Audio-effect toggles. Defaults match DEC-007 (AEC),
|
||||
/// DEC-008 (AGC), DEC-009 (NS), DEC-010 (HPF) — all enabled.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AudioEffects {
|
||||
/// Acoustic echo cancellation (DEC-007).
|
||||
pub aec: bool,
|
||||
/// Automatic gain control (DEC-008).
|
||||
pub agc: bool,
|
||||
/// Noise suppression (DEC-009).
|
||||
pub noise_suppression: bool,
|
||||
/// High-pass filter (DEC-010).
|
||||
pub high_pass: bool,
|
||||
}
|
||||
|
||||
impl Default for AudioEffects {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
aec: true,
|
||||
agc: true,
|
||||
noise_suppression: true,
|
||||
high_pass: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn defaults_match_decisions() {
|
||||
let e = AudioEffects::default();
|
||||
assert!(e.aec && e.agc && e.noise_suppression && e.high_pass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "chanora_bridge"
|
||||
description = "Chanora typed Flutter/Rust bridge — schema-controlled DTOs for commands, results, and events. Backed by flutter_rust_bridge 2.x per DEC-014."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,42 @@
|
||||
//! # `chanora_bridge`
|
||||
//!
|
||||
//! Typed Flutter/Rust bridge. Owns the canonical DTO catalogue for
|
||||
//! commands (Dart → Rust), results (Rust → Dart return values), and
|
||||
//! events (Rust → Dart streams).
|
||||
//!
|
||||
//! Bridge tool per DEC-014: `flutter_rust_bridge` 2.x. The bridge
|
||||
//! integration glue (codegen invocations, the cdylib boundary) is
|
||||
//! added when `apps/chanora_flutter` is wired up; this crate owns
|
||||
//! only the type definitions and error mappings — explicitly *not*
|
||||
//! `tsclientlib` or raw subsystem types (SAD-067, SDD-079).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised at the bridge boundary. Production code must keep
|
||||
/// `BridgeError` user-safe — no secrets, no protocol details, no
|
||||
/// path information beyond what the redaction policy permits.
|
||||
#[derive(Debug, Error, Clone, Serialize, Deserialize)]
|
||||
pub enum BridgeError {
|
||||
/// The caller submitted a malformed command DTO.
|
||||
#[error("invalid command: {0}")]
|
||||
InvalidCommand(String),
|
||||
/// An unknown / unmapped error escaped the subsystem boundary.
|
||||
/// Production callers should never see this; if it appears it is
|
||||
/// a mapping bug in this crate.
|
||||
#[error("unmapped: {0}")]
|
||||
Unmapped(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_diagnostics"
|
||||
description = "Chanora diagnostics — logs, redaction policy, user-initiated diagnostic export. Implements REDACT-TC-001..010 from diagnostic-redaction-audit-report.md."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,61 @@
|
||||
//! # `chanora_diagnostics`
|
||||
//!
|
||||
//! Application diagnostics. Owns:
|
||||
//!
|
||||
//! * the redaction policy and [`Redactor`] (REDACT-TC-001..010 per
|
||||
//! `docs/security/diagnostic-redaction-audit-report.md` §4)
|
||||
//! * the diagnostic-export bundle (audit report §5)
|
||||
//! * the `KnownSecretRegistry` cross-spike contract (SS-AUD-003
|
||||
//! defence in depth)
|
||||
//! * the `tracing-subscriber` layer integration that enforces
|
||||
//! redaction at write-time (REDACT-FIND-002)
|
||||
//!
|
||||
//! Per DEC-016 diagnostics export is **user-initiated only**; there
|
||||
//! is no automatic upload.
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised by the diagnostics subsystem.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DiagnosticsError {
|
||||
/// Failed to build or serialise a diagnostic export.
|
||||
#[error("export failed: {0}")]
|
||||
Export(String),
|
||||
/// I/O error while writing logs or exports.
|
||||
#[error("io: {0}")]
|
||||
Io(String),
|
||||
}
|
||||
|
||||
/// The replacement marker used for redacted segments, identical to
|
||||
/// the PoC value so audit grep patterns survive the promotion.
|
||||
pub const REDACTION_MARKER: &str = "[REDACTED]";
|
||||
|
||||
/// Placeholder for the redactor that will be promoted from
|
||||
/// `poc/diagnostics-redaction-spike`.
|
||||
pub struct Redactor {
|
||||
_seal: (),
|
||||
}
|
||||
|
||||
impl Redactor {
|
||||
/// Construct a redactor with the default policy.
|
||||
pub fn with_default_policy() -> Self {
|
||||
Self { _seal: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn marker_matches_poc() {
|
||||
assert_eq!(REDACTION_MARKER, "[REDACTED]");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_protocol"
|
||||
description = "Chanora protocol adapter — isolates tsclientlib behind a typed boundary. No tsclientlib types may cross out of this crate per SAD-067 / SysDes-011 / SysDes-029."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,51 @@
|
||||
//! # `chanora_protocol`
|
||||
//!
|
||||
//! TeamSpeak-compatible protocol adapter. Isolates `tsclientlib`
|
||||
//! behind a typed boundary so the rest of Chanora is decoupled from
|
||||
//! the upstream library's types (SAD-067, SysDes-011, SysDes-029).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only. Promotion of `poc/tsclientlib-connect-spike` into
|
||||
//! this crate happens later, with its own audit-trail commit.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors surfaced by the protocol adapter. None of these expose
|
||||
/// `tsclientlib`-specific types; raw upstream errors are mapped here.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ProtocolError {
|
||||
/// Configuration is invalid before any I/O is attempted (bad
|
||||
/// hostname, missing identity, etc.).
|
||||
#[error("invalid protocol configuration: {0}")]
|
||||
Invalid(&'static str),
|
||||
/// The connect handshake failed.
|
||||
#[error("connect failed: {0}")]
|
||||
Connect(String),
|
||||
/// The connection ended unexpectedly.
|
||||
#[error("disconnected: {0}")]
|
||||
Disconnected(String),
|
||||
/// Operation timed out.
|
||||
#[error("protocol timeout")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Opaque identifier for a server-side channel. Replaces
|
||||
/// `tsclientlib::ChannelId` at the public boundary so its concrete
|
||||
/// representation can change without leaking through the rest of the
|
||||
/// codebase.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ChannelId(pub u64);
|
||||
|
||||
/// Opaque identifier for a server-side client.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ClientId(pub u64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_state"
|
||||
description = "Chanora state synchronisation — authoritative client-side state mirror, reducers that fold protocol events, and deltas the bridge publishes to Flutter view-models."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,52 @@
|
||||
//! # `chanora_state`
|
||||
//!
|
||||
//! Authoritative client-side mirror of server state: server identity,
|
||||
//! channel tree, client list, our own connection state. Owns the
|
||||
//! reducers that fold protocol events into a snapshot, and the
|
||||
//! deltas that the bridge publishes to Flutter view-models
|
||||
//! (per SAD §7.2 and SDD §5).
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised while reducing protocol events into state or
|
||||
/// producing deltas for the bridge.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StateError {
|
||||
/// Reducer received an event referencing an unknown entity.
|
||||
#[error("unknown entity: {0}")]
|
||||
Unknown(&'static str),
|
||||
/// A reducer invariant was violated (e.g. two clients claiming
|
||||
/// the same id).
|
||||
#[error("state invariant: {0}")]
|
||||
Invariant(&'static str),
|
||||
}
|
||||
|
||||
/// Coarse connection lifecycle states surfaced to the bridge.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConnectionState {
|
||||
/// No connection attempt yet, or fully disconnected.
|
||||
Idle,
|
||||
/// Handshake in progress.
|
||||
Connecting,
|
||||
/// Initial state snapshot has been received; ready for use.
|
||||
Ready,
|
||||
/// Connection lost; will not auto-reconnect at this stage.
|
||||
Lost,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn state_transitions_compile() {
|
||||
let _ = ConnectionState::Idle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "chanora_storage"
|
||||
description = "Chanora storage — non-secret embedded DB (SQLite via rusqlite bundled per DEC-013.1) and platform secure storage (Secret Service preferred + keyutils fallback on Linux per DEC-013.2). Strict SAD-067 separation."
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -0,0 +1,58 @@
|
||||
//! # `chanora_storage`
|
||||
//!
|
||||
//! Two strictly separated repositories per SAD-067:
|
||||
//!
|
||||
//! * [`LocalDatabaseRepository`] — non-secret state (bookmarks,
|
||||
//! settings, identity *references*) via SQLite. Crate choice:
|
||||
//! `rusqlite` bundled (DEC-013.1).
|
||||
//! * [`SecretStorageRepository`] — secret material (identity private
|
||||
//! keys, server passwords) via platform secure storage. Linux
|
||||
//! policy: Secret Service preferred, kernel keyutils fallback
|
||||
//! (DEC-013.2). Other platforms TBD per SS-TC-001/002/004/005.
|
||||
//!
|
||||
//! Secret values **never** appear in the local DB (SS-AUD-001/002);
|
||||
//! bookmarks store only an `identity_ref` lookup name into the
|
||||
//! secure store.
|
||||
//!
|
||||
//! ## Status
|
||||
//!
|
||||
//! Scaffold only. `poc/secure-storage-spike` and
|
||||
//! `poc/sqlite-storage-spike` will be promoted here.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised by either storage repository.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StorageError {
|
||||
/// Looked-up entry does not exist.
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
/// Schema migration error.
|
||||
#[error("migration: {0}")]
|
||||
Migration(String),
|
||||
/// Underlying SQLite error.
|
||||
#[error("sqlite: {0}")]
|
||||
Sqlite(String),
|
||||
/// Platform secure-storage backend rejected an operation.
|
||||
#[error("secure-store: {0}")]
|
||||
SecureStore(String),
|
||||
}
|
||||
|
||||
/// Marker trait for the non-secret database side. Concrete impl will
|
||||
/// land alongside the `BookmarkRepository` / `SettingsRepository`
|
||||
/// traits promoted from the SQLite PoC.
|
||||
pub trait LocalDatabaseRepository: Send + Sync {}
|
||||
|
||||
/// Marker trait for the platform secure-storage side. Concrete impl
|
||||
/// will land alongside the `Secret` newtype + per-platform adapters
|
||||
/// promoted from the secure-storage PoC.
|
||||
pub trait SecretStorageRepository: Send + Sync {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
}
|
||||
Reference in New Issue
Block a user