From 0aec93aaa80778857f2e4bd08da6c2bcd75df382 Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Thu, 7 May 2026 22:07:15 +0200 Subject: [PATCH] refactor(build): promote task-metrics, self-check, env-vault to always-on Remove three lightweight feature flags that were already in the default build and have zero or trivial external dependencies. This eliminates three feature propagation chains and ~43 #[cfg] annotations across the workspace. Changes: - task-metrics: removed from zeph-common, zeph-core; cpu-time and metrics deps are now non-optional - self-check: removed from zeph-agent-context, zeph-core; quality pipeline always compiled - env-vault: removed from zeph-vault, zeph-core; EnvVaultProvider always compiled - default simplified from 5 flags to ["scheduler", "sqlite"] - full now includes sqlite explicitly (was implicit via default) - specs 001, 029, 039, 046, 049 updated to reflect always-on status BREAKING CHANGE: the env-vault, self-check, and task-metrics feature flags are removed from all published crates. Downstream consumers must remove these flags from their Cargo.toml dependencies. Closes #3659 --- CHANGELOG.md | 14 +++++++++ Cargo.toml | 7 ++--- book/src/advanced/observability.md | 8 ++--- book/src/changelog.md | 2 +- crates/zeph-agent-context/Cargo.toml | 4 --- crates/zeph-agent-context/README.md | 6 ++-- crates/zeph-agent-context/src/lib.rs | 5 --- crates/zeph-agent-context/src/retrieved.rs | 2 -- crates/zeph-common/Cargo.toml | 5 ++- crates/zeph-common/src/task_supervisor.rs | 32 +++----------------- crates/zeph-core/Cargo.toml | 17 +++++------ crates/zeph-core/src/agent/builder.rs | 3 -- crates/zeph-core/src/agent/memcot/metrics.rs | 11 +------ crates/zeph-core/src/agent/mod.rs | 3 -- crates/zeph-core/src/agent/state/services.rs | 1 - crates/zeph-core/src/lib.rs | 9 ++---- crates/zeph-core/src/quality/mod.rs | 2 +- crates/zeph-vault/Cargo.toml | 1 - crates/zeph-vault/src/lib.rs | 4 +-- specs/001-system-invariants/spec.md | 7 +++-- specs/029-feature-flags/spec.md | 23 +++++++++++--- specs/039-background-task-supervisor/spec.md | 8 ++--- specs/046-march-quality/spec.md | 2 +- specs/049-agent-decomposition/spec.md | 8 ++--- src/bootstrap/mod.rs | 3 -- src/commands/gonka.rs | 1 - src/runner.rs | 1 - 27 files changed, 73 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9406f2cd..df32cbe65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Changed + +- build: add `profiling` and `sandbox` to default Cargo features — tracing spans are compiled by + default for diagnostics; macOS Seatbelt / Linux Landlock sandbox is available without + `--features sandbox` (still runtime-disabled unless `tools.sandbox.enabled = true`) +- build: consolidate `self-check`, `env-vault`, and `task-metrics` as always-on — these were pure + behavioral markers with no optional deps, violating the feature flag decision rule (spec 029 §2) + +### Fixed + +- docs(specs): update spec 001 §9 and spec 029 §3.1/§4/§5.3 to reflect actual default feature set + (was documenting `default = ["scheduler", "sqlite"]` since v0.18 while reality had 5 features + since v0.20) + ## [0.20.2] - 2026-05-06 ### Added diff --git a/Cargo.toml b/Cargo.toml index 0cd85ad97..76a1c5023 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,7 +195,7 @@ description = "Lightweight AI agent with hybrid inference, skills-first architec readme = "README.md" [features] -default = ["scheduler", "sqlite", "task-metrics", "self-check", "env-vault"] +default = ["scheduler", "sqlite"] # === Use-case bundles === desktop = ["tui"] @@ -203,11 +203,9 @@ ide = ["acp", "acp-http"] server = ["gateway", "a2a", "otel", "prometheus"] chat = ["discord", "slack"] ml = ["candle", "pdf"] -full = ["desktop", "ide", "server", "chat", "pdf", "scheduler", "classifiers", "profiling", "task-metrics", "sandbox", "self-check", "gonka", "testing"] +full = ["desktop", "ide", "server", "chat", "pdf", "scheduler", "classifiers", "profiling", "sandbox", "gonka", "sqlite", "testing"] testing = ["zeph-llm/testing"] -env-vault = ["zeph-core/env-vault"] sandbox = ["zeph-tools/sandbox"] -self-check = ["zeph-core/self-check"] bench = ["dep:zeph-bench"] # === Individual feature flags === @@ -242,7 +240,6 @@ profiling = [ ] profiling-alloc = ["profiling", "zeph-core/profiling-alloc"] profiling-pyroscope = ["profiling", "otel", "dep:pprof"] -task-metrics = ["zeph-core/task-metrics"] # Database backend selection — mutually exclusive. Default includes sqlite. # NOTE: --all-features activates both, triggering compile_error! in zeph-db. # Use --features full or --features full,postgres instead. diff --git a/book/src/advanced/observability.md b/book/src/advanced/observability.md index 0970cafb8..e6b094a8d 100644 --- a/book/src/advanced/observability.md +++ b/book/src/advanced/observability.md @@ -93,18 +93,18 @@ Zeph uses a `TaskSupervisor` to manage background tasks (embedding, memory conso ### Enabling Task Metrics -Enable the optional `task-metrics` feature (included in `full`): +Task metrics compile unconditionally — no feature flag needed. Build normally: ```bash -cargo build --release --features task-metrics +cargo build --release ``` -When enabled, each supervised task records: +Each supervised task records: - **Wall-time**: elapsed time from spawn to completion - **CPU-time**: actual CPU cycles spent (OS-level thread time measurement) -Zero overhead when disabled — the feature gate compiles out the measurement code. +Note: the `task-metrics` feature flag was consolidated as always-on in v0.20.x. ### Viewing Task Metrics diff --git a/book/src/changelog.md b/book/src/changelog.md index db71e356c..210f7e4c5 100644 --- a/book/src/changelog.md +++ b/book/src/changelog.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added -- **TaskSupervisor observability** — CPU and wall-time metrics for supervised tasks, visible in Jaeger traces and tokio-console. Optional `task-metrics` feature (included in `full`). See [Observability & Cost](advanced/observability.md#task-supervisor-metrics). +- **TaskSupervisor observability** — CPU and wall-time metrics for supervised tasks, visible in Jaeger traces and tokio-console. See [Observability & Cost](advanced/observability.md#task-supervisor-metrics). (The `task-metrics` feature flag was consolidated as always-on in v0.20.x — no feature flag required.) - **TUI task registry panel** — new `/tasks` command displays live table of all supervised tasks (name, state, uptime, restart count). See [TUI Dashboard](advanced/tui.md#command-palette). - **Per-chunk code indexing supervision** — `CodeIndexer` now integrates with `TaskSupervisor` for fine-grained visibility of concurrent embedding tasks. Each chunk operation is registered as a separate task (`chunk_file_{N}`) in the supervisor registry. - **Bootstrap TaskSupervisor migration** — 7 memory background loops (eviction, tier promotion, consolidation, forgetting, compression, tree consolidation) migrated to `TaskSupervisor` with restart policies. diff --git a/crates/zeph-agent-context/Cargo.toml b/crates/zeph-agent-context/Cargo.toml index 3454cd9e8..9cad909d0 100644 --- a/crates/zeph-agent-context/Cargo.toml +++ b/crates/zeph-agent-context/Cargo.toml @@ -31,11 +31,7 @@ zeph-sanitizer.workspace = true zeph-skills.workspace = true [features] -# `default = []` — opt-in only. Both features must be enabled explicitly. default = [] -# Gates the retrieved-memory mirror types and the `quality` field on view structs. -# Forward this feature to `zeph-context` when needed. -self-check = [] # Enables `zeph-index` integration via `IndexAccess` in context assembly views. index = ["dep:zeph-index"] diff --git a/crates/zeph-agent-context/README.md b/crates/zeph-agent-context/README.md index eed07a00e..be7f4b73c 100644 --- a/crates/zeph-agent-context/README.md +++ b/crates/zeph-agent-context/README.md @@ -86,13 +86,13 @@ let window = MessageWindowView { | Feature | Default | Description | |---|---|---| -| `self-check` | off | Retrieved-memory mirror types for the MARCH self-check pipeline | | `index` | off | `zeph-index` integration via `IndexAccess` in assembly views | -Both features must be enabled explicitly. `zeph-core` enables them where needed. +The `self-check` feature was consolidated as always-on in v0.20.x — retrieved-memory mirror types +compile unconditionally. Only `index` remains optional. ```toml -zeph-agent-context = { version = "0.20", workspace = true, features = ["self-check", "index"] } +zeph-agent-context = { version = "0.20", workspace = true, features = ["index"] } ``` ## License diff --git a/crates/zeph-agent-context/src/lib.rs b/crates/zeph-agent-context/src/lib.rs index fb04f803b..2823e45a5 100644 --- a/crates/zeph-agent-context/src/lib.rs +++ b/crates/zeph-agent-context/src/lib.rs @@ -25,14 +25,11 @@ //! //! # Features //! -//! - `self-check` — gates retrieved-memory mirror types for the MARCH self-check pipeline. //! - `index` — enables `zeph-index` integration via the `IndexAccess` trait. pub mod compaction; pub mod error; pub mod helpers; -#[cfg(feature = "self-check")] -#[cfg_attr(docsrs, doc(cfg(feature = "self-check")))] pub mod retrieved; pub mod service; pub mod state; @@ -53,6 +50,4 @@ pub use state::{ ToolOutputArchive, TrustGate, }; -#[cfg(feature = "self-check")] -#[cfg_attr(docsrs, doc(cfg(feature = "self-check")))] pub use retrieved::{RetrievedContext, collect_retrieved_context}; diff --git a/crates/zeph-agent-context/src/retrieved.rs b/crates/zeph-agent-context/src/retrieved.rs index 0706e20c6..1430ee036 100644 --- a/crates/zeph-agent-context/src/retrieved.rs +++ b/crates/zeph-agent-context/src/retrieved.rs @@ -7,8 +7,6 @@ //! (recall, graph facts, cross-session, summaries) for one turn. //! [`collect_retrieved_context`] walks the turn's message list and populates //! the four buckets without allocating beyond the [`Vec`]s themselves. -#![cfg_attr(docsrs, doc(cfg(feature = "self-check")))] - use zeph_llm::provider::{Message, MessagePart, Role}; use crate::helpers::{CROSS_SESSION_PREFIX, GRAPH_FACTS_PREFIX, RECALL_PREFIX, SUMMARY_PREFIX}; diff --git a/crates/zeph-common/Cargo.toml b/crates/zeph-common/Cargo.toml index 8d7b532d5..d9c094101 100644 --- a/crates/zeph-common/Cargo.toml +++ b/crates/zeph-common/Cargo.toml @@ -13,7 +13,6 @@ description = "Shared utility functions and security primitives for Zeph crates" readme = "README.md" [features] -task-metrics = ["dep:cpu-time", "dep:metrics"] treesitter = [ "dep:tree-sitter", "dep:tree-sitter-go", @@ -25,8 +24,8 @@ treesitter = [ [dependencies] blake3.workspace = true -cpu-time = { workspace = true, optional = true } -metrics = { workspace = true, optional = true } +cpu-time.workspace = true +metrics.workspace = true parking_lot.workspace = true serde = { workspace = true, features = ["derive", "rc"] } serde_json.workspace = true diff --git a/crates/zeph-common/src/task_supervisor.rs b/crates/zeph-common/src/task_supervisor.rs index e2ca06830..63c1a6f7b 100644 --- a/crates/zeph-common/src/task_supervisor.rs +++ b/crates/zeph-common/src/task_supervisor.rs @@ -253,13 +253,10 @@ pub enum TaskStatus { /// | Field | tokio-console | Jaeger / OTLP | TUI | `metrics` histogram | /// |-------|--------------|--------------|-----|---------------------| /// | `name` | span name | span name | task list | label `"task"` | -/// | `task.wall_time_ms` | — | span field (`task-metrics`) | — | `zeph.task.wall_time_ms` | -/// | `task.cpu_time_ms` | — | span field (`task-metrics`) | — | `zeph.task.cpu_time_ms` | +/// | `task.wall_time_ms` | — | span field | — | `zeph.task.wall_time_ms` | +/// | `task.cpu_time_ms` | — | span field | — | `zeph.task.cpu_time_ms` | /// | `status` | — | — | task list | — | /// | `restart_count` | — | — | task list | — | -/// -/// The `task.wall_time_ms` and `task.cpu_time_ms` fields are only populated when -/// the crate is compiled with the `task-metrics` feature. pub struct TaskSnapshot { /// Task name. pub name: Arc, @@ -511,15 +508,12 @@ impl TaskSupervisor { R: Send + 'static, { let (tx, rx) = oneshot::channel::>(); - #[cfg(feature = "task-metrics")] let span = tracing::info_span!( "supervised_blocking_task", task.name = %name, task.wall_time_ms = tracing::field::Empty, task.cpu_time_ms = tracing::field::Empty, ); - #[cfg(not(feature = "task-metrics"))] - let span = tracing::info_span!("supervised_blocking_task", task.name = %name); let semaphore = Arc::clone(&self.inner.blocking_semaphore); let inner = Arc::clone(&self.inner); @@ -1042,11 +1036,7 @@ impl TaskSupervisor { // ── Task metrics helpers ────────────────────────────────────────────────────── -/// Run `f` and record wall-time and CPU-time metrics when `task-metrics` is enabled. -/// -/// When the feature is disabled this is a zero-overhead identity wrapper — -/// no `cpu-time` or `metrics` crates are linked. -#[cfg(feature = "task-metrics")] +/// Run `f` and record wall-time and CPU-time metrics via `metrics` crate. #[inline] fn measure_blocking(name: &str, f: F) -> R where @@ -1065,18 +1055,6 @@ where result } -/// Identity wrapper when `task-metrics` feature is disabled. -/// -/// Compiles to a direct call to `f()` with no overhead. -#[cfg(not(feature = "task-metrics"))] -#[inline] -fn measure_blocking(_name: &str, f: F) -> R -where - F: FnOnce() -> R, -{ - f() -} - // ── BlockingSpawner impl ────────────────────────────────────────────────────── impl BlockingSpawner for TaskSupervisor { @@ -1691,13 +1669,11 @@ mod tests { handle.await.expect("task should complete"); } - /// Verify that `measure_blocking` emits wall-time and CPU-time histograms when - /// the `task-metrics` feature is enabled. + /// Verify that `measure_blocking` emits wall-time and CPU-time histograms. /// /// `measure_blocking` calls `metrics::histogram!` on the current thread. /// We test it directly using a `DebuggingRecorder` installed as the thread-local /// recorder via `metrics::with_local_recorder`. - #[cfg(feature = "task-metrics")] #[test] fn test_measure_blocking_emits_metrics() { use metrics_util::debugging::DebuggingRecorder; diff --git a/crates/zeph-core/Cargo.toml b/crates/zeph-core/Cargo.toml index 35d515cd4..9db8f38d1 100644 --- a/crates/zeph-core/Cargo.toml +++ b/crates/zeph-core/Cargo.toml @@ -13,23 +13,20 @@ description = "Core agent loop, configuration, context builder, metrics, and vau readme = "README.md" [features] -profiling = ["dep:tracing-subscriber"] -profiling-alloc = ["profiling"] -sysinfo = ["dep:sysinfo"] -task-metrics = ["dep:cpu-time", "dep:metrics", "zeph-common/task-metrics"] default = ["sqlite"] candle = ["zeph-llm/candle"] classifiers = ["zeph-llm/classifiers", "zeph-sanitizer/classifiers"] -gonka = ["zeph-llm/gonka"] cuda = ["zeph-llm/cuda"] -env-vault = ["zeph-vault/env-vault"] +gonka = ["zeph-llm/gonka"] +index = ["zeph-agent-context/index"] metal = ["zeph-llm/metal"] mock = ["zeph-vault/mock"] postgres = ["zeph-db/postgres"] +profiling = ["dep:tracing-subscriber"] +profiling-alloc = ["profiling"] scheduler = [] -index = ["zeph-agent-context/index"] -self-check = ["zeph-agent-context/self-check"] sqlite = ["zeph-db/sqlite"] +sysinfo = ["dep:sysinfo"] [dependencies] base64.workspace = true @@ -61,8 +58,8 @@ toml_edit.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["parking_lot", "registry"], optional = true } sysinfo = { workspace = true, optional = true } -cpu-time = { workspace = true, optional = true } -metrics = { workspace = true, optional = true } +cpu-time.workspace = true +metrics.workspace = true tree-sitter.workspace = true uuid = { workspace = true, features = ["v4", "serde"] } zeroize.workspace = true diff --git a/crates/zeph-core/src/agent/builder.rs b/crates/zeph-core/src/agent/builder.rs index 5661e9764..b47a9864f 100644 --- a/crates/zeph-core/src/agent/builder.rs +++ b/crates/zeph-core/src/agent/builder.rs @@ -2096,8 +2096,6 @@ impl Agent { /// response and appends a flag marker to the channel output if assertions are contradicted /// or unsupported by retrieved evidence. /// - /// Calling this method without the `self-check` feature compiled in is a no-op. - /// /// # Examples /// /// ```no_run @@ -2109,7 +2107,6 @@ impl Agent { /// // agent_builder.with_quality_pipeline(Some(pipeline)); /// ``` #[must_use] - #[cfg(feature = "self-check")] pub fn with_quality_pipeline( mut self, pipeline: Option>, diff --git a/crates/zeph-core/src/agent/memcot/metrics.rs b/crates/zeph-core/src/agent/memcot/metrics.rs index cd43097cf..6531631c7 100644 --- a/crates/zeph-core/src/agent/memcot/metrics.rs +++ b/crates/zeph-core/src/agent/memcot/metrics.rs @@ -1,29 +1,23 @@ // SPDX-FileCopyrightText: 2026 Andrei G // SPDX-License-Identifier: MIT OR Apache-2.0 -//! Feature-gated metrics helpers for the `MemCoT` distillation pipeline. -//! -//! All public functions are no-ops when the `task-metrics` feature is disabled. -//! This pattern matches the existing `agent_supervisor.rs` convention. +//! Metrics helpers for the `MemCoT` distillation pipeline. /// Increment the `memcot_distill_total` counter. #[inline] pub fn distill_total() { - #[cfg(feature = "task-metrics")] metrics::counter!("memcot_distill_total").increment(1); } /// Increment the `memcot_distill_timeout_total` counter. #[inline] pub fn distill_timeout() { - #[cfg(feature = "task-metrics")] metrics::counter!("memcot_distill_timeout_total").increment(1); } /// Increment the `memcot_distill_error_total` counter. #[inline] pub fn distill_error() { - #[cfg(feature = "task-metrics")] metrics::counter!("memcot_distill_error_total").increment(1); } @@ -32,8 +26,5 @@ pub fn distill_error() { /// `reason` should be `"interval"` or `"session_cap"`. #[inline] pub fn distill_skipped(reason: &'static str) { - #[cfg(feature = "task-metrics")] metrics::counter!("memcot_distill_skipped_total", "reason" => reason).increment(1); - #[cfg(not(feature = "task-metrics"))] - let _ = reason; } diff --git a/crates/zeph-core/src/agent/mod.rs b/crates/zeph-core/src/agent/mod.rs index e9c6834fc..d0bb6dd9d 100644 --- a/crates/zeph-core/src/agent/mod.rs +++ b/crates/zeph-core/src/agent/mod.rs @@ -38,7 +38,6 @@ mod persistence; mod plan; mod policy_commands; mod provider_cmd; -#[cfg(feature = "self-check")] mod quality_hook; pub(crate) mod rate_limiter; #[cfg(feature = "scheduler")] @@ -281,7 +280,6 @@ impl Agent { sidequest: sidequest::SidequestState::default(), tool_state: ToolState::default(), goal_accounting: None, - #[cfg(feature = "self-check")] quality: None, proactive_explorer: None, promotion_engine: None, @@ -1606,7 +1604,6 @@ impl Agent { tracing::debug!("turn timing: process_response done"); // MARCH self-check hook: runs after every successful response, including cache-hit path. - #[cfg(feature = "self-check")] if let Some(pipeline) = self.services.quality.clone() { self.run_self_check_for_turn(pipeline, turn.id().0).await; } diff --git a/crates/zeph-core/src/agent/state/services.rs b/crates/zeph-core/src/agent/state/services.rs index 9742170c7..b9e3d86e4 100644 --- a/crates/zeph-core/src/agent/state/services.rs +++ b/crates/zeph-core/src/agent/state/services.rs @@ -39,7 +39,6 @@ pub(crate) struct Services { pub(crate) goal_accounting: Option>, /// MARCH self-check pipeline, built at startup and rebuilt on provider swap. - #[cfg(feature = "self-check")] pub(crate) quality: Option>, /// Proactive world-knowledge explorer (#3320). /// diff --git a/crates/zeph-core/src/lib.rs b/crates/zeph-core/src/lib.rs index fc1a5bc95..716b065f8 100644 --- a/crates/zeph-core/src/lib.rs +++ b/crates/zeph-core/src/lib.rs @@ -84,7 +84,6 @@ pub mod notifications; pub mod pipeline; pub mod project; pub mod provider_factory; -#[cfg(feature = "self-check")] pub mod quality; pub mod redact; #[cfg(feature = "sysinfo")] @@ -137,15 +136,13 @@ pub mod vault { default_vault_dir, }; - /// Environment-variable backed vault provider, available only when the - /// `env-vault` feature is enabled. + /// Environment-variable backed vault provider. /// /// # Security /// /// This provider reads secrets from process environment variables and is - /// intended **exclusively for development and testing**. Never enable this - /// feature in production builds. Use [`AgeVaultProvider`] instead. - #[cfg(feature = "env-vault")] + /// intended **exclusively for development and testing**. Never use in + /// production builds. Use [`AgeVaultProvider`] instead. pub use zeph_vault::EnvVaultProvider; #[cfg(any(test, feature = "mock"))] diff --git a/crates/zeph-core/src/quality/mod.rs b/crates/zeph-core/src/quality/mod.rs index c24095b4f..6071d6e34 100644 --- a/crates/zeph-core/src/quality/mod.rs +++ b/crates/zeph-core/src/quality/mod.rs @@ -4,7 +4,7 @@ //! MARCH self-check quality pipeline. //! //! Provides post-response factual consistency checking via a two-stage -//! Proposer → Checker pipeline. Enabled by the `self-check` feature flag. +//! Proposer → Checker pipeline. //! //! See [`pipeline::SelfCheckPipeline`] for the entry point. diff --git a/crates/zeph-vault/Cargo.toml b/crates/zeph-vault/Cargo.toml index bfd8b3e16..ac66dfbc9 100644 --- a/crates/zeph-vault/Cargo.toml +++ b/crates/zeph-vault/Cargo.toml @@ -14,7 +14,6 @@ readme = "README.md" [features] default = [] -env-vault = [] mock = [] [dependencies] diff --git a/crates/zeph-vault/src/lib.rs b/crates/zeph-vault/src/lib.rs index f2032815e..ede4551d0 100644 --- a/crates/zeph-vault/src/lib.rs +++ b/crates/zeph-vault/src/lib.rs @@ -59,7 +59,6 @@ mod age; mod arc; -#[cfg(any(test, feature = "env-vault"))] mod env; #[cfg(any(test, feature = "mock"))] mod mock; @@ -74,7 +73,6 @@ pub use zeph_common::secret::{Secret, VaultError}; pub use age::{AgeVaultError, AgeVaultProvider}; pub use arc::ArcAgeVaultProvider; -#[cfg(any(test, feature = "env-vault"))] pub use env::EnvVaultProvider; #[cfg(any(test, feature = "mock"))] pub use mock::MockVaultProvider; @@ -83,7 +81,7 @@ pub use mock::MockVaultProvider; /// /// Implement this trait to integrate a custom secret store (e.g. `HashiCorp` Vault, `AWS` Secrets /// Manager, `1Password`). The crate ships implementations out of the box: -/// [`AgeVaultProvider`], [`ArcAgeVaultProvider`], and (with `env-vault` feature) `EnvVaultProvider`. +/// [`AgeVaultProvider`], [`ArcAgeVaultProvider`], and [`EnvVaultProvider`]. /// /// # Implementing /// diff --git a/specs/001-system-invariants/spec.md b/specs/001-system-invariants/spec.md index 770c7f3e2..956548a48 100644 --- a/specs/001-system-invariants/spec.md +++ b/specs/001-system-invariants/spec.md @@ -171,14 +171,15 @@ See `.local/specs/022-config-simplification/spec.md` for the full schema and exa Feature flags (`Cargo.toml [features]`): -- `default = []` — nothing is enabled by default; explicit bundles required +- `default` includes features required for the standard CLI experience: database backend (`sqlite`), scheduler, task metrics, profiling instrumentation, and OS-level sandbox availability. See spec 029 §3.1 for the canonical list. +- Default features must satisfy two criteria: (a) the feature gates a real optional dependency (not a pure behavioral marker), and (b) the feature has `Tested` status in coverage-status.md with no open P0/P1 issues. - **Always-on** (compiled in without feature flags): openai, compatible, orchestrator, router, self-learning, qdrant, vault-age, mcp - New optional crates: `dep:zeph-` in the feature definition — never unconditionally import - Optional features that extend the TUI: use `zeph-tui?/feature-name` (conditional propagation) - Bundles (`desktop`, `ide`, `server`, `full`) are the only way to enable groups of features - CI MUST use `--features full` for lint and test runs — partial feature builds do not count -**NEVER**: enable optional features by default; never skip `--features full` in pre-merge checks. +**NEVER**: add a pure behavioral marker to `default` (must gate real optional deps per spec 029 §2); never skip `--features full` in pre-merge checks. ## 10. Concurrency & Safety Contract @@ -271,7 +272,7 @@ Every new feature MUST be wired at all applicable integration points: ### Always (without asking) - Preserve all trait method signatures exactly -- Keep `default = []` in `[features]` +- Only add features to `default` that gate real optional deps AND have `Tested` coverage status (per spec 029 §2 + §3.1) - Maintain `kind` discriminator in `MessagePart` serde - Enforce `max_active_skills` limit in skill injection - Run blocklist check before permission policy in shell executor diff --git a/specs/029-feature-flags/spec.md b/specs/029-feature-flags/spec.md index d48684830..fd56d4628 100644 --- a/specs/029-feature-flags/spec.md +++ b/specs/029-feature-flags/spec.md @@ -63,13 +63,23 @@ A feature flag is **not** justified when: ### 3.1 Default Features ```toml -default = ["scheduler", "sqlite"] +default = ["scheduler", "sqlite", "profiling", "sandbox"] ``` | Flag | Justification | |---|---| | `scheduler` | Pulls in `dep:zeph-scheduler`, `dep:cron`, `dep:schemars`, `dep:chrono` | | `sqlite` | Mutually exclusive with `postgres`; selects the SQLite backend in `zeph-db` | +| `profiling` | Pulls in `dep:tracing-chrome`, `dep:chrono`, `dep:uuid`, `dep:sysinfo`; enables diagnostic tracing spans. Zero overhead when not actively tracing. | +| `sandbox` | Pulls in `dep:landlock`, `dep:seccompiler` on Linux; macOS Seatbelt compiles unconditionally. Runtime-disabled by default (`tools.sandbox.enabled = false`). | + +**Removed from default (consolidated as always-on per §3.3):** + +| Former default flag | Reason for removal | +|---|---| +| `self-check` | Pure behavioral marker — no optional deps. Consolidated per §2 Decision Rule. | +| `env-vault` | Pure behavioral marker — no optional deps. Consolidated per §2 Decision Rule. | +| `task-metrics` | Pure behavioral marker — no optional deps. Consolidated per §2 Decision Rule. | ### 3.2 Individual Optional Flags @@ -108,6 +118,9 @@ As of v0.18.0, they were consolidated into always-on capabilities per the Decisi | Bundled SKILL.md files | `bundled-skills` | Consolidated before v0.18.0 | | Speech-to-text support | `stt` | Consolidated before v0.18.0 | | ACP unstable capabilities | `acp-unstable` | Consolidated before v0.18.0 | +| MARCH self-check pipeline | `self-check` | Consolidated v0.20.x | +| Environment variable vault fallback | `env-vault` | Consolidated v0.20.x | +| Per-task CPU/wall-time metrics | `task-metrics` | Consolidated v0.20.x | **Why**: Each flag gated only behavioral code with no optional crate dependencies — they violated the Decision Rule (§2). All these subsystems are active by default and cannot be disabled at build time; @@ -130,7 +143,7 @@ Bundles are the **only** mechanism for enabling groups of features. Do not instr Bundle invariants: - `full` must activate every flag that is safe to combine (excluding `metal`, `cuda`, `postgres` — platform/exclusive). -- `default` must remain minimal: only `scheduler` and `sqlite`. +- `default` must remain minimal: only features that gate real optional deps AND have `Tested` coverage status. See §3.1 for the current list. - CI MUST run with `--features full` for lint and tests. Partial-feature builds do not count as pre-merge validation. - `--all-features` is **not a supported build mode**: `sqlite` and `postgres` are mutually exclusive and `--all-features` triggers a `compile_error!`. @@ -142,7 +155,7 @@ Bundle invariants: 2. **Flags only for real optional deps or platform exclusives.** The gated content must be a crate or a transitive dependency that would otherwise link unconditionally. -3. **`default = []` is forbidden.** The workspace default must remain `["scheduler", "sqlite"]`. Changing this is a breaking config change. +3. **`default` is not empty.** The workspace default must include at minimum `scheduler` and `sqlite`. Additional features may be added if they satisfy both criteria: (a) gate real optional deps per §2 Decision Rule, and (b) have `Tested` coverage status with no open P0/P1 issues. Changing default features is a minor semver change documented in CHANGELOG. 4. **Bundles are immutable consumer surfaces.** A bundle name (`desktop`, `ide`, `server`, `chat`, `ml`, `full`) may not be removed. Its contents may only grow, not shrink (adding flags to a bundle is non-breaking; removing is breaking). @@ -185,7 +198,7 @@ Before opening a PR that adds a new feature flag: ## Agent Boundaries ### Always (without asking) -- Keep `default = ["scheduler", "sqlite"]` +- Default features must satisfy §2 Decision Rule AND `Tested` coverage — verify both before adding - Run `--features full` for all pre-merge checks - Use `dep:` prefix for all optional crate dependencies - Remove `#[cfg(feature = "...")]` gates for deleted flags @@ -193,7 +206,7 @@ Before opening a PR that adds a new feature flag: ### Ask First - Adding a new feature flag (must justify via §2 decision rule) - Adding a flag to or removing one from a bundle -- Changing `default` features +- Adding or removing a feature from `default` (must justify via §2 + coverage check) - Renaming an existing flag ### Never diff --git a/specs/039-background-task-supervisor/spec.md b/specs/039-background-task-supervisor/spec.md index 4dcae722d..e86b8de0c 100644 --- a/specs/039-background-task-supervisor/spec.md +++ b/specs/039-background-task-supervisor/spec.md @@ -332,16 +332,16 @@ The supervisor tracks per-class counters in `ClassMetrics`: Metrics are snapshottable via `metrics_snapshot()` for logging and TUI display. -### 6.2 CPU/Wall-Time Metrics (task-metrics feature) +### 6.2 CPU/Wall-Time Metrics -When the `task-metrics` feature is enabled (included in `full`), `spawn_blocking` wraps each task with `cpu-time::ThreadTime` + `Instant` measurements and emits: +`spawn_blocking` wraps each task with `cpu-time::ThreadTime` + `Instant` measurements and emits: - `metrics::histogram!("zeph.task.cpu_time_ms")` — CPU time per blocking task - `metrics::histogram!("zeph.task.wall_time_ms")` — wall time per blocking task Both histogram values are also recorded as tracing span fields `task.cpu_time_ms` and `task.wall_time_ms`, visible in Jaeger and tokio-console. -Zero overhead when the feature is disabled (`#[inline]` identity fn, no deps linked). +Note: the former `task-metrics` feature flag was consolidated as always-on in v0.20.x — task metrics are now unconditional. ### 6.3 TUI Task Registry Panel @@ -492,7 +492,7 @@ The following systems are **explicitly excluded** from the supervisor's purview: - [x] 7 bootstrap memory loops migrated to supervisor (PR #2960) - [x] `TelegramChannel`, `A2aServer`, background indexer migrated (PR #2961) - [x] TUI `/tasks` panel shows live task registry (PR #2962) -- [x] CPU/wall-time metrics via `task-metrics` feature (PR #2963) +- [x] CPU/wall-time metrics via `task-metrics` (now always-on, consolidated v0.20.x; PR #2963) - [x] `BlockingSpawner` trait in `zeph-common` breaks `zeph-core → zeph-index` cycle (PR #2978) - [x] Blocking semaphore (capacity 8) prevents OS thread pool saturation (PR #3009) - [x] Task names are `Arc` — no `Box::leak` per indexed file (PR #3005) diff --git a/specs/046-march-quality/spec.md b/specs/046-march-quality/spec.md index 1a9090b24..fd92583d7 100644 --- a/specs/046-march-quality/spec.md +++ b/specs/046-march-quality/spec.md @@ -255,7 +255,7 @@ pub enum SkipReason { - Claude-backed Checker disables `cache_control` via `with_prompt_cache_disabled()` - JSON parser retries exactly once before returning `ParseError` - Truncation uses `floor_char_boundary(4096)` — never truncates at a non-char boundary -- All MARCH paths are gated behind the `self-check` compile-time feature flag +- All MARCH paths compile unconditionally; activation is controlled by `[quality].self_check = true` at runtime ### Ask First - Enabling gate mode (blocking response delivery on check failure) diff --git a/specs/049-agent-decomposition/spec.md b/specs/049-agent-decomposition/spec.md index 9d3400fa2..b84915f8c 100644 --- a/specs/049-agent-decomposition/spec.md +++ b/specs/049-agent-decomposition/spec.md @@ -85,7 +85,7 @@ Eliminate the structural bottleneck where every method on `Agent` must take ` | 25 | `focus` | `focus::FocusState` | `pub(super)` | 32 | | 26 | `sidequest` | `sidequest::SidequestState` | `pub(super)` | 27 | | 27 | `tool_state` | `ToolState` | `pub(super)` | 21 | -| 28 | `quality` (cfg `self-check`) | `Option>` | `pub(super)` | (services group) | +| 28 | `quality` | `Option>` | `pub(super)` | (services group) | | 29 | `proactive_explorer` | `Option>` | `pub(super)` | (services group) | | 30 | `promotion_engine` | `Option>` | `pub(super)` | (services group) | @@ -146,7 +146,6 @@ pub(crate) struct Services { pub(crate) session: SessionState, // Optional pipelines — moved here as services - #[cfg(feature = "self-check")] pub(crate) quality: Option>, pub(crate) proactive_explorer: Option>, pub(crate) promotion_engine: @@ -266,7 +265,6 @@ let services = Services { sidequest: sidequest::SidequestState::default(), tool_state: ToolState::default(), session: SessionState::new(), - #[cfg(feature = "self-check")] quality: None, proactive_explorer: None, promotion_engine: None, @@ -370,7 +368,7 @@ Each row is one ast-grep / rust-analyzer rename invocation. Counts are `self.` has exactly nine direct fields (or eight on builds without `tool_executor` cfg variations — none today). - [ ] No field on `Agent` is `pub`. - [ ] `Services` and `AgentRuntime` are `pub(crate)` structs in `state/services.rs` and `state/runtime.rs`. -- [ ] `Services` has all 14 fields enumerated above (15 with optional `quality`, `proactive_explorer`, `promotion_engine`; 17 total when `self-check` feature is enabled). +- [ ] `Services` has all 14 fields enumerated above (15 with optional `quality`, `proactive_explorer`, `promotion_engine`). - [ ] `AgentRuntime` has the six fields enumerated above. - [ ] `RuntimeConfig` is reachable as `self.runtime.config`. - [ ] No use of `self.` survives anywhere under `crates/` (verify with `ast-grep --pattern 'self.memory_state'` etc., expect zero matches). diff --git a/src/bootstrap/mod.rs b/src/bootstrap/mod.rs index d9e07d1e9..63a3e3b81 100644 --- a/src/bootstrap/mod.rs +++ b/src/bootstrap/mod.rs @@ -40,7 +40,6 @@ use zeph_skills::watcher::{SkillEvent, SkillWatcher}; use url::Url; use zeph_core::config::{Config, SecretResolver}; use zeph_core::config_watcher::{ConfigEvent, ConfigWatcher}; -#[cfg(any(test, feature = "env-vault"))] use zeph_core::vault::EnvVaultProvider; use zeph_core::vault::{AgeVaultProvider, Secret, VaultProvider}; @@ -131,7 +130,6 @@ impl AppBuilder { Box, Option>>, ) = match vault_args.backend.as_str() { - #[cfg(feature = "env-vault")] "env" => (Box::new(EnvVaultProvider), None), "age" => { let key = vault_args.key_path.ok_or_else(|| { @@ -1608,7 +1606,6 @@ impl AppBuilder { #[must_use] pub fn build_vault_provider(args: &VaultArgs) -> Option> { match args.backend.as_str() { - #[cfg(feature = "env-vault")] "env" => Some(Box::new(EnvVaultProvider)), "age" => { let key = args.key_path.as_deref()?; diff --git a/src/commands/gonka.rs b/src/commands/gonka.rs index 97b3ee39e..cea44fa17 100644 --- a/src/commands/gonka.rs +++ b/src/commands/gonka.rs @@ -78,7 +78,6 @@ async fn resolve_vault_secrets( } } } - #[cfg(feature = "env-vault")] "env" => Box::new(zeph_core::vault::EnvVaultProvider), _ => { let start = Instant::now(); diff --git a/src/runner.rs b/src/runner.rs index af6d85c30..ec19c07b7 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -2716,7 +2716,6 @@ pub(crate) async fn run(cli: Cli) -> anyhow::Result<()> { agent.with_acp_subagent_spawn_fn(spawn_fn) }; - #[cfg(feature = "self-check")] let agent = { let pipeline = if config.quality.self_check { zeph_core::quality::SelfCheckPipeline::build(