use std::path::PathBuf;
use crate::policy::match_tree::CompiledPolicy;
use crate::policy_loader;
use anyhow::{Context, Result};
use dirs::home_dir;
use serde::{Deserialize, Serialize};
use tracing::{Level, info, instrument, warn};
use crate::audit::AuditConfig;
use crate::notifications::NotificationConfig;
pub const CLASH_DISABLE_ENV: &str = "CLASH_DISABLE";
pub const CLASH_PASSTHROUGH_ENV: &str = "CLASH_PASSTHROUGH";
pub fn is_disabled() -> bool {
std::env::var(CLASH_DISABLE_ENV)
.ok()
.is_some_and(|v| is_truthy_disable_value(&v))
}
pub fn is_passthrough() -> bool {
std::env::var(CLASH_PASSTHROUGH_ENV)
.ok()
.is_some_and(|v| is_truthy_disable_value(&v))
}
fn is_truthy_disable_value(value: &str) -> bool {
!value.is_empty() && value != "0" && value != "false"
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum PolicyLevel {
User = 0,
Project = 1,
Session = 2,
}
impl PolicyLevel {
pub fn all_by_precedence() -> &'static [PolicyLevel] {
&[PolicyLevel::Project, PolicyLevel::User]
}
pub fn name(&self) -> &'static str {
match self {
PolicyLevel::User => "user",
PolicyLevel::Project => "project",
PolicyLevel::Session => "session",
}
}
}
impl std::fmt::Display for PolicyLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl std::str::FromStr for PolicyLevel {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"user" => Ok(PolicyLevel::User),
"project" => Ok(PolicyLevel::Project),
"session" => Ok(PolicyLevel::Session),
_ => anyhow::bail!(
"unknown policy level: {s} (expected 'user', 'project', or 'session')"
),
}
}
}
pub const DEFAULT_POLICY_TEMPLATE: &str = include_str!("default_policy.star");
pub const SANDBOX_PRESETS: &[SandboxPreset] = &[
SandboxPreset {
name: "dev",
description: "Build tools, git — read+write project, read home, no network",
},
SandboxPreset {
name: "dev_network",
description: "Package managers, gh — read+write project, full network",
},
SandboxPreset {
name: "read_only",
description: "Linters, analyzers — read project + home, no writes outside temp",
},
SandboxPreset {
name: "restricted",
description: "Untrusted scripts — read-only project, no network",
},
SandboxPreset {
name: "unrestricted",
description: "Fully trusted — all filesystem + network access",
},
];
pub struct SandboxPreset {
pub name: &'static str,
pub description: &'static str,
}
impl crate::dialog::SelectItem for SandboxPreset {
fn label(&self) -> &str {
self.name
}
fn description(&self) -> &str {
self.description
}
fn variants() -> &'static [Self] {
SANDBOX_PRESETS
}
}
pub fn compile_default_policy_to_json_with_preset(preset: &str) -> Result<String> {
let source = DEFAULT_POLICY_TEMPLATE.replace("{preset}", preset);
let output =
clash_starlark::evaluate(&source, "<default_policy>", std::path::Path::new("."))
.with_context(|| format!("failed to compile default policy with preset '{preset}'"))?;
let value: serde_json::Value =
serde_json::from_str(&output.json).context("default policy produced invalid JSON")?;
serde_json::to_string_pretty(&value).context("failed to pretty-print default policy JSON")
}
pub fn compile_default_policy_to_json() -> Result<String> {
compile_default_policy_to_json_with_preset("dev")
}
#[derive(Debug, Clone, Default)]
pub struct HookContext {
pub transcript_dir: Option<String>,
}
impl HookContext {
pub fn from_transcript_path(transcript_path: &str) -> Self {
let transcript_dir = if transcript_path.is_empty() {
None
} else {
std::path::Path::new(transcript_path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.filter(|s| !s.is_empty())
};
Self { transcript_dir }
}
}
#[derive(Debug, Clone)]
pub struct LoadedPolicy {
pub level: PolicyLevel,
pub path: PathBuf,
pub source: String,
}
#[derive(Debug, Default)]
pub struct ClashSettings {
compiled: Option<CompiledPolicy>,
loaded_policies: Vec<LoadedPolicy>,
pub notifications: NotificationConfig,
pub notification_warning: Option<String>,
pub audit: AuditConfig,
policy_error: Option<String>,
}
impl ClashSettings {
pub fn settings_dir() -> Result<PathBuf> {
if let Ok(p) = std::env::var("CLASH_HOME") {
return Ok(PathBuf::from(p));
}
home_dir()
.map(|h| h.join(".clash"))
.ok_or_else(|| anyhow::anyhow!("$HOME is not set; cannot determine settings directory"))
}
pub fn policy_file() -> Result<PathBuf> {
if let Ok(p) = std::env::var("CLASH_POLICY_FILE") {
return Ok(PathBuf::from(p));
}
let dir = Self::settings_dir()?;
Ok(prefer_json_over_star(&dir))
}
pub fn policy_file_for_level(level: PolicyLevel) -> Result<PathBuf> {
match level {
PolicyLevel::User => Self::policy_file(),
PolicyLevel::Project => {
let root = Self::project_root()?;
let dir = root.join(".clash");
Ok(prefer_json_over_star(&dir))
}
PolicyLevel::Session => {
let session_id = Self::active_session_id()?;
Ok(Self::session_policy_path(&session_id))
}
}
}
pub fn session_policy_path(session_id: &str) -> PathBuf {
crate::audit::session_dir(session_id).join("policy.star")
}
fn active_session_file() -> Result<PathBuf> {
Self::settings_dir().map(|d| d.join("active_session"))
}
pub fn active_session_id() -> Result<String> {
let path = Self::active_session_file()?;
let id = std::fs::read_to_string(&path)
.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
anyhow::anyhow!("no active session — start a session with `clash launch` first")
} else {
anyhow::anyhow!("failed to read active session: {e}")
}
})?
.trim()
.to_string();
if id.is_empty() {
anyhow::bail!("active session file is empty");
}
Ok(id)
}
pub fn set_active_session(session_id: &str) -> Result<()> {
let path = Self::active_session_file()?;
std::fs::create_dir_all(path.parent().unwrap())?;
std::fs::write(&path, session_id)?;
Ok(())
}
pub fn project_root() -> Result<PathBuf> {
let cwd = std::env::current_dir()
.map_err(|e| anyhow::anyhow!("cannot determine current directory: {e}"))?;
let stop_at = home_dir();
if let Some(root) = find_ancestor_with(&cwd, ".clash", stop_at.as_deref()) {
return Ok(root);
}
if let Some(root) = find_ancestor_with(&cwd, ".git", stop_at.as_deref()) {
return Ok(root);
}
anyhow::bail!(
"no project root found (looked for .clash/ or .git/ in ancestors of {})",
cwd.display()
)
}
pub fn available_policy_levels() -> Vec<(PolicyLevel, PathBuf)> {
let mut levels = Vec::new();
for &level in PolicyLevel::all_by_precedence() {
if let Ok(path) = Self::policy_file_for_level(level)
&& path.exists()
&& path.is_file()
{
levels.push((level, path));
}
}
levels
}
pub fn diagnose_missing_policies() -> Vec<(String, String, String)> {
let mut results = Vec::new();
for &level in PolicyLevel::all_by_precedence() {
match Self::policy_file_for_level(level) {
Ok(path) => {
let path_str = path.display().to_string();
match std::fs::metadata(&path) {
Ok(m) if m.is_file() => {
results.push((level.name().to_string(), path_str, "ok".to_string()));
}
Ok(_) => {
results.push((
level.name().to_string(),
path_str,
"path exists but is not a file".to_string(),
));
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
results.push((
level.name().to_string(),
path_str,
"file does not exist".to_string(),
));
}
Err(e) => {
results.push((level.name().to_string(), path_str, format!("{e}")));
}
}
}
Err(e) => {
results.push((level.name().to_string(), "—".to_string(), format!("{e}")));
}
}
}
results
}
pub fn default_scope() -> PolicyLevel {
if let Ok(path) = Self::policy_file_for_level(PolicyLevel::Project)
&& path.exists()
&& path.is_file()
{
return PolicyLevel::Project;
}
PolicyLevel::User
}
pub fn ensure_user_policy_exists() -> Result<Option<PathBuf>> {
let path = Self::policy_file().context("failed to determine user policy file path")?;
Self::ensure_policy_at(path)
}
fn ensure_policy_at(path: PathBuf) -> Result<Option<PathBuf>> {
if path.exists() {
return Ok(None);
}
let json_path = path.with_extension("json");
if let Some(parent) = json_path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create directory {}", parent.display()))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700));
}
}
let json =
compile_default_policy_to_json().context("failed to compile default policy to JSON")?;
std::fs::write(&json_path, &json).with_context(|| {
format!("failed to write default policy to {}", json_path.display())
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&json_path, std::fs::Permissions::from_mode(0o600));
}
info!(path = %json_path.display(), "Created default user policy");
Ok(Some(json_path))
}
pub fn policy_error(&self) -> Option<&str> {
self.policy_error.as_deref()
}
pub fn set_policy_source(&mut self, source: &str) {
match policy_loader::compile_source(source) {
Ok(tree) => {
self.compiled = Some(tree);
self.policy_error = None;
}
Err(e) => {
let msg = format!("Failed to compile policy: {}", e);
warn!(error = %e, "Failed to compile policy");
self.policy_error = Some(msg);
self.compiled = None;
}
}
}
#[cfg(test)]
const MAX_POLICY_SIZE: u64 = policy_loader::MAX_POLICY_SIZE;
pub fn policy_tree(&self) -> Option<&CompiledPolicy> {
self.compiled.as_ref()
}
#[doc(hidden)]
pub fn decision_tree(&self) -> Option<&CompiledPolicy> {
self.compiled.as_ref()
}
#[cfg(test)]
fn load_policy_from_path(&mut self, path: &std::path::Path) -> bool {
match policy_loader::load_and_compile_single(path, &mut self.policy_error) {
Some(tree) => {
self.load_notification_audit_config();
self.compiled = Some(tree);
true
}
None => false,
}
}
fn load_notification_audit_config(&mut self) {
let yaml_path = match Self::settings_dir() {
Ok(d) => d.join("policy.yaml"),
Err(_) => return,
};
if let Ok(contents) = std::fs::read_to_string(&yaml_path) {
let (notif_config, notif_warning) = parse_notification_config(&contents);
self.notifications = notif_config;
self.notification_warning = notif_warning;
self.audit = parse_audit_config(&contents);
}
}
pub fn load_or_create() -> Result<Self> {
Self::load_or_create_with_session(None, None)
}
pub fn loaded_policies(&self) -> &[LoadedPolicy] {
&self.loaded_policies
}
#[instrument(level = Level::TRACE, skip(session_id, _hook_ctx))]
pub fn load_or_create_with_session(
session_id: Option<&str>,
_hook_ctx: Option<&HookContext>,
) -> Result<Self> {
let mut this = Self::default();
let mut level_sources: Vec<(PolicyLevel, String, String)> = Vec::new();
for &level in PolicyLevel::all_by_precedence().iter().rev() {
if let Ok(path) = Self::policy_file_for_level(level)
&& let Some(validated) =
policy_loader::try_load_policy(level, &path, &mut this.policy_error)
{
if level == PolicyLevel::User {
this.load_notification_audit_config();
}
let display_path = tilde_path(&path);
level_sources.push((level, validated.json_source, display_path));
this.loaded_policies.push(validated.loaded);
}
}
if let Some(sid) = session_id {
let session_path = Self::session_policy_path(sid);
if let Some(validated) = policy_loader::try_load_policy(
PolicyLevel::Session,
&session_path,
&mut this.policy_error,
) {
let display_path = tilde_path(&session_path);
level_sources.push((PolicyLevel::Session, validated.json_source, display_path));
this.loaded_policies.push(validated.loaded);
}
}
this.loaded_policies.sort_by(|a, b| b.level.cmp(&a.level));
if level_sources.is_empty() {
return Ok(this);
}
match policy_loader::compile_policies(&level_sources) {
Ok(tree) => {
this.compiled = Some(tree);
this.policy_error = None;
}
Err(e) => {
let msg = format!("Failed to compile policy: {}", e);
warn!(error = %e, "Failed to compile policy");
this.policy_error = Some(msg);
}
}
Ok(this)
}
}
fn prefer_json_over_star(dir: &std::path::Path) -> PathBuf {
let json_path = dir.join("policy.json");
if json_path.exists() {
json_path
} else {
dir.join("policy.star")
}
}
fn tilde_path(path: &std::path::Path) -> String {
if let Some(home) = home_dir()
&& let Ok(rest) = path.strip_prefix(&home)
{
return format!("~/{}", rest.display());
}
path.display().to_string()
}
pub fn parse_notification_config(yaml_str: &str) -> (NotificationConfig, Option<String>) {
#[derive(Deserialize)]
struct RawYaml {
#[serde(default)]
notifications: Option<NotificationConfig>,
}
match serde_yaml::from_str::<RawYaml>(yaml_str) {
Ok(raw) => (raw.notifications.unwrap_or_default(), None),
Err(e) => {
let warning = format!("notifications config parse error: {}", e);
warn!(error = %e, "Failed to parse notifications config");
(NotificationConfig::default(), Some(warning))
}
}
}
fn parse_audit_config(yaml_str: &str) -> AuditConfig {
#[derive(Deserialize)]
struct RawYaml {
#[serde(default)]
audit: Option<AuditConfig>,
}
match serde_yaml::from_str::<RawYaml>(yaml_str) {
Ok(raw) => raw.audit.unwrap_or_default(),
Err(_) => AuditConfig::default(),
}
}
pub fn evaluate_star_policy(path: &std::path::Path) -> Result<String> {
policy_loader::evaluate_star_policy(path)
}
pub fn evaluate_policy_file(path: &std::path::Path) -> Result<String> {
if path.extension().is_some_and(|ext| ext == "json") {
policy_loader::load_json_policy(path)
} else {
policy_loader::evaluate_star_policy(path)
}
}
fn find_ancestor_with(
start: &std::path::Path,
name: &str,
stop_at: Option<&std::path::Path>,
) -> Option<PathBuf> {
let mut current = start.to_path_buf();
loop {
if let Some(boundary) = stop_at
&& current == boundary
{
return None;
}
if current.join(name).exists() {
return Some(current);
}
if !current.pop() {
return None;
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Write;
#[allow(dead_code)]
struct TestEnv;
impl crate::policy::compile::EnvResolver for TestEnv {
fn resolve(&self, name: &str) -> anyhow::Result<String> {
match name {
"PWD" => Ok("/tmp".into()),
"HOME" => Ok("/tmp/home".into()),
"TMPDIR" => Ok("/tmp".into()),
other => anyhow::bail!("unknown env var in test: {other}"),
}
}
}
#[test]
fn default_policy_compiles() -> anyhow::Result<()> {
let source = DEFAULT_POLICY_TEMPLATE.replace("{preset}", "dev");
let output =
clash_starlark::evaluate(&source, "default_policy.star", std::path::Path::new("."))?;
let tree = crate::policy::compile::compile_to_tree(&output.json)?;
let _ = tree;
Ok(())
}
#[test]
fn default_policy_compiles_all_presets() -> anyhow::Result<()> {
for preset in SANDBOX_PRESETS {
compile_default_policy_to_json_with_preset(preset.name)?;
}
Ok(())
}
#[test]
fn default_policy_cwd_sandbox_uses_subpath() -> anyhow::Result<()> {
let json_str = compile_default_policy_to_json_with_preset("dev")?;
let policy: serde_json::Value = serde_json::from_str(&json_str)?;
let cwd_sandbox = &policy["sandboxes"]["cwd"];
let rules = cwd_sandbox["rules"].as_array().unwrap();
let pwd_rule = rules
.iter()
.find(|r| r["path"].as_str() == Some("$PWD"))
.expect("should have a $PWD rule");
assert_eq!(
pwd_rule["path_match"].as_str(),
Some("subpath"),
"cwd() with .recurse() should produce subpath match, got: {pwd_rule}"
);
Ok(())
}
#[test]
fn load_missing_file_returns_false() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("nonexistent-policy.star");
let mut settings = ClashSettings::default();
let result = settings.load_policy_from_path(&path);
assert!(!result);
assert!(
settings.policy_error.is_none(),
"missing file should not set error"
);
}
#[test]
fn load_directory_sets_error() {
let dir = tempfile::tempdir().unwrap();
let policy_path = dir.path().join("policy.star");
std::fs::create_dir(&policy_path).unwrap();
let mut settings = ClashSettings::default();
let result = settings.load_policy_from_path(&policy_path);
assert!(!result);
assert!(
settings.policy_error().unwrap().contains("is a directory"),
"expected directory error, got: {:?}",
settings.policy_error()
);
}
#[test]
fn load_empty_file_sets_error() {
let dir = tempfile::tempdir().unwrap();
let policy_path = dir.path().join("policy.star");
std::fs::write(&policy_path, "").unwrap();
let mut settings = ClashSettings::default();
let result = settings.load_policy_from_path(&policy_path);
assert!(!result);
assert!(
settings.policy_error().is_some(),
"expected error for empty file, got: {:?}",
settings.policy_error()
);
}
#[test]
fn load_oversized_file_sets_error() {
let dir = tempfile::tempdir().unwrap();
let policy_path = dir.path().join("policy.star");
let mut f = std::fs::File::create(&policy_path).unwrap();
let chunk = vec![b'#'; 8192];
for _ in 0..(ClashSettings::MAX_POLICY_SIZE / 8192 + 1) {
f.write_all(&chunk).unwrap();
}
drop(f);
let mut settings = ClashSettings::default();
let result = settings.load_policy_from_path(&policy_path);
assert!(!result);
assert!(
settings.policy_error().unwrap().contains("too large"),
"expected size error, got: {:?}",
settings.policy_error()
);
}
#[test]
fn load_valid_policy_succeeds() {
let star_policy = "load(\"@clash//std.star\", \"policy\")\ndef main():\n return policy(default = allow, rules = [])\n";
let dir = tempfile::tempdir().unwrap();
let policy_path = dir.path().join("policy.star");
std::fs::write(&policy_path, star_policy).unwrap();
let mut settings = ClashSettings::default();
let result = settings.load_policy_from_path(&policy_path);
assert!(result, "valid policy should compile successfully");
assert!(settings.policy_error.is_none());
assert!(settings.decision_tree().is_some());
}
#[test]
fn load_malformed_policy_sets_error() {
let dir = tempfile::tempdir().unwrap();
let policy_path = dir.path().join("policy.star");
std::fs::write(&policy_path, "this is not valid starlark {{{").unwrap();
let mut settings = ClashSettings::default();
let result = settings.load_policy_from_path(&policy_path);
assert!(!result);
assert!(
settings.policy_error().is_some(),
"expected error for malformed policy, got: {:?}",
settings.policy_error()
);
}
#[test]
fn set_policy_source_works() {
let simple_policy = r#"{"schema_version":5,"default_effect":"deny","sandboxes":{},"tree":[
{"condition":{"observe":"fs_op","pattern":{"literal":{"literal":"read"}},"children":[
{"condition":{"observe":"fs_path","pattern":{"prefix":{"literal":"/tmp"}},"children":[
{"decision":{"allow":null}}
]}}
]}}
]}"#;
let mut settings = ClashSettings::default();
settings.set_policy_source(simple_policy);
assert!(settings.decision_tree().is_some());
assert!(settings.policy_error.is_none());
}
#[test]
fn ensure_policy_creates_json_file_when_missing() {
let dir = tempfile::tempdir().unwrap();
let star_path = dir.path().join(".clash").join("policy.star");
let json_path = dir.path().join(".clash").join("policy.json");
let result = ClashSettings::ensure_policy_at(star_path).unwrap();
assert!(result.is_some(), "should have created the file");
assert_eq!(result.unwrap(), json_path);
assert!(json_path.exists(), "policy.json should exist on disk");
let contents = std::fs::read_to_string(&json_path).unwrap();
let parsed: serde_json::Value =
serde_json::from_str(&contents).expect("written file should be valid JSON");
assert!(
parsed.get("tree").is_some(),
"JSON should contain a tree field"
);
}
#[test]
fn ensure_policy_noop_when_exists() {
let dir = tempfile::tempdir().unwrap();
let policy_path = dir.path().join("policy.star");
std::fs::write(
&policy_path,
"def main():\n return policy(default = deny, rules = [])\n",
)
.unwrap();
let result = ClashSettings::ensure_policy_at(policy_path.clone()).unwrap();
assert!(result.is_none(), "should not recreate existing file");
let contents = std::fs::read_to_string(&policy_path).unwrap();
assert!(contents.contains("def main"), "original content preserved");
}
#[test]
#[cfg(unix)]
fn ensure_policy_sets_permissions() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let star_path = dir.path().join(".clash").join("policy.star");
let json_path = dir.path().join(".clash").join("policy.json");
ClashSettings::ensure_policy_at(star_path).unwrap();
let mode = std::fs::metadata(&json_path).unwrap().permissions().mode() & 0o777;
assert_eq!(mode, 0o600, "policy file should be owner-only read/write");
}
struct SessionEnvResolver<'a> {
hook_ctx: Option<&'a HookContext>,
}
impl crate::policy::compile::EnvResolver for SessionEnvResolver<'_> {
fn resolve(&self, name: &str) -> anyhow::Result<String> {
if name == "TRANSCRIPT_DIR"
&& let Some(dir) = self.hook_ctx.and_then(|ctx| ctx.transcript_dir.clone())
{
return Ok(dir);
}
crate::policy::compile::StdEnvResolver.resolve(name)
}
}
#[test]
fn hook_context_from_transcript_path() {
let ctx = HookContext::from_transcript_path("/tmp/session-123/transcript.jsonl");
assert_eq!(ctx.transcript_dir.as_deref(), Some("/tmp/session-123"));
}
#[test]
fn hook_context_from_empty_path() {
let ctx = HookContext::from_transcript_path("");
assert!(ctx.transcript_dir.is_none());
}
#[test]
fn hook_context_from_root_file() {
let ctx = HookContext::from_transcript_path("/transcript.jsonl");
assert_eq!(ctx.transcript_dir.as_deref(), Some("/"));
}
#[test]
fn session_resolver_provides_transcript_dir() {
use crate::policy::compile::EnvResolver;
let ctx = HookContext::from_transcript_path("/tmp/session-123/transcript.jsonl");
let resolver = SessionEnvResolver {
hook_ctx: Some(&ctx),
};
assert_eq!(
resolver.resolve("TRANSCRIPT_DIR").unwrap(),
"/tmp/session-123"
);
}
#[test]
fn session_resolver_returns_sentinel_without_context() {
use crate::policy::compile::{EnvResolver, UNAVAILABLE_SESSION_PATH};
let resolver = SessionEnvResolver { hook_ctx: None };
let result = resolver.resolve("TRANSCRIPT_DIR").unwrap();
assert_eq!(result, UNAVAILABLE_SESSION_PATH);
}
#[test]
fn session_resolver_falls_through_to_std_env() {
use crate::policy::compile::EnvResolver;
let resolver = SessionEnvResolver { hook_ctx: None };
let result = resolver.resolve("HOME");
assert!(result.is_ok(), "HOME should resolve via StdEnvResolver");
}
#[test]
fn is_truthy_disable_value_not_set() {
assert!(!is_truthy_disable_value(""));
}
#[test]
fn is_truthy_disable_value_falsy() {
assert!(!is_truthy_disable_value("0"));
assert!(!is_truthy_disable_value("false"));
}
#[test]
fn is_truthy_disable_value_truthy() {
assert!(is_truthy_disable_value("1"));
assert!(is_truthy_disable_value("true"));
assert!(is_truthy_disable_value("yes"));
assert!(is_truthy_disable_value("anything"));
}
}