6.9 KiB
Phase 1 Step 16c — Implementation Notes
Status
Done.
Plan reference
- Parent plan:
docs/implementation/PHASE-1-STEP-16-NOTES.md - Sub-phase goal: "Migrate Vault onto AppState; Vault::init takes
&AppConfig"
Summary
Changed Vault::init(&Config) and Vault::init_bare() to operate on
AppConfig instead of Config. Simplified Vault::handle_vault_flags
to accept a &Vault directly instead of extracting one from a Config
argument. Deleted the duplicate Config::vault_password_file method
(the canonical version lives on AppConfig).
Vault is now fully Config-independent at the signature level. The
Config.vault runtime field still exists because it's populated
inside Config::init (for the current bridge-era flow), but nothing
about Vault's API references Config anymore. The field itself
gets deleted in Step 16f when Config becomes a pure POJO.
What was changed
src/vault/mod.rs
Signature change:
// Before
pub fn init(config: &Config) -> Self
// After
pub fn init(config: &AppConfig) -> Self
Vault::init_bare now uses AppConfig::default() instead of
Config::default() to get the default vault password file path.
Behavioral parity — both .default().vault_password_file() calls
resolve to the same path (the fallback from gman::config).
Vault::handle_vault_flags simplified:
// Before
pub fn handle_vault_flags(cli: Cli, config: Config) -> Result<()>
// After
pub fn handle_vault_flags(cli: Cli, vault: &Vault) -> Result<()>
The old signature took a Config by value just to extract
config.vault. The new signature takes the Vault directly, which
decouples this function from Config entirely. Callers pass
&config.vault or equivalent.
Import updated:
use crate::config::Configremoveduse crate::config::AppConfigadded
src/config/mod.rs
In Config::init:
// Before
let vault = Vault::init(config);
// After
let vault = Vault::init(&config.to_app_config());
This is a transitional call — Config::init builds a temporary
AppConfig via the bridge just to satisfy the new signature. This
temporary conversion disappears in Step 16e when main.rs stops
calling Config::init entirely and AppConfig is built first.
Deleted Config::vault_password_file method. The identical body
lives on AppConfig. All callers go through AppConfig now.
src/main.rs
Vault-flags path:
// Before
return Vault::handle_vault_flags(cli, Config::init_bare()?);
// After
let cfg = Config::init_bare()?;
return Vault::handle_vault_flags(cli, &cfg.vault);
This is a minor restructure — same observable behavior, but the Vault is extracted from the Config and passed directly. Makes the vault-flags path obvious about what it actually needs (a Vault, not a Config).
Behavior parity
Vault::initreadsconfig.vault_password_file()— identical method on both Config and AppConfig (removed from Config in this step, kept on AppConfig).- Password file initialization (
ensure_password_file_initialized) still runs inVault::initas before. Vault::init_barefallback path resolves to the same default password file location.handle_vault_flagsoperates on the same Vault instance either way — just receives it directly instead of indirectly via Config.
Files modified
src/vault/mod.rs— imports,initsignature,init_barefallback source,handle_vault_flagssignature.src/config/mod.rs— transitionalto_app_config()call inConfig::init; deleted duplicatevault_password_filemethod.src/main.rs— vault-flags path takes&cfg.vaultdirectly.
Assumptions made
-
AppConfig::default().vault_password_file()behaves identically toConfig::default().vault_password_file(). Verified by comparing method bodies — identical logic, same fallback viagman::config::Config::local_provider_password_file(). Tests confirm 122 passing, no regressions. -
Transitional
&config.to_app_config()inConfig::initis acceptable. The conversion happens once per Config::init call — trivial cost for a non-hot path. Disappears entirely in 16e. -
handle_vault_flagstaking&Vaultis a strict improvement. The old signature tookConfigby value (wasteful for a function that only needed one field). The new signature is honest about its dependency. -
Config.vaultfield stays for now. The#[serde(skip)]field on Config still exists becauseConfig::initpopulates it for downstream Bridge flow consumers. Deletion deferred to 16f.
Open questions
-
Should
Vault::initreturnResult<Self>instead of panicking? Currently uses.expect("Failed to initialize password file"). The vault flags path can't do anything useful without a vault, so panic vs early return is pragmatically equivalent. Leaving as-is to minimize change surface in 16c. -
Is
Config::init_barestill needed after 16e? It's called from the oauth path inmain.rs. In 16e we'll audit whether those paths really need full Config init or just an AppConfig. Deferred to 16e.
Verification
cargo check— clean, zero warningscargo clippy— clean, zero warningscargo test— 122 passing, zero failures- Grep confirmation:
Vault::init(has one caller (inConfig::init); one remaining via test path (none found — deferred to 16d/16e where AppState::init will own vault construction)Vault::init_bare(has one caller (viainterpolate_secretsflow); no other referencesConfig::vault_password_file— zero references anywhereVault::handle_vault_flags— single caller inmain.rs, signature verified
Remaining work for Step 16
- 16d: Build
AppState::init(app_config, ...).awaitthat takes ownership of vault construction (replacing the currentAppState { vault: cfg.vault.clone(), ... }pattern). - 16e: Switch
main.rsand all 15Config::init()callers to the new flow. - 16f: Delete
Config.vaultfield,Config::init, bridge.rs, and all remaining Config runtime fields.
Migration direction preserved
Before 16c:
Vault::init(&Config) ← tight coupling to Config
Vault::init_bare() ← uses Config::default() internally
handle_vault_flags(Cli, Config) ← takes Config, extracts vault
Config::vault_password_file() ← duplicate with AppConfig's
After 16c:
Vault::init(&AppConfig) ← depends only on AppConfig
Vault::init_bare() ← uses AppConfig::default() internally
handle_vault_flags(Cli, &Vault) ← takes Vault directly
Config::vault_password_file() ← DELETED
The Vault module now has no Config dependency in its public API.
This means Step 16d can build AppState::init that calls
Vault::init(&app_config) without touching Config at all. It also
means Config is one step closer to being a pure POJO — one fewer
method on its surface, one fewer implicit dependency.