Finding
All four VaultProvider implementations in zeph-vault (AgeVaultProvider in age.rs:492-498, ArcAgeVaultProvider in arc.rs:50-62, EnvVaultProvider in env.rs:39-46, MockVaultProvider in mock.rs:111-117) repeat the identical get_secret return-type signature verbatim:
fn get_secret(
&self,
key: &str,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + '_>>
This is the standard workaround for returning a dyn-compatible future from a trait method (native async fn in traits, per this project's Edition 2024 convention, is not object-safe, and the async-trait crate is disallowed per .claude/rules/rust-code.md). The boilerplate is unavoidable in each impl block, but the return type itself is repeated 4 times letter-for-letter, with no shared alias to name it once.
The same unaliased pattern exists workspace-wide (11 files under crates/, e.g. zeph-llm/src/provider.rs, zeph-scheduler/src/task.rs), so a full fix is out of scope for a single-crate finding — this issue is scoped to zeph-vault only, where it is cheap and self-contained (one crate, one trait, 4 call sites).
Location
crates/zeph-vault/src/lib.rs:97-100 (trait definition)
crates/zeph-vault/src/age.rs:492-498
crates/zeph-vault/src/arc.rs:50-62
crates/zeph-vault/src/env.rs:39-46
crates/zeph-vault/src/mock.rs:111-117
Before
pub trait VaultProvider: Send + Sync {
fn get_secret(
&self,
key: &str,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + '_>>;
...
}
// repeated identically in age.rs, arc.rs, env.rs, mock.rs:
fn get_secret(
&self,
key: &str,
) -> Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + '_>> {
...
}
After
// lib.rs
/// A boxed, `Send` future resolving to a secret lookup result, borrowed for the
/// lifetime of the `&self` receiver that produced it.
pub type SecretFuture<'a> = Pin<Box<dyn Future<Output = Result<Option<String>, VaultError>> + Send + 'a>>;
pub trait VaultProvider: Send + Sync {
fn get_secret(&self, key: &str) -> SecretFuture<'_>;
...
}
// age.rs / arc.rs / env.rs / mock.rs:
fn get_secret(&self, key: &str) -> SecretFuture<'_> {
...
}
Why
A named type alias documents intent once (what the future represents) instead of four separate anonymous repetitions of the same 90-character type, and shrinks each impl signature to a single line. It is a pure textual/readability change — the type is identical (Pin<Box<dyn Future<...> + Send + '_>> is what SecretFuture<'_> expands to), so no behavior, no call-site, and no downstream consumer of Box<dyn VaultProvider> is affected. Low priority since the crate already builds and passes rustdoc/clippy cleanly — this is a small ergonomics win, not a defect.
Finding
All four
VaultProviderimplementations inzeph-vault(AgeVaultProviderinage.rs:492-498,ArcAgeVaultProviderinarc.rs:50-62,EnvVaultProviderinenv.rs:39-46,MockVaultProviderinmock.rs:111-117) repeat the identicalget_secretreturn-type signature verbatim:This is the standard workaround for returning a dyn-compatible future from a trait method (native
async fnin traits, per this project's Edition 2024 convention, is not object-safe, and theasync-traitcrate is disallowed per.claude/rules/rust-code.md). The boilerplate is unavoidable in eachimplblock, but the return type itself is repeated 4 times letter-for-letter, with no shared alias to name it once.The same unaliased pattern exists workspace-wide (11 files under
crates/, e.g.zeph-llm/src/provider.rs,zeph-scheduler/src/task.rs), so a full fix is out of scope for a single-crate finding — this issue is scoped tozeph-vaultonly, where it is cheap and self-contained (one crate, one trait, 4 call sites).Location
crates/zeph-vault/src/lib.rs:97-100(trait definition)crates/zeph-vault/src/age.rs:492-498crates/zeph-vault/src/arc.rs:50-62crates/zeph-vault/src/env.rs:39-46crates/zeph-vault/src/mock.rs:111-117Before
After
Why
A named type alias documents intent once (what the future represents) instead of four separate anonymous repetitions of the same 90-character type, and shrinks each
implsignature to a single line. It is a pure textual/readability change — the type is identical (Pin<Box<dyn Future<...> + Send + '_>>is whatSecretFuture<'_>expands to), so no behavior, no call-site, and no downstream consumer ofBox<dyn VaultProvider>is affected. Low priority since the crate already builds and passes rustdoc/clippy cleanly — this is a small ergonomics win, not a defect.