| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
fix(telemetry): join the writer for local persist instead of racing 250ms (#6269) A short CLI command seals bounded local work (consent re-check, tombstone probe, one fsync append) with no network in it. Racing that against CLI_PERSIST_TIMEOUT lost dry-run receipts on slow hosts and failed open into a missing artifact. persist_local now joins unconditionally; the writer acknowledges unconditionally (Dropped on panic) so 'telemetry never takes the process with it' still holds, and only an already-gone writer fails open as TimedOut. Only the network flush keeps a deadline. Gate: codewhale-telemetry 51 passed 0 failed; telemetry_kill_switch_dispatch 5 passed 0 failed; fmt clean on touched files. | 28 天前 | |
fix(release): finish the v0.9.6 candidate Move first-run usage disclosure into the native TUI, keep telemetry unarmed until the decision, and order all telemetry writes and delivery against persistent opt-out with fail-closed setup-state handling. Replace the narrow wide-terminal rail with a responsive full-screen ocean canvas, preserve readable prose measure, and remove per-call padding inside grouped tool activity. Verified with formatting, clippy, locale/version/budget gates, the 10,120-test TUI library suite, telemetry/config suites, focused UI/PTy coverage, and independent release/UI review. The sandbox-only loopback acceptance rerun remains explicitly environmental. Agent assistance: Claude and Codex were used for implementation analysis and verification. | 2 个月前 | |
fix(release): finish the v0.9.6 candidate Move first-run usage disclosure into the native TUI, keep telemetry unarmed until the decision, and order all telemetry writes and delivery against persistent opt-out with fail-closed setup-state handling. Replace the narrow wide-terminal rail with a responsive full-screen ocean canvas, preserve readable prose measure, and remove per-call padding inside grouped tool activity. Verified with formatting, clippy, locale/version/budget gates, the 10,120-test TUI library suite, telemetry/config suites, focused UI/PTy coverage, and independent release/UI review. The sandbox-only loopback acceptance rerun remains explicitly environmental. Agent assistance: Claude and Codex were used for implementation analysis and verification. | 2 个月前 | |
feat(telemetry): add the codewhale-telemetry leaf crate 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. | 2 个月前 | |
feat(telemetry): count anonymous usage by default with durable opt-out Usage counting no longer waits for an accepted processor notice. A missing preference resolves on; a persisted `telemetry = false`, a decline recorded under the earlier opt-in notice, unreadable privacy state, and the environment/run kill switches all stay off. Schema 3 carries `notice_version` 5 in place of `consent_version`; the v1/v2 goldens are untouched and a new v3 golden pins the envelope. Disclosure is presentation, never acceptance: headless surfaces print one stderr line per policy revision and record only that it was shown; the TUI draws its localized toast (15 packs) and records the same display marker from the event loop instead of a stray stderr line. Display bookkeeping and Settings/CLI preference writes share a non-blocking fd lock so a stale disclosure write cannot erase a concurrent decline; corrupt state is never replaced with defaults. `config set telemetry false` durably disables and wipes; `config set telemetry true` deliberately re-enables through the same two-register Settings transition. `doctor` reports recorded opt-outs and unreadable privacy state instead of "consent_required". Gates on this exact tree (macOS, offline cargo): - cargo fmt --check: pass - workspace clippy --all-targets --all-features -D warnings: pass - nextest --workspace --all-features --profile ci: 14284 passed, 0 failed, 14 skipped (1 non-failing leak observation, hooks executor EOF test) - focused: config+telemetry libs 687/0/1; CLI/TUI telemetry+localization +doctor selection 229/0; real CLI process kill-switch dispatch 5/0; TUI integration telemetry_contract 15/0; credential handoff dispatch 3/0 - `npm test && npm run check:web` do not exist in this repository and were not run. No live ingest, PostHog delivery, hosted CI, install, or release is claimed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bs5x11yXAg3sJ4giZf4krF | 1 个月前 | |
fix(telemetry): keep only a canonical v4 install id with a past rotation date Audit finding R05-09 (#6561): `read_or_create_install_id` kept any on-disk id that `Uuid::parse_str(id.trim())` accepted and sent it verbatim. That admitted v1 ids (MAC address + clock: a device fingerprint), the nil id, and braced/uppercase/whitespace-padded spellings that are different wire strings for one id. `is_expired` computed a negative age for a future `rotated_at`, which never reaches the 90-day window, so a future date pinned the id forever. Now an id survives a read only if it is byte-for-byte the lowercase hyphenated form of an RFC 4122 v4 UUID (exactly what this function mints), and a future `rotated_at` counts as expired. Minting a fresh random id remains the safe direction. Evidence (cargo test -p codewhale-telemetry --lib -- install_id): - fixed: test result: ok. 3 passed; 0 failed - reverted: test result: FAILED. 2 passed; 1 failed (kept "6ba7b810-9dad-11d1-80b4-00c04fd430c8", a v1 id) - restored: test result: ok. 3 passed; 0 failed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: CodeWhale Bot <bot@codewhale.net> | 14 天前 | |
feat(telemetry): let an embedder declare the server's surface A client that starts `codewhale serve` can now name itself, so its sessions are not counted as anonymous `serve` traffic from nobody in particular. It sets `CODEWHALE_TELEMETRY_SURFACE` to a value in the surface whitelist before spawning the server; the VS Code extension uses the new `vscode-extension` value. The read is deliberately narrow. Only the `serve` branch consults it, because the variable is inherited by descendants and an agent's shell command runs `codewhale` descendants carrying this environment — a nested `codewhale exec` that picked the label up would report itself as the embedder. `tui` is refused, since nothing starts the interactive terminal UI on a user's behalf, and `serve --mcp` is still decided before the declaration. A name outside the whitelist is dropped rather than repaired: the field is a label, and the default is the honest one. This changes how an embedder's sessions are labelled, never whether anything may be collected. The server resolves that itself, from the same durable config, setup state, and `CODEWHALE_TELEMETRY` kill switch as before, so an embedder cannot enable collection where the user turned it off. The whitelist gains `vscode-extension` — generic, the way `web-app` and `desktop` are, and the editor's own name and version are not collected. No field is added, removed, or retyped, so the schema version is unchanged. In the ingest validator the value is appended rather than inserted, because the v1 contract's whitelist is a prefix of that list and v1 must keep refusing every surface the runtime never emitted. Signed-off-by: Ben Gao <bengao168@msn.com> | 7 天前 | |
fix(telemetry): join the writer for local persist instead of racing 250ms (#6269) A short CLI command seals bounded local work (consent re-check, tombstone probe, one fsync append) with no network in it. Racing that against CLI_PERSIST_TIMEOUT lost dry-run receipts on slow hosts and failed open into a missing artifact. persist_local now joins unconditionally; the writer acknowledges unconditionally (Dropped on panic) so 'telemetry never takes the process with it' still holds, and only an already-gone writer fails open as TimedOut. Only the network flush keeps a deadline. Gate: codewhale-telemetry 51 passed 0 failed; telemetry_kill_switch_dispatch 5 passed 0 failed; fmt clean on touched files. | 28 天前 | |
fix(tui): acknowledge configured first-run routes First-run recovery treated a fresh home as having no saved route, even when config selected one. A usable route that skipped onboarding left no provider step receipt, and setup treated saved-but-unchecked credentials as verified. Recognize explicit startup routes in the recovery predicate. Record an unchecked first-run route as configured through the existing setup owner; reserve verified for observed success. Keep the unconfigured picker and local Ollama discovery, prior setup decisions, and Fleet verification requirements. Generalize the existing locked sidecar updater and migrate every caller so the new startup receipt preserves privacy decisions and rejects corrupt/busy state. Extend and synchronize the existing first-launch changelog bullet. Validation (all cargo commands used CARGO_BUILD_JOBS=4 CARGO_NET_OFFLINE=true): cargo fmt --all sh scripts/sync-changelog.sh sh scripts/sync-changelog.sh --check git diff --check cargo build -p codewhale-tui cargo test -p codewhale-tui --lib -- first_run_ test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 13786 filtered out; finished in 1.06s Negative control: restore the old recovery predicate and remove startup receipt writing while retaining the new tests and type vocabulary. All three configured route regressions fail; both production files were restored afterward. The first control attempt stopped at unused-import compilation; remove only the now-unused imports in the control, without weakening warnings, and rerun: cargo test -p codewhale-tui --lib -- first_run_configured_ test result: FAILED. 0 passed; 3 failed; 0 ignored; 0 measured; 13801 filtered out; finished in 0.13s Restored fix and adjacent setup/provider receipt checks: cargo test -p codewhale-tui --lib -- first_run_ configured_provider_receipt_only_verifies_observed_success tui::setup::progressive_tests doctor_verdict_tests provider_switch_model_override_updates_target_provider_model_slot provider_switch_auth_error_restores_previous_provider_and_model model_picker_apply_is_session_local_until_startup_default_is_requested test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 13771 filtered out; finished in 1.92s cargo test -p codewhale-config --lib -- setup_state::tests telemetry_metadata_update telemetry_disclosure_records test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 723 filtered out; finished in 0.01s Direct PTY, rebuilt debug binary, fresh config-only homes, 130x40: - Supplied OpenAI/loopback/gpui-fixture config opens at the composer, no picker. Footer: OpenAI-compatible / gpui-fixture. Persisted provider_model status: configured; result: provider=openai, model=gpui-fixture; configured, not checked. - Configured OpenAI route without credentials opens the navigable picker with OpenAI-compatible selected. - Both processes exited normally. No inference request was sent. Reproduction boundary: a freshly built baseline at e4c94c849 also skipped the picker in a direct PTY, but produced no provider-step receipt. The reported forced-DeepSeek screen was not reproduced here. The full fixture plus running Ollama tmux check remains pending: tmux socket creation failed with Operation not permitted in the sandbox. No hosted CI, packaged release, or push claim. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ZwqatxgVFxHvovngywnks | 17 天前 | |
feat(telemetry): let an embedder declare the server's surface A client that starts `codewhale serve` can now name itself, so its sessions are not counted as anonymous `serve` traffic from nobody in particular. It sets `CODEWHALE_TELEMETRY_SURFACE` to a value in the surface whitelist before spawning the server; the VS Code extension uses the new `vscode-extension` value. The read is deliberately narrow. Only the `serve` branch consults it, because the variable is inherited by descendants and an agent's shell command runs `codewhale` descendants carrying this environment — a nested `codewhale exec` that picked the label up would report itself as the embedder. `tui` is refused, since nothing starts the interactive terminal UI on a user's behalf, and `serve --mcp` is still decided before the declaration. A name outside the whitelist is dropped rather than repaired: the field is a label, and the default is the honest one. This changes how an embedder's sessions are labelled, never whether anything may be collected. The server resolves that itself, from the same durable config, setup state, and `CODEWHALE_TELEMETRY` kill switch as before, so an embedder cannot enable collection where the user turned it off. The whitelist gains `vscode-extension` — generic, the way `web-app` and `desktop` are, and the editor's own name and version are not collected. No field is added, removed, or retyped, so the schema version is unchanged. In the ingest validator the value is appended rather than inserted, because the v1 contract's whitelist is a prefix of that list and v1 must keep refusing every surface the runtime never emitted. Signed-off-by: Ben Gao <bengao168@msn.com> | 7 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 28 天前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 14 天前 | ||
| 7 天前 | ||
| 28 天前 | ||
| 17 天前 | ||
| 7 天前 |