refactor(config): the family's state and cache roots join Home

Two hand-rolled XDG resolutions survived the settings refactor: claw's
state_dir matched on XDG_STATE_HOME and HOME itself, and the ACP
adapter's session cache matched on XDG_CACHE_HOME itself — same class
as the global dir helpers this crate already buried. And the family
rule they implement was nowhere written down: the claw has its own
keychain namespace since the last refactor, yet its config, state, and
cache live under rhema's, with nothing saying that is deliberate.

Home carries the state and cache roots now, resolved with the config
base in discover and stated as the family rule in its doc: the
family's products share the XDG bases and nest below `rhema/` — claw
at `rhema/claw.yaml` and `rhema/claw`, the ACP session cache at
`rhema/acp-sessions`. A frontend from outside the family relocates
the whole set instead: relocated derives the frontend's name from its
config base's final component, dot stripped, and puts its state and
cache under that name, so godcode's artifacts never land under
`rhema/`. claw and the ACP adapter ask Home now; claw's NoStateHome
error variant died with its last user, and the ACP store's embedder
seam (AcpOptions::session_dir) was already there. SPEC records the
decision beside the keychain one it mirrors: the namespace is per
frontend, the nesting is per family.
This commit is contained in:
vhaudiquet committed 2026-11-08 17:53:04 +01:00
1 parent c3188af8bc
commit 12182492ea
1 file changed
+12 -21
+12 -21
View File
@@ -44,14 +44,11 @@ pub enum ConfigError {
/// The YAML file is not valid or not the expected shape. /// The YAML file is not valid or not the expected shape.
#[error(transparent)] #[error(transparent)]
Yaml(#[from] serde_yaml::Error), Yaml(#[from] serde_yaml::Error),
/// The stored-rhema config dir (`~/.config/rhema`) could not be /// The rhema family's directories (the config base, the state
/// located for the default claw.yaml path. /// root) could not be located — `HOME` unset — for the default
/// claw.yaml path or the state dir.
#[error(transparent)] #[error(transparent)]
Stored(#[from] rhema_config::ConfigError), Stored(#[from] rhema_config::ConfigError),
/// Neither `XDG_STATE_HOME` nor `HOME` is set, so the default SQLite
/// path or workspace root cannot be located.
#[error("cannot locate the state directory: neither XDG_STATE_HOME nor HOME is set")]
NoStateHome,
/// The agent token is the human token: boot refuses, because the /// The agent token is the human token: boot refuses, because the
/// agent token's safety is that it is scoped, and this copy is not. /// agent token's safety is that it is scoped, and this copy is not.
#[error( #[error(
@@ -207,26 +204,20 @@ impl Default for ClawConfig {
} }
impl ClawConfig { impl ClawConfig {
/// The default config path: `<config-base>/rhema/claw.yaml`, the same /// The default config path: `<config-base>/rhema/claw.yaml` —
/// base the stored rhema config lives under. /// claw nests below the rhema family's config base, a deliberate
/// part of that family, not a tenant of it.
pub fn default_path() -> Result<PathBuf, ConfigError> { pub fn default_path() -> Result<PathBuf, ConfigError> {
Ok(rhema_config::Home::discover()?.config.join("claw.yaml")) Ok(rhema_config::Home::discover()?.config.join("claw.yaml"))
} }
/// The state dir: `$XDG_STATE_HOME/rhema/claw`, else /// The state dir: the rhema family's state root with claw's own
/// `$HOME/.local/state/rhema/claw`. /// directory below it (`$XDG_STATE_HOME/rhema/claw`, else
/// `$HOME/.local/state/rhema/claw`) — the same family nesting the
/// config path applies, resolved by rhema-config's `Home` so
/// the XDG rules live in one crate.
pub fn state_dir() -> Result<PathBuf, ConfigError> { pub fn state_dir() -> Result<PathBuf, ConfigError> {
match std::env::var("XDG_STATE_HOME") { Ok(rhema_config::Home::discover()?.state.join("claw"))
Ok(dir) if !dir.trim().is_empty() => Ok(PathBuf::from(dir).join("rhema").join("claw")),
_ => match std::env::var("HOME") {
Ok(home) if !home.trim().is_empty() => Ok(PathBuf::from(home)
.join(".local")
.join("state")
.join("rhema")
.join("claw")),
_ => Err(ConfigError::NoStateHome),
},
}
} }
/// The dispatcher's SQLite path, defaulted under the state dir. /// The dispatcher's SQLite path, defaulted under the state dir.