From 2d6e4194dd87dd0f999a27bf511f3a65d2703207 Mon Sep 17 00:00:00 2001 From: Jonathan Hendler Date: Fri, 20 Mar 2026 11:42:15 -0700 Subject: [PATCH 1/3] config cleanup --- jacs/src/config/mod.rs | 178 +---------------------------------------- 1 file changed, 2 insertions(+), 176 deletions(-) diff --git a/jacs/src/config/mod.rs b/jacs/src/config/mod.rs index 5bf5a247..15c3e591 100644 --- a/jacs/src/config/mod.rs +++ b/jacs/src/config/mod.rs @@ -3,7 +3,7 @@ use crate::error::JacsError; use crate::schema::utils::{CONFIG_SCHEMA_STRING, EmbeddedSchemaResolver}; -use crate::storage::jenv::{EnvError, get_env_var, get_required_env_var, set_env_var_override}; +use crate::storage::jenv::{EnvError, get_env_var, get_required_env_var}; use getset::Getters; use jsonschema::{Draft, Validator}; use serde::Deserialize; @@ -15,7 +15,7 @@ use std::fs; use std::str::FromStr; use tracing::{error, info, warn}; -use crate::validation::{are_valid_uuid_parts, split_agent_id}; +use crate::validation::split_agent_id; /// Source for resolving public keys during signature verification. /// @@ -1193,180 +1193,6 @@ pub fn validate_config(config_json: &str) -> Result { Ok(instance) } -/// DEPRECATED: Use `load_config_12factor_optional` instead. -/// -/// Attempts to find and load a config file from the given path. -/// Falls back to Config::default() if file not found. -#[deprecated( - since = "0.2.0", - note = "Use load_config_12factor_optional() for 12-Factor compliant config loading" -)] -pub fn find_config(path: String) -> Result { - let config: Config = match fs::read_to_string(format!("{}jacs.config.json", path)) { - Ok(content) => { - let validated_value = validate_config(&content)?; - serde_json::from_value(validated_value).map_err(|e| { - JacsError::ConfigError(format!("Failed to deserialize config: {}", e)) - })? - } - Err(_) => Config::default(), - }; - Ok(config) -} - -/// DEPRECATED: Use `load_config_12factor` instead. -/// -/// This function takes config file values and sets them as environment variables, -/// which is the OPPOSITE of 12-Factor principles. Environment variables should -/// be the source of truth, not the target. -/// -/// This function is kept for backwards compatibility only. New code should use -/// `load_config_12factor()` which reads env vars INTO config (correct direction). -#[deprecated( - since = "0.2.0", - note = "Use load_config_12factor() - env vars should override config, not vice versa" -)] -pub fn set_env_vars( - do_override: bool, - config_json: Option<&str>, - ignore_agent_id: bool, -) -> Result { - let config: Config = match config_json { - Some(json_str) => { - let validated_value = validate_config(json_str)?; - serde_json::from_value(validated_value).map_err(|e| { - JacsError::ConfigError(format!("Failed to deserialize config: {}", e)) - })? - } - None => find_config(".".to_string())?, - }; - // debug!("configs from file {:?}", config); - validate_config( - &serde_json::to_string(&config) - .map_err(|e| JacsError::ConfigError(format!("Failed to serialize config: {}", e)))?, - )?; - - // Security: Password should come from environment variable, not config file - if config.jacs_private_key_password.is_some() { - warn!( - "SECURITY WARNING: Password found in config file. \ - This is insecure - passwords should only be set via JACS_PRIVATE_KEY_PASSWORD \ - environment variable. The password in the config file will be ignored." - ); - } - // Do NOT set password from config - it must come from env var only - // The password will be read directly from env var when needed - - let jacs_use_security = config - .jacs_use_security - .as_ref() - .unwrap_or(&"false".to_string()) - .clone(); - set_env_var_override("JACS_USE_SECURITY", &jacs_use_security, do_override) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_data_directory = config - .jacs_data_directory - .as_ref() - .unwrap_or( - &std::env::current_dir() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|_| "./jacs_data".to_string()), - ) - .clone(); - set_env_var_override("JACS_DATA_DIRECTORY", &jacs_data_directory, do_override) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_key_directory = config - .jacs_key_directory - .as_ref() - .unwrap_or(&".".to_string()) - .clone(); - set_env_var_override("JACS_KEY_DIRECTORY", &jacs_key_directory, do_override) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_agent_private_key_filename = config - .jacs_agent_private_key_filename - .as_ref() - .unwrap_or(&"jacs.private.pem.enc".to_string()) - .clone(); - set_env_var_override( - "JACS_AGENT_PRIVATE_KEY_FILENAME", - &jacs_agent_private_key_filename, - do_override, - ) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_agent_public_key_filename = config - .jacs_agent_public_key_filename - .as_ref() - .unwrap_or(&"jacs.public.pem".to_string()) - .clone(); - set_env_var_override( - "JACS_AGENT_PUBLIC_KEY_FILENAME", - &jacs_agent_public_key_filename, - do_override, - ) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_agent_key_algorithm = config - .jacs_agent_key_algorithm - .as_ref() - .unwrap_or(&"pq2025".to_string()) - .clone(); - set_env_var_override( - "JACS_AGENT_KEY_ALGORITHM", - &jacs_agent_key_algorithm, - do_override, - ) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_default_storage = config - .jacs_default_storage - .as_ref() - .unwrap_or(&"fs".to_string()) - .clone(); - set_env_var_override("JACS_DEFAULT_STORAGE", &jacs_default_storage, do_override) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - let jacs_agent_id_and_version = config - .jacs_agent_id_and_version - .as_ref() - .unwrap_or(&"".to_string()) - .clone(); - - if !jacs_agent_id_and_version.is_empty() { - if let Some((id, version)) = split_agent_id(&jacs_agent_id_and_version) { - if !are_valid_uuid_parts(id, version) { - warn!("ID and Version must be in the form UUID:UUID"); - } - } else { - warn!("ID and Version must be in the form UUID:UUID"); - } - } - - set_env_var_override( - "JACS_AGENT_ID_AND_VERSION", - &jacs_agent_id_and_version, - do_override, - ) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - - // Propagate keychain backend setting to env var if set in config - if let Some(ref backend) = config.jacs_keychain_backend { - set_env_var_override("JACS_KEYCHAIN_BACKEND", backend, do_override) - .map_err(|e| JacsError::ConfigError(e.to_string()))?; - } - - let message = format!("{}", config); - info!("{}", message); - check_env_vars(ignore_agent_id).map_err(|e| { - error!("Error checking environment variables: {}", e); - JacsError::ConfigError(e.to_string()) - })?; - Ok(message) -} - pub fn check_env_vars(ignore_agent_id: bool) -> Result { let vars = [ ("JACS_USE_SECURITY", true), From 5bcb74c72f4b23df59c0ddaf5d5f41a2106bac22 Mon Sep 17 00:00:00 2001 From: Jonathan Hendler Date: Fri, 20 Mar 2026 15:11:29 -0700 Subject: [PATCH 2/3] version bump --- CHANGELOG.md | 4 ++++ binding-core/Cargo.toml | 4 ++-- jacs-cli/Cargo.toml | 6 +++--- jacs-duckdb/Cargo.toml | 2 +- jacs-mcp/Cargo.toml | 6 +++--- jacs-mcp/contract/jacs-mcp-contract.json | 2 +- jacs-postgresql/Cargo.toml | 2 +- jacs-redb/Cargo.toml | 2 +- jacs-surrealdb/Cargo.toml | 2 +- jacsnpm/package.json | 2 +- jacspy/pyproject.toml | 2 +- 11 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14493fc7..bfe5a792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.9 + +(unreleased) + ## 0.9.7 (unreleased) ### Features diff --git a/binding-core/Cargo.toml b/binding-core/Cargo.toml index 5314ff28..5be81dbe 100644 --- a/binding-core/Cargo.toml +++ b/binding-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jacs-binding-core" -version = "0.9.8" +version = "0.9.9" edition = "2024" rust-version = "1.93" resolver = "3" @@ -19,7 +19,7 @@ attestation = ["jacs/attestation"] pq-tests = [] [dependencies] -jacs = { version = "0.9.8", path = "../jacs" } +jacs = { version = "0.9.9", path = "../jacs" } serde_json = "1.0" base64 = "0.22.1" serde = { version = "1.0", features = ["derive"] } diff --git a/jacs-cli/Cargo.toml b/jacs-cli/Cargo.toml index db4064b5..ab98ddb7 100644 --- a/jacs-cli/Cargo.toml +++ b/jacs-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jacs-cli" -version = "0.9.8" +version = "0.9.9" edition = "2024" rust-version = "1.93" description = "JACS CLI: command-line interface for JSON AI Communication Standard" @@ -23,8 +23,8 @@ attestation = ["jacs/attestation"] keychain = ["jacs/keychain"] [dependencies] -jacs = { version = "0.9.8", path = "../jacs" } -jacs-mcp = { version = "0.9.8", path = "../jacs-mcp", features = ["mcp", "full-tools"], optional = true } +jacs = { version = "0.9.9", path = "../jacs" } +jacs-mcp = { version = "0.9.9", path = "../jacs-mcp", features = ["mcp", "full-tools"], optional = true } clap = { version = "4.5.4", features = ["derive", "cargo"] } rpassword = "7.3.1" reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "json", "rustls"] } diff --git a/jacs-duckdb/Cargo.toml b/jacs-duckdb/Cargo.toml index f5d4a95b..a7dd9c9d 100644 --- a/jacs-duckdb/Cargo.toml +++ b/jacs-duckdb/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["cryptography", "json", "duckdb", "storage"] categories = ["database", "data-structures"] [dependencies] -jacs = { version = "0.9.8", path = "../jacs", default-features = false } +jacs = { version = "0.9.9", path = "../jacs", default-features = false } duckdb = { version = "1.4", features = ["bundled", "json"] } serde_json = "1.0" diff --git a/jacs-mcp/Cargo.toml b/jacs-mcp/Cargo.toml index 26c039a6..b249e68e 100644 --- a/jacs-mcp/Cargo.toml +++ b/jacs-mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jacs-mcp" -version = "0.9.8" +version = "0.9.9" edition = "2024" rust-version = "1.93" description = "MCP server for JACS: data provenance and cryptographic signing of agent state" @@ -45,8 +45,8 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } rmcp = { version = "0.12", features = ["client", "server", "transport-io", "transport-child-process", "macros"], optional = true } tokio = { version = "1", features = ["rt-multi-thread", "macros", "process", "time"], optional = true } -jacs = { version = "0.9.8", path = "../jacs", default-features = true } -jacs-binding-core = { version = "0.9.8", path = "../binding-core", features = ["a2a"] } +jacs = { version = "0.9.9", path = "../jacs", default-features = true } +jacs-binding-core = { version = "0.9.9", path = "../binding-core", features = ["a2a"] } serde = { version = "1", features = ["derive"] } serde_json = "1" schemars = "1.0" diff --git a/jacs-mcp/contract/jacs-mcp-contract.json b/jacs-mcp/contract/jacs-mcp-contract.json index 3e59523e..1a79082e 100644 --- a/jacs-mcp/contract/jacs-mcp-contract.json +++ b/jacs-mcp/contract/jacs-mcp-contract.json @@ -3,7 +3,7 @@ "server": { "name": "jacs-mcp", "title": "JACS MCP Server", - "version": "0.9.8", + "version": "0.9.9", "website_url": "https://humanassisted.github.io/JACS/", "instructions": "This MCP server provides data provenance and cryptographic signing for agent state files and agent-to-agent messaging. Agent state tools: jacs_sign_state (sign files), jacs_verify_state (verify integrity), jacs_load_state (load with verification), jacs_update_state (update and re-sign), jacs_list_state (list signed docs), jacs_adopt_state (adopt external files). Memory tools: jacs_memory_save (save a memory), jacs_memory_recall (search memories by query), jacs_memory_list (list all memories), jacs_memory_forget (soft-delete a memory), jacs_memory_update (update an existing memory). Messaging tools: jacs_message_send (create and sign a message), jacs_message_update (update and re-sign a message), jacs_message_agree (co-sign/agree to a message), jacs_message_receive (verify and extract a received message). Agent management: jacs_create_agent (create new agent with keys), jacs_reencrypt_key (rotate private key password). A2A artifacts: jacs_wrap_a2a_artifact (sign artifact with provenance), jacs_verify_a2a_artifact (verify wrapped artifact), jacs_assess_a2a_agent (assess remote agent trust level). A2A discovery: jacs_export_agent_card (export Agent Card), jacs_generate_well_known (generate .well-known documents), jacs_export_agent (export full agent JSON). Trust store: jacs_trust_agent (add agent to trust store), jacs_untrust_agent (remove from trust store, requires JACS_MCP_ALLOW_UNTRUST=true), jacs_list_trusted_agents (list all trusted agent IDs), jacs_is_trusted (check if agent is trusted), jacs_get_trusted_agent (get trusted agent JSON). Attestation: jacs_attest_create (create signed attestation with claims), jacs_attest_verify (verify attestation, optionally with evidence checks), jacs_attest_lift (lift signed document into attestation), jacs_attest_export_dsse (export attestation as DSSE envelope). Security: jacs_audit (read-only security audit and health checks). Audit trail: jacs_audit_log (record events as signed audit entries), jacs_audit_query (search audit trail by action, target, time range), jacs_audit_export (export audit trail as signed bundle). Search: jacs_search (unified search across all signed documents)." }, diff --git a/jacs-postgresql/Cargo.toml b/jacs-postgresql/Cargo.toml index e84da755..3ca6f8b1 100644 --- a/jacs-postgresql/Cargo.toml +++ b/jacs-postgresql/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["cryptography", "json", "postgresql", "storage"] categories = ["database", "data-structures"] [dependencies] -jacs = { version = "0.9.8", path = "../jacs", default-features = false } +jacs = { version = "0.9.9", path = "../jacs", default-features = false } sqlx = { version = "0.8.6", default-features = false, features = ["runtime-tokio-rustls", "postgres"] } tokio = { version = "1.0", features = ["rt-multi-thread"] } serde_json = "1.0" diff --git a/jacs-redb/Cargo.toml b/jacs-redb/Cargo.toml index f097494c..b16d6ad2 100644 --- a/jacs-redb/Cargo.toml +++ b/jacs-redb/Cargo.toml @@ -13,7 +13,7 @@ categories.workspace = true description = "Redb (pure-Rust embedded KV) storage backend for JACS documents" [dependencies] -jacs = { version = "0.9.8", path = "../jacs", default-features = false } +jacs = { version = "0.9.9", path = "../jacs", default-features = false } redb = "3.1" chrono = "0.4.40" serde_json = "1.0" diff --git a/jacs-surrealdb/Cargo.toml b/jacs-surrealdb/Cargo.toml index 901ad6ef..a78ecdf1 100644 --- a/jacs-surrealdb/Cargo.toml +++ b/jacs-surrealdb/Cargo.toml @@ -13,7 +13,7 @@ keywords = ["cryptography", "json", "surrealdb", "storage"] categories = ["database", "data-structures"] [dependencies] -jacs = { version = "0.9.8", path = "../jacs", default-features = false } +jacs = { version = "0.9.9", path = "../jacs", default-features = false } surrealdb = { version = "3.0.2", default-features = false, features = ["kv-mem"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/jacsnpm/package.json b/jacsnpm/package.json index ed7a8dd8..105b6e78 100644 --- a/jacsnpm/package.json +++ b/jacsnpm/package.json @@ -1,6 +1,6 @@ { "name": "@hai.ai/jacs", - "version": "0.9.8", + "version": "0.9.9", "description": "JACS (JSON Agent Communication Standard) - Data provenance and cryptographic signing for AI agents", "main": "index.js", "types": "index.d.ts", diff --git a/jacspy/pyproject.toml b/jacspy/pyproject.toml index 087a92d8..e1dc8521 100644 --- a/jacspy/pyproject.toml +++ b/jacspy/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" [project] name = "jacs" -version = "0.9.8" +version = "0.9.9" description = "JACS - JSON AI Communication Standard: Cryptographic signing and verification for AI agents." readme = "README.md" requires-python = ">=3.10" From 9e4b9375c1771f782d0eaeffe7f5c6f4006889d1 Mon Sep 17 00:00:00 2001 From: Jonathan Hendler Date: Fri, 20 Mar 2026 16:20:57 -0700 Subject: [PATCH 3/3] cleanup --- jacs/Cargo.toml | 2 +- jacs/src/config/mod.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/jacs/Cargo.toml b/jacs/Cargo.toml index 591dfd1a..d736f231 100644 --- a/jacs/Cargo.toml +++ b/jacs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jacs" -version = "0.9.8" +version = "0.9.9" edition = "2024" rust-version = "1.93" resolver = "3" diff --git a/jacs/src/config/mod.rs b/jacs/src/config/mod.rs index 15c3e591..db8045b6 100644 --- a/jacs/src/config/mod.rs +++ b/jacs/src/config/mod.rs @@ -1714,6 +1714,32 @@ mod tests { clear_jacs_env_vars(); } + #[test] + #[serial(jacs_env)] + fn test_apply_env_overrides_preserves_config_dir() { + clear_jacs_env_vars(); + + let mut config = Config::with_defaults(); + let test_dir = std::path::PathBuf::from("/some/config/dir"); + config.set_config_dir(Some(test_dir.clone())); + + // Set some env overrides to trigger actual work + set_env_var("JACS_DATA_DIRECTORY", "/env/data").unwrap(); + + config.apply_env_overrides(); + + // config_dir must survive apply_env_overrides — it is runtime metadata + // that Agent::from_config uses for storage_root calculation. + // If this is wiped, storage resolves to CWD or "/" (Issue 024). + assert_eq!( + config.config_dir(), + Some(test_dir.as_path()), + "config_dir must be preserved through apply_env_overrides" + ); + + clear_jacs_env_vars(); + } + #[test] fn test_config_builder_defaults() { // Builder with no options set should produce sensible defaults