mirror of
https://github.com/Hmbown/DeepSeek-TUI.git
synced 2026-09-03 06:50:13 +08:00
A complete, tested telemetry client with no callers. It ships inert: nothing in the tree calls `init`, so nothing is collected, nothing is written, and no directory is created by this commit. The crate is placed at `crates/telemetry` because a module under `crates/tui/src/` is unreachable from two of the three consumers — `codewhale-cli` does not depend on `codewhale-tui`, `codewhale-tui` does not depend on `codewhale-cli`, and app-server is linked into the `codewhale` binary. It depends on `codewhale-config`, whose own dependencies are execpolicy, paths, and secrets, so there is no cycle and no plausible future one: config has no reason to consult telemetry state. That dependency is what lets the emit predicate live in exactly one function instead of being re-derived at six init sites. Consent is a value, not a convention. `decide()` is the only constructor of `TelemetryConsent`; `init` takes one by value and there is no bool-taking sibling. `TelemetryDecision` splits `OptedOut` from `ForcedOff` because "telemetry resolved to false" is the default state of every installation — a wipe keyed on it would delete a consenting user's identity and unflushed buffer every time they ran one command with a transient `CODEWHALE_TELEMETRY=0`, which is the recipe the runtime docs prescribe. Only `OptedOut` touches disk. `ForcedOff` touches nothing, ever, and a test asserts a seeded home is byte-identical across every `ForcedOff` row. Arming is a `OnceLock` consulted by every write path, `record_blocking` included. The process panic hook is installed before the command line is parsed, long before any config resolution, so it cannot consult a resolved value — but it can consult a lock that is by construction empty until resolution completes. A disabled user's panic therefore writes nothing. Arming also truncates the buffer, so no event recorded before consent can be in the batch that follows it. Appends never take a lock. One `O_APPEND` write under `PIPE_BUF`, then `sync_data`. `flock` is per-fd within a process, so a blocking acquisition on the panic hook would self-deadlock if the writer thread panicked holding the compaction lock, and a second process sharing `CODEWHALE_HOME` would hang Ctrl-C. Compaction is the only lock holder and uses `try_write`. The drain tolerates a torn trailing line, because `std::process::exit` on the signal path can cut a concurrent write. The wipe truncates rather than unlinks — replacing the file would leave appenders on a dead inode, and unlinking the sibling lock file would leave holders serialising against nothing. It writes the `disabled` tombstone first and never removes it, every append and every send re-checks that tombstone, and a failed wipe therefore fails closed: the tombstone alone makes the buffer permanently undrainable. Transport requires `https://`. Plaintext is permitted only for loopback, where a batch never reaches a wire. There is no environment variable that overrides this, and `CODEWHALE_ALLOW_INSECURE_HTTP` is deliberately not consulted: that variable authorizes an insecure *provider* base URL for harnesses that intercept model traffic, and honouring it here would let that decision also authorize telemetry POSTs to an arbitrary host. With no endpoint configured — the shipped default — no HTTP client is constructed at all and batches go to `dryrun.jsonl`. `install_id` is `Uuid::new_v4`, never derived from hostname, MAC, `machine-id`, home, username, or executable path, and it rotates every 90 days with `rotated_at` recorded. A derived id is a device fingerprint that survives reinstall and re-identifies a user across their own opt-out. The schema has no free-form string type and no open-keyed map. `counters` and `errors` are structs of named `u32` fields rather than maps, so the key set is closed by the compiler. `every_payload_field_is_bounded` walks a fully-populated batch and asserts every string leaf is a member of a declared enum set or one of three regexed strings. The scrubber assertions run `redact_for_disclosure` per string leaf and never over the serialized document: that function tokenizes on spaces, and a compact JSON batch is one token, so a document-level check would report clean on a payload containing a path, a key, and a whole prompt. `ResolvedRuntimeOptions` gains `telemetry_endpoint`, resolved from `CODEWHALE_TELEMETRY_ENDPOINT` then the config file, because `decide` needs to see the endpoint to refuse a bad one. `crates/workflow` exports its redaction module so the leak assertions above can use it.
86 lines
3.1 KiB
TOML
86 lines
3.1 KiB
TOML
[workspace]
|
|
members = [
|
|
"crates/agent",
|
|
"crates/app-server",
|
|
"crates/build-support",
|
|
"crates/cli",
|
|
"crates/config",
|
|
"crates/core",
|
|
"crates/execpolicy",
|
|
"crates/hooks",
|
|
"crates/lane",
|
|
"crates/mcp",
|
|
"crates/paths",
|
|
"crates/protocol",
|
|
"crates/release",
|
|
"crates/secrets",
|
|
"crates/state",
|
|
"crates/telemetry",
|
|
"crates/tools",
|
|
"crates/tui",
|
|
"crates/workflow",
|
|
"crates/workflow-js",
|
|
]
|
|
default-members = ["crates/cli", "crates/app-server", "crates/tui"]
|
|
resolver = "2"
|
|
|
|
[workspace.package]
|
|
version = "0.9.4"
|
|
edition = "2024"
|
|
# Rust 1.88 stabilized `let_chains` in `if`/`while` conditions, which the
|
|
# codebase relies on extensively. Cargo enforces this so users on older
|
|
# toolchains get a clear "package requires rustc 1.88+" error instead of a
|
|
# confusing E0658 from rustc.
|
|
rust-version = "1.88"
|
|
license = "MIT"
|
|
repository = "https://github.com/Hmbown/CodeWhale"
|
|
|
|
[workspace.dependencies]
|
|
anyhow = "1.0.100"
|
|
async-trait = "0.1.89"
|
|
axum = { version = "0.8.5", features = ["json"] }
|
|
chrono = { version = "0.4.43", features = ["serde"] }
|
|
clap = { version = "4.5.54", features = ["derive"] }
|
|
clap_complete = "4.5"
|
|
dirs = "6.0.0"
|
|
encoding_rs = "0.8.35"
|
|
jsonschema = { version = "0.48", default-features = false }
|
|
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls-no-provider", "socks"] }
|
|
# NOT "parallel": the Workflow VM stays single-threaded and bridges to the
|
|
# multi-thread engine over channels (see crates/workflow-js).
|
|
rquickjs = { version = "0.12", features = ["futures"] }
|
|
rustls = { version = "0.23.36", default-features = false, features = ["ring", "std", "tls12"] }
|
|
rusqlite = { version = "0.39.0", features = ["bundled"] }
|
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
serde_json = "1.0.149"
|
|
semver = "1.0.28"
|
|
thiserror = "2.0"
|
|
tempfile = "3.27"
|
|
tokio = { version = "1.50.0", features = ["fs", "io-util", "io-std", "macros", "net", "process", "rt", "rt-multi-thread", "signal", "sync", "time"] }
|
|
toml = "1.0.6"
|
|
toml_edit = "0.25.12"
|
|
sha2 = "0.11"
|
|
tower-http = { version = "0.7", features = ["cors"] }
|
|
tracing = "0.1"
|
|
tracing-appender = "0.2"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
|
uuid = { version = "1.11", features = ["v4"] }
|
|
mimalloc = { version = "0.1", default-features = false }
|
|
|
|
[profile.release]
|
|
lto = true
|
|
strip = true
|
|
# NOTE: no `panic = "abort"` here — the TUI's panic supervision
|
|
# (catch_unwind/spawn_supervised) needs unwinding so one panicking tool call
|
|
# or task fails gracefully instead of aborting the whole session.
|
|
codegen-units = 1
|
|
|
|
# Patch unicode-width so that width() uses CJK tables when the cjk feature
|
|
# is enabled. Vanilla unicode-width 0.2.2 provides width_cjk() as a separate
|
|
# method but the original width() always uses non-CJK tables, so Ratatui
|
|
# (which calls width() internally) measures ambiguous-width characters
|
|
# (circled digits, enclosed alphanumerics) as 1 column. CJK terminals render
|
|
# them as 2 columns, causing cell-offset rendering glitches. (#4479)
|
|
[patch.crates-io]
|
|
unicode-width = { path = "patches/unicode-width-0.2.2" }
|