From aff3767dcb57252bdd887f80188f094647665dfc Mon Sep 17 00:00:00 2001 From: "Andrei G." Date: Sun, 12 Jul 2026 21:00:57 +0200 Subject: [PATCH] fix: convert 9 remaining Display impls from write_str to pad SessionKind/SessionStatus/SessionChannel were fixed in #6060 for the same latent bug: Formatter::write_str silently ignores width/fill/align flags from the caller's format spec, while Formatter::pad respects them. Converts the remaining 9 instances of this pattern to f.pad(...): ProviderKind, MemoryTier, ContentFidelity, EntityType, SourceKind, ParameterKind, ExperimentSource, EdgeType, and Lang (found during review, using a different self-accessor than the issue's original grep pattern). Adds width-spec regression tests for each type. Closes #6066 --- CHANGELOG.md | 9 ++++++ crates/zeph-common/src/memory.rs | 19 ++++++++++-- crates/zeph-config/src/providers/llm.rs | 2 +- crates/zeph-config/src/providers/tests.rs | 15 ++++++++++ crates/zeph-experiments/src/types.rs | 31 ++++++++++++++++++-- crates/zeph-index/src/languages.rs | 14 ++++++++- crates/zeph-memory/src/graph/types.rs | 17 ++++++++++- crates/zeph-memory/src/optical_forgetting.rs | 17 ++++++++++- crates/zeph-memory/src/store/trust.rs | 17 ++++++++++- crates/zeph-memory/src/types.rs | 17 ++++++++++- 10 files changed, 148 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9fb1c78b..a39957220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Known gap: MCP-provided tools are not inspected and may still perform their own HTTP egress. This is a best-effort tool/command-identity block, not a sandbox-level guarantee — see `specs/069-threat-model/spec.md` INVARIANT-5. + +- `zeph-config`/`zeph-memory`/`zeph-experiments`/`zeph-common`/`zeph-index`: 9 `Display` + impls (`ProviderKind`, `MemoryTier`, `ContentFidelity`, `EntityType`, `SourceKind`, + `ParameterKind`, `ExperimentSource`, `EdgeType`, `Lang`) used `Formatter::write_str`, + which silently ignores width/fill/align flags from the caller's format spec — only + `Formatter::pad` respects them. Switched all 9 to `f.pad(...)`, closing off the same + latent width-spec bug already fixed for `SessionKind`/`SessionStatus`/`SessionChannel` + in #6060 (#6066). + - `zeph-skills`/`src/acp.rs`/`src/serve/`: `SkillOrchestra`'s RL routing head lost learned updates under concurrent ACP/`/sessions` agents (#5974). `#5921` wired `RoutingHead` persistence into `spawn_acp_agent` and `build_agent_factory`, but each session independently diff --git a/crates/zeph-common/src/memory.rs b/crates/zeph-common/src/memory.rs index 91f3bd5df..f4cc89a68 100644 --- a/crates/zeph-common/src/memory.rs +++ b/crates/zeph-common/src/memory.rs @@ -312,7 +312,7 @@ impl EdgeType { impl fmt::Display for EdgeType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -761,7 +761,22 @@ pub trait ContextMemoryBackend: Send + Sync { #[cfg(test)] mod tests { - use super::MemoryRoute; + use super::{EdgeType, MemoryRoute}; + + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn edge_type_display_respects_width() { + assert_eq!( + format!("{:<10}", EdgeType::Causal), + format!("{:<10}", "causal") + ); + assert_eq!( + format!("{:>10}", EdgeType::Semantic), + format!("{:>10}", "semantic") + ); + } #[test] fn memory_route_serde_roundtrip() { diff --git a/crates/zeph-config/src/providers/llm.rs b/crates/zeph-config/src/providers/llm.rs index c7cf5dcf1..8f73bf051 100644 --- a/crates/zeph-config/src/providers/llm.rs +++ b/crates/zeph-config/src/providers/llm.rs @@ -130,7 +130,7 @@ impl ProviderKind { impl std::fmt::Display for ProviderKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } diff --git a/crates/zeph-config/src/providers/tests.rs b/crates/zeph-config/src/providers/tests.rs index e517c2c33..7b62730fc 100644 --- a/crates/zeph-config/src/providers/tests.rs +++ b/crates/zeph-config/src/providers/tests.rs @@ -982,3 +982,18 @@ fn validate_cocoon_pricing_valid_passes() { }); assert!(e.validate().is_ok()); } + +/// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags, so +/// width-specifier `format!` calls used to render unpadded text. `f.pad` must reproduce +/// the same padding a plain `&str` would get under an identical width specifier. +#[test] +fn provider_kind_display_respects_width() { + assert_eq!( + format!("{:<12}", ProviderKind::Claude), + format!("{:<12}", "claude") + ); + assert_eq!( + format!("{:>12}", ProviderKind::Compatible), + format!("{:>12}", "compatible") + ); +} diff --git a/crates/zeph-experiments/src/types.rs b/crates/zeph-experiments/src/types.rs index 8cbf501f1..3f456cfc9 100644 --- a/crates/zeph-experiments/src/types.rs +++ b/crates/zeph-experiments/src/types.rs @@ -132,7 +132,7 @@ impl ParameterKind { impl std::fmt::Display for ParameterKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -283,7 +283,7 @@ impl ExperimentSource { impl std::fmt::Display for ExperimentSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -383,6 +383,33 @@ mod tests { assert_eq!(ExperimentSource::Scheduled.to_string(), "scheduled"); } + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn parameter_kind_display_respects_width() { + assert_eq!( + format!("{:<20}", ParameterKind::TopK), + format!("{:<20}", "top_k") + ); + assert_eq!( + format!("{:>20}", ParameterKind::SimilarityThreshold), + format!("{:>20}", "similarity_threshold") + ); + } + + #[test] + fn experiment_source_display_respects_width() { + assert_eq!( + format!("{:<12}", ExperimentSource::Manual), + format!("{:<12}", "manual") + ); + assert_eq!( + format!("{:>12}", ExperimentSource::Scheduled), + format!("{:>12}", "scheduled") + ); + } + #[test] fn variation_value_int_display() { let v = VariationValue::Int(42); diff --git a/crates/zeph-index/src/languages.rs b/crates/zeph-index/src/languages.rs index a03429bc1..6f10aae39 100644 --- a/crates/zeph-index/src/languages.rs +++ b/crates/zeph-index/src/languages.rs @@ -297,7 +297,7 @@ impl Lang { impl std::fmt::Display for Lang { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.id()) + f.pad(self.id()) } } @@ -455,4 +455,16 @@ mod tests { assert_eq!(lang.to_string(), lang.id()); } } + + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn lang_display_respects_width() { + assert_eq!(format!("{:<12}", Lang::Rust), format!("{:<12}", "rust")); + assert_eq!( + format!("{:>12}", Lang::TypeScript), + format!("{:>12}", "typescript") + ); + } } diff --git a/crates/zeph-memory/src/graph/types.rs b/crates/zeph-memory/src/graph/types.rs index 78da67a5e..2cc93f2e6 100644 --- a/crates/zeph-memory/src/graph/types.rs +++ b/crates/zeph-memory/src/graph/types.rs @@ -58,7 +58,7 @@ impl EntityType { impl fmt::Display for EntityType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -625,6 +625,21 @@ mod tests { } } + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn entity_type_display_respects_width() { + assert_eq!( + format!("{:<15}", EntityType::Person), + format!("{:<15}", "person") + ); + assert_eq!( + format!("{:>15}", EntityType::Organization), + format!("{:>15}", "organization") + ); + } + #[test] fn graph_fact_composite_score() { let fact = GraphFact { diff --git a/crates/zeph-memory/src/optical_forgetting.rs b/crates/zeph-memory/src/optical_forgetting.rs index c70a49dff..190c7b85f 100644 --- a/crates/zeph-memory/src/optical_forgetting.rs +++ b/crates/zeph-memory/src/optical_forgetting.rs @@ -76,7 +76,7 @@ impl ContentFidelity { impl std::fmt::Display for ContentFidelity { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -413,6 +413,21 @@ mod tests { assert!("unknown".parse::().is_err()); } + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn content_fidelity_display_respects_width() { + assert_eq!( + format!("{:<15}", ContentFidelity::Full), + format!("{:<15}", "Full") + ); + assert_eq!( + format!("{:>15}", ContentFidelity::SummaryOnly), + format!("{:>15}", "SummaryOnly") + ); + } + #[test] fn optical_forgetting_config_defaults() { let cfg = OpticalForgettingConfig::default(); diff --git a/crates/zeph-memory/src/store/trust.rs b/crates/zeph-memory/src/store/trust.rs index 1d3aa86b7..49e914461 100644 --- a/crates/zeph-memory/src/store/trust.rs +++ b/crates/zeph-memory/src/store/trust.rs @@ -34,7 +34,7 @@ impl SourceKind { impl std::fmt::Display for SourceKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -600,6 +600,21 @@ mod tests { assert_eq!(SourceKind::File.to_string(), "file"); } + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn source_kind_display_respects_width() { + assert_eq!( + format!("{:<10}", SourceKind::Local), + format!("{:<10}", "local") + ); + assert_eq!( + format!("{:>10}", SourceKind::Bundled), + format!("{:>10}", "bundled") + ); + } + #[test] fn source_kind_from_str_local() { let kind: SourceKind = "local".parse().unwrap(); diff --git a/crates/zeph-memory/src/types.rs b/crates/zeph-memory/src/types.rs index e9811ec2f..d1922ba3f 100644 --- a/crates/zeph-memory/src/types.rs +++ b/crates/zeph-memory/src/types.rs @@ -44,7 +44,7 @@ impl MemoryTier { impl std::fmt::Display for MemoryTier { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) + f.pad(self.as_str()) } } @@ -224,6 +224,21 @@ mod tests { assert!("unknown".parse::().is_err()); } + /// Locks in the `f.pad` fix (#6066): `f.write_str` ignores width/fill/align flags. + /// `f.pad` must reproduce the same padding a plain `&str` would get under an + /// identical width specifier. + #[test] + fn memory_tier_display_respects_width() { + assert_eq!( + format!("{:<10}", MemoryTier::Working), + format!("{:<10}", "working") + ); + assert_eq!( + format!("{:>10}", MemoryTier::Semantic), + format!("{:>10}", "semantic") + ); + } + #[test] fn memory_tier_serde_round_trip() { let json = serde_json::to_string(&MemoryTier::Semantic).unwrap();